feat: mint redb localstore

This commit is contained in:
thesimplekid
2024-01-13 16:33:10 +00:00
parent 8ba1bc5e8f
commit d5887f1097
5 changed files with 304 additions and 13 deletions

View File

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

View File

@@ -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(())
}

View File

@@ -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>;
}

View File

@@ -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(())
}
}

View File

@@ -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();
}