Skip to content

Commit

Permalink
feat: redb localstore
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Jan 2, 2024
1 parent e00f4c3 commit be8ef41
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 1 deletion.
1 change: 1 addition & 0 deletions crates/cashu-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ gloo = { version = "0.10.0", optional = true, features = ["net"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync"] }
minreq = { version = "2.7.0", optional = true, features = ["json-using-serde", "https"] }
redb = "1.4.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { workspace = true, features = ["rt", "macros", "sync", "time"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/cashu-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub mod wallet;

pub use bip39::Mnemonic;
pub use cashu::{self, *};
#[cfg(not(target_arch = "wasm32"))]
pub use localstore::redb_store::RedbLocalStore;

#[cfg(feature = "blocking")]
static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("Can't start Tokio runtime"));
Expand Down
21 changes: 20 additions & 1 deletion crates/cashu-sdk/src/localstore/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
mod memory;

#[cfg(not(target_arch = "wasm32"))]
pub mod redb_store;

use async_trait::async_trait;
use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs};
use cashu::types::{MeltQuote, MintQuote};
use cashu::url::UncheckedUrl;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {}
pub enum Error {
#[error("`{0}`")]
Redb(#[from] redb::Error),
#[error("`{0}`")]
Database(#[from] redb::DatabaseError),
#[error("`{0}`")]
Transaction(#[from] redb::TransactionError),
#[error("`{0}`")]
Commit(#[from] redb::CommitError),
#[error("`{0}`")]
Table(#[from] redb::TableError),
#[error("`{0}`")]
Storage(#[from] redb::StorageError),
#[error("`{0}`")]
Serde(#[from] serde_json::Error),
}

#[async_trait(?Send)]
pub trait LocalStore {
Expand Down
301 changes: 301 additions & 0 deletions crates/cashu-sdk/src/localstore/redb_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
use std::sync::Arc;

use async_trait::async_trait;
use cashu::nuts::{Id, KeySetInfo, Keys, MintInfo, Proofs};
use cashu::types::{MeltQuote, MintQuote};
use cashu::url::UncheckedUrl;
use redb::{
Database, MultimapTableDefinition, ReadableMultimapTable, ReadableTable, TableDefinition,
};
use tokio::sync::Mutex;

use super::{Error, LocalStore};

const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table");
const MINT_KEYSETS_TABLE: MultimapTableDefinition<&str, &str> =
MultimapTableDefinition::new("mint_keysets");
const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_keys");
const PROOFS_TABLE: MultimapTableDefinition<&str, &str> = MultimapTableDefinition::new("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(MINTS_TABLE)?;
let _ = write_txn.open_multimap_table(MINT_KEYSETS_TABLE)?;
let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
let _ = write_txn.open_multimap_table(PROOFS_TABLE)?;
}
write_txn.commit()?;

Ok(Self {
db: Arc::new(Mutex::new(db)),
})
}
}

#[async_trait(?Send)]
impl LocalStore for RedbLocalStore {
async fn add_mint(
&self,
mint_url: UncheckedUrl,
mint_info: Option<MintInfo>,
) -> Result<(), Error> {
let db = self.db.lock().await;

let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_table(MINTS_TABLE)?;
table.insert(
mint_url.to_string().as_str(),
serde_json::to_string(&mint_info)?.as_str(),
)?;
}
write_txn.commit()?;

Ok(())
}

async fn get_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error> {
let db = self.db.lock().await;
let read_txn = db.begin_read()?;
let table = read_txn.open_table(MINTS_TABLE)?;

if let Some(mint_info) = table.get(mint_url.to_string().as_str())? {
return Ok(serde_json::from_str(mint_info.value())?);
}

Ok(None)
}

async fn add_mint_keysets(
&self,
mint_url: UncheckedUrl,
keysets: Vec<KeySetInfo>,
) -> Result<(), Error> {
let db = self.db.lock().await;

let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_multimap_table(MINT_KEYSETS_TABLE)?;

for keyset in keysets {
table.insert(
mint_url.to_string().as_str(),
serde_json::to_string(&keyset)?.as_str(),
)?;
}
}
write_txn.commit()?;

Ok(())
}

async fn get_mint_keysets(
&self,
mint_url: UncheckedUrl,
) -> Result<Option<Vec<KeySetInfo>>, Error> {
let db = self.db.lock().await;
let read_txn = db.begin_read()?;
let table = read_txn.open_multimap_table(MINT_KEYSETS_TABLE)?;

let keysets = table
.get(mint_url.to_string().as_str())?
.flatten()
.flat_map(|k| serde_json::from_str(k.value()))
.collect();

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)?;

if let Some(mint_info) = table.get(quote_id)? {
return Ok(serde_json::from_str(mint_info.value())?);
}

Ok(None)
}

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)?;

if let Some(mint_info) = table.get(quote_id)? {
return Ok(serde_json::from_str(mint_info.value())?);
}

Ok(None)
}

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_keys(&self, keys: Keys) -> Result<(), Error> {
let db = self.db.lock().await;
let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_table(MINT_KEYS_TABLE)?;
table.insert(
Id::from(&keys).to_string().as_str(),
serde_json::to_string(&keys)?.as_str(),
)?;
}

write_txn.commit()?;

Ok(())
}

async fn get_keys(&self, id: &Id) -> Result<Option<Keys>, Error> {
let db = self.db.lock().await;
let read_txn = db.begin_read()?;
let table = read_txn.open_table(MINT_KEYS_TABLE)?;

if let Some(mint_info) = table.get(id.to_string().as_str())? {
return Ok(serde_json::from_str(mint_info.value())?);
}

Ok(None)
}

async fn remove_keys(&self, id: &Id) -> Result<(), Error> {
let db = self.db.lock().await;
let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_table(MINT_KEYS_TABLE)?;

table.remove(id.to_string().as_str())?;
}

write_txn.commit()?;

Ok(())
}

async fn add_proofs(&self, mint_url: UncheckedUrl, proofs: Proofs) -> Result<(), Error> {
let db = self.db.lock().await;

let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_multimap_table(PROOFS_TABLE)?;

for proof in proofs {
table.insert(
mint_url.to_string().as_str(),
serde_json::to_string(&proof)?.as_str(),
)?;
}
}
write_txn.commit()?;

Ok(())
}

async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
let db = self.db.lock().await;
let read_txn = db.begin_read()?;
let table = read_txn.open_multimap_table(PROOFS_TABLE)?;

let proofs = table
.get(mint_url.to_string().as_str())?
.flatten()
.flat_map(|k| serde_json::from_str(k.value()))
.collect();

Ok(proofs)
}

async fn remove_proofs(&self, mint_url: UncheckedUrl, proofs: &Proofs) -> Result<(), Error> {
let db = self.db.lock().await;

let write_txn = db.begin_write()?;

{
let mut table = write_txn.open_multimap_table(PROOFS_TABLE)?;

for proof in proofs {
table.remove(
mint_url.to_string().as_str(),
serde_json::to_string(&proof)?.as_str(),
)?;
}
}
write_txn.commit()?;

Ok(())
}
}

0 comments on commit be8ef41

Please sign in to comment.