mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
fix(mint): redb localstore checking wrong table for spent proofs
feat(wallet): store pending proofs
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("e_info.id).await?;
|
||||
|
||||
self.localstore
|
||||
.remove_proofs(mint_url.clone(), &proofs)
|
||||
.await?;
|
||||
|
||||
Ok(melted)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user