credential storage for spending on client

This commit is contained in:
Simon Wicky
2024-05-07 13:29:28 +02:00
parent 3c7b7c7a8f
commit 4cc1b65e0a
5 changed files with 105 additions and 79 deletions
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoinIndicesSignature;
use crate::models::StoredIssuedCredential;
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -14,7 +15,6 @@ pub struct CoconutCredentialManager {
#[derive(Default)]
struct CoconutCredentialManagerInner {
credentials: Vec<StoredIssuedCredential>,
credential_usage: Vec<CredentialUsage>,
_next_id: i64,
}
@@ -51,56 +51,33 @@ impl CoconutCredentialManager {
credential_type,
epoch_id,
expired: false,
consumed: false,
})
}
async fn bandwidth_voucher_spent(&self, id: i64) -> bool {
self.inner
.read()
.await
.credential_usage
.iter()
.any(|c| c.credential_id == id)
}
async fn freepass_spent(&self, id: i64, gateway_id: &str) -> bool {
self.inner
.read()
.await
.credential_usage
.iter()
.any(|c| c.credential_id == id && c.gateway_id_bs58 == gateway_id)
}
/// Tries to retrieve one of the stored, unused credentials.
pub async fn get_next_unspect_bandwidth_voucher(&self) -> Option<StoredIssuedCredential> {
pub async fn get_next_unspent_ticketbook(&self) -> Option<StoredIssuedCredential> {
let guard = self.inner.read().await;
for credential in guard
.credentials
.iter()
.filter(|c| c.credential_type == "BandwidthVoucher")
.filter(|c| c.credential_type == "TicketBook")
{
if !self.bandwidth_voucher_spent(credential.id).await {
if !credential.consumed && !credential.expired {
return Some(credential.clone());
}
}
None
}
pub async fn get_next_unspect_freepass(
&self,
gateway_id: &str,
) -> Option<StoredIssuedCredential> {
pub async fn get_next_unspent_freepass(&self) -> Option<StoredIssuedCredential> {
let guard = self.inner.read().await;
for credential in guard
.credentials
.iter()
.filter(|c| c.credential_type == "FreeBandwidthPass")
{
if credential.expired {
continue;
}
if !self.freepass_spent(credential.id, gateway_id).await {
if !credential.consumed && !credential.expired {
return Some(credential.clone());
}
}
@@ -112,8 +89,21 @@ impl CoconutCredentialManager {
/// # Arguments
///
/// * `id`: Database id.
pub async fn consume_coconut_credential(&self, id: i64, gateway_id: &str) {
pub async fn consume_coconut_credential(&self, id: i64) {
let mut guard = self.inner.write().await;
if let Some(cred) = guard.credentials.get_mut(id as usize) {
cred.consumed = true;
}
}
pub async fn update_issued_credential(&self, credential_data: &[u8], id: i64, consumed: bool) {
let mut guard = self.inner.write().await;
if let Some(cred) = guard.credentials.get_mut(id as usize) {
cred.credential_data = credential_data.to_vec();
cred.consumed = consumed;
}
}
/// Inserts provided coin_indices_signatures into the database.
///
/// # Arguments
@@ -28,50 +28,45 @@ impl CoconutCredentialManager {
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, expired)
VALUES (?, ?, ?, ?, false)
INSERT INTO ecash_credentials(serialization_revision, credential_type, credential_data, epoch_id, expired, consumed)
VALUES (?, ?, ?, ?, false, false)
"#,
serialization_revision, credential_type, credential_data, epoch_id
).execute(&self.connection_pool).await?;
Ok(())
}
pub async fn get_next_unspect_freepass(
pub async fn get_next_unspent_freepass(
&self,
gateway_id: &str,
) -> Result<Option<StoredIssuedCredential>, sqlx::Error> {
// get a credential of freepass type that doesn't appear in `credential_usage` for the provided gateway_id
// get a credential of freepass type
sqlx::query_as(
r#"
SELECT *
FROM coconut_credentials
WHERE coconut_credentials.credential_type == "FreeBandwidthPass" AND coconut_credentials.expired = false
AND NOT EXISTS (SELECT 1
FROM credential_usage
WHERE credential_usage.credential_id = coconut_credentials.id
AND credential_usage.gateway_id_bs58 == ?)
ORDER BY coconut_credentials.id
FROM ecash_credentials
WHERE credential_type == "FreeBandwidthPass"
AND expired = false
AND consumed = false
ORDER BY id ASC
LIMIT 1
"#,
)
.bind(gateway_id)
.fetch_optional(&self.connection_pool)
.await
}
pub async fn get_next_unspect_bandwidth_voucher(
pub async fn get_next_unspent_ticketbook(
&self,
) -> Result<Option<StoredIssuedCredential>, sqlx::Error> {
// get a credential of bandwidth voucher type that doesn't appear in `credential_usage` for any gateway_id
// get a credential of bandwidth voucher type
sqlx::query_as(
r#"
SELECT *
FROM coconut_credentials
WHERE coconut_credentials.credential_type == "BandwidthVoucher"
AND NOT EXISTS (SELECT 1
FROM credential_usage
WHERE credential_usage.credential_id = coconut_credentials.id)
ORDER BY coconut_credentials.id
FROM ecash_credentials
WHERE credential_type == "TicketBook"
AND expired = false
AND consumed = false
ORDER BY id ASC
LIMIT 1
"#,
)
@@ -85,21 +80,32 @@ impl CoconutCredentialManager {
///
/// * `id`: Database id.
/// * `gateway_id`: id of the gateway that received the credential
pub async fn consume_coconut_credential(
&self,
id: i64,
gateway_id: &str,
) -> Result<(), sqlx::Error> {
pub async fn consume_coconut_credential(&self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO credential_usage (credential_id, gateway_id_bs58) VALUES (?, ?)",
id,
gateway_id
"UPDATE ecash_credentials SET consumed = TRUE WHERE id = ?",
id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub async fn update_issued_credential(
&self,
credential_data: &[u8],
id: i64,
consumed: bool,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE ecash_credentials SET credential_data = ?, consumed = ? WHERE id = ?",
credential_data,
consumed,
id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Marks the specified credential as expired
///
/// # Arguments
@@ -107,7 +113,7 @@ impl CoconutCredentialManager {
/// * `id`: Id of the credential to mark as expired.
pub async fn mark_expired(&self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE coconut_credentials SET expired = TRUE WHERE id = ?",
"UPDATE ecash_credentials SET expired = TRUE WHERE id = ?",
id
)
.execute(&self.connection_pool)
@@ -53,12 +53,11 @@ impl Storage for EphemeralStorage {
async fn get_next_unspent_credential(
&self,
gateway_id: &str,
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
// first try to get a free pass if available, otherwise fallback to bandwidth voucher
let maybe_freepass = self
.coconut_credential_manager
.get_next_unspect_freepass(gateway_id)
.get_next_unspent_freepass()
.await;
if maybe_freepass.is_some() {
return Ok(maybe_freepass);
@@ -66,18 +65,29 @@ impl Storage for EphemeralStorage {
Ok(self
.coconut_credential_manager
.get_next_unspect_bandwidth_voucher()
.get_next_unspent_ticketbook()
.await)
}
async fn consume_coconut_credential(
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
self.coconut_credential_manager
.consume_coconut_credential(id)
.await;
Ok(())
}
async fn update_issued_credential<'a>(
&self,
bandwidth_credential: StorableIssuedCredential<'a>,
id: i64,
gateway_id: &str,
consumed: bool,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.consume_coconut_credential(id, gateway_id)
.update_issued_credential(bandwidth_credential.credential_data, id, consumed)
.await;
Ok(())
}
async fn insert_coin_indices_sig(
&self,
@@ -77,12 +77,11 @@ impl Storage for PersistentStorage {
async fn get_next_unspent_credential(
&self,
gateway_id: &str,
) -> Result<Option<StoredIssuedCredential>, Self::StorageError> {
// first try to get a free pass if available, otherwise fallback to bandwidth voucher
let maybe_freepass = self
.coconut_credential_manager
.get_next_unspect_freepass(gateway_id)
.get_next_unspent_freepass()
.await?;
if maybe_freepass.is_some() {
return Ok(maybe_freepass);
@@ -90,17 +89,26 @@ impl Storage for PersistentStorage {
Ok(self
.coconut_credential_manager
.get_next_unspect_bandwidth_voucher()
.get_next_unspent_ticketbook()
.await?)
}
async fn consume_coconut_credential(
&self,
id: i64,
gateway_id: &str,
) -> Result<(), StorageError> {
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
self.coconut_credential_manager
.consume_coconut_credential(id, gateway_id)
.consume_coconut_credential(id)
.await?;
Ok(())
}
async fn update_issued_credential<'a>(
&self,
bandwidth_credential: StorableIssuedCredential<'a>,
id: i64,
consumed: bool,
) -> Result<(), Self::StorageError> {
self.coconut_credential_manager
.update_issued_credential(bandwidth_credential.credential_data, id, consumed)
.await?;
Ok(())
+15 -3
View File
@@ -19,7 +19,6 @@ pub trait Storage: Send + Sync {
/// that is also not marked as expired
async fn get_next_unspent_credential(
&self,
gateway_id: &str,
) -> Result<Option<StoredIssuedCredential>, Self::StorageError>;
/// Marks as consumed in the database the specified credential.
@@ -27,10 +26,23 @@ pub trait Storage: Send + Sync {
/// # Arguments
///
/// * `id`: Id of the credential to be consumed.
/// * `gateway_id`: id of the gateway that received the credential.
async fn consume_coconut_credential(
async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>;
/// Update in the database the specified credential.
///
/// # Arguments
///
/// * `bandwidth_credential` : New credential
/// * `id`: Id of the credential to be updated.
/// * `consumed`: if the credential is consumed or not
///
async fn update_issued_credential<'a>(
&self,
bandwidth_credential: StorableIssuedCredential<'a>,
id: i64,
consumed: bool,
) -> Result<(), Self::StorageError>;
/// Inserts provided coin_indices_signatures into the database.
///
/// # Arguments