Browse Source

feat: mint redb localstore

thesimplekid 1 year ago
parent
commit
d5887f1097

+ 1 - 1
.helix/languages.toml

@@ -1,4 +1,4 @@
 [[language]]
 name = "rust"
-config = { cargo = { features = [ "wallet", "mint" ] } }
+config = { cargo = { features = [ "wallet", "mint", "redb" ] } }
 

+ 10 - 4
crates/cashu-sdk/src/mint/localstore/memory.rs

@@ -84,8 +84,11 @@ impl LocalStore for MemoryLocalStore {
         Ok(())
     }
 
-    async fn add_spent_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error> {
-        self.spent_proofs.lock().await.insert(secret, proof);
+    async fn add_spent_proof(&self, proof: Proof) -> Result<(), Error> {
+        self.spent_proofs
+            .lock()
+            .await
+            .insert(proof.secret.clone(), proof);
         Ok(())
     }
 
@@ -93,8 +96,11 @@ impl LocalStore for MemoryLocalStore {
         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);
+    async fn add_pending_proof(&self, proof: Proof) -> Result<(), Error> {
+        self.pending_proofs
+            .lock()
+            .await
+            .insert(proof.secret.clone(), proof);
         Ok(())
     }
 

+ 6 - 2
crates/cashu-sdk/src/mint/localstore/mod.rs

@@ -1,4 +1,6 @@
 mod memory;
+#[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+mod redb_store;
 
 use std::collections::HashMap;
 
@@ -7,6 +9,8 @@ use cashu::nuts::nut02::mint::KeySet;
 use cashu::nuts::{CurrencyUnit, Id, Proof};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
+#[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+pub use redb_store::RedbLocalStore;
 use thiserror::Error;
 
 #[derive(Debug, Error)]
@@ -52,10 +56,10 @@ pub trait LocalStore {
     async fn get_keyset(&self, id: &Id) -> Result<Option<KeySet>, Error>;
     async fn get_keysets(&self) -> Result<Vec<KeySet>, Error>;
 
-    async fn add_spent_proof(&self, secret: Secret, proof: Proof) -> Result<(), Error>;
+    async fn add_spent_proof(&self, 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 add_pending_proof(&self, 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>;
 }

+ 282 - 0
crates/cashu-sdk/src/mint/localstore/redb_store.rs

@@ -0,0 +1,282 @@
+use std::collections::HashMap;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use cashu::nuts::{CurrencyUnit, Id, MintKeySet as KeySet, Proof};
+use cashu::secret::Secret;
+use cashu::types::{MeltQuote, MintQuote};
+use redb::{Database, ReadableTable, TableDefinition};
+use tokio::sync::Mutex;
+
+use super::{Error, LocalStore};
+
+const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("active_keysets");
+const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
+const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
+const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
+const PENDING_PROOFS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("pending_proofs");
+const SPENT_PROOFS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("spent_proofs");
+
+#[derive(Debug, Clone)]
+pub struct RedbLocalStore {
+    db: Arc<Mutex<Database>>,
+}
+
+impl RedbLocalStore {
+    pub fn new(path: &str) -> Result<Self, Error> {
+        let db = Database::create(path)?;
+
+        let write_txn = db.begin_write()?;
+        {
+            let _ = write_txn.open_table(ACTIVE_KEYSETS_TABLE)?;
+            let _ = write_txn.open_table(KEYSETS_TABLE)?;
+            let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
+            let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
+            let _ = write_txn.open_table(PENDING_PROOFS_TABLE)?;
+            let _ = write_txn.open_table(SPENT_PROOFS_TABLE)?;
+        }
+        write_txn.commit()?;
+
+        Ok(Self {
+            db: Arc::new(Mutex::new(db)),
+        })
+    }
+}
+
+#[async_trait(?Send)]
+impl LocalStore for RedbLocalStore {
+    async fn add_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(ACTIVE_KEYSETS_TABLE)?;
+            table.insert(unit.to_string().as_str(), id.to_string().as_str())?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(ACTIVE_KEYSETS_TABLE)?;
+
+        if let Some(id) = table.get(unit.to_string().as_str())? {
+            return Ok(Some(Id::from_str(id.value()).unwrap()));
+        }
+
+        Ok(None)
+    }
+
+    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(ACTIVE_KEYSETS_TABLE)?;
+
+        let mut active_keysets = HashMap::new();
+
+        for keyset in table.iter()? {
+            if let Ok((unit, id)) = keyset {
+                let unit = serde_json::from_str(unit.value())?;
+                let id = serde_json::from_str(id.value())?;
+
+                active_keysets.insert(unit, id);
+            }
+        }
+
+        Ok(active_keysets)
+    }
+
+    async fn add_keyset(&self, keyset: KeySet) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(KEYSETS_TABLE)?;
+            table.insert(
+                Id::from(keyset.clone()).to_string().as_str(),
+                serde_json::to_string(&keyset)?.as_str(),
+            )?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_keyset(&self, keyset_id: &Id) -> Result<Option<KeySet>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(KEYSETS_TABLE)?;
+
+        let keyset = table.get(keyset_id.to_string().as_str())?;
+
+        Ok(keyset.map(|k| serde_json::from_str(&k.value()).unwrap()))
+    }
+
+    async fn get_keysets(&self) -> Result<Vec<KeySet>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(KEYSETS_TABLE)?;
+
+        let mut keysets = Vec::new();
+
+        for keyset in table.iter()? {
+            if let Ok((_id, keyset)) = keyset {
+                let keyset = serde_json::from_str(keyset.value())?;
+
+                keysets.push(keyset)
+            }
+        }
+
+        Ok(keysets)
+    }
+
+    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(MINT_QUOTES_TABLE)?;
+            table.insert(quote.id.as_str(), serde_json::to_string(&quote)?.as_str())?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(MINT_QUOTES_TABLE)?;
+
+        let quote = table.get(quote_id)?;
+
+        Ok(quote.map(|q| serde_json::from_str(&q.value()).unwrap()))
+    }
+
+    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(MINT_QUOTES_TABLE)?;
+            table.remove(quote_id)?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(MELT_QUOTES_TABLE)?;
+            table.insert(quote.id.as_str(), serde_json::to_string(&quote)?.as_str())?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<MeltQuote>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(MELT_QUOTES_TABLE)?;
+
+        let quote = table.get(quote_id)?;
+
+        Ok(quote.map(|q| serde_json::from_str(&q.value()).unwrap()))
+    }
+
+    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(MELT_QUOTES_TABLE)?;
+            table.remove(quote_id)?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn add_spent_proof(&self, proof: Proof) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(SPENT_PROOFS_TABLE)?;
+            table.insert(
+                proof.secret.to_string().as_str(),
+                serde_json::to_string(&proof)?.as_str(),
+            )?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_spent_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(MELT_QUOTES_TABLE)?;
+
+        let quote = table.get(secret.to_string().as_str())?;
+
+        Ok(quote.map(|q| serde_json::from_str(&q.value()).unwrap()))
+    }
+
+    async fn add_pending_proof(&self, proof: Proof) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
+            table.insert(
+                proof.secret.to_string().as_str(),
+                serde_json::to_string(&proof)?.as_str(),
+            )?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_pending_proof(&self, secret: &Secret) -> Result<Option<Proof>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(MELT_QUOTES_TABLE)?;
+
+        let quote = table.get(secret.to_string().as_str())?;
+
+        Ok(quote.map(|q| serde_json::from_str(&q.value()).unwrap()))
+    }
+
+    async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error> {
+        let db = self.db.lock().await;
+
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(PENDING_PROOFS_TABLE)?;
+            table.remove(secret.to_string().as_str())?;
+        }
+        write_txn.commit()?;
+
+        Ok(())
+    }
+}

+ 5 - 6
crates/cashu-sdk/src/mint/mod.rs

@@ -20,7 +20,9 @@ use crate::Mnemonic;
 
 mod localstore;
 
-use localstore::LocalStore;
+pub use localstore::LocalStore;
+#[cfg(all(not(target_arch = "wasm32"), feature = "redb"))]
+pub use localstore::RedbLocalStore;
 
 #[derive(Debug, Error)]
 pub enum Error {
@@ -314,10 +316,7 @@ impl<L: LocalStore> Mint<L> {
         }
 
         for proof in swap_request.inputs {
-            self.localstore
-                .add_spent_proof(proof.secret.clone(), proof)
-                .await
-                .unwrap();
+            self.localstore.add_spent_proof(proof).await.unwrap();
         }
 
         let mut promises = Vec::with_capacity(swap_request.outputs.len());
@@ -484,7 +483,7 @@ impl<L: LocalStore> Mint<L> {
 
         for input in &melt_request.inputs {
             self.localstore
-                .add_spent_proof(input.secret.clone(), input.clone())
+                .add_spent_proof(input.clone())
                 .await
                 .unwrap();
         }