Browse Source

feat: add memory localstore

thesimplekid 1 year ago
parent
commit
76ec46c024

+ 1 - 0
crates/cashu-sdk/src/lib.rs

@@ -11,6 +11,7 @@ use tokio::runtime::Runtime;
 #[cfg(feature = "wallet")]
 pub mod client;
 
+mod localstore;
 #[cfg(feature = "mint")]
 pub mod mint;
 pub mod utils;

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

@@ -0,0 +1,110 @@
+use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo};
+use cashu::types::{MeltQuote, MintQuote};
+use cashu::url::UncheckedUrl;
+use tokio::sync::Mutex;
+
+use super::{Error, LocalStore};
+
+#[derive(Default, Debug, Clone)]
+pub struct MemoryLocalStore {
+    mints: Arc<Mutex<HashMap<UncheckedUrl, Option<MintInfo>>>>,
+    mint_keysets: Arc<Mutex<HashMap<UncheckedUrl, HashSet<KeySetInfo>>>>,
+    mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
+    melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
+    mint_keys: Arc<Mutex<HashMap<Id, Keys>>>,
+}
+
+#[async_trait(?Send)]
+impl LocalStore for MemoryLocalStore {
+    async fn add_mint(
+        &self,
+        mint_url: UncheckedUrl,
+        mint_info: Option<MintInfo>,
+    ) -> Result<(), Error> {
+        self.mints.lock().await.insert(mint_url, mint_info);
+        Ok(())
+    }
+
+    async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error> {
+        Ok(self.mints.lock().await.get(&mint_url).cloned().flatten())
+    }
+
+    async fn add_mint_keysets(
+        &self,
+        mint_url: UncheckedUrl,
+        keysets: Vec<KeySetInfo>,
+    ) -> 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);
+
+        Ok(())
+    }
+
+    async fn get_mint_keysets(
+        &self,
+        mint_url: UncheckedUrl,
+    ) -> Result<Option<Vec<KeySetInfo>>, Error> {
+        Ok(self
+            .mint_keysets
+            .lock()
+            .await
+            .get(&mint_url)
+            .map(|ks| ks.iter().cloned().collect()))
+    }
+
+    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_keys(&self, keys: Keys) -> Result<(), Error> {
+        self.mint_keys.lock().await.insert(Id::from(&keys), keys);
+        Ok(())
+    }
+
+    async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Error> {
+        Ok(self.mint_keys.lock().await.get(id).cloned())
+    }
+
+    async fn remove_keys(&self, id: &Id) -> Result<(), Error> {
+        self.mint_keys.lock().await.remove(id);
+        Ok(())
+    }
+}

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

@@ -0,0 +1,41 @@
+mod memory;
+use async_trait::async_trait;
+use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo};
+use cashu::types::{MeltQuote, MintQuote};
+use cashu::url::UncheckedUrl;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {}
+
+#[async_trait(?Send)]
+pub trait LocalStore {
+    async fn add_mint(
+        &self,
+        mint_url: UncheckedUrl,
+        mint_info: Option<MintInfo>,
+    ) -> Result<(), Error>;
+    async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error>;
+
+    async fn add_mint_keysets(
+        &self,
+        mint_url: UncheckedUrl,
+        keysets: Vec<KeySetInfo>,
+    ) -> Result<(), Error>;
+    async fn get_mint_keysets(
+        &self,
+        mint_url: UncheckedUrl,
+    ) -> Result<Option<Vec<KeySetInfo>>, Error>;
+
+    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_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>;
+}

+ 45 - 38
crates/cashu-sdk/src/wallet.rs

@@ -1,5 +1,5 @@
 //! Cashu Wallet
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::str::FromStr;
 
 use bip39::Mnemonic;
@@ -7,8 +7,8 @@ use cashu::dhke::{construct_proofs, unblind_message};
 #[cfg(feature = "nut07")]
 use cashu::nuts::nut00::mint;
 use cashu::nuts::{
-    BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, PreMintSecrets, PreSwap, Proof,
-    Proofs, SwapRequest, Token,
+    BlindedSignature, CurrencyUnit, Id, Keys, PreMintSecrets, PreSwap, Proof, Proofs, SwapRequest,
+    Token,
 };
 #[cfg(feature = "nut07")]
 use cashu::types::ProofsStatus;
@@ -20,6 +20,7 @@ use thiserror::Error;
 use tracing::warn;
 
 use crate::client::Client;
+use crate::localstore::LocalStore;
 use crate::utils::unix_time;
 
 #[derive(Debug, Error)]
@@ -39,6 +40,8 @@ pub enum Error {
     #[error("Quote Unknown")]
     QuoteUnknown,
     #[error("`{0}`")]
+    LocalStore(#[from] super::localstore::Error),
+    #[error("`{0}`")]
     Custom(String),
 }
 
@@ -49,36 +52,37 @@ pub struct BackupInfo {
 }
 
 #[derive(Clone, Debug)]
-pub struct Wallet<C: Client> {
+pub struct Wallet<C: Client, L: LocalStore> {
     backup_info: Option<BackupInfo>,
     pub client: C,
-    pub mints: HashMap<UncheckedUrl, Option<MintInfo>>,
-    pub mint_keysets: HashMap<UncheckedUrl, HashSet<KeySetInfo>>,
-    pub mint_quotes: HashMap<String, MintQuote>,
-    pub melt_quotes: HashMap<String, MeltQuote>,
-    pub mint_keys: HashMap<Id, Keys>,
-    pub balance: Amount,
+    pub localstore: L,
 }
 
-impl<C: Client> Wallet<C> {
-    pub fn new(
+impl<C: Client, L: LocalStore> Wallet<C, L> {
+    pub async fn new(
         client: C,
-        mints: HashMap<UncheckedUrl, Option<MintInfo>>,
-        mint_keysets: HashMap<UncheckedUrl, HashSet<KeySetInfo>>,
+        localstore: L,
         mint_quotes: Vec<MintQuote>,
         melt_quotes: Vec<MeltQuote>,
         backup_info: Option<BackupInfo>,
-        mint_keys: HashMap<Id, Keys>,
+        mint_keys: Vec<Keys>,
     ) -> Self {
+        for quote in mint_quotes {
+            localstore.add_mint_quote(quote).await.ok();
+        }
+
+        for quote in melt_quotes {
+            localstore.add_melt_quote(quote).await.ok();
+        }
+
+        for keys in mint_keys {
+            localstore.add_keys(keys).await.ok();
+        }
+
         Self {
             backup_info,
             client,
-            mints,
-            mint_keysets,
-            mint_keys,
-            mint_quotes: mint_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
-            melt_quotes: melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
-            balance: Amount::ZERO,
+            localstore,
         }
     }
 
@@ -168,8 +172,7 @@ impl<C: Client> Wallet<C> {
             expiry: quote_res.expiry,
         };
 
-        self.mint_quotes
-            .insert(quote_res.quote.clone(), quote.clone());
+        self.localstore.add_mint_quote(quote.clone()).await?;
 
         Ok(quote)
     }
@@ -179,7 +182,7 @@ impl<C: Client> Wallet<C> {
         mint_url: &UncheckedUrl,
         unit: &CurrencyUnit,
     ) -> Result<Option<Id>, Error> {
-        if let Some(keysets) = self.mint_keysets.get(mint_url) {
+        if let Some(keysets) = self.localstore.get_mint_keysets(mint_url.clone()).await? {
             for keyset in keysets {
                 if keyset.unit.eq(unit) && keyset.active {
                     return Ok(Some(keyset.id));
@@ -188,8 +191,9 @@ impl<C: Client> Wallet<C> {
         } else {
             let keysets = self.client.get_mint_keysets(mint_url.try_into()?).await?;
 
-            self.mint_keysets
-                .insert(mint_url.clone(), keysets.keysets.into_iter().collect());
+            self.localstore
+                .add_mint_keysets(mint_url.clone(), keysets.keysets.into_iter().collect())
+                .await?;
         }
 
         Ok(None)
@@ -204,7 +208,7 @@ impl<C: Client> Wallet<C> {
 
         let mut keys = None;
 
-        if let Some(k) = self.mint_keys.get(&active_keyset_id) {
+        if let Some(k) = self.localstore.get_keys(&active_keyset_id).await? {
             keys = Some(k.clone())
         } else {
             let keysets = self.client.get_mint_keys(mint_url.try_into()?).await?;
@@ -213,7 +217,7 @@ impl<C: Client> Wallet<C> {
                 if keyset.id.eq(&active_keyset_id) {
                     keys = Some(keyset.keys.clone())
                 }
-                self.mint_keys.insert(keyset.id, keyset.keys);
+                self.localstore.add_keys(keyset.keys).await?;
             }
         }
 
@@ -222,7 +226,7 @@ impl<C: Client> Wallet<C> {
 
     /// Mint
     pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Proofs, Error> {
-        let quote_info = self.mint_quotes.get(quote_id);
+        let quote_info = self.localstore.get_mint_quote(quote_id).await?;
 
         let quote_info = if let Some(quote) = quote_info {
             if quote.expiry.le(&unix_time()) {
@@ -258,16 +262,16 @@ impl<C: Client> Wallet<C> {
             )
             .await?;
 
-        let keys = self.mint_keys.get(&active_keyset_id).unwrap();
+        let keys = self.localstore.get_keys(&active_keyset_id).await?.unwrap();
 
         let proofs = construct_proofs(
             mint_res.signatures,
             premint_secrets.rs(),
             premint_secrets.secrets(),
-            keys,
+            &keys,
         )?;
 
-        self.mint_quotes.remove(&quote_info.id);
+        self.localstore.remove_mint_quote(&quote_info.id).await?;
 
         Ok(proofs)
     }
@@ -295,7 +299,7 @@ impl<C: Client> Wallet<C> {
 
             // TODO: if none fetch keyset for mint
 
-            let keys = self.mint_keys.get(&active_keyset_id.unwrap()).cloned();
+            let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
 
             // Sum amount of all proofs
             let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
@@ -359,7 +363,7 @@ impl<C: Client> Wallet<C> {
         })
     }
 
-    pub fn process_split_response(
+    pub async fn process_split_response(
         &self,
         blinded_messages: PreMintSecrets,
         promises: Vec<BlindedSignature>,
@@ -368,8 +372,9 @@ impl<C: Client> Wallet<C> {
 
         for (promise, premint) in promises.iter().zip(blinded_messages) {
             let a = self
-                .mint_keys
-                .get(&promise.keyset_id)
+                .localstore
+                .get_keys(&promise.keyset_id)
+                .await?
                 .unwrap()
                 .amount_key(promise.amount)
                 .unwrap()
@@ -475,7 +480,7 @@ impl<C: Client> Wallet<C> {
             expiry: quote_res.expiry,
         };
 
-        self.melt_quotes.insert(quote.id.clone(), quote.clone());
+        self.localstore.add_melt_quote(quote.clone()).await?;
 
         Ok(quote)
     }
@@ -487,7 +492,7 @@ impl<C: Client> Wallet<C> {
         quote_id: &str,
         proofs: Proofs,
     ) -> Result<Melted, Error> {
-        let quote_info = self.melt_quotes.get(quote_id);
+        let quote_info = self.localstore.get_melt_quote(quote_id).await?;
 
         let quote_info = if let Some(quote) = quote_info {
             if quote.expiry.le(&unix_time()) {
@@ -532,6 +537,8 @@ impl<C: Client> Wallet<C> {
             change: change_proofs,
         };
 
+        self.localstore.remove_melt_quote(&quote_info.id).await?;
+
         Ok(melted)
     }