fix(mint): redb localstore checking wrong table for spent proofs

feat(wallet): store pending proofs
This commit is contained in:
thesimplekid
2024-02-05 21:30:18 +00:00
parent be2b51b25a
commit 28cc181982
7 changed files with 149 additions and 25 deletions

View File

@@ -121,10 +121,9 @@ impl Client for HttpClient {
let request = MeltQuoteBolt11Request { request, unit };
let value = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let value = minreq::post(url).with_json(&request)?.send()?;
let value = value.json::<Value>()?;
let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
serde_json::from_value(value.clone());
@@ -152,11 +151,9 @@ impl Client for HttpClient {
outputs,
};
let value = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let value = minreq::post(url).with_json(&request)?.send()?;
let value = value.json::<Value>()?;
let response: Result<MeltBolt11Response, serde_json::Error> =
serde_json::from_value(value.clone());

View File

@@ -8,6 +8,7 @@ use cashu::secret::Secret;
use cashu::types::{MeltQuote, MintQuote};
use redb::{Database, ReadableTable, TableDefinition};
use tokio::sync::Mutex;
use tracing::debug;
use super::{Error, LocalStore};
@@ -280,16 +281,20 @@ impl LocalStore for RedbLocalStore {
}
write_txn.commit()?;
debug!("Added spend secret: {}", proof.secret.to_string());
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 table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
let quote = table.get(secret.to_string().as_str())?;
debug!("Checking secret: {}", secret.to_string());
Ok(quote.map(|q| serde_json::from_str(q.value()).unwrap()))
}

View File

@@ -15,7 +15,7 @@ use cashu::types::{MeltQuote, MintQuote};
use cashu::Amount;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{debug, info};
use tracing::{debug, error, info};
use crate::Mnemonic;
@@ -381,6 +381,7 @@ impl Mint {
// in the future it maybe possible to support multiple units but unsupported for
// now
if keyset_units.len().gt(&1) {
error!("Only one unit is allowed in request: {:?}", keyset_units);
return Err(Error::MultipleUnits);
}
@@ -464,7 +465,7 @@ impl Mint {
pub async fn verify_melt_request(
&mut self,
melt_request: &MeltBolt11Request,
) -> Result<(), Error> {
) -> Result<MeltQuote, Error> {
let quote = self
.localstore
.get_melt_quote(&melt_request.quote)
@@ -486,7 +487,7 @@ impl Mint {
let input_keyset_ids: HashSet<Id> =
melt_request.inputs.iter().map(|p| p.keyset_id).collect();
let mut keyset_units = Vec::with_capacity(input_keyset_ids.capacity());
let mut keyset_units = HashSet::with_capacity(input_keyset_ids.capacity());
for id in input_keyset_ids {
let keyset = self
@@ -494,7 +495,7 @@ impl Mint {
.get_keyset(&id)
.await?
.ok_or(Error::UnknownKeySet)?;
keyset_units.push(keyset.unit);
keyset_units.insert(keyset.unit);
}
if let Some(outputs) = &melt_request.outputs {
@@ -517,13 +518,12 @@ impl Mint {
if id.ne(&active_keyset_id) {
return Err(Error::InactiveKeyset);
}
keyset_units.push(keyset.unit);
keyset_units.insert(keyset.unit);
}
}
// Check that all input and output proofs are the same unit
let seen_units: HashSet<CurrencyUnit> = HashSet::new();
if keyset_units.iter().any(|unit| !seen_units.contains(unit)) && seen_units.len() != 1 {
if keyset_units.len().gt(&1) {
return Err(Error::MultipleUnits);
}
@@ -535,10 +535,10 @@ impl Mint {
}
for proof in &melt_request.inputs {
self.verify_proof(proof).await?
self.verify_proof(proof).await?;
}
Ok(())
Ok(quote)
}
pub async fn process_melt_request(

View File

@@ -17,6 +17,7 @@ pub struct MemoryLocalStore {
melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
mint_keys: Arc<Mutex<HashMap<Id, Keys>>>,
proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
pending_proofs: Arc<Mutex<HashMap<UncheckedUrl, HashSet<Proof>>>>,
}
impl MemoryLocalStore {
@@ -38,6 +39,7 @@ impl MemoryLocalStore {
mint_keys.into_iter().map(|k| (Id::from(&k), k)).collect(),
)),
proofs: Arc::new(Mutex::new(HashMap::new())),
pending_proofs: Arc::new(Mutex::new(HashMap::new())),
}
}
}
@@ -165,4 +167,42 @@ impl LocalStore for MemoryLocalStore {
Ok(())
}
async fn add_pending_proofs(
&self,
mint_url: UncheckedUrl,
proofs: Proofs,
) -> Result<(), Error> {
let mut all_proofs = self.pending_proofs.lock().await;
let mint_proofs = all_proofs.entry(mint_url).or_insert(HashSet::new());
mint_proofs.extend(proofs);
Ok(())
}
async fn get_pending_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
Ok(self
.pending_proofs
.lock()
.await
.get(&mint_url)
.map(|p| p.iter().cloned().collect()))
}
async fn remove_pending_proofs(
&self,
mint_url: UncheckedUrl,
proofs: &Proofs,
) -> Result<(), Error> {
let mut mint_proofs = self.pending_proofs.lock().await;
if let Some(mint_proofs) = mint_proofs.get_mut(&mint_url) {
for proof in proofs {
mint_proofs.remove(proof);
}
}
Ok(())
}
}

View File

@@ -74,4 +74,12 @@ pub trait LocalStore {
async fn add_proofs(&self, mint_url: UncheckedUrl, proof: Proofs) -> Result<(), Error>;
async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error>;
async fn remove_proofs(&self, mint_url: UncheckedUrl, proofs: &Proofs) -> Result<(), Error>;
async fn add_pending_proofs(&self, mint_url: UncheckedUrl, proof: Proofs) -> Result<(), Error>;
async fn get_pending_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error>;
async fn remove_pending_proofs(
&self,
mint_url: UncheckedUrl,
proofs: &Proofs,
) -> Result<(), Error>;
}

View File

@@ -20,6 +20,8 @@ const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("min
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");
const PENDING_PROOFS_TABLE: MultimapTableDefinition<&str, &str> =
MultimapTableDefinition::new("pending_proofs");
#[derive(Debug, Clone)]
pub struct RedbLocalStore {
@@ -319,4 +321,66 @@ impl LocalStore for RedbLocalStore {
Ok(())
}
async fn add_pending_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(PENDING_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_pending_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(PENDING_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_pending_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(PENDING_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(())
}
}

View File

@@ -17,7 +17,7 @@ use cashu::url::UncheckedUrl;
use cashu::{Amount, Bolt11Invoice};
use localstore::LocalStore;
use thiserror::Error;
use tracing::warn;
use tracing::{debug, warn};
use crate::client::Client;
use crate::utils::unix_time;
@@ -508,12 +508,12 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
.await?;
self.localstore
.add_proofs(mint_url.clone(), keep_proofs)
.add_pending_proofs(mint_url.clone(), proofs)
.await?;
// REVIEW: send proofs are not added to the store since they should be
// used. but if they are not they will be lost. There should likely be a
// pendiing proof store
self.localstore
.add_proofs(mint_url.clone(), keep_proofs)
.await?;
Ok(send_proofs)
}
@@ -635,7 +635,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
.post_melt(
mint_url.clone().try_into()?,
quote_id.to_string(),
proofs,
proofs.clone(),
Some(blinded.blinded_messages()),
)
.await?;
@@ -653,11 +653,21 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
let melted = Melted {
paid: true,
preimage: melt_response.payment_preimage,
change: change_proofs,
change: change_proofs.clone(),
};
if let Some(change_proofs) = change_proofs {
self.localstore
.add_proofs(mint_url.clone(), change_proofs)
.await?;
}
self.localstore.remove_melt_quote(&quote_info.id).await?;
self.localstore
.remove_proofs(mint_url.clone(), &proofs)
.await?;
Ok(melted)
}