mod.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. //! CDK Database
  2. use std::collections::HashMap;
  3. use async_trait::async_trait;
  4. use cashu::quote_id::QuoteId;
  5. use cashu::Amount;
  6. use super::{DbTransactionFinalizer, Error};
  7. use crate::database::Acquired;
  8. use crate::mint::{
  9. self, MeltQuote, MintKeySetInfo, MintQuote as MintMintQuote, Operation, ProofsWithState,
  10. };
  11. use crate::nuts::{
  12. BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
  13. State,
  14. };
  15. use crate::payment::PaymentIdentifier;
  16. #[cfg(feature = "auth")]
  17. mod auth;
  18. #[cfg(feature = "test")]
  19. pub mod test;
  20. #[cfg(feature = "auth")]
  21. pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
  22. // Re-export KVStore types from shared module for backward compatibility
  23. pub use super::kvstore::{
  24. validate_kvstore_params, validate_kvstore_string, KVStore, KVStoreDatabase, KVStoreTransaction,
  25. KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN,
  26. };
  27. /// Information about a melt request stored in the database
  28. #[derive(Debug, Clone, PartialEq, Eq)]
  29. pub struct MeltRequestInfo {
  30. /// Total amount of all input proofs in the melt request
  31. pub inputs_amount: Amount,
  32. /// Fee amount associated with the input proofs
  33. pub inputs_fee: Amount,
  34. /// Blinded messages for change outputs
  35. pub change_outputs: Vec<BlindedMessage>,
  36. }
  37. /// Result of locking a melt quote and all related quotes atomically.
  38. ///
  39. /// This struct is returned by [`QuotesTransaction::lock_melt_quote_and_related`]
  40. /// and contains both the target quote and all quotes sharing the same `request_lookup_id`.
  41. #[derive(Debug)]
  42. pub struct LockedMeltQuotes {
  43. /// The target quote that was requested, if found
  44. pub target: Option<Acquired<MeltQuote>>,
  45. /// All quotes sharing the same `request_lookup_id` (including the target)
  46. pub all_related: Vec<Acquired<MeltQuote>>,
  47. }
  48. /// KeysDatabaseWriter
  49. #[async_trait]
  50. pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
  51. /// Add Active Keyset
  52. async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
  53. /// Add [`MintKeySetInfo`]
  54. async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
  55. }
  56. /// Mint Keys Database trait
  57. #[async_trait]
  58. pub trait KeysDatabase {
  59. /// Mint Keys Database Error
  60. type Err: Into<Error> + From<Error>;
  61. /// Begins a transaction
  62. async fn begin_transaction<'a>(
  63. &'a self,
  64. ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
  65. /// Get Active Keyset
  66. async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
  67. /// Get all Active Keyset
  68. async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
  69. /// Get [`MintKeySetInfo`]
  70. async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
  71. /// Get [`MintKeySetInfo`]s
  72. async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
  73. }
  74. /// Mint Quote Database writer trait
  75. #[async_trait]
  76. pub trait QuotesTransaction {
  77. /// Mint Quotes Database Error
  78. type Err: Into<Error> + From<Error>;
  79. /// Add melt_request with quote_id, inputs_amount, and inputs_fee
  80. async fn add_melt_request(
  81. &mut self,
  82. quote_id: &QuoteId,
  83. inputs_amount: Amount,
  84. inputs_fee: Amount,
  85. ) -> Result<(), Self::Err>;
  86. /// Add blinded_messages for a quote_id
  87. async fn add_blinded_messages(
  88. &mut self,
  89. quote_id: Option<&QuoteId>,
  90. blinded_messages: &[BlindedMessage],
  91. operation: &Operation,
  92. ) -> Result<(), Self::Err>;
  93. /// Delete blinded_messages by their blinded secrets
  94. async fn delete_blinded_messages(
  95. &mut self,
  96. blinded_secrets: &[PublicKey],
  97. ) -> Result<(), Self::Err>;
  98. /// Get melt_request and associated blinded_messages by quote_id
  99. async fn get_melt_request_and_blinded_messages(
  100. &mut self,
  101. quote_id: &QuoteId,
  102. ) -> Result<Option<MeltRequestInfo>, Self::Err>;
  103. /// Delete melt_request and associated blinded_messages by quote_id
  104. async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
  105. /// Get [`MintMintQuote`] and lock it for update in this transaction
  106. async fn get_mint_quote(
  107. &mut self,
  108. quote_id: &QuoteId,
  109. ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
  110. /// Add [`MintMintQuote`]
  111. async fn add_mint_quote(
  112. &mut self,
  113. quote: MintMintQuote,
  114. ) -> Result<Acquired<MintMintQuote>, Self::Err>;
  115. /// Persists any pending changes made to the mint quote.
  116. ///
  117. /// This method extracts changes accumulated in the quote (via [`mint::MintQuote::take_changes`])
  118. /// and persists them to the database. Changes may include new payments received or new
  119. /// issuances recorded against the quote.
  120. ///
  121. /// If no changes are pending, this method returns successfully without performing
  122. /// any database operations.
  123. ///
  124. /// # Arguments
  125. ///
  126. /// * `quote` - A mutable reference to an acquired (row-locked) mint quote. The quote
  127. /// must be locked to ensure transactional consistency when persisting changes.
  128. ///
  129. /// # Implementation Notes
  130. ///
  131. /// Implementations should call [`mint::MintQuote::take_changes`] to retrieve pending
  132. /// changes, then persist each payment and issuance record, and finally update the
  133. /// quote's aggregate counters (`amount_paid`, `amount_issued`) in the database.
  134. async fn update_mint_quote(
  135. &mut self,
  136. quote: &mut Acquired<mint::MintQuote>,
  137. ) -> Result<(), Self::Err>;
  138. /// Get [`mint::MeltQuote`] and lock it for update in this transaction
  139. async fn get_melt_quote(
  140. &mut self,
  141. quote_id: &QuoteId,
  142. ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err>;
  143. /// Add [`mint::MeltQuote`]
  144. async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
  145. /// Retrieves all melt quotes matching a payment lookup identifier and locks them for update.
  146. ///
  147. /// This method returns multiple quotes because certain payment methods (notably BOLT12 offers)
  148. /// can generate multiple payment attempts that share the same lookup identifier. Locking all
  149. /// related quotes prevents race conditions where concurrent melt operations could interfere
  150. /// with each other, potentially leading to double-spending or state inconsistencies.
  151. ///
  152. /// The returned quotes are locked within the current transaction to ensure safe concurrent
  153. /// modification. This is essential during melt saga initiation and finalization to guarantee
  154. /// atomic state transitions across all related quotes.
  155. ///
  156. /// # Arguments
  157. ///
  158. /// * `request_lookup_id` - The payment identifier used by the Lightning backend to track
  159. /// payment state (e.g., payment hash, offer ID, or label).
  160. async fn get_melt_quotes_by_request_lookup_id(
  161. &mut self,
  162. request_lookup_id: &PaymentIdentifier,
  163. ) -> Result<Vec<Acquired<MeltQuote>>, Self::Err>;
  164. /// Locks a melt quote and all related quotes sharing the same request_lookup_id atomically.
  165. ///
  166. /// This method prevents deadlocks by acquiring all locks in a single query with consistent
  167. /// ordering, rather than locking the target quote first and then related quotes separately.
  168. ///
  169. /// # Deadlock Prevention
  170. ///
  171. /// When multiple transactions try to melt quotes sharing the same `request_lookup_id`,
  172. /// acquiring locks in two steps (first the target quote, then all related quotes) can cause
  173. /// circular wait deadlocks. This method avoids that by:
  174. /// 1. Using a subquery to find the `request_lookup_id` for the target quote
  175. /// 2. Locking ALL quotes with that `request_lookup_id` in one atomic operation
  176. /// 3. Ordering locks consistently by quote ID
  177. ///
  178. /// # Arguments
  179. ///
  180. /// * `quote_id` - The ID of the target melt quote
  181. ///
  182. /// # Returns
  183. ///
  184. /// A [`LockedMeltQuotes`] containing:
  185. /// - `target`: The target quote (if found)
  186. /// - `all_related`: All quotes sharing the same `request_lookup_id` (including the target)
  187. ///
  188. /// If the quote has no `request_lookup_id`, only the target quote is returned and locked.
  189. async fn lock_melt_quote_and_related(
  190. &mut self,
  191. quote_id: &QuoteId,
  192. ) -> Result<LockedMeltQuotes, Self::Err>;
  193. /// Updates the request lookup id for a melt quote.
  194. ///
  195. /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
  196. async fn update_melt_quote_request_lookup_id(
  197. &mut self,
  198. quote: &mut Acquired<mint::MeltQuote>,
  199. new_request_lookup_id: &PaymentIdentifier,
  200. ) -> Result<(), Self::Err>;
  201. /// Update [`mint::MeltQuote`] state.
  202. ///
  203. /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
  204. /// Returns the previous state.
  205. async fn update_melt_quote_state(
  206. &mut self,
  207. quote: &mut Acquired<mint::MeltQuote>,
  208. new_state: MeltQuoteState,
  209. payment_proof: Option<String>,
  210. ) -> Result<MeltQuoteState, Self::Err>;
  211. /// Get all [`MintMintQuote`]s and lock it for update in this transaction
  212. async fn get_mint_quote_by_request(
  213. &mut self,
  214. request: &str,
  215. ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
  216. /// Get all [`MintMintQuote`]s
  217. async fn get_mint_quote_by_request_lookup_id(
  218. &mut self,
  219. request_lookup_id: &PaymentIdentifier,
  220. ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
  221. }
  222. /// Mint Quote Database trait
  223. #[async_trait]
  224. pub trait QuotesDatabase {
  225. /// Mint Quotes Database Error
  226. type Err: Into<Error> + From<Error>;
  227. /// Get [`MintMintQuote`]
  228. async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
  229. /// Get all [`MintMintQuote`]s
  230. async fn get_mint_quote_by_request(
  231. &self,
  232. request: &str,
  233. ) -> Result<Option<MintMintQuote>, Self::Err>;
  234. /// Get all [`MintMintQuote`]s
  235. async fn get_mint_quote_by_request_lookup_id(
  236. &self,
  237. request_lookup_id: &PaymentIdentifier,
  238. ) -> Result<Option<MintMintQuote>, Self::Err>;
  239. /// Get Mint Quotes
  240. async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
  241. /// Get [`mint::MeltQuote`]
  242. async fn get_melt_quote(
  243. &self,
  244. quote_id: &QuoteId,
  245. ) -> Result<Option<mint::MeltQuote>, Self::Err>;
  246. /// Get all [`mint::MeltQuote`]s
  247. async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
  248. }
  249. /// Mint Proof Transaction trait
  250. #[async_trait]
  251. pub trait ProofsTransaction {
  252. /// Mint Proof Database Error
  253. type Err: Into<Error> + From<Error>;
  254. /// Add [`Proofs`]
  255. ///
  256. /// Adds proofs to the database. The database should error if the proof already exits, with a
  257. /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
  258. async fn add_proofs(
  259. &mut self,
  260. proof: Proofs,
  261. quote_id: Option<QuoteId>,
  262. operation: &Operation,
  263. ) -> Result<Acquired<ProofsWithState>, Self::Err>;
  264. /// Updates the proofs to the given state in the database.
  265. ///
  266. /// Also updates the `state` field on the [`ProofsWithState`] wrapper to reflect
  267. /// the new state after the database update succeeds.
  268. async fn update_proofs_state(
  269. &mut self,
  270. proofs: &mut Acquired<ProofsWithState>,
  271. new_state: State,
  272. ) -> Result<(), Self::Err>;
  273. /// get proofs states
  274. async fn get_proofs(
  275. &mut self,
  276. ys: &[PublicKey],
  277. ) -> Result<Acquired<ProofsWithState>, Self::Err>;
  278. /// Remove [`Proofs`]
  279. async fn remove_proofs(
  280. &mut self,
  281. ys: &[PublicKey],
  282. quote_id: Option<QuoteId>,
  283. ) -> Result<(), Self::Err>;
  284. /// Get ys by quote id
  285. async fn get_proof_ys_by_quote_id(
  286. &mut self,
  287. quote_id: &QuoteId,
  288. ) -> Result<Vec<PublicKey>, Self::Err>;
  289. /// Get proof ys by operation id
  290. async fn get_proof_ys_by_operation_id(
  291. &mut self,
  292. operation_id: &uuid::Uuid,
  293. ) -> Result<Vec<PublicKey>, Self::Err>;
  294. }
  295. /// Mint Proof Database trait
  296. #[async_trait]
  297. pub trait ProofsDatabase {
  298. /// Mint Proof Database Error
  299. type Err: Into<Error> + From<Error>;
  300. /// Get [`Proofs`] by ys
  301. async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
  302. /// Get ys by quote id
  303. async fn get_proof_ys_by_quote_id(
  304. &self,
  305. quote_id: &QuoteId,
  306. ) -> Result<Vec<PublicKey>, Self::Err>;
  307. /// Get [`Proofs`] state
  308. async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
  309. /// Get [`Proofs`] by state
  310. async fn get_proofs_by_keyset_id(
  311. &self,
  312. keyset_id: &Id,
  313. ) -> Result<(Proofs, Vec<Option<State>>), Self::Err>;
  314. /// Get total proofs redeemed by keyset id
  315. async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
  316. /// Get proof ys by operation id
  317. async fn get_proof_ys_by_operation_id(
  318. &self,
  319. operation_id: &uuid::Uuid,
  320. ) -> Result<Vec<PublicKey>, Self::Err>;
  321. }
  322. #[async_trait]
  323. /// Mint Signatures Transaction trait
  324. pub trait SignaturesTransaction {
  325. /// Mint Signature Database Error
  326. type Err: Into<Error> + From<Error>;
  327. /// Add [`BlindSignature`]
  328. async fn add_blind_signatures(
  329. &mut self,
  330. blinded_messages: &[PublicKey],
  331. blind_signatures: &[BlindSignature],
  332. quote_id: Option<QuoteId>,
  333. ) -> Result<(), Self::Err>;
  334. /// Get [`BlindSignature`]s
  335. async fn get_blind_signatures(
  336. &mut self,
  337. blinded_messages: &[PublicKey],
  338. ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
  339. }
  340. #[async_trait]
  341. /// Mint Signatures Database trait
  342. pub trait SignaturesDatabase {
  343. /// Mint Signature Database Error
  344. type Err: Into<Error> + From<Error>;
  345. /// Get [`BlindSignature`]s
  346. async fn get_blind_signatures(
  347. &self,
  348. blinded_messages: &[PublicKey],
  349. ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
  350. /// Get [`BlindSignature`]s for keyset_id
  351. async fn get_blind_signatures_for_keyset(
  352. &self,
  353. keyset_id: &Id,
  354. ) -> Result<Vec<BlindSignature>, Self::Err>;
  355. /// Get [`BlindSignature`]s for quote
  356. async fn get_blind_signatures_for_quote(
  357. &self,
  358. quote_id: &QuoteId,
  359. ) -> Result<Vec<BlindSignature>, Self::Err>;
  360. /// Get total amount issued by keyset id
  361. async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
  362. /// Get blinded secrets (B values) by operation id
  363. async fn get_blinded_secrets_by_operation_id(
  364. &self,
  365. operation_id: &uuid::Uuid,
  366. ) -> Result<Vec<PublicKey>, Self::Err>;
  367. }
  368. #[async_trait]
  369. /// Saga Transaction trait
  370. pub trait SagaTransaction {
  371. /// Saga Database Error
  372. type Err: Into<Error> + From<Error>;
  373. /// Get saga by operation_id
  374. async fn get_saga(
  375. &mut self,
  376. operation_id: &uuid::Uuid,
  377. ) -> Result<Option<mint::Saga>, Self::Err>;
  378. /// Add saga
  379. async fn add_saga(&mut self, saga: &mint::Saga) -> Result<(), Self::Err>;
  380. /// Update saga state (only updates state and updated_at fields)
  381. async fn update_saga(
  382. &mut self,
  383. operation_id: &uuid::Uuid,
  384. new_state: mint::SagaStateEnum,
  385. ) -> Result<(), Self::Err>;
  386. /// Delete saga
  387. async fn delete_saga(&mut self, operation_id: &uuid::Uuid) -> Result<(), Self::Err>;
  388. }
  389. #[async_trait]
  390. /// Saga Database trait
  391. pub trait SagaDatabase {
  392. /// Saga Database Error
  393. type Err: Into<Error> + From<Error>;
  394. /// Get all incomplete sagas for a given operation kind
  395. async fn get_incomplete_sagas(
  396. &self,
  397. operation_kind: mint::OperationKind,
  398. ) -> Result<Vec<mint::Saga>, Self::Err>;
  399. }
  400. #[async_trait]
  401. /// Completed Operations Transaction trait
  402. pub trait CompletedOperationsTransaction {
  403. /// Completed Operations Database Error
  404. type Err: Into<Error> + From<Error>;
  405. /// Add completed operation
  406. async fn add_completed_operation(
  407. &mut self,
  408. operation: &mint::Operation,
  409. fee_by_keyset: &std::collections::HashMap<crate::nuts::Id, crate::Amount>,
  410. ) -> Result<(), Self::Err>;
  411. }
  412. #[async_trait]
  413. /// Completed Operations Database trait
  414. pub trait CompletedOperationsDatabase {
  415. /// Completed Operations Database Error
  416. type Err: Into<Error> + From<Error>;
  417. /// Get completed operation by operation_id
  418. async fn get_completed_operation(
  419. &self,
  420. operation_id: &uuid::Uuid,
  421. ) -> Result<Option<mint::Operation>, Self::Err>;
  422. /// Get completed operations by operation kind
  423. async fn get_completed_operations_by_kind(
  424. &self,
  425. operation_kind: mint::OperationKind,
  426. ) -> Result<Vec<mint::Operation>, Self::Err>;
  427. /// Get all completed operations
  428. async fn get_completed_operations(&self) -> Result<Vec<mint::Operation>, Self::Err>;
  429. }
  430. /// Base database writer
  431. pub trait Transaction<Error>:
  432. DbTransactionFinalizer<Err = Error>
  433. + QuotesTransaction<Err = Error>
  434. + SignaturesTransaction<Err = Error>
  435. + ProofsTransaction<Err = Error>
  436. + KVStoreTransaction<Error>
  437. + SagaTransaction<Err = Error>
  438. + CompletedOperationsTransaction<Err = Error>
  439. {
  440. }
  441. /// Mint Database trait
  442. #[async_trait]
  443. pub trait Database<Error>:
  444. KVStoreDatabase<Err = Error>
  445. + QuotesDatabase<Err = Error>
  446. + ProofsDatabase<Err = Error>
  447. + SignaturesDatabase<Err = Error>
  448. + SagaDatabase<Err = Error>
  449. + CompletedOperationsDatabase<Err = Error>
  450. {
  451. /// Begins a transaction
  452. async fn begin_transaction(&self) -> Result<Box<dyn Transaction<Error> + Send + Sync>, Error>;
  453. }
  454. /// Type alias for Mint Database
  455. pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;
  456. /// Type alias for Mint Transaction
  457. pub type DynMintTransaction = Box<dyn Transaction<Error> + Send + Sync>;