Procházet zdrojové kódy

refactor: proofs in localstore

thesimplekid před 1 rokem
rodič
revize
e8db30af3a

+ 34 - 3
crates/cashu-sdk/src/localstore/memory.rs

@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
 use std::sync::Arc;
 
 use async_trait::async_trait;
-use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo};
+use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo, Proof, Proofs};
 use cashu::types::{MeltQuote, MintQuote};
 use cashu::url::UncheckedUrl;
 use tokio::sync::Mutex;
@@ -16,6 +16,7 @@ pub struct MemoryLocalStore {
     mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
     melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
     mint_keys: Arc<Mutex<HashMap<Id, Keys>>>,
+    proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
 }
 
 #[async_trait(?Send)]
@@ -40,8 +41,8 @@ impl LocalStore for MemoryLocalStore {
     ) -> Result<(), Error> {
         let mut current_keysets = self.mint_keysets.lock().await;
 
-        let current_keysets = current_keysets.entry(mint_url).or_insert(HashSet::new());
-        current_keysets.extend(keysets);
+        let mint_keysets = current_keysets.entry(mint_url).or_insert(HashSet::new());
+        mint_keysets.extend(keysets);
 
         Ok(())
     }
@@ -107,4 +108,34 @@ impl LocalStore for MemoryLocalStore {
         self.mint_keys.lock().await.remove(id);
         Ok(())
     }
+
+    async fn add_proofs(&self, mint_url: UncheckedUrl, proofs: Proofs) -> Result<(), Error> {
+        let mut all_proofs = self.proofs.lock().await;
+
+        let mint_proofs = all_proofs.entry(mint_url).or_insert(HashSet::new());
+        mint_proofs.extend(proofs);
+
+        Ok(())
+    }
+
+    async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
+        Ok(self
+            .proofs
+            .lock()
+            .await
+            .get(&mint_url)
+            .map(|p| p.iter().cloned().collect()))
+    }
+
+    async fn remove_proofs(&self, mint_url: UncheckedUrl, proofs: Proofs) -> Result<(), Error> {
+        let mut mint_proofs = self.proofs.lock().await;
+
+        if let Some(mint_proofs) = mint_proofs.get_mut(&mint_url) {
+            for proof in proofs {
+                mint_proofs.remove(&proof);
+            }
+        }
+
+        Ok(())
+    }
 }

+ 5 - 1
crates/cashu-sdk/src/localstore/mod.rs

@@ -1,6 +1,6 @@
 mod memory;
 use async_trait::async_trait;
-use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo};
+use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs};
 use cashu::types::{MeltQuote, MintQuote};
 use cashu::url::UncheckedUrl;
 use thiserror::Error;
@@ -38,4 +38,8 @@ pub trait LocalStore {
     async fn add_keys(&self, keys: Keys) -> Result<(), Error>;
     async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Error>;
     async fn remove_keys(&self, id: &Id) -> Result<(), Error>;
+
+    async fn add_proofs(&self, mint_url: UncheckedUrl, proof: Proofs) -> Result<(), Error>;
+    async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error>;
+    async fn remove_proofs(&self, mint_url: UncheckedUrl, proofs: Proofs) -> Result<(), Error>;
 }

+ 49 - 40
crates/cashu-sdk/src/wallet.rs

@@ -53,9 +53,9 @@ pub struct BackupInfo {
 
 #[derive(Clone, Debug)]
 pub struct Wallet<C: Client, L: LocalStore> {
-    backup_info: Option<BackupInfo>,
     pub client: C,
-    pub localstore: L,
+    localstore: L,
+    backup_info: Option<BackupInfo>,
 }
 
 impl<C: Client, L: LocalStore> Wallet<C, L> {
@@ -128,29 +128,32 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         })
     }
 
-    // Mint a token
-    pub async fn mint_token(
-        &mut self,
-        mint_url: UncheckedUrl,
-        amount: Amount,
-        memo: Option<String>,
-        unit: Option<CurrencyUnit>,
-    ) -> Result<Token, Error> {
-        let quote = self
-            .mint_quote(
-                mint_url.clone(),
-                amount,
-                unit.clone()
-                    .ok_or(Error::Custom("Unit required".to_string()))?,
-            )
-            .await?;
-
-        let proofs = self.mint(mint_url.clone(), &quote.id).await?;
+    /*
+        // TODO: This should be create token
+        // the requited proofs for the token amount may already be in the wallet and mint is not needed
+        // Mint a token
+        pub async fn mint_token(
+            &mut self,
+            mint_url: UncheckedUrl,
+            amount: Amount,
+            memo: Option<String>,
+            unit: Option<CurrencyUnit>,
+        ) -> Result<Token, Error> {
+            let quote = self
+                .mint_quote(
+                    mint_url.clone(),
+                    amount,
+                    unit.clone()
+                        .ok_or(Error::Custom("Unit required".to_string()))?,
+                )
+                .await?;
 
-        let token = Token::new(mint_url.clone(), proofs, memo, unit);
-        Ok(token?)
-    }
+            let proofs = self.mint(mint_url.clone(), &quote.id).await?;
 
+            let token = Token::new(mint_url.clone(), proofs, memo, unit);
+            Ok(token?)
+        }
+    */
     /// Mint Quote
     pub async fn mint_quote(
         &mut self,
@@ -225,7 +228,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
     }
 
     /// Mint
-    pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Proofs, Error> {
+    pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Amount, Error> {
         let quote_info = self.localstore.get_mint_quote(quote_id).await?;
 
         let quote_info = if let Some(quote) = quote_info {
@@ -271,29 +274,28 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
             &keys,
         )?;
 
+        let minted_amount = proofs.iter().map(|p| p.amount).sum();
+
+        // Remove filled quote from store
         self.localstore.remove_mint_quote(&quote_info.id).await?;
 
-        Ok(proofs)
+        // Add new proofs to store
+        self.localstore.add_proofs(mint_url, proofs).await?;
+
+        Ok(minted_amount)
     }
 
     /// Receive
-    pub async fn receive(&mut self, encoded_token: &str) -> Result<Proofs, Error> {
+    pub async fn receive(&mut self, encoded_token: &str) -> Result<(), Error> {
         let token_data = Token::from_str(encoded_token)?;
 
         let unit = token_data.unit.unwrap_or_default();
 
-        let mut proofs: Vec<Proofs> = vec![vec![]];
+        let mut proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
         for token in token_data.token {
             if token.proofs.is_empty() {
                 continue;
             }
-            /*
-                        let keys = if token.mint.to_string().eq(&self.mint_url.to_string()) {
-                            self.mint_keys.clone()
-                        } else {
-                            self.client.get_mint_keys(token.mint.try_into()?).await?
-                        };
-            */
 
             let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
 
@@ -305,7 +307,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
             let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
 
             let pre_swap = self
-                .create_split(&token.mint, &unit, Some(amount), token.proofs)
+                .create_swap(&token.mint, &unit, Some(amount), token.proofs)
                 .await?;
 
             let swap_response = self
@@ -320,13 +322,20 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
                 pre_swap.pre_mint_secrets.secrets(),
                 &keys.unwrap(),
             )?;
-            proofs.push(p);
+            let mint_proofs = proofs.entry(token.mint).or_insert(Vec::new());
+
+            mint_proofs.extend(p);
         }
-        Ok(proofs.iter().flatten().cloned().collect())
+
+        for (mint, proofs) in proofs {
+            self.localstore.add_proofs(mint, proofs).await?;
+        }
+
+        Ok(())
     }
 
     /// Create Split Payload
-    async fn create_split(
+    async fn create_swap(
         &mut self,
         mint_url: &UncheckedUrl,
         unit: &CurrencyUnit,
@@ -363,7 +372,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         })
     }
 
-    pub async fn process_split_response(
+    pub async fn process_swap_response(
         &self,
         blinded_messages: PreMintSecrets,
         promises: Vec<BlindedSignature>,
@@ -412,7 +421,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         }
 
         let pre_swap = self
-            .create_split(mint_url, unit, Some(amount), proofs)
+            .create_swap(mint_url, unit, Some(amount), proofs)
             .await?;
 
         let swap_response = self

+ 7 - 0
crates/cashu/src/nuts/nut00.rs

@@ -2,6 +2,7 @@
 // https://github.com/cashubtc/nuts/blob/main/00.md
 
 use std::fmt;
+use std::hash::{Hash, Hasher};
 use std::str::FromStr;
 
 use serde::{Deserialize, Serialize};
@@ -409,6 +410,12 @@ pub struct Proof {
     pub c: PublicKey,
 }
 
+impl Hash for Proof {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.secret.hash(state);
+    }
+}
+
 impl Ord for Proof {
     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
         self.amount.cmp(&other.amount)