refactor: use sproof state update

This commit is contained in:
thesimplekid
2024-07-18 22:38:07 +01:00
parent 9789475686
commit fb014573c1
10 changed files with 337 additions and 272 deletions

View File

@@ -40,6 +40,7 @@
- cdk(wallet): Add `fn melt_proofs` that uses specific proofs for `melt` instead of selecting ([thesimplekid]). - cdk(wallet): Add `fn melt_proofs` that uses specific proofs for `melt` instead of selecting ([thesimplekid]).
- cdk-cli(receive): Add support for signing keys to be nostr nsec encoded ([thesimplekid]). - cdk-cli(receive): Add support for signing keys to be nostr nsec encoded ([thesimplekid]).
- cdk-fake-wallet: Add Fake wallet for testing ([thesimplekid]). - cdk-fake-wallet: Add Fake wallet for testing ([thesimplekid]).
- cdk(cdk-database/mint): Add `add_proofs`, `get_proofs_by_ys`, `get_proofs_states`, and `update_proofs_states` ([thesimplekid]).
### Fixed ### Fixed
- cdk(mint): `SIG_ALL` is not allowed in `melt` ([thesimplekid]). - cdk(mint): `SIG_ALL` is not allowed in `melt` ([thesimplekid]).
@@ -47,6 +48,7 @@
### Removed ### Removed
- cdk(wallet): Remove unused argument `SplitTarget` on `melt` ([thesimplekid]). - cdk(wallet): Remove unused argument `SplitTarget` on `melt` ([thesimplekid]).
- cdk(cdk-database/mint): Remove `get_spent_proofs`, `get_spent_proofs_by_ys`,`get_pending_proofs`, `get_pending_proofs_by_ys`, and `remove_pending_proofs` ([thesimplekid]).
## [v0.2.0] ## [v0.2.0]

View File

@@ -1,23 +1,31 @@
use core::str;
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use cdk::mint::MintQuote; use cdk::mint::MintQuote;
use cdk::nuts::{CurrencyUnit, MintQuoteState}; use cdk::nuts::{CurrencyUnit, MintQuoteState, Proof, State};
use cdk::{Amount, UncheckedUrl}; use cdk::{Amount, UncheckedUrl};
use lightning_invoice::Bolt11Invoice; use lightning_invoice::Bolt11Invoice;
use redb::{Database, ReadableTable, TableDefinition}; use redb::{Database, ReadableTable, TableDefinition};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::Error; use super::{Error, PROOFS_STATE_TABLE, PROOFS_TABLE};
const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes"); const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> =
TableDefinition::new("pending_proofs");
const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs");
pub fn migrate_01_to_02(db: Arc<Database>) -> Result<u32, Error> { pub fn migrate_01_to_02(db: Arc<Database>) -> Result<u32, Error> {
migrate_mint_quotes_01_to_02(db)?; migrate_mint_quotes_01_to_02(db)?;
Ok(2) Ok(2)
} }
pub fn migrate_02_to_03(db: Arc<Database>) -> Result<u32, Error> {
migrate_mint_proofs_02_to_03(db)?;
Ok(3)
}
/// Mint Quote Info /// Mint Quote Info
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
struct V1MintQuote { struct V1MintQuote {
@@ -97,3 +105,70 @@ fn migrate_mint_quotes_01_to_02(db: Arc<Database>) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn migrate_mint_proofs_02_to_03(db: Arc<Database>) -> Result<(), Error> {
let pending_proofs: Vec<([u8; 33], Option<Proof>)>;
let spent_proofs: Vec<([u8; 33], Option<Proof>)>;
{
let read_txn = db.begin_read().map_err(Error::from)?;
let table = read_txn
.open_table(PENDING_PROOFS_TABLE)
.map_err(Error::from)?;
pending_proofs = table
.iter()
.map_err(Error::from)?
.flatten()
.map(|(quote_id, mint_quote)| {
(
quote_id.value(),
serde_json::from_str(mint_quote.value()).ok(),
)
})
.collect();
}
{
let read_txn = db.begin_read().map_err(Error::from)?;
let table = read_txn
.open_table(SPENT_PROOFS_TABLE)
.map_err(Error::from)?;
spent_proofs = table
.iter()
.map_err(Error::from)?
.flatten()
.map(|(quote_id, mint_quote)| {
(
quote_id.value(),
serde_json::from_str(mint_quote.value()).ok(),
)
})
.collect();
}
let write_txn = db.begin_write().map_err(Error::from)?;
{
let mut proofs_table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
let mut state_table = write_txn
.open_table(PROOFS_STATE_TABLE)
.map_err(Error::from)?;
for (y, proof) in pending_proofs {
if let Some(proof) = proof {
proofs_table.insert(y, serde_json::to_string(&proof)?.as_str())?;
state_table.insert(y, State::Pending.to_string().as_str())?;
}
}
for (y, proof) in spent_proofs {
if let Some(proof) = proof {
proofs_table.insert(y, serde_json::to_string(&proof)?.as_str())?;
state_table.insert(y, State::Spent.to_string().as_str())?;
}
}
}
write_txn.commit()?;
Ok(())
}

View File

@@ -11,9 +11,9 @@ use cdk::cdk_database::MintDatabase;
use cdk::dhke::hash_to_curve; use cdk::dhke::hash_to_curve;
use cdk::mint::{MintKeySetInfo, MintQuote}; use cdk::mint::{MintKeySetInfo, MintQuote};
use cdk::nuts::{ use cdk::nuts::{
BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, PublicKey, BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey,
State,
}; };
use cdk::secret::Secret;
use cdk::{cdk_database, mint}; use cdk::{cdk_database, mint};
use migrations::migrate_01_to_02; use migrations::migrate_01_to_02;
use redb::{Database, ReadableTable, TableDefinition}; use redb::{Database, ReadableTable, TableDefinition};
@@ -21,6 +21,7 @@ use tokio::sync::Mutex;
use super::error::Error; use super::error::Error;
use crate::migrations::migrate_00_to_01; use crate::migrations::migrate_00_to_01;
use crate::mint::migrations::migrate_02_to_03;
mod migrations; mod migrations;
@@ -28,15 +29,14 @@ const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("
const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets"); const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes"); const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes"); const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = const PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs");
TableDefinition::new("pending_proofs"); const PROOFS_STATE_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs_state");
const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs");
const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config"); const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
// Key is hex blinded_message B_ value is blinded_signature // Key is hex blinded_message B_ value is blinded_signature
const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> = const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
TableDefinition::new("blinded_signatures"); TableDefinition::new("blinded_signatures");
const DATABASE_VERSION: u32 = 2; const DATABASE_VERSION: u32 = 3;
/// Mint Redbdatabase /// Mint Redbdatabase
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -78,6 +78,10 @@ impl MintRedbDatabase {
current_file_version = migrate_01_to_02(Arc::clone(&db))?; current_file_version = migrate_01_to_02(Arc::clone(&db))?;
} }
if current_file_version == 2 {
current_file_version = migrate_02_to_03(Arc::clone(&db))?;
}
if current_file_version != DATABASE_VERSION { if current_file_version != DATABASE_VERSION {
tracing::warn!( tracing::warn!(
"Database upgrade did not complete at {} current is {}", "Database upgrade did not complete at {} current is {}",
@@ -109,8 +113,8 @@ impl MintRedbDatabase {
let _ = write_txn.open_table(KEYSETS_TABLE)?; let _ = write_txn.open_table(KEYSETS_TABLE)?;
let _ = write_txn.open_table(MINT_QUOTES_TABLE)?; let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
let _ = write_txn.open_table(MELT_QUOTES_TABLE)?; let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
let _ = write_txn.open_table(PENDING_PROOFS_TABLE)?; let _ = write_txn.open_table(PROOFS_TABLE)?;
let _ = write_txn.open_table(SPENT_PROOFS_TABLE)?; let _ = write_txn.open_table(PROOFS_STATE_TABLE)?;
let _ = write_txn.open_table(BLINDED_SIGNATURES)?; let _ = write_txn.open_table(BLINDED_SIGNATURES)?;
table.insert("db_version", DATABASE_VERSION.to_string().as_str())?; table.insert("db_version", DATABASE_VERSION.to_string().as_str())?;
@@ -494,15 +498,13 @@ impl MintDatabase for MintRedbDatabase {
Ok(()) Ok(())
} }
async fn add_spent_proofs(&self, proofs: Vec<Proof>) -> Result<(), Self::Err> { async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> {
let db = self.db.lock().await; let db = self.db.lock().await;
let write_txn = db.begin_write().map_err(Error::from)?; let write_txn = db.begin_write().map_err(Error::from)?;
{ {
let mut table = write_txn let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
.open_table(SPENT_PROOFS_TABLE)
.map_err(Error::from)?;
for proof in proofs { for proof in proofs {
let y: PublicKey = hash_to_curve(&proof.secret.to_bytes()).map_err(Error::from)?; let y: PublicKey = hash_to_curve(&proof.secret.to_bytes()).map_err(Error::from)?;
table table
@@ -518,15 +520,10 @@ impl MintDatabase for MintRedbDatabase {
Ok(()) Ok(())
} }
async fn get_spent_proofs_by_ys( async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
&self,
ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> {
let db = self.db.lock().await; let db = self.db.lock().await;
let read_txn = db.begin_read().map_err(Error::from)?; let read_txn = db.begin_read().map_err(Error::from)?;
let table = read_txn let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
.open_table(SPENT_PROOFS_TABLE)
.map_err(Error::from)?;
let mut proofs = Vec::with_capacity(ys.len()); let mut proofs = Vec::with_capacity(ys.len());
@@ -542,70 +539,55 @@ impl MintDatabase for MintRedbDatabase {
Ok(proofs) Ok(proofs)
} }
async fn add_pending_proofs(&self, proofs: Vec<Proof>) -> Result<(), Self::Err> { async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
let db = self.db.lock().await;
let write_txn = db.begin_write().map_err(Error::from)?;
{
let mut table = write_txn
.open_table(PENDING_PROOFS_TABLE)
.map_err(Error::from)?;
for proof in proofs {
table
.insert(
hash_to_curve(&proof.secret.to_bytes())?.to_bytes(),
serde_json::to_string(&proof).map_err(Error::from)?.as_str(),
)
.map_err(Error::from)?;
}
}
write_txn.commit().map_err(Error::from)?;
Ok(())
}
async fn get_pending_proofs_by_ys(
&self,
ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> {
let db = self.db.lock().await; let db = self.db.lock().await;
let read_txn = db.begin_read().map_err(Error::from)?; let read_txn = db.begin_read().map_err(Error::from)?;
let table = read_txn let table = read_txn
.open_table(PENDING_PROOFS_TABLE) .open_table(PROOFS_STATE_TABLE)
.map_err(Error::from)?; .map_err(Error::from)?;
let mut proofs = Vec::with_capacity(ys.len()); let mut states = Vec::with_capacity(ys.len());
for y in ys { for y in ys {
match table.get(y.to_bytes()).map_err(Error::from)? { match table.get(y.to_bytes()).map_err(Error::from)? {
Some(proof) => proofs.push(Some( Some(state) => states.push(Some(
serde_json::from_str(proof.value()).map_err(Error::from)?, serde_json::from_str(state.value()).map_err(Error::from)?,
)), )),
None => proofs.push(None), None => states.push(None),
} }
} }
Ok(proofs) Ok(states)
} }
async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> { async fn update_proofs_states(
&self,
ys: &[PublicKey],
proofs_state: State,
) -> Result<Vec<Option<State>>, Self::Err> {
let db = self.db.lock().await; let db = self.db.lock().await;
let write_txn = db.begin_write().map_err(Error::from)?; let write_txn = db.begin_write().map_err(Error::from)?;
let mut table = write_txn
.open_table(PROOFS_STATE_TABLE)
.map_err(Error::from)?;
{ let mut states = Vec::with_capacity(ys.len());
let mut table = write_txn
.open_table(PENDING_PROOFS_TABLE) let state_str = serde_json::to_string(&proofs_state).map_err(Error::from)?;
.map_err(Error::from)?;
for secret in secrets { for y in ys {
let secret_hash = hash_to_curve(&secret.to_bytes()).map_err(Error::from)?; match table
table.remove(secret_hash.to_bytes()).map_err(Error::from)?; .insert(y.to_bytes(), state_str.as_str())
.map_err(Error::from)?
{
Some(state) => states.push(Some(
serde_json::from_str(state.value()).map_err(Error::from)?,
)),
None => states.push(None),
} }
} }
write_txn.commit().map_err(Error::from)?;
Ok(()) Ok(states)
} }
async fn add_blind_signatures( async fn add_blind_signatures(

View File

@@ -23,6 +23,9 @@ pub enum Error {
/// NUT05 Error /// NUT05 Error
#[error(transparent)] #[error(transparent)]
CDKNUT05(#[from] cdk::nuts::nut05::Error), CDKNUT05(#[from] cdk::nuts::nut05::Error),
/// NUT07 Error
#[error(transparent)]
CDKNUT07(#[from] cdk::nuts::nut07::Error),
/// Secret Error /// Secret Error
#[error(transparent)] #[error(transparent)]
CDKSECRET(#[from] cdk::secret::Error), CDKSECRET(#[from] cdk::secret::Error),

View File

@@ -0,0 +1,21 @@
-- Create a new table with the updated CHECK constraint
CREATE TABLE proof_new (
y BLOB PRIMARY KEY,
amount INTEGER NOT NULL,
keyset_id TEXT NOT NULL,
secret TEXT NOT NULL,
c BLOB NOT NULL,
witness TEXT,
state TEXT CHECK (state IN ('SPENT', 'PENDING', 'UNSPENT')) NOT NULL
);
-- Copy the data from the old table to the new table
INSERT INTO proof_new (y, amount, keyset_id, secret, c, witness, state)
SELECT y, amount, keyset_id, secret, c, witness, state
FROM proof;
-- Drop the old table
DROP TABLE proof;
-- Rename the new table to the original table name
ALTER TABLE proof_new RENAME TO proof;

View File

@@ -11,6 +11,7 @@ use cdk::mint::{MintKeySetInfo, MintQuote};
use cdk::nuts::nut05::QuoteState; use cdk::nuts::nut05::QuoteState;
use cdk::nuts::{ use cdk::nuts::{
BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey,
State,
}; };
use cdk::secret::Secret; use cdk::secret::Secret;
use cdk::{mint, Amount}; use cdk::{mint, Amount};
@@ -480,13 +481,12 @@ FROM keyset;
.collect()) .collect())
} }
async fn add_spent_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?; let mut transaction = self.pool.begin().await.map_err(Error::from)?;
for proof in proofs { for proof in proofs {
sqlx::query( if let Err(err) = sqlx::query(
r#" r#"
INSERT OR REPLACE INTO proof INSERT INTO proof
(y, amount, keyset_id, secret, c, witness, state) (y, amount, keyset_id, secret, c, witness, state)
VALUES (?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?);
"#, "#,
@@ -497,79 +497,19 @@ VALUES (?, ?, ?, ?, ?, ?, ?);
.bind(proof.secret.to_string()) .bind(proof.secret.to_string())
.bind(proof.c.to_bytes().to_vec()) .bind(proof.c.to_bytes().to_vec())
.bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap())) .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap()))
.bind("SPENT") .bind("UNSPENT")
.execute(&mut transaction) .execute(&mut transaction)
.await .await
.map_err(Error::from)?; .map_err(Error::from)
} {
transaction.commit().await.map_err(Error::from)?; tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err);
Ok(()) }
}
async fn get_spent_proofs_by_ys(
&self,
ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?;
let mut proofs = Vec::with_capacity(ys.len());
for y in ys {
let rec = sqlx::query(
r#"
SELECT *
FROM proof
WHERE y=?
AND state="SPENT";
"#,
)
.bind(y.to_bytes().to_vec())
.fetch_one(&mut transaction)
.await;
match rec {
Ok(rec) => {
proofs.push(Some(sqlite_row_to_proof(rec)?));
}
Err(err) => match err {
sqlx::Error::RowNotFound => proofs.push(None),
_ => return Err(Error::SQLX(err).into()),
},
};
}
transaction.commit().await.map_err(Error::from)?;
Ok(proofs)
}
async fn add_pending_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?;
for proof in proofs {
sqlx::query(
r#"
INSERT OR REPLACE INTO proof
(y, amount, keyset_id, secret, c, witness, state)
VALUES (?, ?, ?, ?, ?, ?, ?);
"#,
)
.bind(proof.y()?.to_bytes().to_vec())
.bind(u64::from(proof.amount) as i64)
.bind(proof.keyset_id.to_string())
.bind(proof.secret.to_string())
.bind(proof.c.to_bytes().to_vec())
.bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap()))
.bind("PENDING")
.execute(&mut transaction)
.await
.map_err(Error::from)?;
} }
transaction.commit().await.map_err(Error::from)?; transaction.commit().await.map_err(Error::from)?;
Ok(()) Ok(())
} }
async fn get_pending_proofs_by_ys( async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
&self,
ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?; let mut transaction = self.pool.begin().await.map_err(Error::from)?;
let mut proofs = Vec::with_capacity(ys.len()); let mut proofs = Vec::with_capacity(ys.len());
@@ -579,8 +519,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?);
r#" r#"
SELECT * SELECT *
FROM proof FROM proof
WHERE y=? WHERE y=?;
AND state="PENDING";
"#, "#,
) )
.bind(y.to_bytes().to_vec()) .bind(y.to_bytes().to_vec())
@@ -600,26 +539,91 @@ AND state="PENDING";
Ok(proofs) Ok(proofs)
} }
async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> {
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?; let mut transaction = self.pool.begin().await.map_err(Error::from)?;
for secret in secrets {
sqlx::query( let mut states = Vec::with_capacity(ys.len());
for y in ys {
let rec = sqlx::query(
r#" r#"
DELETE FROM proof SELECT state
WHERE secret=? FROM proof
AND state="PENDING"; WHERE y=?;
"#, "#,
) )
.bind(secret.to_string()) .bind(y.to_bytes().to_vec())
.fetch_one(&mut transaction)
.await;
match rec {
Ok(rec) => {
let state: String = rec.get("state");
let state = State::from_str(&state).map_err(Error::from)?;
states.push(Some(state));
}
Err(err) => match err {
sqlx::Error::RowNotFound => states.push(None),
_ => return Err(Error::SQLX(err).into()),
},
};
}
Ok(states)
}
async fn update_proofs_states(
&self,
ys: &[PublicKey],
proofs_state: State,
) -> Result<Vec<Option<State>>, Self::Err> {
let mut transaction = self.pool.begin().await.map_err(Error::from)?;
let mut states = Vec::with_capacity(ys.len());
let proofs_state = proofs_state.to_string();
for y in ys {
let y = y.to_bytes().to_vec();
let rec = sqlx::query(
r#"
SELECT state
FROM proof
WHERE y=?;
"#,
)
.bind(&y)
.fetch_one(&mut transaction)
.await;
match rec {
Ok(rec) => {
let state: String = rec.get("state");
let state = State::from_str(&state).map_err(Error::from)?;
states.push(Some(state));
}
Err(err) => match err {
sqlx::Error::RowNotFound => states.push(None),
_ => return Err(Error::SQLX(err).into()),
},
};
sqlx::query(
r#"
UPDATE proof SET state = ? WHERE y = ?
"#,
)
.bind(&proofs_state)
.bind(y)
.execute(&mut transaction) .execute(&mut transaction)
.await .await
.map_err(Error::from)?; .map_err(Error::from)?;
} }
transaction.commit().await.map_err(Error::from)?; transaction.commit().await.map_err(Error::from)?;
Ok(()) Ok(states)
} }
async fn add_blind_signatures( async fn add_blind_signatures(
&self, &self,
blinded_messages: &[PublicKey], blinded_messages: &[PublicKey],

View File

@@ -4,15 +4,16 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::sync::RwLock; use tokio::sync::{Mutex, RwLock};
use super::{Error, MintDatabase}; use super::{Error, MintDatabase};
use crate::dhke::hash_to_curve; use crate::dhke::hash_to_curve;
use crate::mint::{self, MintKeySetInfo, MintQuote}; use crate::mint::{self, MintKeySetInfo, MintQuote};
use crate::nuts::nut07::State;
use crate::nuts::{ use crate::nuts::{
BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, nut07, BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs,
PublicKey,
}; };
use crate::secret::Secret;
/// Mint Memory Database /// Mint Memory Database
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -21,8 +22,8 @@ pub struct MintMemoryDatabase {
keysets: Arc<RwLock<HashMap<Id, MintKeySetInfo>>>, keysets: Arc<RwLock<HashMap<Id, MintKeySetInfo>>>,
mint_quotes: Arc<RwLock<HashMap<String, MintQuote>>>, mint_quotes: Arc<RwLock<HashMap<String, MintQuote>>>,
melt_quotes: Arc<RwLock<HashMap<String, mint::MeltQuote>>>, melt_quotes: Arc<RwLock<HashMap<String, mint::MeltQuote>>>,
pending_proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>, proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>,
spent_proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>, proof_state: Arc<Mutex<HashMap<[u8; 33], nut07::State>>>,
blinded_signatures: Arc<RwLock<HashMap<[u8; 33], BlindSignature>>>, blinded_signatures: Arc<RwLock<HashMap<[u8; 33], BlindSignature>>>,
} }
@@ -38,6 +39,21 @@ impl MintMemoryDatabase {
spent_proofs: Proofs, spent_proofs: Proofs,
blinded_signatures: HashMap<[u8; 33], BlindSignature>, blinded_signatures: HashMap<[u8; 33], BlindSignature>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut proofs = HashMap::new();
let mut proof_states = HashMap::new();
for proof in pending_proofs {
let y = hash_to_curve(&proof.secret.to_bytes())?.to_bytes();
proofs.insert(y, proof);
proof_states.insert(y, State::Pending);
}
for proof in spent_proofs {
let y = hash_to_curve(&proof.secret.to_bytes())?.to_bytes();
proofs.insert(y, proof);
proof_states.insert(y, State::Spent);
}
Ok(Self { Ok(Self {
active_keysets: Arc::new(RwLock::new(active_keysets)), active_keysets: Arc::new(RwLock::new(active_keysets)),
keysets: Arc::new(RwLock::new( keysets: Arc::new(RwLock::new(
@@ -49,18 +65,8 @@ impl MintMemoryDatabase {
melt_quotes: Arc::new(RwLock::new( melt_quotes: Arc::new(RwLock::new(
melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(), melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
)), )),
pending_proofs: Arc::new(RwLock::new( proofs: Arc::new(RwLock::new(proofs)),
pending_proofs proof_state: Arc::new(Mutex::new(proof_states)),
.into_iter()
.map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
.collect(),
)),
spent_proofs: Arc::new(RwLock::new(
spent_proofs
.into_iter()
.map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p))
.collect(),
)),
blinded_signatures: Arc::new(RwLock::new(blinded_signatures)), blinded_signatures: Arc::new(RwLock::new(blinded_signatures)),
}) })
} }
@@ -213,21 +219,18 @@ impl MintDatabase for MintMemoryDatabase {
Ok(()) Ok(())
} }
async fn add_spent_proofs(&self, spent_proofs: Proofs) -> Result<(), Self::Err> { async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> {
let mut proofs = self.spent_proofs.write().await; let mut db_proofs = self.proofs.write().await;
for proof in spent_proofs { for proof in proofs {
let secret_point = hash_to_curve(&proof.secret.to_bytes())?; let secret_point = hash_to_curve(&proof.secret.to_bytes())?;
proofs.insert(secret_point.to_bytes(), proof); db_proofs.insert(secret_point.to_bytes(), proof);
} }
Ok(()) Ok(())
} }
async fn get_spent_proofs_by_ys( async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
&self, let spent_proofs = self.proofs.read().await;
ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> {
let spent_proofs = self.spent_proofs.read().await;
let mut proofs = Vec::with_capacity(ys.len()); let mut proofs = Vec::with_capacity(ys.len());
@@ -240,41 +243,34 @@ impl MintDatabase for MintMemoryDatabase {
Ok(proofs) Ok(proofs)
} }
async fn add_pending_proofs(&self, pending_proofs: Proofs) -> Result<(), Self::Err> { async fn update_proofs_states(
let mut proofs = self.pending_proofs.write().await;
for proof in pending_proofs {
proofs.insert(hash_to_curve(&proof.secret.to_bytes())?.to_bytes(), proof);
}
Ok(())
}
async fn get_pending_proofs_by_ys(
&self, &self,
ys: &[PublicKey], ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err> { proof_state: State,
let spent_proofs = self.pending_proofs.read().await; ) -> Result<Vec<Option<State>>, Self::Err> {
let mut proofs_states = self.proof_state.lock().await;
let mut proofs = Vec::with_capacity(ys.len()); let mut states = Vec::new();
for y in ys { for y in ys {
let proof = spent_proofs.get(&y.to_bytes()).cloned(); let state = proofs_states.insert(y.to_bytes(), proof_state);
states.push(state);
proofs.push(proof);
} }
Ok(proofs) Ok(states)
} }
async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> { async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
let mut proofs = self.pending_proofs.write().await; let proofs_states = self.proof_state.lock().await;
for secret in secrets { let mut states = Vec::new();
let secret_point = hash_to_curve(&secret.to_bytes())?;
proofs.remove(&secret_point.to_bytes()); for y in ys {
let state = proofs_states.get(&y.to_bytes()).cloned();
states.push(state);
} }
Ok(()) Ok(states)
} }
async fn add_blind_signatures( async fn add_blind_signatures(

View File

@@ -14,16 +14,12 @@ use crate::mint;
use crate::mint::MintKeySetInfo; use crate::mint::MintKeySetInfo;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use crate::mint::MintQuote as MintMintQuote; use crate::mint::MintQuote as MintMintQuote;
#[cfg(feature = "wallet")]
use crate::nuts::State;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
use crate::nuts::{BlindSignature, MeltQuoteState, MintQuoteState, Proof}; use crate::nuts::{BlindSignature, MeltQuoteState, MintQuoteState, Proof};
#[cfg(any(feature = "wallet", feature = "mint"))] #[cfg(any(feature = "wallet", feature = "mint"))]
use crate::nuts::{CurrencyUnit, Id, Proofs, PublicKey}; use crate::nuts::{CurrencyUnit, Id, Proofs, PublicKey, State};
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
use crate::nuts::{KeySetInfo, Keys, MintInfo, SpendingConditions}; use crate::nuts::{KeySetInfo, Keys, MintInfo, SpendingConditions};
#[cfg(feature = "mint")]
use crate::secret::Secret;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
use crate::types::ProofInfo; use crate::types::ProofInfo;
#[cfg(feature = "wallet")] #[cfg(feature = "wallet")]
@@ -218,22 +214,17 @@ pub trait MintDatabase {
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>; async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
/// Add spent [`Proofs`] /// Add spent [`Proofs`]
async fn add_spent_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; async fn add_proofs(&self, proof: Proofs) -> Result<(), Self::Err>;
/// Get spent [`Proofs`] by ys /// Get [`Proofs`] by ys
async fn get_spent_proofs_by_ys( async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
&self, /// Get [`Proofs`] state
y: &[PublicKey], async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
) -> Result<Vec<Option<Proof>>, Self::Err>; /// Get [`Proofs`] state
async fn update_proofs_states(
/// Add pending [`Proofs`]
async fn add_pending_proofs(&self, proof: Proofs) -> Result<(), Self::Err>;
/// Get pending [`Proofs`] by ys
async fn get_pending_proofs_by_ys(
&self, &self,
ys: &[PublicKey], ys: &[PublicKey],
) -> Result<Vec<Option<Proof>>, Self::Err>; proofs_state: State,
/// Remove pending [`Proofs`] ) -> Result<Vec<Option<State>>, Self::Err>;
async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>;
/// Add [`BlindSignature`] /// Add [`BlindSignature`]
async fn add_blind_signatures( async fn add_blind_signatures(

View File

@@ -4,6 +4,7 @@
#![warn(rustdoc::bare_urls)] #![warn(rustdoc::bare_urls)]
pub mod amount; pub mod amount;
#[cfg(any(feature = "wallet", feature = "mint"))]
pub mod cdk_database; pub mod cdk_database;
#[cfg(feature = "mint")] #[cfg(feature = "mint")]
pub mod cdk_lightning; pub mod cdk_lightning;

View File

@@ -614,16 +614,19 @@ impl Mint {
let proof_count = swap_request.inputs.len(); let proof_count = swap_request.inputs.len();
let ys: Vec<PublicKey> = swap_request let input_ys: Vec<PublicKey> = swap_request
.inputs .inputs
.iter() .iter()
.flat_map(|p| hash_to_curve(&p.secret.to_bytes())) .flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
.collect(); .collect();
self.check_ys_unspent(&ys).await?; self.localstore
.add_proofs(swap_request.inputs.clone())
.await?;
self.check_ys_spendable(&input_ys, State::Pending).await?;
// Check that there are no duplicate proofs in request // Check that there are no duplicate proofs in request
if ys if input_ys
.iter() .iter()
.collect::<HashSet<&PublicKey>>() .collect::<HashSet<&PublicKey>>()
.len() .len()
@@ -684,10 +687,6 @@ impl Mint {
} }
} }
self.localstore
.add_spent_proofs(swap_request.inputs)
.await?;
let mut promises = Vec::with_capacity(swap_request.outputs.len()); let mut promises = Vec::with_capacity(swap_request.outputs.len());
for blinded_message in swap_request.outputs.iter() { for blinded_message in swap_request.outputs.iter() {
@@ -695,6 +694,10 @@ impl Mint {
promises.push(blinded_signature); promises.push(blinded_signature);
} }
self.localstore
.update_proofs_states(&input_ys, State::Spent)
.await?;
self.localstore self.localstore
.add_blind_signatures( .add_blind_signatures(
&swap_request &swap_request
@@ -749,31 +752,17 @@ impl Mint {
&self, &self,
check_state: &CheckStateRequest, check_state: &CheckStateRequest,
) -> Result<CheckStateResponse, Error> { ) -> Result<CheckStateResponse, Error> {
let spent_proofs = self let states = self.localstore.get_proofs_states(&check_state.ys).await?;
.localstore
.get_spent_proofs_by_ys(&check_state.ys)
.await?;
let pending_proofs = self
.localstore
.get_pending_proofs_by_ys(&check_state.ys)
.await?;
let states = spent_proofs let states = states
.iter() .iter()
.zip(&pending_proofs)
.zip(&check_state.ys) .zip(&check_state.ys)
.map(|((spent, pending), y)| { .map(|(state, y)| {
let state = match (spent, pending) { let state = match state {
(None, None) => State::Unspent, Some(state) => *state,
(Some(_), None) => State::Spent, None => State::Unspent,
(None, Some(_)) => State::Pending,
(Some(_), Some(_)) => {
tracing::error!(
"Proof should not be both pending and spent. Assuming Spent"
);
State::Spent
}
}; };
ProofState { ProofState {
y: *y, y: *y,
state, state,
@@ -787,28 +776,23 @@ impl Mint {
/// Check Tokens are not spent or pending /// Check Tokens are not spent or pending
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn check_ys_unspent(&self, ys: &[PublicKey]) -> Result<(), Error> { pub async fn check_ys_spendable(
let pending_proofs: Proofs = self &self,
ys: &[PublicKey],
proof_state: State,
) -> Result<(), Error> {
let proofs_state = self
.localstore .localstore
.get_pending_proofs_by_ys(ys) .update_proofs_states(ys, proof_state)
.await? .await?;
.into_iter()
.flatten()
.collect();
if !pending_proofs.is_empty() { let proofs_state = proofs_state.iter().flatten().collect::<HashSet<&State>>();
if proofs_state.contains(&State::Pending) {
return Err(Error::TokenPending); return Err(Error::TokenPending);
} }
let spent_proofs: Proofs = self if proofs_state.contains(&State::Spent) {
.localstore
.get_spent_proofs_by_ys(ys)
.await?
.into_iter()
.flatten()
.collect();
if !spent_proofs.is_empty() {
return Err(Error::TokenAlreadySpent); return Err(Error::TokenAlreadySpent);
} }
@@ -832,7 +816,10 @@ impl Mint {
return Err(Error::DuplicateProofs); return Err(Error::DuplicateProofs);
} }
self.check_ys_unspent(&ys).await?; self.localstore
.add_proofs(melt_request.inputs.clone())
.await?;
self.check_ys_spendable(&ys, State::Pending).await?;
for proof in &melt_request.inputs { for proof in &melt_request.inputs {
self.verify_proof(proof).await?; self.verify_proof(proof).await?;
@@ -929,11 +916,6 @@ impl Mint {
return Err(Error::MultipleUnits); return Err(Error::MultipleUnits);
} }
// Add proofs to pending
self.localstore
.add_pending_proofs(melt_request.inputs.clone())
.await?;
tracing::debug!("Verified melt quote: {}", melt_request.quote); tracing::debug!("Verified melt quote: {}", melt_request.quote);
Ok(quote) Ok(quote)
} }
@@ -943,8 +925,14 @@ impl Mint {
/// The [`Proofs`] should be returned to an unspent state and the quote should be unpaid /// The [`Proofs`] should be returned to an unspent state and the quote should be unpaid
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn process_unpaid_melt(&self, melt_request: &MeltBolt11Request) -> Result<(), Error> { pub async fn process_unpaid_melt(&self, melt_request: &MeltBolt11Request) -> Result<(), Error> {
let input_ys: Vec<PublicKey> = melt_request
.inputs
.iter()
.flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
.collect();
self.localstore self.localstore
.remove_pending_proofs(melt_request.inputs.iter().map(|p| &p.secret).collect()) .update_proofs_states(&input_ys, State::Unspent)
.await?; .await?;
self.localstore self.localstore
@@ -987,10 +975,6 @@ impl Mint {
} }
} }
self.localstore
.add_spent_proofs(melt_request.inputs.clone())
.await?;
let mut change = None; let mut change = None;
if let Some(outputs) = melt_request.outputs.clone() { if let Some(outputs) = melt_request.outputs.clone() {
@@ -1038,8 +1022,14 @@ impl Mint {
); );
} }
let input_ys: Vec<PublicKey> = melt_request
.inputs
.iter()
.flat_map(|p| hash_to_curve(&p.secret.to_bytes()))
.collect();
self.localstore self.localstore
.remove_pending_proofs(melt_request.inputs.iter().map(|p| &p.secret).collect()) .update_proofs_states(&input_ys, State::Spent)
.await?; .await?;
self.localstore self.localstore