Bläddra i källkod

refactor: mint memory localstore

thesimplekid 1 år sedan
förälder
incheckning
d2118a18c2

+ 91 - 0
crates/cashu-sdk/src/mint/localstore/memory.rs

@@ -0,0 +1,91 @@
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use cashu::nuts::nut02::mint::KeySet;
+use cashu::nuts::{Id, Proof};
+use cashu::secret::Secret;
+use cashu::types::{MeltQuote, MintQuote};
+use tokio::sync::Mutex;
+
+use super::{Error, LocalStore};
+
+#[derive(Default, Debug, Clone)]
+pub struct MemoryLocalStore {
+    keysets: Arc<Mutex<HashMap<Id, KeySet>>>,
+    mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
+    melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
+    pending_proofs: Arc<Mutex<HashMap<Secret, Proof>>>,
+    spent_proofs: Arc<Mutex<HashMap<Secret, Proof>>>,
+}
+
+#[async_trait(?Send)]
+impl LocalStore for MemoryLocalStore {
+    async fn add_keyset(&self, keyset: KeySet) -> Result<(), Error> {
+        self.keysets.lock().await.insert(keyset.id, keyset);
+        Ok(())
+    }
+
+    async fn get_keyset(&self, keyset_id: &Id) -> Result<Option<KeySet>, Error> {
+        Ok(self.keysets.lock().await.get(keyset_id).cloned())
+    }
+
+    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
+        self.mint_quotes
+            .lock()
+            .await
+            .insert(quote.id.clone(), quote);
+        Ok(())
+    }
+
+    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Error> {
+        Ok(self.mint_quotes.lock().await.get(quote_id).cloned())
+    }
+
+    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error> {
+        self.mint_quotes.lock().await.remove(quote_id);
+
+        Ok(())
+    }
+
+    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
+        self.melt_quotes
+            .lock()
+            .await
+            .insert(quote.id.clone(), quote);
+        Ok(())
+    }
+
+    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Error> {
+        Ok(self.melt_quotes.lock().await.get(quote_id).cloned())
+    }
+
+    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Error> {
+        self.melt_quotes.lock().await.remove(quote_id);
+
+        Ok(())
+    }
+
+    async fn add_spent_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error> {
+        self.spent_proofs.lock().await.insert(secret, proof);
+        Ok(())
+    }
+
+    async fn get_spent_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error> {
+        Ok(self.spent_proofs.lock().await.get(secret).cloned())
+    }
+
+    async fn add_pending_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error> {
+        self.pending_proofs.lock().await.insert(secret, proof);
+        Ok(())
+    }
+
+    async fn get_pending_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error> {
+        Ok(self.pending_proofs.lock().await.get(secret).cloned())
+    }
+
+    async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error> {
+        self.pending_proofs.lock().await.remove(secret);
+        Ok(())
+    }
+}

+ 54 - 0
crates/cashu-sdk/src/mint/localstore/mod.rs

@@ -0,0 +1,54 @@
+mod memory;
+
+use async_trait::async_trait;
+use cashu::nuts::nut02::mint::KeySet;
+use cashu::nuts::{Id, Proof};
+use cashu::secret::Secret;
+use cashu::types::{MeltQuote, MintQuote};
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Redb(#[from] redb::Error),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Database(#[from] redb::DatabaseError),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Transaction(#[from] redb::TransactionError),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Commit(#[from] redb::CommitError),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Table(#[from] redb::TableError),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Storage(#[from] redb::StorageError),
+    #[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+    #[error("`{0}`")]
+    Serde(#[from] serde_json::Error),
+}
+
+#[async_trait(?Send)]
+pub trait LocalStore {
+    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Error>;
+    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Error>;
+    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error>;
+
+    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Error>;
+    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Error>;
+    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Error>;
+
+    async fn add_keyset(&self, keyset: KeySet) -> Result<(), Error>;
+    async fn get_keyset(&self, id: &Id) -> Result<Option<KeySet>, Error>;
+
+    async fn add_spent_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error>;
+    async fn get_spent_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error>;
+
+    async fn add_pending_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error>;
+    async fn get_pending_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error>;
+    async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error>;
+}

+ 133 - 61
crates/cashu-sdk/src/mint.rs → crates/cashu-sdk/src/mint/mod.rs

@@ -1,7 +1,6 @@
 use std::collections::{HashMap, HashSet};
 
 use cashu::dhke::{sign_message, verify_message};
-pub use cashu::error::mint::Error;
 use cashu::nuts::{
     BlindedMessage, BlindedSignature, MeltBolt11Request, MeltBolt11Response, Proof, SwapRequest,
     SwapResponse, *,
@@ -11,40 +10,61 @@ use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};
 use cashu::secret::Secret;
 use cashu::Amount;
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 use tracing::{debug, info};
 
-use crate::types::MeltQuote;
 use crate::utils::unix_time;
 use crate::Mnemonic;
 
-pub struct Mint {
+mod localstore;
+
+use localstore::LocalStore;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    /// Unknown Keyset
+    #[error("Unknown Keyset")]
+    UnknownKeySet,
+    /// Inactive Keyset
+    #[error("Inactive Keyset")]
+    InactiveKeyset,
+    #[error("No key for amount")]
+    AmountKey,
+    #[error("Amount")]
+    Amount,
+    #[error("Duplicate proofs")]
+    DuplicateProofs,
+    #[error("Token Spent")]
+    TokenSpent,
+    #[error("`{0}`")]
+    Custom(String),
+    #[error("`{0}`")]
+    Cashu(#[from] cashu::error::mint::Error),
+    #[error("`{0}`")]
+    Localstore(#[from] localstore::Error),
+}
+
+pub struct Mint<L: LocalStore> {
     //    pub pubkey: PublicKey
-    pub keysets: HashMap<Id, nut02::mint::KeySet>,
     pub keysets_info: HashMap<Id, MintKeySetInfo>,
     //    pub pubkey: PublicKey,
     mnemonic: Mnemonic,
-    pub spent_secrets: HashSet<Secret>,
-    pub pending_secrets: HashSet<Secret>,
     pub fee_reserve: FeeReserve,
-    pub melt_quotes: HashMap<String, MeltQuote>,
+    localstore: L,
 }
 
-impl Mint {
-    pub fn new(
+impl<L: LocalStore> Mint<L> {
+    pub async fn new(
+        localstore: L,
         mnemonic: Mnemonic,
         keysets_info: HashSet<MintKeySetInfo>,
-        spent_secrets: HashSet<Secret>,
-        melt_quotes: Vec<MeltQuote>,
         min_fee_reserve: Amount,
         percent_fee_reserve: f32,
-    ) -> Self {
-        let mut keysets = HashMap::default();
+    ) -> Result<Self, Error> {
         let mut info = HashMap::default();
 
         let mut active_units: HashSet<CurrencyUnit> = HashSet::default();
 
-        let melt_quotes = melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect();
-
         // Check that there is only one active keyset per unit
         for keyset_info in keysets_info {
             if keyset_info.active && !active_units.insert(keyset_info.unit.clone()) {
@@ -59,38 +79,35 @@ impl Mint {
                 keyset_info.max_order,
             );
 
-            keysets.insert(keyset.id, keyset);
-
             info.insert(keyset_info.id, keyset_info);
+
+            localstore.add_keyset(keyset).await?;
         }
 
-        Self {
+        Ok(Self {
+            localstore,
             mnemonic,
-            keysets,
-            melt_quotes,
             keysets_info: info,
-            spent_secrets,
-            pending_secrets: HashSet::new(),
             fee_reserve: FeeReserve {
                 min_fee_reserve,
                 percent_fee_reserve,
             },
-        }
+        })
     }
 
     /// Retrieve the public keys of the active keyset for distribution to
     /// wallet clients
-    pub fn keyset_pubkeys(&self, keyset_id: &Id) -> Option<KeysResponse> {
-        let keyset = match self.keysets.get(keyset_id) {
+    pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<Option<KeysResponse>, Error> {
+        let keyset = match self.localstore.get_keyset(keyset_id).await? {
             Some(keyset) => keyset.clone(),
             None => {
-                return None;
+                return Ok(None);
             }
         };
 
-        Some(KeysResponse {
+        Ok(Some(KeysResponse {
             keysets: vec![keyset.into()],
-        })
+        }))
     }
 
     /// Return a list of all supported keysets
@@ -104,13 +121,22 @@ impl Mint {
         KeysetResponse { keysets }
     }
 
-    pub fn keyset(&self, id: &Id) -> Option<KeySet> {
-        self.keysets.get(id).map(|ks| ks.clone().into())
+    pub async fn keyset(&self, id: &Id) -> Result<Option<KeySet>, Error> {
+        Ok(self
+            .localstore
+            .get_keyset(id)
+            .await?
+            .map(|ks| ks.clone().into()))
     }
 
     /// Add current keyset to inactive keysets
     /// Generate new keyset
-    pub fn rotate_keyset(&mut self, unit: CurrencyUnit, derivation_path: &str, max_order: u8) {
+    pub async fn rotate_keyset(
+        &mut self,
+        unit: CurrencyUnit,
+        derivation_path: &str,
+        max_order: u8,
+    ) -> Result<(), Error> {
         let new_keyset = MintKeySet::generate(
             &self.mnemonic.to_seed_normalized(""),
             unit.clone(),
@@ -118,7 +144,7 @@ impl Mint {
             max_order,
         );
 
-        self.keysets.insert(new_keyset.id, new_keyset.clone());
+        self.localstore.add_keyset(new_keyset.clone()).await?;
 
         for mint_keyset_info in self.keysets_info.values_mut() {
             if mint_keyset_info.active && mint_keyset_info.unit.eq(&unit) {
@@ -137,16 +163,17 @@ impl Mint {
         };
 
         self.keysets_info.insert(new_keyset.id, mint_keyset_info);
+        Ok(())
     }
 
-    pub fn process_mint_request(
+    pub async fn process_mint_request(
         &mut self,
         mint_request: nut04::MintBolt11Request,
     ) -> Result<nut04::MintBolt11Response, Error> {
         let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
 
         for blinded_message in mint_request.outputs {
-            blind_signatures.push(self.blind_sign(&blinded_message)?);
+            blind_signatures.push(self.blind_sign(&blinded_message).await?);
         }
 
         Ok(nut04::MintBolt11Response {
@@ -154,14 +181,21 @@ impl Mint {
         })
     }
 
-    fn blind_sign(&self, blinded_message: &BlindedMessage) -> Result<BlindedSignature, Error> {
+    async fn blind_sign(
+        &self,
+        blinded_message: &BlindedMessage,
+    ) -> Result<BlindedSignature, Error> {
         let BlindedMessage {
             amount,
             b,
             keyset_id,
         } = blinded_message;
 
-        let keyset = self.keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?;
+        let keyset = self
+            .localstore
+            .get_keyset(keyset_id)
+            .await?
+            .ok_or(Error::UnknownKeySet)?;
 
         // Check that the keyset is active and should be used to sign
         if !self
@@ -187,7 +221,7 @@ impl Mint {
         })
     }
 
-    pub fn process_swap_request(
+    pub async fn process_swap_request(
         &mut self,
         swap_request: SwapRequest,
     ) -> Result<SwapResponse, Error> {
@@ -213,30 +247,41 @@ impl Mint {
         }
 
         for proof in &swap_request.inputs {
-            self.verify_proof(proof)?
+            self.verify_proof(proof).await?
         }
 
-        for secret in secrets {
-            self.spent_secrets.insert(secret);
+        for (secret, proof) in secrets.iter().zip(swap_request.inputs) {
+            self.localstore
+                .add_spent_proof(secret.clone(), proof)
+                .await
+                .unwrap();
         }
 
-        let promises: Vec<BlindedSignature> = swap_request
-            .outputs
-            .iter()
-            .map(|b| self.blind_sign(b).unwrap())
-            .collect();
+        let mut promises = Vec::with_capacity(swap_request.outputs.len());
+
+        for output in swap_request.outputs {
+            let promise = self.blind_sign(&output).await?;
+            promises.push(promise);
+        }
 
         Ok(SwapResponse::new(promises))
     }
 
-    fn verify_proof(&self, proof: &Proof) -> Result<(), Error> {
-        if self.spent_secrets.contains(&proof.secret) {
+    async fn verify_proof(&self, proof: &Proof) -> Result<(), Error> {
+        if self
+            .localstore
+            .get_spent_proof(&proof.secret)
+            .await
+            .unwrap()
+            .is_some()
+        {
             return Err(Error::TokenSpent);
         }
 
         let keyset = self
-            .keysets
-            .get(&proof.keyset_id)
+            .localstore
+            .get_keyset(&proof.keyset_id)
+            .await?
             .ok_or(Error::UnknownKeySet)?;
 
         let Some(keypair) = keyset.keys.0.get(&proof.amount) else {
@@ -253,7 +298,7 @@ impl Mint {
     }
 
     #[cfg(feature = "nut07")]
-    pub fn check_spendable(
+    pub async fn check_spendable(
         &self,
         check_spendable: &CheckSpendableRequest,
     ) -> Result<CheckSpendableResponse, Error> {
@@ -261,15 +306,40 @@ impl Mint {
         let mut pending = Vec::with_capacity(check_spendable.proofs.len());
 
         for proof in &check_spendable.proofs {
-            spendable.push(!self.spent_secrets.contains(&proof.secret));
-            pending.push(self.pending_secrets.contains(&proof.secret));
+            spendable.push(
+                self.localstore
+                    .get_spent_proof(&proof.secret)
+                    .await
+                    .unwrap()
+                    .is_none(),
+            );
+            pending.push(
+                self.localstore
+                    .get_pending_proof(&proof.secret)
+                    .await
+                    .unwrap()
+                    .is_some(),
+            );
         }
 
         Ok(CheckSpendableResponse { spendable, pending })
     }
 
-    pub fn verify_melt_request(&mut self, melt_request: &MeltBolt11Request) -> Result<(), Error> {
-        let quote = self.melt_quotes.get(&melt_request.quote).unwrap();
+    pub async fn verify_melt_request(
+        &mut self,
+        melt_request: &MeltBolt11Request,
+    ) -> Result<(), Error> {
+        let quote = self
+            .localstore
+            .get_melt_quote(&melt_request.quote)
+            .await
+            .unwrap();
+        let quote = if let Some(quote) = quote {
+            quote
+        } else {
+            return Err(Error::Custom("Unknown Quote".to_string()));
+        };
+
         let proofs_total = melt_request.proofs_amount();
 
         let required_total = quote.amount + quote.fee_reserve;
@@ -290,23 +360,25 @@ impl Mint {
         }
 
         for proof in &melt_request.inputs {
-            self.verify_proof(proof)?
+            self.verify_proof(proof).await?
         }
 
         Ok(())
     }
 
-    pub fn process_melt_request(
+    pub async fn process_melt_request(
         &mut self,
         melt_request: &MeltBolt11Request,
         preimage: &str,
         total_spent: Amount,
     ) -> Result<MeltBolt11Response, Error> {
-        self.verify_melt_request(melt_request)?;
+        self.verify_melt_request(melt_request).await?;
 
-        let secrets = Vec::with_capacity(melt_request.inputs.len());
-        for secret in secrets {
-            self.spent_secrets.insert(secret);
+        for input in &melt_request.inputs {
+            self.localstore
+                .add_spent_proof(input.secret.clone(), input.clone())
+                .await
+                .unwrap();
         }
 
         let mut change = None;
@@ -333,7 +405,7 @@ impl Mint {
                 let mut blinded_message = blinded_message;
                 blinded_message.amount = *amount;
 
-                let signature = self.blind_sign(&blinded_message)?;
+                let signature = self.blind_sign(&blinded_message).await?;
                 change_sigs.push(signature)
             }