mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-20 14:14:49 +01:00
Split the database trait into read and transactions. (#826)
* Split the database trait into read and transactions. The transaction traits will encapsulate all database changes and also expect READ-and-lock operations to read and lock records from the database for exclusive access, thereby avoiding race conditions. The Transaction trait expects a `rollback` operation on Drop unless the transaction has been committed. * fix: melt quote duplicate error This change stops a second melt quote from being created if there is an existing valid melt quote for an invoice already. If the first melt quote has expired then we allow for a new melt quote to be created. --------- Co-authored-by: thesimplekid <tsk@thesimplekid.com>
This commit is contained in:
@@ -26,7 +26,7 @@ pub async fn check_pending(multi_mint_wallet: &MultiMintWallet) -> Result<()> {
|
|||||||
// Try to reclaim any proofs that are no longer pending
|
// Try to reclaim any proofs that are no longer pending
|
||||||
match wallet.reclaim_unspent(pending_proofs).await {
|
match wallet.reclaim_unspent(pending_proofs).await {
|
||||||
Ok(()) => println!("Successfully reclaimed pending proofs"),
|
Ok(()) => println!("Successfully reclaimed pending proofs"),
|
||||||
Err(e) => println!("Error reclaimed pending proofs: {}", e),
|
Err(e) => println!("Error reclaimed pending proofs: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -5,61 +5,79 @@ use std::collections::HashMap;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cashu::{AuthRequired, ProtectedEndpoint};
|
use cashu::{AuthRequired, ProtectedEndpoint};
|
||||||
|
|
||||||
|
use super::DbTransactionFinalizer;
|
||||||
use crate::database::Error;
|
use crate::database::Error;
|
||||||
use crate::mint::MintKeySetInfo;
|
use crate::mint::MintKeySetInfo;
|
||||||
use crate::nuts::nut07::State;
|
use crate::nuts::nut07::State;
|
||||||
use crate::nuts::{AuthProof, BlindSignature, Id, PublicKey};
|
use crate::nuts::{AuthProof, BlindSignature, Id, PublicKey};
|
||||||
|
|
||||||
|
/// Mint Database transaction
|
||||||
|
#[async_trait]
|
||||||
|
pub trait MintAuthTransaction<Error>: DbTransactionFinalizer<Err = Error> {
|
||||||
|
/// Add Active Keyset
|
||||||
|
async fn set_active_keyset(&mut self, id: Id) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Add [`MintKeySetInfo`]
|
||||||
|
async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Add spent [`AuthProof`]
|
||||||
|
async fn add_proof(&mut self, proof: AuthProof) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Update [`AuthProof`]s state
|
||||||
|
async fn update_proof_state(
|
||||||
|
&mut self,
|
||||||
|
y: &PublicKey,
|
||||||
|
proofs_state: State,
|
||||||
|
) -> Result<Option<State>, Error>;
|
||||||
|
|
||||||
|
/// Add [`BlindSignature`]
|
||||||
|
async fn add_blind_signatures(
|
||||||
|
&mut self,
|
||||||
|
blinded_messages: &[PublicKey],
|
||||||
|
blind_signatures: &[BlindSignature],
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Add protected endpoints
|
||||||
|
async fn add_protected_endpoints(
|
||||||
|
&mut self,
|
||||||
|
protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Removed Protected endpoints
|
||||||
|
async fn remove_protected_endpoints(
|
||||||
|
&mut self,
|
||||||
|
protected_endpoints: Vec<ProtectedEndpoint>,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mint Database trait
|
/// Mint Database trait
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait MintAuthDatabase {
|
pub trait MintAuthDatabase {
|
||||||
/// Mint Database Error
|
/// Mint Database Error
|
||||||
type Err: Into<Error> + From<Error>;
|
type Err: Into<Error> + From<Error>;
|
||||||
/// Add Active Keyset
|
|
||||||
async fn set_active_keyset(&self, id: Id) -> Result<(), Self::Err>;
|
/// Begins a transaction
|
||||||
|
async fn begin_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> Result<Box<dyn MintAuthTransaction<Self::Err> + Send + Sync + 'a>, Self::Err>;
|
||||||
|
|
||||||
/// Get Active Keyset
|
/// Get Active Keyset
|
||||||
async fn get_active_keyset_id(&self) -> Result<Option<Id>, Self::Err>;
|
async fn get_active_keyset_id(&self) -> Result<Option<Id>, Self::Err>;
|
||||||
|
|
||||||
/// Add [`MintKeySetInfo`]
|
|
||||||
async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`MintKeySetInfo`]
|
/// Get [`MintKeySetInfo`]
|
||||||
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
|
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
|
||||||
/// Get [`MintKeySetInfo`]s
|
/// Get [`MintKeySetInfo`]s
|
||||||
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
|
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
|
||||||
|
|
||||||
/// Add spent [`AuthProof`]
|
|
||||||
async fn add_proof(&self, proof: AuthProof) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`AuthProof`] state
|
/// Get [`AuthProof`] state
|
||||||
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
|
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
|
||||||
/// Update [`AuthProof`]s state
|
|
||||||
async fn update_proof_state(
|
|
||||||
&self,
|
|
||||||
y: &PublicKey,
|
|
||||||
proofs_state: State,
|
|
||||||
) -> Result<Option<State>, Self::Err>;
|
|
||||||
|
|
||||||
/// Add [`BlindSignature`]
|
|
||||||
async fn add_blind_signatures(
|
|
||||||
&self,
|
|
||||||
blinded_messages: &[PublicKey],
|
|
||||||
blind_signatures: &[BlindSignature],
|
|
||||||
) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`BlindSignature`]s
|
/// Get [`BlindSignature`]s
|
||||||
async fn get_blind_signatures(
|
async fn get_blind_signatures(
|
||||||
&self,
|
&self,
|
||||||
blinded_messages: &[PublicKey],
|
blinded_messages: &[PublicKey],
|
||||||
) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
|
) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
|
||||||
|
|
||||||
/// Add protected endpoints
|
|
||||||
async fn add_protected_endpoints(
|
|
||||||
&self,
|
|
||||||
protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
|
|
||||||
) -> Result<(), Self::Err>;
|
|
||||||
/// Removed Protected endpoints
|
|
||||||
async fn remove_protected_endpoints(
|
|
||||||
&self,
|
|
||||||
protected_endpoints: Vec<ProtectedEndpoint>,
|
|
||||||
) -> Result<(), Self::Err>;
|
|
||||||
/// Get auth for protected_endpoint
|
/// Get auth for protected_endpoint
|
||||||
async fn get_auth_for_endpoint(
|
async fn get_auth_for_endpoint(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -21,7 +21,17 @@ mod auth;
|
|||||||
pub mod test;
|
pub mod test;
|
||||||
|
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
pub use auth::MintAuthDatabase;
|
pub use auth::{MintAuthDatabase, MintAuthTransaction};
|
||||||
|
|
||||||
|
/// KeysDatabaseWriter
|
||||||
|
#[async_trait]
|
||||||
|
pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
|
||||||
|
/// Add Active Keyset
|
||||||
|
async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Add [`MintKeySetInfo`]
|
||||||
|
async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mint Keys Database trait
|
/// Mint Keys Database trait
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -29,36 +39,84 @@ pub trait KeysDatabase {
|
|||||||
/// Mint Keys Database Error
|
/// Mint Keys Database Error
|
||||||
type Err: Into<Error> + From<Error>;
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
/// Add Active Keyset
|
/// Beings a transaction
|
||||||
async fn set_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err>;
|
async fn begin_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
|
||||||
|
|
||||||
/// Get Active Keyset
|
/// Get Active Keyset
|
||||||
async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
|
async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
|
||||||
|
|
||||||
/// Get all Active Keyset
|
/// Get all Active Keyset
|
||||||
async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
|
async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
|
||||||
/// Add [`MintKeySetInfo`]
|
|
||||||
async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`MintKeySetInfo`]
|
/// Get [`MintKeySetInfo`]
|
||||||
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
|
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
|
||||||
|
|
||||||
/// Get [`MintKeySetInfo`]s
|
/// Get [`MintKeySetInfo`]s
|
||||||
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
|
async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mint Quote Database writer trait
|
||||||
|
#[async_trait]
|
||||||
|
pub trait QuotesTransaction<'a> {
|
||||||
|
/// Mint Quotes Database Error
|
||||||
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
|
/// Get [`MintMintQuote`] and lock it for update in this transaction
|
||||||
|
async fn get_mint_quote(&mut self, quote_id: &Uuid)
|
||||||
|
-> Result<Option<MintMintQuote>, Self::Err>;
|
||||||
|
/// Add [`MintMintQuote`]
|
||||||
|
async fn add_or_replace_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>;
|
||||||
|
/// Update state of [`MintMintQuote`]
|
||||||
|
async fn update_mint_quote_state(
|
||||||
|
&mut self,
|
||||||
|
quote_id: &Uuid,
|
||||||
|
state: MintQuoteState,
|
||||||
|
) -> Result<MintQuoteState, Self::Err>;
|
||||||
|
/// Remove [`MintMintQuote`]
|
||||||
|
async fn remove_mint_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
|
||||||
|
/// Get [`mint::MeltQuote`] and lock it for update in this transaction
|
||||||
|
async fn get_melt_quote(
|
||||||
|
&mut self,
|
||||||
|
quote_id: &Uuid,
|
||||||
|
) -> Result<Option<mint::MeltQuote>, Self::Err>;
|
||||||
|
/// Add [`mint::MeltQuote`]
|
||||||
|
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
|
||||||
|
|
||||||
|
/// Updates the request lookup id for a melt quote
|
||||||
|
async fn update_melt_quote_request_lookup_id(
|
||||||
|
&mut self,
|
||||||
|
quote_id: &Uuid,
|
||||||
|
new_request_lookup_id: &str,
|
||||||
|
) -> Result<(), Self::Err>;
|
||||||
|
|
||||||
|
/// Update [`mint::MeltQuote`] state
|
||||||
|
///
|
||||||
|
/// It is expected for this function to fail if the state is already set to the new state
|
||||||
|
async fn update_melt_quote_state(
|
||||||
|
&mut self,
|
||||||
|
quote_id: &Uuid,
|
||||||
|
new_state: MeltQuoteState,
|
||||||
|
) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
|
||||||
|
/// Remove [`mint::MeltQuote`]
|
||||||
|
async fn remove_melt_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
|
||||||
|
/// Get all [`MintMintQuote`]s and lock it for update in this transaction
|
||||||
|
async fn get_mint_quote_by_request(
|
||||||
|
&mut self,
|
||||||
|
request: &str,
|
||||||
|
) -> Result<Option<MintMintQuote>, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mint Quote Database trait
|
/// Mint Quote Database trait
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait QuotesDatabase {
|
pub trait QuotesDatabase {
|
||||||
/// Mint Quotes Database Error
|
/// Mint Quotes Database Error
|
||||||
type Err: Into<Error> + From<Error>;
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
/// Add [`MintMintQuote`]
|
|
||||||
async fn add_mint_quote(&self, quote: MintMintQuote) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`MintMintQuote`]
|
/// Get [`MintMintQuote`]
|
||||||
async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
|
async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
|
||||||
/// Update state of [`MintMintQuote`]
|
|
||||||
async fn update_mint_quote_state(
|
|
||||||
&self,
|
|
||||||
quote_id: &Uuid,
|
|
||||||
state: MintQuoteState,
|
|
||||||
) -> Result<MintQuoteState, Self::Err>;
|
|
||||||
/// Get all [`MintMintQuote`]s
|
/// Get all [`MintMintQuote`]s
|
||||||
async fn get_mint_quote_by_request(
|
async fn get_mint_quote_by_request(
|
||||||
&self,
|
&self,
|
||||||
@@ -76,25 +134,36 @@ pub trait QuotesDatabase {
|
|||||||
&self,
|
&self,
|
||||||
state: MintQuoteState,
|
state: MintQuoteState,
|
||||||
) -> Result<Vec<MintMintQuote>, Self::Err>;
|
) -> Result<Vec<MintMintQuote>, Self::Err>;
|
||||||
/// Remove [`MintMintQuote`]
|
|
||||||
async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
|
|
||||||
|
|
||||||
/// Add [`mint::MeltQuote`]
|
|
||||||
async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`mint::MeltQuote`]
|
/// Get [`mint::MeltQuote`]
|
||||||
async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
|
async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
|
||||||
/// Update [`mint::MeltQuote`] state
|
|
||||||
///
|
|
||||||
/// It is expected for this function to fail if the state is already set to the new state
|
|
||||||
async fn update_melt_quote_state(
|
|
||||||
&self,
|
|
||||||
quote_id: &Uuid,
|
|
||||||
new_state: MeltQuoteState,
|
|
||||||
) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
|
|
||||||
/// Get all [`mint::MeltQuote`]s
|
/// Get all [`mint::MeltQuote`]s
|
||||||
async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
|
async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
|
||||||
/// Remove [`mint::MeltQuote`]
|
}
|
||||||
async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
|
|
||||||
|
/// Mint Proof Transaction trait
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ProofsTransaction<'a> {
|
||||||
|
/// Mint Proof Database Error
|
||||||
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
|
/// Add [`Proofs`]
|
||||||
|
///
|
||||||
|
/// Adds proofs to the database. The database should error if the proof already exits, with a
|
||||||
|
/// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
|
||||||
|
async fn add_proofs(&mut self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
|
||||||
|
/// Updates the proofs to a given states and return the previous states
|
||||||
|
async fn update_proofs_states(
|
||||||
|
&mut self,
|
||||||
|
ys: &[PublicKey],
|
||||||
|
proofs_state: State,
|
||||||
|
) -> Result<Vec<Option<State>>, Self::Err>;
|
||||||
|
|
||||||
|
/// Remove [`Proofs`]
|
||||||
|
async fn remove_proofs(
|
||||||
|
&mut self,
|
||||||
|
ys: &[PublicKey],
|
||||||
|
quote_id: Option<Uuid>,
|
||||||
|
) -> Result<(), Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mint Proof Database trait
|
/// Mint Proof Database trait
|
||||||
@@ -103,29 +172,12 @@ pub trait ProofsDatabase {
|
|||||||
/// Mint Proof Database Error
|
/// Mint Proof Database Error
|
||||||
type Err: Into<Error> + From<Error>;
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
/// Add [`Proofs`]
|
|
||||||
///
|
|
||||||
/// Adds proofs to the database. The database should error if the proof already exits, with a
|
|
||||||
/// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
|
|
||||||
async fn add_proofs(&self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
|
|
||||||
/// Remove [`Proofs`]
|
|
||||||
async fn remove_proofs(
|
|
||||||
&self,
|
|
||||||
ys: &[PublicKey],
|
|
||||||
quote_id: Option<Uuid>,
|
|
||||||
) -> Result<(), Self::Err>;
|
|
||||||
/// Get [`Proofs`] by ys
|
/// Get [`Proofs`] by ys
|
||||||
async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
|
async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
|
||||||
/// Get ys by quote id
|
/// Get ys by quote id
|
||||||
async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
|
async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
|
||||||
/// Get [`Proofs`] state
|
/// Get [`Proofs`] state
|
||||||
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
|
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
|
||||||
/// Get [`Proofs`] state
|
|
||||||
async fn update_proofs_states(
|
|
||||||
&self,
|
|
||||||
ys: &[PublicKey],
|
|
||||||
proofs_state: State,
|
|
||||||
) -> Result<Vec<Option<State>>, Self::Err>;
|
|
||||||
/// Get [`Proofs`] by state
|
/// Get [`Proofs`] by state
|
||||||
async fn get_proofs_by_keyset_id(
|
async fn get_proofs_by_keyset_id(
|
||||||
&self,
|
&self,
|
||||||
@@ -134,18 +186,32 @@ pub trait ProofsDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
/// Mint Signatures Database trait
|
/// Mint Signatures Transaction trait
|
||||||
pub trait SignaturesDatabase {
|
pub trait SignaturesTransaction<'a> {
|
||||||
/// Mint Signature Database Error
|
/// Mint Signature Database Error
|
||||||
type Err: Into<Error> + From<Error>;
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
/// Add [`BlindSignature`]
|
/// Add [`BlindSignature`]
|
||||||
async fn add_blind_signatures(
|
async fn add_blind_signatures(
|
||||||
&self,
|
&mut self,
|
||||||
blinded_messages: &[PublicKey],
|
blinded_messages: &[PublicKey],
|
||||||
blind_signatures: &[BlindSignature],
|
blind_signatures: &[BlindSignature],
|
||||||
quote_id: Option<Uuid>,
|
quote_id: Option<Uuid>,
|
||||||
) -> Result<(), Self::Err>;
|
) -> Result<(), Self::Err>;
|
||||||
|
|
||||||
|
/// Get [`BlindSignature`]s
|
||||||
|
async fn get_blind_signatures(
|
||||||
|
&mut self,
|
||||||
|
blinded_messages: &[PublicKey],
|
||||||
|
) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
/// Mint Signatures Database trait
|
||||||
|
pub trait SignaturesDatabase {
|
||||||
|
/// Mint Signature Database Error
|
||||||
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
/// Get [`BlindSignature`]s
|
/// Get [`BlindSignature`]s
|
||||||
async fn get_blind_signatures(
|
async fn get_blind_signatures(
|
||||||
&self,
|
&self,
|
||||||
@@ -163,18 +229,47 @@ pub trait SignaturesDatabase {
|
|||||||
) -> Result<Vec<BlindSignature>, Self::Err>;
|
) -> Result<Vec<BlindSignature>, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
/// Commit and Rollback
|
||||||
|
pub trait DbTransactionFinalizer {
|
||||||
|
/// Mint Signature Database Error
|
||||||
|
type Err: Into<Error> + From<Error>;
|
||||||
|
|
||||||
|
/// Commits all the changes into the database
|
||||||
|
async fn commit(self: Box<Self>) -> Result<(), Self::Err>;
|
||||||
|
|
||||||
|
/// Rollbacks the write transaction
|
||||||
|
async fn rollback(self: Box<Self>) -> Result<(), Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base database writer
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Transaction<'a, Error>:
|
||||||
|
DbTransactionFinalizer<Err = Error>
|
||||||
|
+ QuotesTransaction<'a, Err = Error>
|
||||||
|
+ SignaturesTransaction<'a, Err = Error>
|
||||||
|
+ ProofsTransaction<'a, Err = Error>
|
||||||
|
{
|
||||||
|
/// Set [`QuoteTTL`]
|
||||||
|
async fn set_quote_ttl(&mut self, quote_ttl: QuoteTTL) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Set [`MintInfo`]
|
||||||
|
async fn set_mint_info(&mut self, mint_info: MintInfo) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mint Database trait
|
/// Mint Database trait
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Database<Error>:
|
pub trait Database<Error>:
|
||||||
QuotesDatabase<Err = Error> + ProofsDatabase<Err = Error> + SignaturesDatabase<Err = Error>
|
QuotesDatabase<Err = Error> + ProofsDatabase<Err = Error> + SignaturesDatabase<Err = Error>
|
||||||
{
|
{
|
||||||
/// Set [`MintInfo`]
|
/// Beings a transaction
|
||||||
async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error>;
|
async fn begin_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
|
||||||
|
|
||||||
/// Get [`MintInfo`]
|
/// Get [`MintInfo`]
|
||||||
async fn get_mint_info(&self) -> Result<MintInfo, Error>;
|
async fn get_mint_info(&self) -> Result<MintInfo, Error>;
|
||||||
|
|
||||||
/// Set [`QuoteTTL`]
|
|
||||||
async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error>;
|
|
||||||
/// Get [`QuoteTTL`]
|
/// Get [`QuoteTTL`]
|
||||||
async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error>;
|
async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,20 @@
|
|||||||
//!
|
//!
|
||||||
//! This set is generic and checks the default and expected behaviour for a mint database
|
//! This set is generic and checks the default and expected behaviour for a mint database
|
||||||
//! implementation
|
//! implementation
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use cashu::secret::Secret;
|
use cashu::secret::Secret;
|
||||||
use cashu::{Amount, CurrencyUnit, SecretKey};
|
use cashu::{Amount, CurrencyUnit, SecretKey};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::database;
|
||||||
use crate::mint::MintKeySetInfo;
|
use crate::mint::MintKeySetInfo;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn setup_keyset<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: &DB) -> Id {
|
async fn setup_keyset<DB>(db: &DB) -> Id
|
||||||
|
where
|
||||||
|
DB: KeysDatabase<Err = database::Error>,
|
||||||
|
{
|
||||||
let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
|
let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
|
||||||
let keyset_info = MintKeySetInfo {
|
let keyset_info = MintKeySetInfo {
|
||||||
id: keyset_id,
|
id: keyset_id,
|
||||||
@@ -25,12 +28,17 @@ async fn setup_keyset<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: &DB
|
|||||||
max_order: 32,
|
max_order: 32,
|
||||||
input_fee_ppk: 0,
|
input_fee_ppk: 0,
|
||||||
};
|
};
|
||||||
db.add_keyset_info(keyset_info).await.unwrap();
|
let mut writer = db.begin_transaction().await.expect("db.begin()");
|
||||||
|
writer.add_keyset_info(keyset_info).await.unwrap();
|
||||||
|
writer.commit().await.expect("commit()");
|
||||||
keyset_id
|
keyset_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State transition test
|
/// State transition test
|
||||||
pub async fn state_transition<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: DB) {
|
pub async fn state_transition<DB>(db: DB)
|
||||||
|
where
|
||||||
|
DB: Database<database::Error> + KeysDatabase<Err = database::Error>,
|
||||||
|
{
|
||||||
let keyset_id = setup_keyset(&db).await;
|
let keyset_id = setup_keyset(&db).await;
|
||||||
|
|
||||||
let proofs = vec![
|
let proofs = vec![
|
||||||
@@ -53,19 +61,21 @@ pub async fn state_transition<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Add proofs to database
|
// Add proofs to database
|
||||||
db.add_proofs(proofs.clone(), None).await.unwrap();
|
let mut tx = Database::begin_transaction(&db).await.unwrap();
|
||||||
|
tx.add_proofs(proofs.clone(), None).await.unwrap();
|
||||||
|
|
||||||
// Mark one proof as `pending`
|
// Mark one proof as `pending`
|
||||||
assert!(db
|
assert!(tx
|
||||||
.update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
|
.update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// Attempt to select the `pending` proof, as `pending` again (which should fail)
|
// Attempt to select the `pending` proof, as `pending` again (which should fail)
|
||||||
assert!(db
|
assert!(tx
|
||||||
.update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
|
.update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err());
|
||||||
|
tx.commit().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unit test that is expected to be passed for a correct database implementation
|
/// Unit test that is expected to be passed for a correct database implementation
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ pub mod mint;
|
|||||||
#[cfg(feature = "wallet")]
|
#[cfg(feature = "wallet")]
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
#[cfg(all(feature = "mint", feature = "auth"))]
|
|
||||||
pub use mint::MintAuthDatabase;
|
|
||||||
#[cfg(feature = "mint")]
|
#[cfg(feature = "mint")]
|
||||||
pub use mint::{
|
pub use mint::{
|
||||||
Database as MintDatabase, KeysDatabase as MintKeysDatabase,
|
Database as MintDatabase, DbTransactionFinalizer as MintDbWriterFinalizer,
|
||||||
ProofsDatabase as MintProofsDatabase, QuotesDatabase as MintQuotesDatabase,
|
KeysDatabase as MintKeysDatabase, KeysDatabaseTransaction as MintKeyDatabaseTransaction,
|
||||||
|
ProofsDatabase as MintProofsDatabase, ProofsTransaction as MintProofsTransaction,
|
||||||
|
QuotesDatabase as MintQuotesDatabase, QuotesTransaction as MintQuotesTransaction,
|
||||||
SignaturesDatabase as MintSignaturesDatabase,
|
SignaturesDatabase as MintSignaturesDatabase,
|
||||||
|
SignaturesTransaction as MintSignatureTransaction, Transaction as MintTransaction,
|
||||||
};
|
};
|
||||||
|
#[cfg(all(feature = "mint", feature = "auth"))]
|
||||||
|
pub use mint::{MintAuthDatabase, MintAuthTransaction};
|
||||||
#[cfg(feature = "wallet")]
|
#[cfg(feature = "wallet")]
|
||||||
pub use wallet::Database as WalletDatabase;
|
pub use wallet::Database as WalletDatabase;
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ where
|
|||||||
acc
|
acc
|
||||||
});
|
});
|
||||||
|
|
||||||
auth_database
|
let mut tx = auth_database.begin_transaction().await?;
|
||||||
.add_protected_endpoints(blind_auth_endpoints)
|
|
||||||
.await?;
|
tx.add_protected_endpoints(blind_auth_endpoints).await?;
|
||||||
|
|
||||||
let mut clear_auth_endpoint = HashMap::new();
|
let mut clear_auth_endpoint = HashMap::new();
|
||||||
clear_auth_endpoint.insert(
|
clear_auth_endpoint.insert(
|
||||||
@@ -81,9 +81,9 @@ where
|
|||||||
AuthRequired::Clear,
|
AuthRequired::Clear,
|
||||||
);
|
);
|
||||||
|
|
||||||
auth_database
|
tx.add_protected_endpoints(clear_auth_endpoint).await?;
|
||||||
.add_protected_endpoints(clear_auth_endpoint)
|
|
||||||
.await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
mint_builder = mint_builder.with_auth_localstore(Arc::new(auth_database));
|
mint_builder = mint_builder.with_auth_localstore(Arc::new(auth_database));
|
||||||
|
|
||||||
|
|||||||
@@ -227,11 +227,12 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
|
|||||||
.map(|x| x.clone())
|
.map(|x| x.clone())
|
||||||
.expect("localstore");
|
.expect("localstore");
|
||||||
|
|
||||||
localstore
|
let mut tx = localstore.begin_transaction().await?;
|
||||||
.set_mint_info(mint_builder.mint_info.clone())
|
tx.set_mint_info(mint_builder.mint_info.clone()).await?;
|
||||||
.await?;
|
|
||||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||||
localstore.set_quote_ttl(quote_ttl).await?;
|
tx.set_quote_ttl(quote_ttl).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
let mint = mint_builder.build().await?;
|
let mint = mint_builder.build().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -425,9 +425,7 @@ async fn test_pay_invoice_twice() {
|
|||||||
|
|
||||||
let melt = wallet.melt(&melt_quote.id).await.unwrap();
|
let melt = wallet.melt(&melt_quote.id).await.unwrap();
|
||||||
|
|
||||||
let melt_two = wallet.melt_quote(invoice, None).await.unwrap();
|
let melt_two = wallet.melt_quote(invoice, None).await;
|
||||||
|
|
||||||
let melt_two = wallet.melt(&melt_two.id).await;
|
|
||||||
|
|
||||||
match melt_two {
|
match melt_two {
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
|
|||||||
@@ -51,13 +51,15 @@ async fn test_correct_keyset() {
|
|||||||
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
.with_seed(mnemonic.to_seed_normalized("").to_vec());
|
||||||
|
|
||||||
let mint = mint_builder.build().await.unwrap();
|
let mint = mint_builder.build().await.unwrap();
|
||||||
|
let mut tx = localstore.begin_transaction().await.unwrap();
|
||||||
|
|
||||||
localstore
|
tx.set_mint_info(mint_builder.mint_info.clone())
|
||||||
.set_mint_info(mint_builder.mint_info.clone())
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let quote_ttl = QuoteTTL::new(10000, 10000);
|
let quote_ttl = QuoteTTL::new(10000, 10000);
|
||||||
localstore.set_quote_ttl(quote_ttl).await.unwrap();
|
tx.set_quote_ttl(quote_ttl).await.unwrap();
|
||||||
|
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
|
||||||
let active = mint.get_active_keysets();
|
let active = mint.get_active_keysets();
|
||||||
|
|
||||||
|
|||||||
@@ -655,8 +655,16 @@ impl CdkMint for MintRPCServer {
|
|||||||
|
|
||||||
mint_quote.state = state;
|
mint_quote.state = state;
|
||||||
|
|
||||||
self.mint
|
let mut tx = self
|
||||||
.update_mint_quote(mint_quote)
|
.mint
|
||||||
|
.localstore
|
||||||
|
.begin_transaction()
|
||||||
|
.await
|
||||||
|
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
|
||||||
|
tx.add_or_replace_mint_quote(mint_quote)
|
||||||
|
.await
|
||||||
|
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
|
||||||
|
tx.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
|
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -526,12 +526,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat);
|
mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat);
|
||||||
|
|
||||||
auth_localstore
|
let mut tx = auth_localstore.begin_transaction().await?;
|
||||||
.remove_protected_endpoints(unprotected_endpoints)
|
|
||||||
.await?;
|
tx.remove_protected_endpoints(unprotected_endpoints).await?;
|
||||||
auth_localstore
|
tx.add_protected_endpoints(protected_endpoints).await?;
|
||||||
.add_protected_endpoints(protected_endpoints)
|
tx.commit().await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mint = mint_builder.build().await?;
|
let mint = mint_builder.build().await?;
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ pub async fn init_keysets(
|
|||||||
// Get keysets info from DB
|
// Get keysets info from DB
|
||||||
let keysets_infos = localstore.get_keyset_infos().await?;
|
let keysets_infos = localstore.get_keyset_infos().await?;
|
||||||
|
|
||||||
|
let mut tx = localstore.begin_transaction().await?;
|
||||||
if !keysets_infos.is_empty() {
|
if !keysets_infos.is_empty() {
|
||||||
tracing::debug!("Setting all saved keysets to inactive");
|
tracing::debug!("Setting all saved keysets to inactive");
|
||||||
for keyset in keysets_infos.clone() {
|
for keyset in keysets_infos.clone() {
|
||||||
// Set all to in active
|
// Set all to in active
|
||||||
let mut keyset = keyset;
|
let mut keyset = keyset;
|
||||||
keyset.active = false;
|
keyset.active = false;
|
||||||
localstore.add_keyset_info(keyset).await?;
|
tx.add_keyset_info(keyset).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
|
let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
|
||||||
@@ -74,9 +75,9 @@ pub async fn init_keysets(
|
|||||||
active_keysets.insert(id, keyset);
|
active_keysets.insert(id, keyset);
|
||||||
let mut keyset_info = highest_index_keyset;
|
let mut keyset_info = highest_index_keyset;
|
||||||
keyset_info.active = true;
|
keyset_info.active = true;
|
||||||
localstore.add_keyset_info(keyset_info).await?;
|
tx.add_keyset_info(keyset_info).await?;
|
||||||
active_keyset_units.push(unit.clone());
|
active_keyset_units.push(unit.clone());
|
||||||
localstore.set_active_keyset(unit, id).await?;
|
tx.set_active_keyset(unit, id).await?;
|
||||||
} else {
|
} else {
|
||||||
// Check to see if there are not keysets by this unit
|
// Check to see if there are not keysets by this unit
|
||||||
let derivation_path_index = if keysets.is_empty() {
|
let derivation_path_index = if keysets.is_empty() {
|
||||||
@@ -104,8 +105,8 @@ pub async fn init_keysets(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let id = keyset_info.id;
|
let id = keyset_info.id;
|
||||||
localstore.add_keyset_info(keyset_info).await?;
|
tx.add_keyset_info(keyset_info).await?;
|
||||||
localstore.set_active_keyset(unit.clone(), id).await?;
|
tx.set_active_keyset(unit.clone(), id).await?;
|
||||||
active_keysets.insert(id, keyset);
|
active_keysets.insert(id, keyset);
|
||||||
active_keyset_units.push(unit.clone());
|
active_keyset_units.push(unit.clone());
|
||||||
};
|
};
|
||||||
@@ -113,6 +114,8 @@ pub async fn init_keysets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok((active_keysets, active_keyset_units))
|
Ok((active_keysets, active_keyset_units))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ impl DbSignatory {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
supported_units.entry(CurrencyUnit::Auth).or_insert((0, 1));
|
supported_units.entry(CurrencyUnit::Auth).or_insert((0, 1));
|
||||||
|
let mut tx = localstore.begin_transaction().await?;
|
||||||
|
|
||||||
// Create new keysets for supported units that aren't covered by the current keysets
|
// Create new keysets for supported units that aren't covered by the current keysets
|
||||||
for (unit, (fee, max_order)) in supported_units {
|
for (unit, (fee, max_order)) in supported_units {
|
||||||
@@ -77,12 +78,14 @@ impl DbSignatory {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let id = keyset_info.id;
|
let id = keyset_info.id;
|
||||||
localstore.add_keyset_info(keyset_info).await?;
|
tx.add_keyset_info(keyset_info).await?;
|
||||||
localstore.set_active_keyset(unit, id).await?;
|
tx.set_active_keyset(unit, id).await?;
|
||||||
active_keysets.insert(id, keyset);
|
active_keysets.insert(id, keyset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
let keys = Self {
|
let keys = Self {
|
||||||
keysets: Default::default(),
|
keysets: Default::default(),
|
||||||
active_keysets: Default::default(),
|
active_keysets: Default::default(),
|
||||||
@@ -244,8 +247,10 @@ impl Signatory for DbSignatory {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let id = info.id;
|
let id = info.id;
|
||||||
self.localstore.add_keyset_info(info.clone()).await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
self.localstore.set_active_keyset(args.unit, id).await?;
|
tx.add_keyset_info(info.clone()).await?;
|
||||||
|
tx.set_active_keyset(args.unit, id).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
self.reload_keys_from_db().await?;
|
self.reload_keys_from_db().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ use std::path::Path;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cdk_common::database::{self, MintAuthDatabase};
|
use cdk_common::database::{self, MintAuthDatabase, MintAuthTransaction};
|
||||||
use cdk_common::mint::MintKeySetInfo;
|
use cdk_common::mint::MintKeySetInfo;
|
||||||
use cdk_common::nuts::{AuthProof, BlindSignature, Id, PublicKey, State};
|
use cdk_common::nuts::{AuthProof, BlindSignature, Id, PublicKey, State};
|
||||||
use cdk_common::{AuthRequired, ProtectedEndpoint};
|
use cdk_common::{AuthRequired, ProtectedEndpoint};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::async_rusqlite::AsyncRusqlite;
|
use super::async_rusqlite::AsyncRusqlite;
|
||||||
use super::{sqlite_row_to_blind_signature, sqlite_row_to_keyset_info};
|
use super::{sqlite_row_to_blind_signature, sqlite_row_to_keyset_info, SqliteTransaction};
|
||||||
use crate::column_as_string;
|
use crate::column_as_string;
|
||||||
use crate::common::{create_sqlite_pool, migrate};
|
use crate::common::{create_sqlite_pool, migrate};
|
||||||
use crate::mint::async_rusqlite::query;
|
use crate::mint::async_rusqlite::query;
|
||||||
@@ -56,11 +56,9 @@ impl MintSqliteAuthDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MintAuthDatabase for MintSqliteAuthDatabase {
|
impl MintAuthTransaction<database::Error> for SqliteTransaction<'_> {
|
||||||
type Err = database::Error;
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn set_active_keyset(&self, id: Id) -> Result<(), Self::Err> {
|
async fn set_active_keyset(&mut self, id: Id) -> Result<(), database::Error> {
|
||||||
tracing::info!("Setting auth keyset {id} active");
|
tracing::info!("Setting auth keyset {id} active");
|
||||||
query(
|
query(
|
||||||
r#"
|
r#"
|
||||||
@@ -72,30 +70,13 @@ impl MintAuthDatabase for MintSqliteAuthDatabase {
|
|||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(":id", id.to_string())
|
.bind(":id", id.to_string())
|
||||||
.execute(&self.pool)
|
.execute(&self.inner)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_active_keyset_id(&self) -> Result<Option<Id>, Self::Err> {
|
async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), database::Error> {
|
||||||
Ok(query(
|
|
||||||
r#"
|
|
||||||
SELECT
|
|
||||||
id
|
|
||||||
FROM
|
|
||||||
keyset
|
|
||||||
WHERE
|
|
||||||
active = 1;
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.pluck(&self.pool)
|
|
||||||
.await?
|
|
||||||
.map(|id| Ok::<_, Error>(column_as_string!(id, Id::from_str, Id::from_bytes)))
|
|
||||||
.transpose()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err> {
|
|
||||||
query(
|
query(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO
|
INSERT INTO
|
||||||
@@ -125,12 +106,159 @@ impl MintAuthDatabase for MintSqliteAuthDatabase {
|
|||||||
.bind(":derivation_path", keyset.derivation_path.to_string())
|
.bind(":derivation_path", keyset.derivation_path.to_string())
|
||||||
.bind(":max_order", keyset.max_order)
|
.bind(":max_order", keyset.max_order)
|
||||||
.bind(":derivation_path_index", keyset.derivation_path_index)
|
.bind(":derivation_path_index", keyset.derivation_path_index)
|
||||||
.execute(&self.pool)
|
.execute(&self.inner)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_proof(&mut self, proof: AuthProof) -> Result<(), database::Error> {
|
||||||
|
if let Err(err) = query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO proof
|
||||||
|
(y, keyset_id, secret, c, state)
|
||||||
|
VALUES
|
||||||
|
(:y, :keyset_id, :secret, :c, :state)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(":y", proof.y()?.to_bytes().to_vec())
|
||||||
|
.bind(":keyset_id", proof.keyset_id.to_string())
|
||||||
|
.bind(":secret", proof.secret.to_string())
|
||||||
|
.bind(":c", proof.c.to_bytes().to_vec())
|
||||||
|
.bind(":state", "UNSPENT".to_string())
|
||||||
|
.execute(&self.inner)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_proof_state(
|
||||||
|
&mut self,
|
||||||
|
y: &PublicKey,
|
||||||
|
proofs_state: State,
|
||||||
|
) -> Result<Option<State>, Self::Err> {
|
||||||
|
let current_state = query(r#"SELECT state FROM proof WHERE y = :y"#)
|
||||||
|
.bind(":y", y.to_bytes().to_vec())
|
||||||
|
.pluck(&self.inner)
|
||||||
|
.await?
|
||||||
|
.map(|state| Ok::<_, Error>(column_as_string!(state, State::from_str)))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
query(r#"UPDATE proof SET state = :new_state WHERE state = :state AND y = :y"#)
|
||||||
|
.bind(":y", y.to_bytes().to_vec())
|
||||||
|
.bind(
|
||||||
|
":state",
|
||||||
|
current_state.as_ref().map(|state| state.to_string()),
|
||||||
|
)
|
||||||
|
.bind(":new_state", proofs_state.to_string())
|
||||||
|
.execute(&self.inner)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(current_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_blind_signatures(
|
||||||
|
&mut self,
|
||||||
|
blinded_messages: &[PublicKey],
|
||||||
|
blind_signatures: &[BlindSignature],
|
||||||
|
) -> Result<(), database::Error> {
|
||||||
|
for (message, signature) in blinded_messages.iter().zip(blind_signatures) {
|
||||||
|
query(
|
||||||
|
r#"
|
||||||
|
INSERT
|
||||||
|
INTO blind_signature
|
||||||
|
(y, amount, keyset_id, c)
|
||||||
|
VALUES
|
||||||
|
(:y, :amount, :keyset_id, :c)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(":y", message.to_bytes().to_vec())
|
||||||
|
.bind(":amount", u64::from(signature.amount) as i64)
|
||||||
|
.bind(":keyset_id", signature.keyset_id.to_string())
|
||||||
|
.bind(":c", signature.c.to_bytes().to_vec())
|
||||||
|
.execute(&self.inner)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_protected_endpoints(
|
||||||
|
&mut self,
|
||||||
|
protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
|
||||||
|
) -> Result<(), database::Error> {
|
||||||
|
for (endpoint, auth) in protected_endpoints.iter() {
|
||||||
|
if let Err(err) = query(
|
||||||
|
r#"
|
||||||
|
INSERT OR REPLACE INTO protected_endpoints
|
||||||
|
(endpoint, auth)
|
||||||
|
VALUES (:endpoint, :auth);
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(":endpoint", serde_json::to_string(endpoint)?)
|
||||||
|
.bind(":auth", serde_json::to_string(auth)?)
|
||||||
|
.execute(&self.inner)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::debug!(
|
||||||
|
"Attempting to add protected endpoint. Skipping.... {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn remove_protected_endpoints(
|
||||||
|
&mut self,
|
||||||
|
protected_endpoints: Vec<ProtectedEndpoint>,
|
||||||
|
) -> Result<(), database::Error> {
|
||||||
|
query(r#"DELETE FROM protected_endpoints WHERE endpoint IN (:endpoints)"#)
|
||||||
|
.bind_vec(
|
||||||
|
":endpoints",
|
||||||
|
protected_endpoints
|
||||||
|
.iter()
|
||||||
|
.map(serde_json::to_string)
|
||||||
|
.collect::<Result<_, _>>()?,
|
||||||
|
)
|
||||||
|
.execute(&self.inner)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl MintAuthDatabase for MintSqliteAuthDatabase {
|
||||||
|
type Err = database::Error;
|
||||||
|
|
||||||
|
async fn begin_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> Result<Box<dyn MintAuthTransaction<database::Error> + Send + Sync + 'a>, database::Error>
|
||||||
|
{
|
||||||
|
Ok(Box::new(SqliteTransaction {
|
||||||
|
inner: self.pool.begin().await?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_active_keyset_id(&self) -> Result<Option<Id>, Self::Err> {
|
||||||
|
Ok(query(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id
|
||||||
|
FROM
|
||||||
|
keyset
|
||||||
|
WHERE
|
||||||
|
active = 1;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.pluck(&self.pool)
|
||||||
|
.await?
|
||||||
|
.map(|id| Ok::<_, Error>(column_as_string!(id, Id::from_str, Id::from_bytes)))
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
|
async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
|
||||||
Ok(query(
|
Ok(query(
|
||||||
r#"SELECT
|
r#"SELECT
|
||||||
@@ -177,28 +305,6 @@ impl MintAuthDatabase for MintSqliteAuthDatabase {
|
|||||||
.collect::<Result<Vec<_>, _>>()?)
|
.collect::<Result<Vec<_>, _>>()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_proof(&self, proof: AuthProof) -> Result<(), Self::Err> {
|
|
||||||
if let Err(err) = query(
|
|
||||||
r#"
|
|
||||||
INSERT INTO proof
|
|
||||||
(y, keyset_id, secret, c, state)
|
|
||||||
VALUES
|
|
||||||
(:y, :keyset_id, :secret, :c, :state)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(":y", proof.y()?.to_bytes().to_vec())
|
|
||||||
.bind(":keyset_id", proof.keyset_id.to_string())
|
|
||||||
.bind(":secret", proof.secret.to_string())
|
|
||||||
.bind(":c", proof.c.to_bytes().to_vec())
|
|
||||||
.bind(":state", "UNSPENT".to_string())
|
|
||||||
.execute(&self.pool)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
|
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
|
||||||
let mut current_states = query(r#"SELECT y, state FROM proof WHERE y IN (:ys)"#)
|
let mut current_states = query(r#"SELECT y, state FROM proof WHERE y IN (:ys)"#)
|
||||||
.bind_vec(":ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
.bind_vec(":ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
||||||
@@ -216,65 +322,6 @@ impl MintAuthDatabase for MintSqliteAuthDatabase {
|
|||||||
Ok(ys.iter().map(|y| current_states.remove(y)).collect())
|
Ok(ys.iter().map(|y| current_states.remove(y)).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_proof_state(
|
|
||||||
&self,
|
|
||||||
y: &PublicKey,
|
|
||||||
proofs_state: State,
|
|
||||||
) -> Result<Option<State>, Self::Err> {
|
|
||||||
let transaction = self.pool.begin().await?;
|
|
||||||
|
|
||||||
let current_state = query(r#"SELECT state FROM proof WHERE y = :y"#)
|
|
||||||
.bind(":y", y.to_bytes().to_vec())
|
|
||||||
.pluck(&transaction)
|
|
||||||
.await?
|
|
||||||
.map(|state| Ok::<_, Error>(column_as_string!(state, State::from_str)))
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
query(r#"UPDATE proof SET state = :new_state WHERE state = :state AND y = :y"#)
|
|
||||||
.bind(":y", y.to_bytes().to_vec())
|
|
||||||
.bind(
|
|
||||||
":state",
|
|
||||||
current_state.as_ref().map(|state| state.to_string()),
|
|
||||||
)
|
|
||||||
.bind(":new_state", proofs_state.to_string())
|
|
||||||
.execute(&transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(current_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_blind_signatures(
|
|
||||||
&self,
|
|
||||||
blinded_messages: &[PublicKey],
|
|
||||||
blind_signatures: &[BlindSignature],
|
|
||||||
) -> Result<(), Self::Err> {
|
|
||||||
let transaction = self.pool.begin().await?;
|
|
||||||
|
|
||||||
for (message, signature) in blinded_messages.iter().zip(blind_signatures) {
|
|
||||||
query(
|
|
||||||
r#"
|
|
||||||
INSERT
|
|
||||||
INTO blind_signature
|
|
||||||
(y, amount, keyset_id, c)
|
|
||||||
VALUES
|
|
||||||
(:y, :amount, :keyset_id, :c)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(":y", message.to_bytes().to_vec())
|
|
||||||
.bind(":amount", u64::from(signature.amount) as i64)
|
|
||||||
.bind(":keyset_id", signature.keyset_id.to_string())
|
|
||||||
.bind(":c", signature.c.to_bytes().to_vec())
|
|
||||||
.execute(&transaction)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_blind_signatures(
|
async fn get_blind_signatures(
|
||||||
&self,
|
&self,
|
||||||
blinded_messages: &[PublicKey],
|
blinded_messages: &[PublicKey],
|
||||||
@@ -319,53 +366,6 @@ impl MintAuthDatabase for MintSqliteAuthDatabase {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_protected_endpoints(
|
|
||||||
&self,
|
|
||||||
protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
|
|
||||||
) -> Result<(), Self::Err> {
|
|
||||||
let transaction = self.pool.begin().await?;
|
|
||||||
|
|
||||||
for (endpoint, auth) in protected_endpoints.iter() {
|
|
||||||
if let Err(err) = query(
|
|
||||||
r#"
|
|
||||||
INSERT OR REPLACE INTO protected_endpoints
|
|
||||||
(endpoint, auth)
|
|
||||||
VALUES (:endpoint, :auth);
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(":endpoint", serde_json::to_string(endpoint)?)
|
|
||||||
.bind(":auth", serde_json::to_string(auth)?)
|
|
||||||
.execute(&transaction)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::debug!(
|
|
||||||
"Attempting to add protected endpoint. Skipping.... {:?}",
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn remove_protected_endpoints(
|
|
||||||
&self,
|
|
||||||
protected_endpoints: Vec<ProtectedEndpoint>,
|
|
||||||
) -> Result<(), Self::Err> {
|
|
||||||
query(r#"DELETE FROM protected_endpoints WHERE endpoint IN (:endpoints)"#)
|
|
||||||
.bind_vec(
|
|
||||||
":endpoints",
|
|
||||||
protected_endpoints
|
|
||||||
.iter()
|
|
||||||
.map(serde_json::to_string)
|
|
||||||
.collect::<Result<_, _>>()?,
|
|
||||||
)
|
|
||||||
.execute(&self.pool)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_auth_for_endpoint(
|
async fn get_auth_for_endpoint(
|
||||||
&self,
|
&self,
|
||||||
protected_endpoint: ProtectedEndpoint,
|
protected_endpoint: ProtectedEndpoint,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
//! In-memory database that is provided by the `cdk-sqlite` crate, mainly for testing purposes.
|
//! In-memory database that is provided by the `cdk-sqlite` crate, mainly for testing purposes.
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cdk_common::database::{
|
use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
|
||||||
self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
|
|
||||||
};
|
|
||||||
use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
|
use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
|
||||||
use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
|
use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
|
||||||
use cdk_common::MintInfo;
|
use cdk_common::MintInfo;
|
||||||
@@ -31,28 +29,32 @@ pub async fn new_with_state(
|
|||||||
mint_info: MintInfo,
|
mint_info: MintInfo,
|
||||||
) -> Result<MintSqliteDatabase, database::Error> {
|
) -> Result<MintSqliteDatabase, database::Error> {
|
||||||
let db = empty().await?;
|
let db = empty().await?;
|
||||||
|
let mut tx = MintKeysDatabase::begin_transaction(&db).await?;
|
||||||
|
|
||||||
for active_keyset in active_keysets {
|
for active_keyset in active_keysets {
|
||||||
db.set_active_keyset(active_keyset.0, active_keyset.1)
|
tx.set_active_keyset(active_keyset.0, active_keyset.1)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for keyset in keysets {
|
for keyset in keysets {
|
||||||
db.add_keyset_info(keyset).await?;
|
tx.add_keyset_info(keyset).await?;
|
||||||
}
|
}
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
let mut tx = MintDatabase::begin_transaction(&db).await?;
|
||||||
|
|
||||||
for quote in mint_quotes {
|
for quote in mint_quotes {
|
||||||
db.add_mint_quote(quote).await?;
|
tx.add_or_replace_mint_quote(quote).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for quote in melt_quotes {
|
for quote in melt_quotes {
|
||||||
db.add_melt_quote(quote).await?;
|
tx.add_melt_quote(quote).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
db.add_proofs(pending_proofs, None).await?;
|
tx.add_proofs(pending_proofs, None).await?;
|
||||||
db.add_proofs(spent_proofs, None).await?;
|
tx.add_proofs(spent_proofs, None).await?;
|
||||||
|
tx.set_mint_info(mint_info).await?;
|
||||||
db.set_mint_info(mint_info).await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ pub mod cdk_database {
|
|||||||
#[cfg(feature = "mint")]
|
#[cfg(feature = "mint")]
|
||||||
pub use cdk_common::database::{
|
pub use cdk_common::database::{
|
||||||
MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
|
MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
|
||||||
MintSignaturesDatabase,
|
MintSignaturesDatabase, MintTransaction,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,17 +163,16 @@ impl Mint {
|
|||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let mut tx = auth_localstore.begin_transaction().await?;
|
||||||
|
|
||||||
// Add proof to the database
|
// Add proof to the database
|
||||||
auth_localstore
|
tx.add_proof(proof.clone()).await.map_err(|err| {
|
||||||
.add_proof(proof.clone())
|
tracing::error!("Failed to add proof to database: {:?}", err);
|
||||||
.await
|
err
|
||||||
.map_err(|err| {
|
})?;
|
||||||
tracing::error!("Failed to add proof to database: {:?}", err);
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Update proof state to spent
|
// Update proof state to spent
|
||||||
let state = match auth_localstore.update_proof_state(&y, State::Spent).await {
|
let state = match tx.update_proof_state(&y, State::Spent).await {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Successfully updated proof state to SPENT, previous state: {:?}",
|
"Successfully updated proof state to SPENT, previous state: {:?}",
|
||||||
@@ -205,6 +204,8 @@ impl Mint {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,10 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{CheckStateRequest, CheckStateResponse, Mint, ProofState, PublicKey, State};
|
use super::{CheckStateRequest, CheckStateResponse, Mint, ProofState, State};
|
||||||
use crate::{cdk_database, Error};
|
use crate::Error;
|
||||||
|
|
||||||
impl Mint {
|
impl Mint {
|
||||||
/// Helper function to reset proofs to their original state, skipping spent proofs
|
|
||||||
async fn reset_proofs_to_original_state(
|
|
||||||
&self,
|
|
||||||
ys: &[PublicKey],
|
|
||||||
original_states: Vec<Option<State>>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut ys_by_state = HashMap::new();
|
|
||||||
let mut unknown_proofs = Vec::new();
|
|
||||||
for (y, state) in ys.iter().zip(original_states) {
|
|
||||||
if let Some(state) = state {
|
|
||||||
// Skip attempting to update proofs that were originally spent
|
|
||||||
if state != State::Spent {
|
|
||||||
ys_by_state.entry(state).or_insert_with(Vec::new).push(*y);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unknown_proofs.push(*y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (state, ys) in ys_by_state {
|
|
||||||
self.localstore.update_proofs_states(&ys, state).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.localstore.remove_proofs(&unknown_proofs, None).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check state
|
/// Check state
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn check_state(
|
pub async fn check_state(
|
||||||
@@ -70,50 +40,4 @@ impl Mint {
|
|||||||
states: proof_states,
|
states: proof_states,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check Tokens are not spent or pending
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn check_ys_spendable(
|
|
||||||
&self,
|
|
||||||
ys: &[PublicKey],
|
|
||||||
proof_state: State,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let original_proofs_state =
|
|
||||||
match self.localstore.update_proofs_states(ys, proof_state).await {
|
|
||||||
Ok(states) => states,
|
|
||||||
Err(cdk_database::Error::AttemptUpdateSpentProof)
|
|
||||||
| Err(cdk_database::Error::AttemptRemoveSpentProof) => {
|
|
||||||
return Err(Error::TokenAlreadySpent)
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(ys.len() == original_proofs_state.len());
|
|
||||||
|
|
||||||
let proofs_state = original_proofs_state
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<HashSet<&State>>();
|
|
||||||
|
|
||||||
if proofs_state.contains(&State::Pending) {
|
|
||||||
// Reset states before returning error
|
|
||||||
self.reset_proofs_to_original_state(ys, original_proofs_state)
|
|
||||||
.await?;
|
|
||||||
return Err(Error::TokenPending);
|
|
||||||
}
|
|
||||||
|
|
||||||
if proofs_state.contains(&State::Spent) {
|
|
||||||
// Reset states before returning error
|
|
||||||
self.reset_proofs_to_original_state(ys, original_proofs_state)
|
|
||||||
.await?;
|
|
||||||
return Err(Error::TokenAlreadySpent);
|
|
||||||
}
|
|
||||||
|
|
||||||
for public_key in ys {
|
|
||||||
tracing::trace!("proof: {} set to {}", public_key.to_hex(), proof_state);
|
|
||||||
self.pubsub_manager.proof_state((*public_key, proof_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,9 @@ impl Mint {
|
|||||||
create_invoice_response.request_lookup_id,
|
create_invoice_response.request_lookup_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.localstore.add_mint_quote(quote.clone()).await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
tx.add_or_replace_mint_quote(quote.clone()).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
let quote: MintQuoteBolt11Response<Uuid> = quote.into();
|
let quote: MintQuoteBolt11Response<Uuid> = quote.into();
|
||||||
|
|
||||||
@@ -121,8 +123,8 @@ impl Mint {
|
|||||||
&self,
|
&self,
|
||||||
quote_id: &Uuid,
|
quote_id: &Uuid,
|
||||||
) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
|
) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
|
||||||
let quote = self
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
.localstore
|
let mut mint_quote = tx
|
||||||
.get_mint_quote(quote_id)
|
.get_mint_quote(quote_id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Error::UnknownQuote)?;
|
.ok_or(Error::UnknownQuote)?;
|
||||||
@@ -130,30 +132,24 @@ impl Mint {
|
|||||||
// Since the pending state is not part of the NUT it should not be part of the
|
// Since the pending state is not part of the NUT it should not be part of the
|
||||||
// response. In practice the wallet should not be checking the state of
|
// response. In practice the wallet should not be checking the state of
|
||||||
// a quote while waiting for the mint response.
|
// a quote while waiting for the mint response.
|
||||||
let state = match quote.state {
|
if mint_quote.state == MintQuoteState::Unpaid {
|
||||||
MintQuoteState::Pending => MintQuoteState::Paid,
|
self.check_mint_quote_paid(tx, &mut mint_quote)
|
||||||
MintQuoteState::Unpaid => self.check_mint_quote_paid(quote_id).await?,
|
.await?
|
||||||
s => s,
|
.commit()
|
||||||
};
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(MintQuoteBolt11Response {
|
Ok(MintQuoteBolt11Response {
|
||||||
quote: quote.id,
|
quote: mint_quote.id,
|
||||||
request: quote.request,
|
request: mint_quote.request,
|
||||||
state,
|
state: mint_quote.state,
|
||||||
expiry: Some(quote.expiry),
|
expiry: Some(mint_quote.expiry),
|
||||||
pubkey: quote.pubkey,
|
pubkey: mint_quote.pubkey,
|
||||||
amount: Some(quote.amount),
|
amount: Some(mint_quote.amount),
|
||||||
unit: Some(quote.unit.clone()),
|
unit: Some(mint_quote.unit.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update mint quote
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn update_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
|
|
||||||
self.localstore.add_mint_quote(quote).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get mint quotes
|
/// Get mint quotes
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
|
pub async fn mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
|
||||||
@@ -186,7 +182,9 @@ impl Mint {
|
|||||||
/// Remove mint quote
|
/// Remove mint quote
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
|
pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
|
||||||
self.localstore.remove_mint_quote(quote_id).await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
tx.remove_mint_quote(quote_id).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -215,9 +213,10 @@ impl Mint {
|
|||||||
mint_quote.id
|
mint_quote.id
|
||||||
);
|
);
|
||||||
if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
|
if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
|
||||||
self.localstore
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
|
tx.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
|
||||||
.await?;
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"{} Quote already {} continuing",
|
"{} Quote already {} continuing",
|
||||||
@@ -238,39 +237,27 @@ impl Mint {
|
|||||||
&self,
|
&self,
|
||||||
mint_request: MintRequest<Uuid>,
|
mint_request: MintRequest<Uuid>,
|
||||||
) -> Result<MintResponse, Error> {
|
) -> Result<MintResponse, Error> {
|
||||||
let mint_quote = self
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
.localstore
|
|
||||||
|
let mut mint_quote = tx
|
||||||
.get_mint_quote(&mint_request.quote)
|
.get_mint_quote(&mint_request.quote)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Error::UnknownQuote)?;
|
.ok_or(Error::UnknownQuote)?;
|
||||||
|
|
||||||
let state = self
|
let mut tx = if mint_quote.state == MintQuoteState::Unpaid {
|
||||||
.localstore
|
self.check_mint_quote_paid(tx, &mut mint_quote).await?
|
||||||
.update_mint_quote_state(&mint_request.quote, MintQuoteState::Pending)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let state = if state == MintQuoteState::Unpaid {
|
|
||||||
self.check_mint_quote_paid(&mint_quote.id).await?
|
|
||||||
} else {
|
} else {
|
||||||
state
|
tx
|
||||||
};
|
};
|
||||||
|
|
||||||
match state {
|
match mint_quote.state {
|
||||||
MintQuoteState::Unpaid => {
|
MintQuoteState::Unpaid => {
|
||||||
let _state = self
|
|
||||||
.localstore
|
|
||||||
.update_mint_quote_state(&mint_request.quote, MintQuoteState::Unpaid)
|
|
||||||
.await?;
|
|
||||||
return Err(Error::UnpaidQuote);
|
return Err(Error::UnpaidQuote);
|
||||||
}
|
}
|
||||||
MintQuoteState::Pending => {
|
MintQuoteState::Pending => {
|
||||||
return Err(Error::PendingQuote);
|
return Err(Error::PendingQuote);
|
||||||
}
|
}
|
||||||
MintQuoteState::Issued => {
|
MintQuoteState::Issued => {
|
||||||
let _state = self
|
|
||||||
.localstore
|
|
||||||
.update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
|
|
||||||
.await?;
|
|
||||||
return Err(Error::IssuedQuote);
|
return Err(Error::IssuedQuote);
|
||||||
}
|
}
|
||||||
MintQuoteState::Paid => (),
|
MintQuoteState::Paid => (),
|
||||||
@@ -282,17 +269,14 @@ impl Mint {
|
|||||||
mint_request.verify_signature(pubkey)?;
|
mint_request.verify_signature(pubkey)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Verification { amount, unit } = match self.verify_outputs(&mint_request.outputs).await {
|
let Verification { amount, unit } =
|
||||||
Ok(verification) => verification,
|
match self.verify_outputs(&mut tx, &mint_request.outputs).await {
|
||||||
Err(err) => {
|
Ok(verification) => verification,
|
||||||
tracing::debug!("Could not verify mint outputs");
|
Err(err) => {
|
||||||
self.localstore
|
tracing::debug!("Could not verify mint outputs");
|
||||||
.update_mint_quote_state(&mint_request.quote, MintQuoteState::Paid)
|
return Err(err);
|
||||||
.await?;
|
}
|
||||||
|
};
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// We check the total value of blinded messages == mint quote
|
// We check the total value of blinded messages == mint quote
|
||||||
if amount != mint_quote.amount {
|
if amount != mint_quote.amount {
|
||||||
@@ -313,21 +297,21 @@ impl Mint {
|
|||||||
blind_signatures.push(blind_signature);
|
blind_signatures.push(blind_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.localstore
|
tx.add_blind_signatures(
|
||||||
.add_blind_signatures(
|
&mint_request
|
||||||
&mint_request
|
.outputs
|
||||||
.outputs
|
.iter()
|
||||||
.iter()
|
.map(|p| p.blinded_secret)
|
||||||
.map(|p| p.blinded_secret)
|
.collect::<Vec<PublicKey>>(),
|
||||||
.collect::<Vec<PublicKey>>(),
|
&blind_signatures,
|
||||||
&blind_signatures,
|
Some(mint_request.quote),
|
||||||
Some(mint_request.quote),
|
)
|
||||||
)
|
.await?;
|
||||||
|
|
||||||
|
tx.update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.localstore
|
tx.commit().await?;
|
||||||
.update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.pubsub_manager
|
self.pubsub_manager
|
||||||
.mint_quote_bolt11_status(mint_quote, MintQuoteState::Issued);
|
.mint_quote_bolt11_status(mint_quote, MintQuoteState::Issued);
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
use cdk_common::common::PaymentProcessorKey;
|
use cdk_common::common::PaymentProcessorKey;
|
||||||
|
use cdk_common::database::{self, MintTransaction};
|
||||||
|
use cdk_common::mint::MintQuote;
|
||||||
use cdk_common::MintQuoteState;
|
use cdk_common::MintQuoteState;
|
||||||
|
|
||||||
use super::Mint;
|
use super::Mint;
|
||||||
use crate::mint::Uuid;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
impl Mint {
|
impl Mint {
|
||||||
/// Check the status of an ln payment for a quote
|
/// Check the status of an ln payment for a quote
|
||||||
pub async fn check_mint_quote_paid(&self, quote_id: &Uuid) -> Result<MintQuoteState, Error> {
|
pub async fn check_mint_quote_paid(
|
||||||
let mut quote = self
|
&self,
|
||||||
.localstore
|
tx: Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
|
||||||
.get_mint_quote(quote_id)
|
quote: &mut MintQuote,
|
||||||
.await?
|
) -> Result<Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>, Error> {
|
||||||
.ok_or(Error::UnknownQuote)?;
|
|
||||||
|
|
||||||
let ln = match self.ln.get(&PaymentProcessorKey::new(
|
let ln = match self.ln.get(&PaymentProcessorKey::new(
|
||||||
quote.unit.clone(),
|
quote.unit.clone(),
|
||||||
cdk_common::PaymentMethod::Bolt11,
|
cdk_common::PaymentMethod::Bolt11,
|
||||||
@@ -26,14 +25,16 @@ impl Mint {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
let ln_status = ln
|
let ln_status = ln
|
||||||
.check_incoming_payment_status("e.request_lookup_id)
|
.check_incoming_payment_status("e.request_lookup_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
|
||||||
if ln_status != quote.state && quote.state != MintQuoteState::Issued {
|
if ln_status != quote.state && quote.state != MintQuoteState::Issued {
|
||||||
self.localstore
|
tx.update_mint_quote_state("e.id, ln_status).await?;
|
||||||
.update_mint_quote_state(quote_id, ln_status)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
quote.state = ln_status;
|
quote.state = ln_status;
|
||||||
|
|
||||||
@@ -41,6 +42,6 @@ impl Mint {
|
|||||||
.mint_quote_bolt11_status(quote.clone(), ln_status);
|
.mint_quote_bolt11_status(quote.clone(), ln_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(quote.state)
|
Ok(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
use cdk_common::database::{self, MintTransaction};
|
||||||
use cdk_common::nut00::ProofsMethods;
|
use cdk_common::nut00::ProofsMethods;
|
||||||
use cdk_common::nut05::MeltMethodOptions;
|
use cdk_common::nut05::MeltMethodOptions;
|
||||||
use cdk_common::MeltOptions;
|
use cdk_common::MeltOptions;
|
||||||
@@ -14,6 +15,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::amount::to_unit;
|
use crate::amount::to_unit;
|
||||||
use crate::cdk_payment::{MakePaymentResponse, MintPayment};
|
use crate::cdk_payment::{MakePaymentResponse, MintPayment};
|
||||||
|
use crate::mint::proof_writer::ProofWriter;
|
||||||
use crate::mint::verification::Verification;
|
use crate::mint::verification::Verification;
|
||||||
use crate::mint::SigFlag;
|
use crate::mint::SigFlag;
|
||||||
use crate::nuts::nut11::{enforce_sig_flag, EnforceSigFlag};
|
use crate::nuts::nut11::{enforce_sig_flag, EnforceSigFlag};
|
||||||
@@ -170,7 +172,30 @@ impl Mint {
|
|||||||
payment_quote.request_lookup_id
|
payment_quote.request_lookup_id
|
||||||
);
|
);
|
||||||
|
|
||||||
self.localstore.add_melt_quote(quote.clone()).await?;
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
if let Some(mut from_db_quote) = tx.get_melt_quote("e.id).await? {
|
||||||
|
if from_db_quote.state != quote.state {
|
||||||
|
tx.update_melt_quote_state("e.id, from_db_quote.state)
|
||||||
|
.await?;
|
||||||
|
from_db_quote.state = quote.state;
|
||||||
|
}
|
||||||
|
if from_db_quote.request_lookup_id != quote.request_lookup_id {
|
||||||
|
tx.update_melt_quote_request_lookup_id("e.id, "e.request_lookup_id)
|
||||||
|
.await?;
|
||||||
|
from_db_quote.request_lookup_id = quote.request_lookup_id.clone();
|
||||||
|
}
|
||||||
|
if from_db_quote != quote {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
}
|
||||||
|
} else if let Err(err) = tx.add_melt_quote(quote.clone()).await {
|
||||||
|
match err {
|
||||||
|
database::Error::Duplicate => {
|
||||||
|
return Err(Error::RequestAlreadyPaid);
|
||||||
|
}
|
||||||
|
_ => return Err(Error::from(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(quote.into())
|
Ok(quote.into())
|
||||||
}
|
}
|
||||||
@@ -208,13 +233,6 @@ impl Mint {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update melt quote
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn update_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
|
|
||||||
self.localstore.add_melt_quote(quote).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get melt quotes
|
/// Get melt quotes
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn melt_quotes(&self) -> Result<Vec<MeltQuote>, Error> {
|
pub async fn melt_quotes(&self) -> Result<Vec<MeltQuote>, Error> {
|
||||||
@@ -222,14 +240,6 @@ impl Mint {
|
|||||||
Ok(quotes)
|
Ok(quotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove melt quote
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
|
|
||||||
self.localstore.remove_melt_quote(quote_id).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check melt has expected fees
|
/// Check melt has expected fees
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn check_melt_expected_ln_fees(
|
pub async fn check_melt_expected_ln_fees(
|
||||||
@@ -291,10 +301,10 @@ impl Mint {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn verify_melt_request(
|
pub async fn verify_melt_request(
|
||||||
&self,
|
&self,
|
||||||
|
tx: &mut Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
|
||||||
melt_request: &MeltRequest<Uuid>,
|
melt_request: &MeltRequest<Uuid>,
|
||||||
) -> Result<MeltQuote, Error> {
|
) -> Result<(ProofWriter, MeltQuote), Error> {
|
||||||
let (state, quote) = self
|
let (state, quote) = tx
|
||||||
.localstore
|
|
||||||
.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Pending)
|
.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Pending)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -315,8 +325,6 @@ impl Mint {
|
|||||||
|
|
||||||
ensure_cdk!(input_unit.is_some(), Error::UnsupportedUnit);
|
ensure_cdk!(input_unit.is_some(), Error::UnsupportedUnit);
|
||||||
|
|
||||||
let input_ys = melt_request.inputs().ys()?;
|
|
||||||
|
|
||||||
let fee = self.get_proofs_fee(melt_request.inputs()).await?;
|
let fee = self.get_proofs_fee(melt_request.inputs()).await?;
|
||||||
|
|
||||||
let required_total = quote.amount + quote.fee_reserve + fee;
|
let required_total = quote.amount + quote.fee_reserve + fee;
|
||||||
@@ -337,27 +345,10 @@ impl Mint {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(err) = self
|
let mut proof_writer =
|
||||||
.localstore
|
ProofWriter::new(self.localstore.clone(), self.pubsub_manager.clone());
|
||||||
.add_proofs(melt_request.inputs().clone(), None)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
{
|
|
||||||
return match err {
|
|
||||||
cdk_common::database::Error::Duplicate => Err(Error::TokenPending),
|
|
||||||
cdk_common::database::Error::AttemptUpdateSpentProof => {
|
|
||||||
Err(Error::TokenAlreadySpent)
|
|
||||||
}
|
|
||||||
err => Err(Error::Database(err)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
self.check_ys_spendable(&input_ys, State::Pending).await?;
|
proof_writer.add_proofs(tx, melt_request.inputs()).await?;
|
||||||
|
|
||||||
for proof in melt_request.inputs() {
|
|
||||||
self.pubsub_manager
|
|
||||||
.proof_state((proof.y()?, State::Pending));
|
|
||||||
}
|
|
||||||
|
|
||||||
let EnforceSigFlag { sig_flag, .. } = enforce_sig_flag(melt_request.inputs().clone());
|
let EnforceSigFlag { sig_flag, .. } = enforce_sig_flag(melt_request.inputs().clone());
|
||||||
|
|
||||||
@@ -368,43 +359,14 @@ impl Mint {
|
|||||||
let Verification {
|
let Verification {
|
||||||
amount: _,
|
amount: _,
|
||||||
unit: output_unit,
|
unit: output_unit,
|
||||||
} = self.verify_outputs(outputs).await?;
|
} = self.verify_outputs(tx, outputs).await?;
|
||||||
|
|
||||||
ensure_cdk!(input_unit == output_unit, Error::UnsupportedUnit);
|
ensure_cdk!(input_unit == output_unit, Error::UnsupportedUnit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("Verified melt quote: {}", melt_request.quote());
|
tracing::debug!("Verified melt quote: {}", melt_request.quote());
|
||||||
Ok(quote)
|
Ok((proof_writer, quote))
|
||||||
}
|
|
||||||
|
|
||||||
/// Process unpaid melt request
|
|
||||||
/// In the event that a melt request fails and the lighthing payment is not
|
|
||||||
/// made The proofs should be returned to an unspent state and the
|
|
||||||
/// quote should be unpaid
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub async fn process_unpaid_melt(&self, melt_request: &MeltRequest<Uuid>) -> Result<(), Error> {
|
|
||||||
let input_ys = melt_request.inputs().ys()?;
|
|
||||||
|
|
||||||
self.localstore
|
|
||||||
.remove_proofs(&input_ys, Some(*melt_request.quote()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.localstore
|
|
||||||
.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Unpaid)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Ok(Some(quote)) = self.localstore.get_melt_quote(melt_request.quote()).await {
|
|
||||||
self.pubsub_manager
|
|
||||||
.melt_quote_status(quote, None, None, MeltQuoteState::Unpaid);
|
|
||||||
}
|
|
||||||
|
|
||||||
for public_key in input_ys {
|
|
||||||
self.pubsub_manager
|
|
||||||
.proof_state((public_key, State::Unspent));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Melt Bolt11
|
/// Melt Bolt11
|
||||||
@@ -435,40 +397,27 @@ impl Mint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let quote = match self.verify_melt_request(melt_request).await {
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
Ok(quote) => quote,
|
|
||||||
Err(err) => {
|
let (proof_writer, quote) = self
|
||||||
|
.verify_melt_request(&mut tx, melt_request)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
tracing::debug!("Error attempting to verify melt quote: {}", err);
|
tracing::debug!("Error attempting to verify melt quote: {}", err);
|
||||||
|
err
|
||||||
|
})?;
|
||||||
|
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
let settled_internally_amount = self
|
||||||
tracing::error!(
|
.handle_internal_melt_mint(&mut tx, "e, melt_request)
|
||||||
"Could not reset melt quote {} state: {}",
|
.await
|
||||||
melt_request.quote(),
|
.map_err(|err| {
|
||||||
err
|
tracing::error!("Attempting to settle internally failed: {}", err);
|
||||||
);
|
err
|
||||||
}
|
})?;
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let settled_internally_amount =
|
let (tx, preimage, amount_spent_quote_unit, quote) = match settled_internally_amount {
|
||||||
match self.handle_internal_melt_mint("e, melt_request).await {
|
Some(amount_spent) => (tx, None, amount_spent, quote),
|
||||||
Ok(amount) => amount,
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Attempting to settle internally failed");
|
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
|
||||||
tracing::error!(
|
|
||||||
"Could not reset melt quote {} state: {}",
|
|
||||||
melt_request.quote(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (preimage, amount_spent_quote_unit) = match settled_internally_amount {
|
|
||||||
Some(amount_spent) => (None, amount_spent),
|
|
||||||
None => {
|
None => {
|
||||||
// If the quote unit is SAT or MSAT we can check that the expected fees are
|
// If the quote unit is SAT or MSAT we can check that the expected fees are
|
||||||
// provided. We also check if the quote is less then the invoice
|
// provided. We also check if the quote is less then the invoice
|
||||||
@@ -484,9 +433,6 @@ impl Mint {
|
|||||||
Ok(amount) => amount,
|
Ok(amount) => amount,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("Fee is not expected: {}", err);
|
tracing::error!("Fee is not expected: {}", err);
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
|
||||||
tracing::error!("Could not reset melt quote state: {}", err);
|
|
||||||
}
|
|
||||||
return Err(Error::Internal);
|
return Err(Error::Internal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -501,14 +447,13 @@ impl Mint {
|
|||||||
Some(ln) => ln,
|
Some(ln) => ln,
|
||||||
None => {
|
None => {
|
||||||
tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
|
tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
|
||||||
tracing::error!("Could not reset melt quote state: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(Error::UnsupportedUnit);
|
return Err(Error::UnsupportedUnit);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Commit before talking to the external call
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
let pre = match ln
|
let pre = match ln
|
||||||
.make_payment(quote.clone(), partial_amount, Some(quote.fee_reserve))
|
.make_payment(quote.clone(), partial_amount, Some(quote.fee_reserve))
|
||||||
.await
|
.await
|
||||||
@@ -517,13 +462,18 @@ impl Mint {
|
|||||||
if pay.status == MeltQuoteState::Unknown
|
if pay.status == MeltQuoteState::Unknown
|
||||||
|| pay.status == MeltQuoteState::Failed =>
|
|| pay.status == MeltQuoteState::Failed =>
|
||||||
{
|
{
|
||||||
let check_response = check_payment_state(Arc::clone(ln), "e)
|
let check_response =
|
||||||
.await
|
if let Ok(ok) = check_payment_state(Arc::clone(ln), "e).await {
|
||||||
.map_err(|_| Error::Internal)?;
|
ok
|
||||||
|
} else {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
};
|
||||||
|
|
||||||
if check_response.status == MeltQuoteState::Paid {
|
if check_response.status == MeltQuoteState::Paid {
|
||||||
tracing::warn!("Pay invoice returned {} but check returned {}. Proofs stuck as pending", pay.status.to_string(), check_response.status.to_string());
|
tracing::warn!("Pay invoice returned {} but check returned {}. Proofs stuck as pending", pay.status.to_string(), check_response.status.to_string());
|
||||||
|
|
||||||
|
proof_writer.commit();
|
||||||
|
|
||||||
return Err(Error::Internal);
|
return Err(Error::Internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,21 +485,22 @@ impl Mint {
|
|||||||
// hold the proofs as pending to we reset them and return an error.
|
// hold the proofs as pending to we reset them and return an error.
|
||||||
if matches!(err, cdk_payment::Error::InvoiceAlreadyPaid) {
|
if matches!(err, cdk_payment::Error::InvoiceAlreadyPaid) {
|
||||||
tracing::debug!("Invoice already paid, resetting melt quote");
|
tracing::debug!("Invoice already paid, resetting melt quote");
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
|
||||||
tracing::error!("Could not reset melt quote state: {}", err);
|
|
||||||
}
|
|
||||||
return Err(Error::RequestAlreadyPaid);
|
return Err(Error::RequestAlreadyPaid);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::error!("Error returned attempting to pay: {} {}", quote.id, err);
|
tracing::error!("Error returned attempting to pay: {} {}", quote.id, err);
|
||||||
|
|
||||||
let check_response = check_payment_state(Arc::clone(ln), "e)
|
let check_response =
|
||||||
.await
|
if let Ok(ok) = check_payment_state(Arc::clone(ln), "e).await {
|
||||||
.map_err(|_| Error::Internal)?;
|
ok
|
||||||
|
} else {
|
||||||
|
proof_writer.commit();
|
||||||
|
return Err(Error::Internal);
|
||||||
|
};
|
||||||
// If there error is something else we want to check the status of the payment ensure it is not pending or has been made.
|
// If there error is something else we want to check the status of the payment ensure it is not pending or has been made.
|
||||||
if check_response.status == MeltQuoteState::Paid {
|
if check_response.status == MeltQuoteState::Paid {
|
||||||
tracing::warn!("Pay invoice returned an error but check returned {}. Proofs stuck as pending", check_response.status.to_string());
|
tracing::warn!("Pay invoice returned an error but check returned {}. Proofs stuck as pending", check_response.status.to_string());
|
||||||
|
proof_writer.commit();
|
||||||
return Err(Error::Internal);
|
return Err(Error::Internal);
|
||||||
}
|
}
|
||||||
check_response
|
check_response
|
||||||
@@ -563,9 +514,6 @@ impl Mint {
|
|||||||
"Lightning payment for quote {} failed.",
|
"Lightning payment for quote {} failed.",
|
||||||
melt_request.quote()
|
melt_request.quote()
|
||||||
);
|
);
|
||||||
if let Err(err) = self.process_unpaid_melt(melt_request).await {
|
|
||||||
tracing::error!("Could not reset melt quote state: {}", err);
|
|
||||||
}
|
|
||||||
return Err(Error::PaymentFailed);
|
return Err(Error::PaymentFailed);
|
||||||
}
|
}
|
||||||
MeltQuoteState::Pending => {
|
MeltQuoteState::Pending => {
|
||||||
@@ -573,6 +521,7 @@ impl Mint {
|
|||||||
"LN payment pending, proofs are stuck as pending for quote: {}",
|
"LN payment pending, proofs are stuck as pending for quote: {}",
|
||||||
melt_request.quote()
|
melt_request.quote()
|
||||||
);
|
);
|
||||||
|
proof_writer.commit();
|
||||||
return Err(Error::PendingQuote);
|
return Err(Error::PendingQuote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,6 +533,7 @@ impl Mint {
|
|||||||
to_unit(pre.total_spent, &pre.unit, "e.unit).unwrap_or_default();
|
to_unit(pre.total_spent, &pre.unit, "e.unit).unwrap_or_default();
|
||||||
|
|
||||||
let payment_lookup_id = pre.payment_lookup_id;
|
let payment_lookup_id = pre.payment_lookup_id;
|
||||||
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
|
||||||
if payment_lookup_id != quote.request_lookup_id {
|
if payment_lookup_id != quote.request_lookup_id {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@@ -595,19 +545,34 @@ impl Mint {
|
|||||||
let mut melt_quote = quote;
|
let mut melt_quote = quote;
|
||||||
melt_quote.request_lookup_id = payment_lookup_id;
|
melt_quote.request_lookup_id = payment_lookup_id;
|
||||||
|
|
||||||
if let Err(err) = self.localstore.add_melt_quote(melt_quote).await {
|
if let Err(err) = tx
|
||||||
|
.update_melt_quote_request_lookup_id(
|
||||||
|
&melt_quote.id,
|
||||||
|
&melt_quote.request_lookup_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
tracing::warn!("Could not update payment lookup id: {}", err);
|
tracing::warn!("Could not update payment lookup id: {}", err);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(pre.payment_proof, amount_spent)
|
(tx, pre.payment_proof, amount_spent, melt_quote)
|
||||||
|
} else {
|
||||||
|
(tx, pre.payment_proof, amount_spent, quote)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we made it here the payment has been made.
|
// If we made it here the payment has been made.
|
||||||
// We process the melt burning the inputs and returning change
|
// We process the melt burning the inputs and returning change
|
||||||
let res = self
|
let res = self
|
||||||
.process_melt_request(melt_request, preimage, amount_spent_quote_unit)
|
.process_melt_request(
|
||||||
|
tx,
|
||||||
|
proof_writer,
|
||||||
|
quote,
|
||||||
|
melt_request,
|
||||||
|
preimage,
|
||||||
|
amount_spent_quote_unit,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
tracing::error!("Could not process melt request: {}", err);
|
tracing::error!("Could not process melt request: {}", err);
|
||||||
@@ -622,26 +587,22 @@ impl Mint {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn process_melt_request(
|
pub async fn process_melt_request(
|
||||||
&self,
|
&self,
|
||||||
|
mut tx: Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
|
||||||
|
mut proof_writer: ProofWriter,
|
||||||
|
quote: MeltQuote,
|
||||||
melt_request: &MeltRequest<Uuid>,
|
melt_request: &MeltRequest<Uuid>,
|
||||||
payment_preimage: Option<String>,
|
payment_preimage: Option<String>,
|
||||||
total_spent: Amount,
|
total_spent: Amount,
|
||||||
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
|
) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
|
||||||
tracing::debug!("Processing melt quote: {}", melt_request.quote());
|
tracing::debug!("Processing melt quote: {}", melt_request.quote());
|
||||||
|
|
||||||
let quote = self
|
|
||||||
.localstore
|
|
||||||
.get_melt_quote(melt_request.quote())
|
|
||||||
.await?
|
|
||||||
.ok_or(Error::UnknownQuote)?;
|
|
||||||
|
|
||||||
let input_ys = melt_request.inputs().ys()?;
|
let input_ys = melt_request.inputs().ys()?;
|
||||||
|
|
||||||
self.localstore
|
proof_writer
|
||||||
.update_proofs_states(&input_ys, State::Spent)
|
.update_proofs_states(&mut tx, &input_ys, State::Spent)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.localstore
|
tx.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Paid)
|
||||||
.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Paid)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.pubsub_manager.melt_quote_status(
|
self.pubsub_manager.melt_quote_status(
|
||||||
@@ -651,10 +612,6 @@ impl Mint {
|
|||||||
MeltQuoteState::Paid,
|
MeltQuoteState::Paid,
|
||||||
);
|
);
|
||||||
|
|
||||||
for public_key in input_ys {
|
|
||||||
self.pubsub_manager.proof_state((public_key, State::Spent));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut change = None;
|
let mut change = None;
|
||||||
|
|
||||||
// Check if there is change to return
|
// Check if there is change to return
|
||||||
@@ -664,8 +621,7 @@ impl Mint {
|
|||||||
let blinded_messages: Vec<PublicKey> =
|
let blinded_messages: Vec<PublicKey> =
|
||||||
outputs.iter().map(|b| b.blinded_secret).collect();
|
outputs.iter().map(|b| b.blinded_secret).collect();
|
||||||
|
|
||||||
if self
|
if tx
|
||||||
.localstore
|
|
||||||
.get_blind_signatures(&blinded_messages)
|
.get_blind_signatures(&blinded_messages)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
@@ -707,21 +663,23 @@ impl Mint {
|
|||||||
change_sigs.push(blinded_signature)
|
change_sigs.push(blinded_signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.localstore
|
tx.add_blind_signatures(
|
||||||
.add_blind_signatures(
|
&outputs[0..change_sigs.len()]
|
||||||
&outputs[0..change_sigs.len()]
|
.iter()
|
||||||
.iter()
|
.map(|o| o.blinded_secret)
|
||||||
.map(|o| o.blinded_secret)
|
.collect::<Vec<PublicKey>>(),
|
||||||
.collect::<Vec<PublicKey>>(),
|
&change_sigs,
|
||||||
&change_sigs,
|
Some(quote.id),
|
||||||
Some(quote.id),
|
)
|
||||||
)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
change = Some(change_sigs);
|
change = Some(change_sigs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proof_writer.commit();
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(MeltQuoteBolt11Response {
|
Ok(MeltQuoteBolt11Response {
|
||||||
amount: quote.amount,
|
amount: quote.amount,
|
||||||
paid: Some(true),
|
paid: Some(true),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use arc_swap::ArcSwap;
|
|||||||
use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
|
use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
use cdk_common::database::MintAuthDatabase;
|
use cdk_common::database::MintAuthDatabase;
|
||||||
use cdk_common::database::{self, MintDatabase};
|
use cdk_common::database::{self, MintDatabase, MintTransaction};
|
||||||
use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
|
use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
|
||||||
use cdk_common::secret;
|
use cdk_common::secret;
|
||||||
use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
|
use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
|
||||||
@@ -24,9 +24,9 @@ use crate::cdk_payment::{self, MintPayment};
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::fees::calculate_fee;
|
use crate::fees::calculate_fee;
|
||||||
use crate::nuts::*;
|
use crate::nuts::*;
|
||||||
use crate::Amount;
|
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
use crate::OidcClient;
|
use crate::OidcClient;
|
||||||
|
use crate::{cdk_database, Amount};
|
||||||
|
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
pub(crate) mod auth;
|
pub(crate) mod auth;
|
||||||
@@ -36,6 +36,7 @@ mod issue;
|
|||||||
mod keysets;
|
mod keysets;
|
||||||
mod ln;
|
mod ln;
|
||||||
mod melt;
|
mod melt;
|
||||||
|
mod proof_writer;
|
||||||
mod start_up_check;
|
mod start_up_check;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
mod swap;
|
mod swap;
|
||||||
@@ -225,7 +226,9 @@ impl Mint {
|
|||||||
/// Set mint info
|
/// Set mint info
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
|
pub async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error> {
|
||||||
Ok(self.localstore.set_mint_info(mint_info).await?)
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
tx.set_mint_info(mint_info).await?;
|
||||||
|
Ok(tx.commit().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get quote ttl
|
/// Get quote ttl
|
||||||
@@ -237,7 +240,9 @@ impl Mint {
|
|||||||
/// Set quote ttl
|
/// Set quote ttl
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
pub async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Error> {
|
||||||
Ok(self.localstore.set_quote_ttl(quote_ttl).await?)
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
tx.set_quote_ttl(quote_ttl).await?;
|
||||||
|
Ok(tx.commit().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for any invoice to be paid
|
/// Wait for any invoice to be paid
|
||||||
@@ -407,14 +412,11 @@ impl Mint {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn handle_internal_melt_mint(
|
pub async fn handle_internal_melt_mint(
|
||||||
&self,
|
&self,
|
||||||
|
tx: &mut Box<dyn MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
|
||||||
melt_quote: &MeltQuote,
|
melt_quote: &MeltQuote,
|
||||||
melt_request: &MeltRequest<Uuid>,
|
melt_request: &MeltRequest<Uuid>,
|
||||||
) -> Result<Option<Amount>, Error> {
|
) -> Result<Option<Amount>, Error> {
|
||||||
let mint_quote = match self
|
let mint_quote = match tx.get_mint_quote_by_request(&melt_quote.request).await {
|
||||||
.localstore
|
|
||||||
.get_mint_quote_by_request(&melt_quote.request)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(Some(mint_quote)) => mint_quote,
|
Ok(Some(mint_quote)) => mint_quote,
|
||||||
// Not an internal melt -> mint
|
// Not an internal melt -> mint
|
||||||
Ok(None) => return Ok(None),
|
Ok(None) => return Ok(None),
|
||||||
@@ -423,6 +425,7 @@ impl Mint {
|
|||||||
return Err(Error::Internal);
|
return Err(Error::Internal);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
tracing::error!("internal stuff");
|
||||||
|
|
||||||
// Mint quote has already been settled, proofs should not be burned or held.
|
// Mint quote has already been settled, proofs should not be burned or held.
|
||||||
if mint_quote.state == MintQuoteState::Issued || mint_quote.state == MintQuoteState::Paid {
|
if mint_quote.state == MintQuoteState::Issued || mint_quote.state == MintQuoteState::Paid {
|
||||||
@@ -449,7 +452,7 @@ impl Mint {
|
|||||||
|
|
||||||
let amount = melt_quote.amount;
|
let amount = melt_quote.amount;
|
||||||
|
|
||||||
self.update_mint_quote(mint_quote).await?;
|
tx.add_or_replace_mint_quote(mint_quote).await?;
|
||||||
|
|
||||||
Ok(Some(amount))
|
Ok(Some(amount))
|
||||||
}
|
}
|
||||||
|
|||||||
214
crates/cdk/src/mint/proof_writer.rs
Normal file
214
crates/cdk/src/mint/proof_writer.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
//! Proof writer
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use cdk_common::database::{self, MintDatabase, MintTransaction};
|
||||||
|
use cdk_common::{Error, Proofs, ProofsMethods, PublicKey, State};
|
||||||
|
|
||||||
|
use super::subscription::PubSubManager;
|
||||||
|
|
||||||
|
type Db = Arc<dyn MintDatabase<database::Error> + Send + Sync>;
|
||||||
|
type Tx<'a, 'b> = Box<dyn MintTransaction<'a, database::Error> + Send + Sync + 'b>;
|
||||||
|
|
||||||
|
/// Proof writer
|
||||||
|
///
|
||||||
|
/// This is a proof writer that emulates a database transaction but without holding the transaction
|
||||||
|
/// alive while waiting for external events to be fully committed to the database; instead, it
|
||||||
|
/// maintains a `pending` state.
|
||||||
|
///
|
||||||
|
/// This struct allows for premature exit on error, enabling it to remove proofs or reset their
|
||||||
|
/// status.
|
||||||
|
///
|
||||||
|
/// This struct is not fully ACID. If the process exits due to a panic, and the `Drop` function
|
||||||
|
/// cannot be run, the reset process should reset the state.
|
||||||
|
pub struct ProofWriter {
|
||||||
|
db: Option<Db>,
|
||||||
|
pubsub_manager: Arc<PubSubManager>,
|
||||||
|
proof_original_states: Option<HashMap<PublicKey, Option<State>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProofWriter {
|
||||||
|
/// Creates a new ProofWriter on top of the database
|
||||||
|
pub fn new(db: Db, pubsub_manager: Arc<PubSubManager>) -> Self {
|
||||||
|
Self {
|
||||||
|
db: Some(db),
|
||||||
|
pubsub_manager,
|
||||||
|
proof_original_states: Some(Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The changes are permanent, consume the struct removing the database, so the Drop does
|
||||||
|
/// nothing
|
||||||
|
pub fn commit(mut self) {
|
||||||
|
self.db.take();
|
||||||
|
self.proof_original_states.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add proofs
|
||||||
|
pub async fn add_proofs(
|
||||||
|
&mut self,
|
||||||
|
tx: &mut Tx<'_, '_>,
|
||||||
|
proofs: &Proofs,
|
||||||
|
) -> Result<Vec<PublicKey>, Error> {
|
||||||
|
let proof_states = if let Some(proofs) = self.proof_original_states.as_mut() {
|
||||||
|
proofs
|
||||||
|
} else {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = tx.add_proofs(proofs.clone(), None).await.err() {
|
||||||
|
return match err {
|
||||||
|
cdk_common::database::Error::Duplicate => Err(Error::TokenPending),
|
||||||
|
cdk_common::database::Error::AttemptUpdateSpentProof => {
|
||||||
|
Err(Error::TokenAlreadySpent)
|
||||||
|
}
|
||||||
|
err => Err(Error::Database(err)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let ys = proofs.ys()?;
|
||||||
|
|
||||||
|
for pk in ys.iter() {
|
||||||
|
proof_states.insert(*pk, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_proofs_states(tx, &ys, State::Pending).await?;
|
||||||
|
|
||||||
|
Ok(ys)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update proof status
|
||||||
|
pub async fn update_proofs_states(
|
||||||
|
&mut self,
|
||||||
|
tx: &mut Tx<'_, '_>,
|
||||||
|
ys: &[PublicKey],
|
||||||
|
new_proof_state: State,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let proof_states = if let Some(proofs) = self.proof_original_states.as_mut() {
|
||||||
|
proofs
|
||||||
|
} else {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
};
|
||||||
|
|
||||||
|
let original_proofs_state = match tx.update_proofs_states(ys, new_proof_state).await {
|
||||||
|
Ok(states) => states,
|
||||||
|
Err(database::Error::AttemptUpdateSpentProof)
|
||||||
|
| Err(database::Error::AttemptRemoveSpentProof) => {
|
||||||
|
return Err(Error::TokenAlreadySpent)
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if ys.len() != original_proofs_state.len() {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
let proofs_state = original_proofs_state
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|x| x.to_owned())
|
||||||
|
.collect::<HashSet<State>>();
|
||||||
|
|
||||||
|
let forbidden_states = if new_proof_state == State::Pending {
|
||||||
|
// If the new state is `State::Pending` it cannot be pending already
|
||||||
|
vec![State::Pending, State::Spent]
|
||||||
|
} else {
|
||||||
|
// For other state it cannot be spent
|
||||||
|
vec![State::Spent]
|
||||||
|
};
|
||||||
|
|
||||||
|
for forbidden_state in forbidden_states.iter() {
|
||||||
|
if proofs_state.contains(forbidden_state) {
|
||||||
|
reset_proofs_to_original_state(tx, ys, original_proofs_state).await?;
|
||||||
|
|
||||||
|
return Err(if proofs_state.contains(&State::Pending) {
|
||||||
|
Error::TokenPending
|
||||||
|
} else {
|
||||||
|
Error::TokenAlreadySpent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (idx, ys) in ys.iter().enumerate() {
|
||||||
|
proof_states
|
||||||
|
.entry(*ys)
|
||||||
|
.or_insert(original_proofs_state[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for pk in ys {
|
||||||
|
self.pubsub_manager.proof_state((*pk, new_proof_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rollback all changes in this ProofWriter consuming it.
|
||||||
|
pub async fn rollback(mut self, tx: &mut Tx<'_, '_>) -> Result<(), Error> {
|
||||||
|
let (ys, original_states) = if let Some(proofs) = self.proof_original_states.take() {
|
||||||
|
proofs.into_iter().unzip::<_, _, Vec<_>, Vec<_>>()
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
reset_proofs_to_original_state(tx, &ys, original_states).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resets proofs to their original states or removes them
|
||||||
|
#[inline(always)]
|
||||||
|
async fn reset_proofs_to_original_state(
|
||||||
|
tx: &mut Tx<'_, '_>,
|
||||||
|
ys: &[PublicKey],
|
||||||
|
original_states: Vec<Option<State>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut ys_by_state = HashMap::new();
|
||||||
|
let mut unknown_proofs = Vec::new();
|
||||||
|
for (y, state) in ys.iter().zip(original_states) {
|
||||||
|
if let Some(state) = state {
|
||||||
|
// Skip attempting to update proofs that were originally spent
|
||||||
|
if state != State::Spent {
|
||||||
|
ys_by_state.entry(state).or_insert_with(Vec::new).push(*y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unknown_proofs.push(*y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (state, ys) in ys_by_state {
|
||||||
|
tx.update_proofs_states(&ys, state).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.remove_proofs(&unknown_proofs, None).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
async fn rollback(
|
||||||
|
db: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
|
||||||
|
ys: Vec<PublicKey>,
|
||||||
|
original_states: Vec<Option<State>>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut tx = db.begin_transaction().await?;
|
||||||
|
reset_proofs_to_original_state(&mut tx, &ys, original_states).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ProofWriter {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let db = if let Some(db) = self.db.take() {
|
||||||
|
db
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (ys, states) = if let Some(proofs) = self.proof_original_states.take() {
|
||||||
|
proofs.into_iter().unzip()
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn(rollback(db, ys, states));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,10 +21,14 @@ impl Mint {
|
|||||||
"There are {} pending and unpaid mint quotes.",
|
"There are {} pending and unpaid mint quotes.",
|
||||||
all_quotes.len()
|
all_quotes.len()
|
||||||
);
|
);
|
||||||
for quote in all_quotes.iter() {
|
for mut quote in all_quotes.into_iter() {
|
||||||
tracing::debug!("Checking status of mint quote: {}", quote.id);
|
tracing::debug!("Checking status of mint quote: {}", quote.id);
|
||||||
if let Err(err) = self.check_mint_quote_paid("e.id).await {
|
match self
|
||||||
tracing::error!("Could not check status of {}, {}", quote.id, err);
|
.check_mint_quote_paid(self.localstore.begin_transaction().await?, &mut quote)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(tx) => tx.commit().await?,
|
||||||
|
Err(err) => tracing::error!("Could not check status of {}, {}", quote.id, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -39,6 +43,8 @@ impl Mint {
|
|||||||
.collect();
|
.collect();
|
||||||
tracing::info!("There are {} pending melt quotes.", pending_quotes.len());
|
tracing::info!("There are {} pending melt quotes.", pending_quotes.len());
|
||||||
|
|
||||||
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
|
||||||
for pending_quote in pending_quotes {
|
for pending_quote in pending_quotes {
|
||||||
tracing::debug!("Checking status for melt quote {}.", pending_quote.id);
|
tracing::debug!("Checking status for melt quote {}.", pending_quote.id);
|
||||||
|
|
||||||
@@ -72,8 +78,7 @@ impl Mint {
|
|||||||
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
|
MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = self
|
if let Err(err) = tx
|
||||||
.localstore
|
|
||||||
.update_melt_quote_state(&pending_quote.id, melt_quote_state)
|
.update_melt_quote_state(&pending_quote.id, melt_quote_state)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -86,6 +91,9 @@ impl Mint {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::nut11::{enforce_sig_flag, EnforceSigFlag};
|
use super::nut11::{enforce_sig_flag, EnforceSigFlag};
|
||||||
|
use super::proof_writer::ProofWriter;
|
||||||
use super::{Mint, PublicKey, SigFlag, State, SwapRequest, SwapResponse};
|
use super::{Mint, PublicKey, SigFlag, State, SwapRequest, SwapResponse};
|
||||||
use crate::nuts::nut00::ProofsMethods;
|
use crate::Error;
|
||||||
use crate::{cdk_database, Error};
|
|
||||||
|
|
||||||
impl Mint {
|
impl Mint {
|
||||||
/// Process Swap
|
/// Process Swap
|
||||||
@@ -12,8 +12,10 @@ impl Mint {
|
|||||||
&self,
|
&self,
|
||||||
swap_request: SwapRequest,
|
swap_request: SwapRequest,
|
||||||
) -> Result<SwapResponse, Error> {
|
) -> Result<SwapResponse, Error> {
|
||||||
|
let mut tx = self.localstore.begin_transaction().await?;
|
||||||
|
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.verify_transaction_balanced(swap_request.inputs(), swap_request.outputs())
|
.verify_transaction_balanced(&mut tx, swap_request.inputs(), swap_request.outputs())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::debug!("Attempt to swap unbalanced transaction, aborting: {err}");
|
tracing::debug!("Attempt to swap unbalanced transaction, aborting: {err}");
|
||||||
@@ -22,23 +24,11 @@ impl Mint {
|
|||||||
|
|
||||||
self.validate_sig_flag(&swap_request).await?;
|
self.validate_sig_flag(&swap_request).await?;
|
||||||
|
|
||||||
// After swap request is fully validated, add the new proofs to DB
|
let mut proof_writer =
|
||||||
let input_ys = swap_request.inputs().ys()?;
|
ProofWriter::new(self.localstore.clone(), self.pubsub_manager.clone());
|
||||||
if let Some(err) = self
|
let input_ys = proof_writer
|
||||||
.localstore
|
.add_proofs(&mut tx, swap_request.inputs())
|
||||||
.add_proofs(swap_request.inputs().clone(), None)
|
.await?;
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
{
|
|
||||||
return match err {
|
|
||||||
cdk_common::database::Error::Duplicate => Err(Error::TokenPending),
|
|
||||||
cdk_common::database::Error::AttemptUpdateSpentProof => {
|
|
||||||
Err(Error::TokenAlreadySpent)
|
|
||||||
}
|
|
||||||
err => Err(Error::Database(err)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self.check_ys_spendable(&input_ys, State::Pending).await?;
|
|
||||||
|
|
||||||
let mut promises = Vec::with_capacity(swap_request.outputs().len());
|
let mut promises = Vec::with_capacity(swap_request.outputs().len());
|
||||||
|
|
||||||
@@ -47,36 +37,24 @@ impl Mint {
|
|||||||
promises.push(blinded_signature);
|
promises.push(blinded_signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: It may be possible to have a race condition, that's why an error when changing the
|
proof_writer
|
||||||
// state can be converted to a TokenAlreadySpent error.
|
.update_proofs_states(&mut tx, &input_ys, State::Spent)
|
||||||
//
|
|
||||||
// A concept of transaction/writer for the Database trait would eliminate this problem and
|
|
||||||
// will remove all the "reset" codebase, resulting in fewer lines of code, and less
|
|
||||||
// error-prone database updates
|
|
||||||
self.localstore
|
|
||||||
.update_proofs_states(&input_ys, State::Spent)
|
|
||||||
.await
|
|
||||||
.map_err(|e| match e {
|
|
||||||
cdk_database::Error::AttemptUpdateSpentProof => Error::TokenAlreadySpent,
|
|
||||||
e => e.into(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for pub_key in input_ys {
|
|
||||||
self.pubsub_manager.proof_state((pub_key, State::Spent));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.localstore
|
|
||||||
.add_blind_signatures(
|
|
||||||
&swap_request
|
|
||||||
.outputs()
|
|
||||||
.iter()
|
|
||||||
.map(|o| o.blinded_secret)
|
|
||||||
.collect::<Vec<PublicKey>>(),
|
|
||||||
&promises,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
tx.add_blind_signatures(
|
||||||
|
&swap_request
|
||||||
|
.outputs()
|
||||||
|
.iter()
|
||||||
|
.map(|o| o.blinded_secret)
|
||||||
|
.collect::<Vec<PublicKey>>(),
|
||||||
|
&promises,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
proof_writer.commit();
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
Ok(SwapResponse::new(promises))
|
Ok(SwapResponse::new(promises))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use cdk_common::{Amount, BlindedMessage, CurrencyUnit, Id, Proofs, ProofsMethods
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::{Error, Mint};
|
use super::{Error, Mint};
|
||||||
|
use crate::cdk_database;
|
||||||
|
|
||||||
/// Verification result
|
/// Verification result
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
@@ -149,12 +150,12 @@ impl Mint {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn check_output_already_signed(
|
pub async fn check_output_already_signed(
|
||||||
&self,
|
&self,
|
||||||
|
tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
|
||||||
outputs: &[BlindedMessage],
|
outputs: &[BlindedMessage],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let blinded_messages: Vec<PublicKey> = outputs.iter().map(|o| o.blinded_secret).collect();
|
let blinded_messages: Vec<PublicKey> = outputs.iter().map(|o| o.blinded_secret).collect();
|
||||||
|
|
||||||
if self
|
if tx
|
||||||
.localstore
|
|
||||||
.get_blind_signatures(&blinded_messages)
|
.get_blind_signatures(&blinded_messages)
|
||||||
.await?
|
.await?
|
||||||
.iter()
|
.iter()
|
||||||
@@ -173,7 +174,11 @@ impl Mint {
|
|||||||
/// Verifies outputs
|
/// Verifies outputs
|
||||||
/// Checks outputs are unique, of the same unit and not signed before
|
/// Checks outputs are unique, of the same unit and not signed before
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn verify_outputs(&self, outputs: &[BlindedMessage]) -> Result<Verification, Error> {
|
pub async fn verify_outputs(
|
||||||
|
&self,
|
||||||
|
tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
|
||||||
|
outputs: &[BlindedMessage],
|
||||||
|
) -> Result<Verification, Error> {
|
||||||
if outputs.is_empty() {
|
if outputs.is_empty() {
|
||||||
return Ok(Verification {
|
return Ok(Verification {
|
||||||
amount: Amount::ZERO,
|
amount: Amount::ZERO,
|
||||||
@@ -182,7 +187,7 @@ impl Mint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mint::check_outputs_unique(outputs)?;
|
Mint::check_outputs_unique(outputs)?;
|
||||||
self.check_output_already_signed(outputs).await?;
|
self.check_output_already_signed(tx, outputs).await?;
|
||||||
|
|
||||||
let unit = self.verify_outputs_keyset(outputs).await?;
|
let unit = self.verify_outputs_keyset(outputs).await?;
|
||||||
|
|
||||||
@@ -215,10 +220,11 @@ impl Mint {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn verify_transaction_balanced(
|
pub async fn verify_transaction_balanced(
|
||||||
&self,
|
&self,
|
||||||
|
tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
|
||||||
inputs: &Proofs,
|
inputs: &Proofs,
|
||||||
outputs: &[BlindedMessage],
|
outputs: &[BlindedMessage],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let output_verification = self.verify_outputs(outputs).await.map_err(|err| {
|
let output_verification = self.verify_outputs(tx, outputs).await.map_err(|err| {
|
||||||
tracing::debug!("Output verification failed: {:?}", err);
|
tracing::debug!("Output verification failed: {:?}", err);
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ if [ $? -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Running happy_path_mint_wallet test with CLN mint"
|
echo "Running happy_path_mint_wallet test with CLN mint"
|
||||||
cargo test -p cdk-integration-tests --test happy_path_mint_wallet test_happy_mint_melt_round_trip
|
cargo test -p cdk-integration-tests --test happy_path_mint_wallet
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "happy_path_mint_wallet test failed, exiting"
|
echo "happy_path_mint_wallet test failed, exiting"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
Reference in New Issue
Block a user