Skip to main content

cdk_common/database/mint/
mod.rs

1//! CDK Database
2
3use std::collections::HashMap;
4use std::ops::{Deref, DerefMut};
5
6use async_trait::async_trait;
7use cashu::quote_id::QuoteId;
8use cashu::Amount;
9
10use super::{DbTransactionFinalizer, Error};
11use crate::mint::{
12    self, MeltQuote, MintKeySetInfo, MintQuote as MintMintQuote, Operation, ProofsWithState,
13};
14use crate::nuts::{
15    BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
16    State,
17};
18use crate::payment::PaymentIdentifier;
19
20mod auth;
21
22#[cfg(feature = "test")]
23pub mod test;
24
25pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
26
27// Re-export KVStore types from shared module for backward compatibility
28pub use super::kvstore::{
29    validate_kvstore_params, validate_kvstore_string, KVStore, KVStoreDatabase, KVStoreTransaction,
30    KVSTORE_NAMESPACE_KEY_ALPHABET, KVSTORE_NAMESPACE_KEY_MAX_LEN,
31};
32
33/// A wrapper indicating that a resource has been acquired with a database lock.
34///
35/// This type is returned by database operations that lock rows for update
36/// (e.g., `SELECT ... FOR UPDATE`). It serves as a compile-time marker that
37/// the wrapped resource was properly locked before being returned, ensuring
38/// that subsequent modifications are safe from race conditions.
39///
40/// # Usage
41///
42/// When you need to modify a database record, first acquire it using a locking
43/// query method. The returned `Acquired<T>` guarantees the row is locked for
44/// the duration of the transaction.
45///
46/// ```ignore
47/// // Acquire a quote with a row lock
48/// let mut quote: Acquired<MintQuote> = tx.get_mint_quote_for_update(&quote_id).await?;
49///
50/// // Safely modify the quote (row is locked)
51/// quote.state = QuoteState::Paid;
52///
53/// // Persist the changes
54/// tx.update_mint_quote(&mut quote).await?;
55/// ```
56///
57/// # Deref Behavior
58///
59/// `Acquired<T>` implements `Deref` and `DerefMut`, allowing transparent access
60/// to the inner value's methods and fields.
61#[derive(Debug)]
62pub struct Acquired<T> {
63    inner: T,
64}
65
66impl<T> From<T> for Acquired<T> {
67    /// Wraps a value to indicate it has been acquired with a lock.
68    ///
69    /// This is typically called by database layer implementations after
70    /// executing a locking query.
71    fn from(value: T) -> Self {
72        Acquired { inner: value }
73    }
74}
75
76impl<T> Acquired<T> {
77    /// Consumes the wrapper and returns the inner resource.
78    ///
79    /// Use this when you need to take ownership of the inner value,
80    /// for example when passing it to a function that doesn't accept
81    /// `Acquired<T>`.
82    pub fn inner(self) -> T {
83        self.inner
84    }
85}
86
87impl<T> Deref for Acquired<T> {
88    type Target = T;
89
90    /// Returns a reference to the inner resource.
91    fn deref(&self) -> &Self::Target {
92        &self.inner
93    }
94}
95
96impl<T> DerefMut for Acquired<T> {
97    /// Returns a mutable reference to the inner resource.
98    fn deref_mut(&mut self) -> &mut Self::Target {
99        &mut self.inner
100    }
101}
102
103/// Information about a melt request stored in the database
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct MeltRequestInfo {
106    /// Total amount of all input proofs in the melt request
107    pub inputs_amount: Amount<CurrencyUnit>,
108    /// Fee amount associated with the input proofs
109    pub inputs_fee: Amount<CurrencyUnit>,
110    /// Blinded messages for change outputs
111    pub change_outputs: Vec<BlindedMessage>,
112}
113
114/// Result of locking a melt quote and all related quotes atomically.
115///
116/// This struct is returned by [`QuotesTransaction::lock_melt_quote_and_related`]
117/// and contains both the target quote and all quotes sharing the same `request_lookup_id`.
118#[derive(Debug)]
119pub struct LockedMeltQuotes {
120    /// The target quote that was requested, if found
121    pub target: Option<Acquired<MeltQuote>>,
122    /// All quotes sharing the same `request_lookup_id` (including the target)
123    pub all_related: Vec<Acquired<MeltQuote>>,
124}
125
126/// KeysDatabaseWriter
127#[async_trait]
128pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
129    /// Add Active Keyset
130    async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
131
132    /// Add [`MintKeySetInfo`]
133    async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
134
135    /// Allocate the next derivation-path index for a unit.
136    ///
137    /// Returns `max(derivation_path_index) + 1` for the unit (1 when the unit
138    /// has no keysets yet). The transaction holds the global keyset advisory
139    /// lock (taken when it begins), so all keyset transactions serialize and two
140    /// signatory instances rotating the same unit cannot allocate the same index
141    /// and derive divergent keysets. This is the authoritative index source;
142    /// callers must not compute it from process-local state.
143    async fn next_derivation_index(&mut self, unit: &CurrencyUnit) -> Result<u32, Error>;
144
145    /// Read a unit's keyset infos inside the transaction, under the global
146    /// keyset advisory lock the transaction holds.
147    ///
148    /// Boot-time reactivation uses this to pick a unit's highest-index keyset
149    /// and reassign the active pointer as one atomic step. Both happen inside the
150    /// transaction, under the global keyset lock, so a peer rotation cannot commit
151    /// a higher keyset between the read and the reassignment and get clobbered
152    /// back to the older one.
153    async fn get_keyset_infos_by_unit(
154        &mut self,
155        unit: &CurrencyUnit,
156    ) -> Result<Vec<MintKeySetInfo>, Error>;
157
158    /// Read all active keyset pointers inside the transaction.
159    ///
160    /// Keyset reads go through a transaction, which holds the global keyset lock,
161    /// so no other keyset transaction can commit between reads. The signatory
162    /// uses these transaction reads to reload its in-memory keysets, so the
163    /// collision check and default amounts see peers' committed rotations rather
164    /// than a stale snapshot.
165    async fn get_active_keysets(&mut self) -> Result<HashMap<CurrencyUnit, Id>, Error>;
166
167    /// Read all keyset infos inside the transaction. See
168    /// [`get_active_keysets`](Self::get_active_keysets) for why keyset reads go
169    /// through the transaction.
170    async fn get_keyset_infos(&mut self) -> Result<Vec<MintKeySetInfo>, Error>;
171
172    /// Read the keyset epoch inside the transaction.
173    ///
174    /// Transaction-scoped counterpart of [`KeysDatabase::keysets_epoch`], used to
175    /// gate the reload under the global lock.
176    async fn keysets_epoch(&mut self) -> Result<u64, Error>;
177}
178
179/// Mint Keys Database trait
180#[async_trait]
181pub trait KeysDatabase {
182    /// Mint Keys Database Error
183    type Err: Into<Error> + From<Error>;
184
185    /// Begins a transaction
186    ///
187    /// All keyset reads and writes go through a transaction: it takes the global
188    /// keyset advisory lock, so the signatory's reload sees a consistent
189    /// snapshot. The autocommit reads that once existed here are gone; use the
190    /// transaction's read methods instead.
191    async fn begin_transaction<'a>(
192        &'a self,
193    ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
194
195    /// An opaque epoch for the current keyset set.
196    ///
197    /// It must change whenever the set changes, both when a keyset is added and
198    /// when the active pointer is reassigned. Callers only compare it for
199    /// equality to decide whether to reload their in-memory view, so an epoch
200    /// that misses reactivations would leave peers serving a stale active
201    /// keyset. The storage decides how to compute it cheaply (e.g. a counter
202    /// bumped inside every keyset-writing transaction); there is deliberately no
203    /// default, since a count-based fallback would not move on reactivation.
204    async fn keysets_epoch(&self) -> Result<u64, Self::Err>;
205}
206
207/// Mint Quote Database writer trait
208#[async_trait]
209pub trait QuotesTransaction {
210    /// Mint Quotes Database Error
211    type Err: Into<Error> + From<Error>;
212
213    /// Add melt_request with quote_id, inputs_amount, and inputs_fee
214    async fn add_melt_request(
215        &mut self,
216        quote_id: &QuoteId,
217        inputs_amount: Amount<CurrencyUnit>,
218        inputs_fee: Amount<CurrencyUnit>,
219    ) -> Result<(), Self::Err>;
220
221    /// Add blinded_messages for a quote_id
222    async fn add_blinded_messages(
223        &mut self,
224        quote_id: Option<&QuoteId>,
225        blinded_messages: &[BlindedMessage],
226        operation: &Operation,
227    ) -> Result<(), Self::Err>;
228
229    /// Delete blinded_messages by their blinded secrets
230    async fn delete_blinded_messages(
231        &mut self,
232        blinded_secrets: &[PublicKey],
233    ) -> Result<(), Self::Err>;
234
235    /// Get melt_request and associated blinded_messages by quote_id
236    async fn get_melt_request_and_blinded_messages(
237        &mut self,
238        quote_id: &QuoteId,
239    ) -> Result<Option<MeltRequestInfo>, Self::Err>;
240
241    /// Delete melt_request and associated blinded_messages by quote_id
242    async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
243
244    /// Get [`MintMintQuote`] and lock it for update in this transaction
245    async fn get_mint_quote(
246        &mut self,
247        quote_id: &QuoteId,
248    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
249
250    /// Get multiple [`MintMintQuote`]s by their IDs and lock them for update in this transaction.
251    ///
252    /// Returns results in the same order as the input IDs, with `None` for any IDs not found.
253    /// This method locks all found quotes to prevent race conditions during concurrent modifications.
254    async fn get_mint_quotes_by_ids(
255        &mut self,
256        quote_ids: &[QuoteId],
257    ) -> Result<Vec<Option<Acquired<MintMintQuote>>>, Self::Err>;
258
259    /// Add [`MintMintQuote`]
260    async fn add_mint_quote(
261        &mut self,
262        quote: MintMintQuote,
263    ) -> Result<Acquired<MintMintQuote>, Self::Err>;
264
265    /// Persists any pending changes made to the mint quote.
266    ///
267    /// This method extracts changes accumulated in the quote (via [`mint::MintQuote::take_changes`])
268    /// and persists them to the database. Changes may include new payments received or new
269    /// issuances recorded against the quote.
270    ///
271    /// If no changes are pending, this method returns successfully without performing
272    /// any database operations.
273    ///
274    /// # Arguments
275    ///
276    /// * `quote` - A mutable reference to an acquired (row-locked) mint quote. The quote
277    ///   must be locked to ensure transactional consistency when persisting changes.
278    ///
279    /// # Implementation Notes
280    ///
281    /// Implementations should call [`mint::MintQuote::take_changes`] to retrieve pending
282    /// changes, then persist each payment and issuance record, and finally update the
283    /// quote's aggregate counters (`amount_paid`, `amount_issued`) in the database.
284    async fn update_mint_quote(
285        &mut self,
286        quote: &mut Acquired<mint::MintQuote>,
287    ) -> Result<(), Self::Err>;
288
289    /// Get [`mint::MeltQuote`] and lock it for update in this transaction
290    async fn get_melt_quote(
291        &mut self,
292        quote_id: &QuoteId,
293    ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err>;
294
295    /// Add [`mint::MeltQuote`]
296    async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
297
298    /// Retrieves all melt quotes matching a payment lookup identifier and locks them for update.
299    ///
300    /// This method returns multiple quotes because certain payment methods (notably BOLT12 offers)
301    /// can generate multiple payment attempts that share the same lookup identifier. Locking all
302    /// related quotes prevents race conditions where concurrent melt operations could interfere
303    /// with each other, potentially leading to double-spending or state inconsistencies.
304    ///
305    /// The returned quotes are locked within the current transaction to ensure safe concurrent
306    /// modification. This is essential during melt saga initiation and finalization to guarantee
307    /// atomic state transitions across all related quotes.
308    ///
309    /// # Arguments
310    ///
311    /// * `request_lookup_id` - The payment identifier used by the payment backend to track
312    ///   payment state (e.g., payment hash, offer ID, or label).
313    async fn get_melt_quotes_by_request_lookup_id(
314        &mut self,
315        request_lookup_id: &PaymentIdentifier,
316    ) -> Result<Vec<Acquired<MeltQuote>>, Self::Err>;
317
318    /// Locks a melt quote and all related quotes sharing the same request_lookup_id atomically.
319    ///
320    /// This method prevents deadlocks by acquiring all locks in a single query with consistent
321    /// ordering, rather than locking the target quote first and then related quotes separately.
322    ///
323    /// # Deadlock Prevention
324    ///
325    /// When multiple transactions try to melt quotes sharing the same `request_lookup_id`,
326    /// acquiring locks in two steps (first the target quote, then all related quotes) can cause
327    /// circular wait deadlocks. This method avoids that by:
328    /// 1. Using a subquery to find the `request_lookup_id` for the target quote
329    /// 2. Locking ALL quotes with that `request_lookup_id` in one atomic operation
330    /// 3. Ordering locks consistently by quote ID
331    ///
332    /// # Arguments
333    ///
334    /// * `quote_id` - The ID of the target melt quote
335    ///
336    /// # Returns
337    ///
338    /// A [`LockedMeltQuotes`] containing:
339    /// - `target`: The target quote (if found)
340    /// - `all_related`: All quotes sharing the same `request_lookup_id` (including the target)
341    ///
342    /// If the quote has no `request_lookup_id`, only the target quote is returned and locked.
343    async fn lock_melt_quote_and_related(
344        &mut self,
345        quote_id: &QuoteId,
346    ) -> Result<LockedMeltQuotes, Self::Err>;
347
348    /// Updates the request lookup id for a melt quote.
349    ///
350    /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
351    async fn update_melt_quote_request_lookup_id(
352        &mut self,
353        quote: &mut Acquired<mint::MeltQuote>,
354        new_request_lookup_id: &PaymentIdentifier,
355    ) -> Result<(), Self::Err>;
356
357    /// Update [`mint::MeltQuote`] state.
358    ///
359    /// Requires an [`Acquired`] melt quote to ensure the row is locked before modification.
360    /// Returns the previous state.
361    async fn update_melt_quote_state(
362        &mut self,
363        quote: &mut Acquired<mint::MeltQuote>,
364        new_state: MeltQuoteState,
365        payment_proof: Option<String>,
366    ) -> Result<MeltQuoteState, Self::Err>;
367
368    /// Get all [`MintMintQuote`]s and lock it for update in this transaction
369    async fn get_mint_quote_by_request(
370        &mut self,
371        request: &str,
372    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
373
374    /// Get all [`MintMintQuote`]s
375    async fn get_mint_quote_by_request_lookup_id(
376        &mut self,
377        request_lookup_id: &PaymentIdentifier,
378    ) -> Result<Option<Acquired<MintMintQuote>>, Self::Err>;
379}
380
381/// Mint Quote Database trait
382#[async_trait]
383pub trait QuotesDatabase {
384    /// Mint Quotes Database Error
385    type Err: Into<Error> + From<Error>;
386
387    /// Get [`MintMintQuote`]
388    async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
389
390    /// Atomically claim a payment backend status check for a mint quote.
391    ///
392    /// Returns `true` when `last_checked` was updated, or `false` when another check occurred
393    /// within `min_interval` seconds.
394    async fn try_update_mint_quote_last_checked(
395        &self,
396        quote_id: &QuoteId,
397        last_checked: u64,
398        min_interval: u64,
399    ) -> Result<bool, Self::Err>;
400
401    /// Get multiple [`MintMintQuote`]s by their IDs.
402    ///
403    /// Returns results in the same order as the input IDs, with `None` for any IDs not found.
404    async fn get_mint_quotes_by_ids(
405        &self,
406        quote_ids: &[QuoteId],
407    ) -> Result<Vec<Option<MintMintQuote>>, Self::Err>;
408
409    /// Get all [`MintMintQuote`]s
410    async fn get_mint_quote_by_request(
411        &self,
412        request: &str,
413    ) -> Result<Option<MintMintQuote>, Self::Err>;
414    /// Get all [`MintMintQuote`]s
415    async fn get_mint_quote_by_request_lookup_id(
416        &self,
417        request_lookup_id: &PaymentIdentifier,
418    ) -> Result<Option<MintMintQuote>, Self::Err>;
419    /// Get Mint Quotes
420    async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
421    /// Get [`mint::MeltQuote`]
422    async fn get_melt_quote(
423        &self,
424        quote_id: &QuoteId,
425    ) -> Result<Option<mint::MeltQuote>, Self::Err>;
426    /// Get all [`mint::MeltQuote`]s
427    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
428}
429
430/// Mint Proof Transaction trait
431#[async_trait]
432pub trait ProofsTransaction {
433    /// Mint Proof Database Error
434    type Err: Into<Error> + From<Error>;
435
436    /// Add  [`Proofs`]
437    ///
438    /// Adds proofs to the database. The database should error if the proof already exits, with a
439    /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
440    async fn add_proofs(
441        &mut self,
442        proof: Proofs,
443        quote_id: Option<QuoteId>,
444        operation: &Operation,
445    ) -> Result<Acquired<ProofsWithState>, Self::Err>;
446
447    /// Updates the proofs to the given state in the database.
448    ///
449    /// Also updates the `state` field on the [`ProofsWithState`] wrapper to reflect
450    /// the new state after the database update succeeds.
451    async fn update_proofs_state(
452        &mut self,
453        proofs: &mut Acquired<ProofsWithState>,
454        new_state: State,
455    ) -> Result<(), Self::Err>;
456
457    /// get proofs states
458    async fn get_proofs(
459        &mut self,
460        ys: &[PublicKey],
461    ) -> Result<Acquired<ProofsWithState>, Self::Err>;
462
463    /// Remove [`Proofs`]
464    async fn remove_proofs(
465        &mut self,
466        ys: &[PublicKey],
467        quote_id: Option<QuoteId>,
468    ) -> Result<(), Self::Err>;
469
470    /// Get ys by quote id
471    async fn get_proof_ys_by_quote_id(
472        &mut self,
473        quote_id: &QuoteId,
474    ) -> Result<Vec<PublicKey>, Self::Err>;
475
476    /// Get proof ys by operation id
477    async fn get_proof_ys_by_operation_id(
478        &mut self,
479        operation_id: &uuid::Uuid,
480    ) -> Result<Vec<PublicKey>, Self::Err>;
481}
482
483/// Mint Proof Database trait
484#[async_trait]
485pub trait ProofsDatabase {
486    /// Mint Proof Database Error
487    type Err: Into<Error> + From<Error>;
488
489    /// Get [`Proofs`] by ys
490    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
491    /// Get ys by quote id
492    async fn get_proof_ys_by_quote_id(
493        &self,
494        quote_id: &QuoteId,
495    ) -> Result<Vec<PublicKey>, Self::Err>;
496    /// Get [`Proofs`] state
497    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
498
499    /// Get [`Proofs`] by state
500    async fn get_proofs_by_keyset_id(
501        &self,
502        keyset_id: &Id,
503    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err>;
504
505    /// Get total proofs redeemed by keyset id
506    async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
507
508    /// Get proof ys by operation id
509    async fn get_proof_ys_by_operation_id(
510        &self,
511        operation_id: &uuid::Uuid,
512    ) -> Result<Vec<PublicKey>, Self::Err>;
513}
514
515#[async_trait]
516/// Mint Signatures Transaction trait
517pub trait SignaturesTransaction {
518    /// Mint Signature Database Error
519    type Err: Into<Error> + From<Error>;
520
521    /// Add [`BlindSignature`]
522    async fn add_blind_signatures(
523        &mut self,
524        blinded_messages: &[PublicKey],
525        blind_signatures: &[BlindSignature],
526        quote_id: Option<QuoteId>,
527    ) -> Result<(), Self::Err>;
528
529    /// Get [`BlindSignature`]s
530    async fn get_blind_signatures(
531        &mut self,
532        blinded_messages: &[PublicKey],
533    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
534}
535
536#[async_trait]
537/// Mint Signatures Database trait
538pub trait SignaturesDatabase {
539    /// Mint Signature Database Error
540    type Err: Into<Error> + From<Error>;
541
542    /// Get [`BlindSignature`]s
543    async fn get_blind_signatures(
544        &self,
545        blinded_messages: &[PublicKey],
546    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
547
548    /// Get [`BlindSignature`]s for keyset_id
549    async fn get_blind_signatures_for_keyset(
550        &self,
551        keyset_id: &Id,
552    ) -> Result<Vec<BlindSignature>, Self::Err>;
553
554    /// Get [`BlindSignature`]s for quote
555    async fn get_blind_signatures_for_quote(
556        &self,
557        quote_id: &QuoteId,
558    ) -> Result<Vec<BlindSignature>, Self::Err>;
559
560    /// Get total amount issued by keyset id
561    async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
562
563    /// Get blinded secrets (B values) by operation id
564    async fn get_blinded_secrets_by_operation_id(
565        &self,
566        operation_id: &uuid::Uuid,
567    ) -> Result<Vec<PublicKey>, Self::Err>;
568}
569
570#[async_trait]
571/// Saga Transaction trait
572pub trait SagaTransaction {
573    /// Saga Database Error
574    type Err: Into<Error> + From<Error>;
575
576    /// Get saga by operation_id
577    async fn get_saga(
578        &mut self,
579        operation_id: &uuid::Uuid,
580    ) -> Result<Option<mint::Saga>, Self::Err>;
581
582    /// Get a saga by operation ID and mark it as acquired for mutation.
583    ///
584    /// Implementations must lock the returned row for the lifetime of the
585    /// transaction. Saga mutation methods require this marker so callers
586    /// cannot update a stale snapshot loaded outside the transaction.
587    async fn get_saga_for_update(
588        &mut self,
589        operation_id: &uuid::Uuid,
590    ) -> Result<Option<Acquired<mint::Saga>>, Self::Err>;
591
592    /// Add saga
593    async fn add_saga(&mut self, saga: &mint::Saga) -> Result<(), Self::Err>;
594
595    /// Update the state of an acquired saga.
596    async fn update_acquired_saga(
597        &mut self,
598        saga: &mut Acquired<mint::Saga>,
599        new_state: mint::SagaStateEnum,
600    ) -> Result<(), Self::Err>;
601
602    /// Update an acquired saga state and optional finalization metadata.
603    async fn update_acquired_saga_with_finalization_data(
604        &mut self,
605        saga: &mut Acquired<mint::Saga>,
606        new_state: mint::SagaStateEnum,
607        finalization_data: Option<&mint::MeltFinalizationData>,
608    ) -> Result<(), Self::Err>;
609
610    /// Delete saga
611    async fn delete_saga(&mut self, operation_id: &uuid::Uuid) -> Result<(), Self::Err>;
612}
613
614#[async_trait]
615/// Saga Database trait
616pub trait SagaDatabase {
617    /// Saga Database Error
618    type Err: Into<Error> + From<Error>;
619
620    /// Get the melt saga associated with a melt quote id
621    async fn get_melt_saga_by_quote_id(
622        &self,
623        quote_id: &QuoteId,
624    ) -> Result<Option<mint::Saga>, Self::Err>;
625
626    /// Get all incomplete sagas for a given operation kind
627    async fn get_incomplete_sagas(
628        &self,
629        operation_kind: mint::OperationKind,
630    ) -> Result<Vec<mint::Saga>, Self::Err>;
631}
632
633#[async_trait]
634/// Completed Operations Transaction trait
635pub trait CompletedOperationsTransaction {
636    /// Completed Operations Database Error
637    type Err: Into<Error> + From<Error>;
638
639    /// Add completed operation
640    async fn add_completed_operation(
641        &mut self,
642        operation: &mint::Operation,
643        fee_by_keyset: &std::collections::HashMap<crate::nuts::Id, crate::Amount>,
644    ) -> Result<(), Self::Err>;
645}
646
647#[async_trait]
648/// Completed Operations Database trait
649pub trait CompletedOperationsDatabase {
650    /// Completed Operations Database Error
651    type Err: Into<Error> + From<Error>;
652
653    /// Get completed operation by operation_id
654    async fn get_completed_operation(
655        &self,
656        operation_id: &uuid::Uuid,
657    ) -> Result<Option<mint::Operation>, Self::Err>;
658
659    /// Get completed operations by operation kind
660    async fn get_completed_operations_by_kind(
661        &self,
662        operation_kind: mint::OperationKind,
663    ) -> Result<Vec<mint::Operation>, Self::Err>;
664
665    /// Get all completed operations
666    async fn get_completed_operations(&self) -> Result<Vec<mint::Operation>, Self::Err>;
667}
668
669/// Base database writer
670pub trait Transaction<Error>:
671    DbTransactionFinalizer<Err = Error>
672    + QuotesTransaction<Err = Error>
673    + SignaturesTransaction<Err = Error>
674    + ProofsTransaction<Err = Error>
675    + KVStoreTransaction<Error>
676    + SagaTransaction<Err = Error>
677    + CompletedOperationsTransaction<Err = Error>
678{
679}
680
681/// Mint Database trait
682#[async_trait]
683pub trait Database<Error>:
684    KVStoreDatabase<Err = Error>
685    + QuotesDatabase<Err = Error>
686    + ProofsDatabase<Err = Error>
687    + SignaturesDatabase<Err = Error>
688    + SagaDatabase<Err = Error>
689    + CompletedOperationsDatabase<Err = Error>
690{
691    /// Begins a transaction
692    async fn begin_transaction(&self) -> Result<Box<dyn Transaction<Error> + Send + Sync>, Error>;
693}
694
695/// Type alias for Mint Database
696pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;
697
698/// Type alias for Mint Transaction
699pub type DynMintTransaction = Box<dyn Transaction<Error> + Send + Sync>;