mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
feat: redb localstore
This commit is contained in:
@@ -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"] }
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
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};
|
||||
@@ -6,7 +10,22 @@ 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 {
|
||||
|
||||
301
crates/cashu-sdk/src/localstore/redb_store.rs
Normal file
301
crates/cashu-sdk/src/localstore/redb_store.rs
Normal file
@@ -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("e)?.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("e)?.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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user