cdk_sql_common/mint/
mod.rs

1//! SQL database implementation of the Mint
2//!
3//! This is a generic SQL implementation for the mint storage layer. Any database can be plugged in
4//! as long as standard ANSI SQL is used, as Postgres and SQLite would understand it.
5//!
6//! This implementation also has a rudimentary but standard migration and versioning system.
7//!
8//! The trait expects an asynchronous interaction, but it also provides tools to spawn blocking
9//! clients in a pool and expose them to an asynchronous environment, making them compatible with
10//! Mint.
11use std::collections::HashMap;
12use std::fmt::Debug;
13use std::str::FromStr;
14use std::sync::Arc;
15
16use async_trait::async_trait;
17use bitcoin::bip32::DerivationPath;
18use cdk_common::database::mint::validate_kvstore_params;
19use cdk_common::database::{
20    self, ConversionError, Error, MintDatabase, MintDbWriterFinalizer, MintKeyDatabaseTransaction,
21    MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase, MintQuotesTransaction,
22    MintSignatureTransaction, MintSignaturesDatabase,
23};
24use cdk_common::mint::{
25    self, IncomingPayment, Issuance, MeltPaymentRequest, MeltQuote, MintKeySetInfo, MintQuote,
26};
27use cdk_common::nut00::ProofsMethods;
28use cdk_common::payment::PaymentIdentifier;
29use cdk_common::quote_id::QuoteId;
30use cdk_common::secret::Secret;
31use cdk_common::state::check_state_transition;
32use cdk_common::util::unix_time;
33use cdk_common::{
34    Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
35    PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
36};
37use lightning_invoice::Bolt11Invoice;
38use migrations::MIGRATIONS;
39use tracing::instrument;
40
41use crate::common::migrate;
42use crate::database::{ConnectionWithTransaction, DatabaseExecutor};
43use crate::pool::{DatabasePool, Pool, PooledResource};
44use crate::stmt::{query, Column};
45use crate::{
46    column_as_nullable_number, column_as_nullable_string, column_as_number, column_as_string,
47    unpack_into,
48};
49
50#[cfg(feature = "auth")]
51mod auth;
52
53#[rustfmt::skip]
54mod migrations {
55    include!(concat!(env!("OUT_DIR"), "/migrations_mint.rs"));
56}
57
58#[cfg(feature = "auth")]
59pub use auth::SQLMintAuthDatabase;
60#[cfg(feature = "prometheus")]
61use cdk_prometheus::METRICS;
62
63/// Mint SQL Database
64#[derive(Debug, Clone)]
65pub struct SQLMintDatabase<RM>
66where
67    RM: DatabasePool + 'static,
68{
69    pool: Arc<Pool<RM>>,
70}
71
72/// SQL Transaction Writer
73pub struct SQLTransaction<RM>
74where
75    RM: DatabasePool + 'static,
76{
77    inner: ConnectionWithTransaction<RM::Connection, PooledResource<RM>>,
78}
79
80#[inline(always)]
81async fn get_current_states<C>(
82    conn: &C,
83    ys: &[PublicKey],
84) -> Result<HashMap<PublicKey, State>, Error>
85where
86    C: DatabaseExecutor + Send + Sync,
87{
88    if ys.is_empty() {
89        return Ok(Default::default());
90    }
91    query(r#"SELECT y, state FROM proof WHERE y IN (:ys)"#)?
92        .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
93        .fetch_all(conn)
94        .await?
95        .into_iter()
96        .map(|row| {
97            Ok((
98                column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice),
99                column_as_string!(&row[1], State::from_str),
100            ))
101        })
102        .collect::<Result<HashMap<_, _>, _>>()
103}
104
105impl<RM> SQLMintDatabase<RM>
106where
107    RM: DatabasePool + 'static,
108{
109    /// Creates a new instance
110    pub async fn new<X>(db: X) -> Result<Self, Error>
111    where
112        X: Into<RM::Config>,
113    {
114        let pool = Pool::new(db.into());
115
116        Self::migrate(pool.get().map_err(|e| Error::Database(Box::new(e)))?).await?;
117
118        Ok(Self { pool })
119    }
120
121    /// Migrate
122    async fn migrate(conn: PooledResource<RM>) -> Result<(), Error> {
123        let tx = ConnectionWithTransaction::new(conn).await?;
124        migrate(&tx, RM::Connection::name(), MIGRATIONS).await?;
125        tx.commit().await?;
126        Ok(())
127    }
128}
129
130#[async_trait]
131impl<RM> database::MintProofsTransaction<'_> for SQLTransaction<RM>
132where
133    RM: DatabasePool + 'static,
134{
135    type Err = Error;
136
137    async fn add_proofs(
138        &mut self,
139        proofs: Proofs,
140        quote_id: Option<QuoteId>,
141    ) -> Result<(), Self::Err> {
142        let current_time = unix_time();
143
144        // Check any previous proof, this query should return None in order to proceed storing
145        // Any result here would error
146        match query(r#"SELECT state FROM proof WHERE y IN (:ys) LIMIT 1 FOR UPDATE"#)?
147            .bind_vec(
148                "ys",
149                proofs
150                    .iter()
151                    .map(|y| y.y().map(|y| y.to_bytes().to_vec()))
152                    .collect::<Result<_, _>>()?,
153            )
154            .pluck(&self.inner)
155            .await?
156            .map(|state| Ok::<_, Error>(column_as_string!(&state, State::from_str)))
157            .transpose()?
158        {
159            Some(State::Spent) => Err(database::Error::AttemptUpdateSpentProof),
160            Some(_) => Err(database::Error::Duplicate),
161            None => Ok(()), // no previous record
162        }?;
163
164        for proof in proofs {
165            query(
166                r#"
167                  INSERT INTO proof
168                  (y, amount, keyset_id, secret, c, witness, state, quote_id, created_time)
169                  VALUES
170                  (:y, :amount, :keyset_id, :secret, :c, :witness, :state, :quote_id, :created_time)
171                  "#,
172            )?
173            .bind("y", proof.y()?.to_bytes().to_vec())
174            .bind("amount", proof.amount.to_i64())
175            .bind("keyset_id", proof.keyset_id.to_string())
176            .bind("secret", proof.secret.to_string())
177            .bind("c", proof.c.to_bytes().to_vec())
178            .bind(
179                "witness",
180                proof.witness.map(|w| serde_json::to_string(&w).unwrap()),
181            )
182            .bind("state", "UNSPENT".to_string())
183            .bind("quote_id", quote_id.clone().map(|q| q.to_string()))
184            .bind("created_time", current_time as i64)
185            .execute(&self.inner)
186            .await?;
187        }
188
189        Ok(())
190    }
191
192    async fn update_proofs_states(
193        &mut self,
194        ys: &[PublicKey],
195        new_state: State,
196    ) -> Result<Vec<Option<State>>, Self::Err> {
197        let mut current_states = get_current_states(&self.inner, ys).await?;
198
199        if current_states.len() != ys.len() {
200            tracing::warn!(
201                "Attempted to update state of non-existent proof {} {}",
202                current_states.len(),
203                ys.len()
204            );
205            return Err(database::Error::ProofNotFound);
206        }
207
208        for state in current_states.values() {
209            check_state_transition(*state, new_state)?;
210        }
211
212        query(r#"UPDATE proof SET state = :new_state WHERE y IN (:ys)"#)?
213            .bind("new_state", new_state.to_string())
214            .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
215            .execute(&self.inner)
216            .await?;
217
218        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
219    }
220
221    async fn remove_proofs(
222        &mut self,
223        ys: &[PublicKey],
224        _quote_id: Option<QuoteId>,
225    ) -> Result<(), Self::Err> {
226        if ys.is_empty() {
227            return Ok(());
228        }
229        let total_deleted = query(
230            r#"
231            DELETE FROM proof WHERE y IN (:ys) AND state NOT IN (:exclude_state)
232            "#,
233        )?
234        .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
235        .bind_vec("exclude_state", vec![State::Spent.to_string()])
236        .execute(&self.inner)
237        .await?;
238
239        if total_deleted != ys.len() {
240            return Err(Self::Err::AttemptRemoveSpentProof);
241        }
242
243        Ok(())
244    }
245
246    async fn get_proof_ys_by_quote_id(
247        &self,
248        quote_id: &QuoteId,
249    ) -> Result<Vec<PublicKey>, Self::Err> {
250        Ok(query(
251            r#"
252            SELECT
253                amount,
254                keyset_id,
255                secret,
256                c,
257                witness
258            FROM
259                proof
260            WHERE
261                quote_id = :quote_id
262            "#,
263        )?
264        .bind("quote_id", quote_id.to_string())
265        .fetch_all(&self.inner)
266        .await?
267        .into_iter()
268        .map(sql_row_to_proof)
269        .collect::<Result<Vec<Proof>, _>>()?
270        .ys()?)
271    }
272}
273
274#[async_trait]
275impl<RM> database::MintTransaction<'_, Error> for SQLTransaction<RM> where RM: DatabasePool + 'static
276{}
277
278#[async_trait]
279impl<RM> MintDbWriterFinalizer for SQLTransaction<RM>
280where
281    RM: DatabasePool + 'static,
282{
283    type Err = Error;
284
285    async fn commit(self: Box<Self>) -> Result<(), Error> {
286        let result = self.inner.commit().await;
287        #[cfg(feature = "prometheus")]
288        {
289            let success = result.is_ok();
290            METRICS.record_mint_operation("transaction_commit", success);
291            METRICS.record_mint_operation_histogram("transaction_commit", success, 1.0);
292        }
293
294        Ok(result?)
295    }
296
297    async fn rollback(self: Box<Self>) -> Result<(), Error> {
298        let result = self.inner.rollback().await;
299
300        #[cfg(feature = "prometheus")]
301        {
302            let success = result.is_ok();
303            METRICS.record_mint_operation("transaction_rollback", success);
304            METRICS.record_mint_operation_histogram("transaction_rollback", success, 1.0);
305        }
306        Ok(result?)
307    }
308}
309
310#[inline(always)]
311async fn get_mint_quote_payments<C>(
312    conn: &C,
313    quote_id: &QuoteId,
314) -> Result<Vec<IncomingPayment>, Error>
315where
316    C: DatabaseExecutor + Send + Sync,
317{
318    // Get payment IDs and timestamps from the mint_quote_payments table
319    query(
320        r#"
321        SELECT
322            payment_id,
323            timestamp,
324            amount
325        FROM
326            mint_quote_payments
327        WHERE
328            quote_id=:quote_id
329        "#,
330    )?
331    .bind("quote_id", quote_id.to_string())
332    .fetch_all(conn)
333    .await?
334    .into_iter()
335    .map(|row| {
336        let amount: u64 = column_as_number!(row[2].clone());
337        let time: u64 = column_as_number!(row[1].clone());
338        Ok(IncomingPayment::new(
339            amount.into(),
340            column_as_string!(&row[0]),
341            time,
342        ))
343    })
344    .collect()
345}
346
347#[inline(always)]
348async fn get_mint_quote_issuance<C>(conn: &C, quote_id: &QuoteId) -> Result<Vec<Issuance>, Error>
349where
350    C: DatabaseExecutor + Send + Sync,
351{
352    // Get payment IDs and timestamps from the mint_quote_payments table
353    query(
354        r#"
355SELECT amount, timestamp
356FROM mint_quote_issued
357WHERE quote_id=:quote_id
358            "#,
359    )?
360    .bind("quote_id", quote_id.to_string())
361    .fetch_all(conn)
362    .await?
363    .into_iter()
364    .map(|row| {
365        let time: u64 = column_as_number!(row[1].clone());
366        Ok(Issuance::new(
367            Amount::from_i64(column_as_number!(row[0].clone()))
368                .expect("Is amount when put into db"),
369            time,
370        ))
371    })
372    .collect()
373}
374
375#[async_trait]
376impl<RM> MintKeyDatabaseTransaction<'_, Error> for SQLTransaction<RM>
377where
378    RM: DatabasePool + 'static,
379{
380    async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error> {
381        query(
382            r#"
383        INSERT INTO
384            keyset (
385                id, unit, active, valid_from, valid_to, derivation_path,
386                max_order, amounts, input_fee_ppk, derivation_path_index
387            )
388        VALUES (
389            :id, :unit, :active, :valid_from, :valid_to, :derivation_path,
390            :max_order, :amounts, :input_fee_ppk, :derivation_path_index
391        )
392        ON CONFLICT(id) DO UPDATE SET
393            unit = excluded.unit,
394            active = excluded.active,
395            valid_from = excluded.valid_from,
396            valid_to = excluded.valid_to,
397            derivation_path = excluded.derivation_path,
398            max_order = excluded.max_order,
399            amounts = excluded.amounts,
400            input_fee_ppk = excluded.input_fee_ppk,
401            derivation_path_index = excluded.derivation_path_index
402        "#,
403        )?
404        .bind("id", keyset.id.to_string())
405        .bind("unit", keyset.unit.to_string())
406        .bind("active", keyset.active)
407        .bind("valid_from", keyset.valid_from as i64)
408        .bind("valid_to", keyset.final_expiry.map(|v| v as i64))
409        .bind("derivation_path", keyset.derivation_path.to_string())
410        .bind("max_order", keyset.max_order)
411        .bind("amounts", serde_json::to_string(&keyset.amounts).ok())
412        .bind("input_fee_ppk", keyset.input_fee_ppk as i64)
413        .bind("derivation_path_index", keyset.derivation_path_index)
414        .execute(&self.inner)
415        .await?;
416
417        Ok(())
418    }
419
420    async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error> {
421        query(r#"UPDATE keyset SET active=FALSE WHERE unit = :unit"#)?
422            .bind("unit", unit.to_string())
423            .execute(&self.inner)
424            .await?;
425
426        query(r#"UPDATE keyset SET active=TRUE WHERE unit = :unit AND id = :id"#)?
427            .bind("unit", unit.to_string())
428            .bind("id", id.to_string())
429            .execute(&self.inner)
430            .await?;
431
432        Ok(())
433    }
434}
435
436#[async_trait]
437impl<RM> MintKeysDatabase for SQLMintDatabase<RM>
438where
439    RM: DatabasePool + 'static,
440{
441    type Err = Error;
442
443    async fn begin_transaction<'a>(
444        &'a self,
445    ) -> Result<Box<dyn MintKeyDatabaseTransaction<'a, Error> + Send + Sync + 'a>, Error> {
446        let tx = SQLTransaction {
447            inner: ConnectionWithTransaction::new(
448                self.pool.get().map_err(|e| Error::Database(Box::new(e)))?,
449            )
450            .await?,
451        };
452
453        Ok(Box::new(tx))
454    }
455
456    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err> {
457        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
458        Ok(
459            query(r#" SELECT id FROM keyset WHERE active = :active AND unit = :unit"#)?
460                .bind("active", true)
461                .bind("unit", unit.to_string())
462                .pluck(&*conn)
463                .await?
464                .map(|id| match id {
465                    Column::Text(text) => Ok(Id::from_str(&text)?),
466                    Column::Blob(id) => Ok(Id::from_bytes(&id)?),
467                    _ => Err(Error::InvalidKeysetId),
468                })
469                .transpose()?,
470        )
471    }
472
473    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err> {
474        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
475        Ok(
476            query(r#"SELECT id, unit FROM keyset WHERE active = :active"#)?
477                .bind("active", true)
478                .fetch_all(&*conn)
479                .await?
480                .into_iter()
481                .map(|row| {
482                    Ok((
483                        column_as_string!(&row[1], CurrencyUnit::from_str),
484                        column_as_string!(&row[0], Id::from_str, Id::from_bytes),
485                    ))
486                })
487                .collect::<Result<HashMap<_, _>, Error>>()?,
488        )
489    }
490
491    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
492        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
493        Ok(query(
494            r#"SELECT
495                id,
496                unit,
497                active,
498                valid_from,
499                valid_to,
500                derivation_path,
501                derivation_path_index,
502                max_order,
503                amounts,
504                input_fee_ppk
505            FROM
506                keyset
507                WHERE id=:id"#,
508        )?
509        .bind("id", id.to_string())
510        .fetch_one(&*conn)
511        .await?
512        .map(sql_row_to_keyset_info)
513        .transpose()?)
514    }
515
516    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
517        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
518        Ok(query(
519            r#"SELECT
520                id,
521                unit,
522                active,
523                valid_from,
524                valid_to,
525                derivation_path,
526                derivation_path_index,
527                max_order,
528                amounts,
529                input_fee_ppk
530            FROM
531                keyset
532            "#,
533        )?
534        .fetch_all(&*conn)
535        .await?
536        .into_iter()
537        .map(sql_row_to_keyset_info)
538        .collect::<Result<Vec<_>, _>>()?)
539    }
540}
541
542#[async_trait]
543impl<RM> MintQuotesTransaction<'_> for SQLTransaction<RM>
544where
545    RM: DatabasePool + 'static,
546{
547    type Err = Error;
548
549    async fn add_melt_request_and_blinded_messages(
550        &mut self,
551        quote_id: &QuoteId,
552        inputs_amount: Amount,
553        inputs_fee: Amount,
554        blinded_messages: &[BlindedMessage],
555    ) -> Result<(), Self::Err> {
556        query(
557            r#"
558            INSERT INTO melt_request
559            (quote_id, inputs_amount, inputs_fee)
560            VALUES
561            (:quote_id, :inputs_amount, :inputs_fee)
562            "#,
563        )?
564        .bind("quote_id", quote_id.to_string())
565        .bind("inputs_amount", inputs_amount.to_i64())
566        .bind("inputs_fee", inputs_fee.to_i64())
567        .execute(&self.inner)
568        .await?;
569
570        for message in blinded_messages {
571            query(
572                r#"
573                INSERT INTO blinded_messages
574                (quote_id, blinded_message, keyset_id, amount)
575                VALUES
576                (:quote_id, :blinded_message, :keyset_id, :amount)
577                "#,
578            )?
579            .bind("quote_id", quote_id.to_string())
580            .bind(
581                "blinded_message",
582                message.blinded_secret.to_bytes().to_vec(),
583            )
584            .bind("keyset_id", message.keyset_id.to_string())
585            .bind("amount", message.amount.to_i64())
586            .execute(&self.inner)
587            .await?;
588        }
589
590        Ok(())
591    }
592
593    async fn get_melt_request_and_blinded_messages(
594        &mut self,
595        quote_id: &QuoteId,
596    ) -> Result<Option<database::mint::MeltRequestInfo>, Self::Err> {
597        let melt_request_row = query(
598            r#"
599            SELECT inputs_amount, inputs_fee
600            FROM melt_request
601            WHERE quote_id = :quote_id
602            FOR UPDATE
603            "#,
604        )?
605        .bind("quote_id", quote_id.to_string())
606        .fetch_one(&self.inner)
607        .await?;
608
609        if let Some(row) = melt_request_row {
610            let inputs_amount: u64 = column_as_number!(row[0].clone());
611            let inputs_fee: u64 = column_as_number!(row[1].clone());
612
613            let blinded_messages_rows = query(
614                r#"
615                SELECT blinded_message, keyset_id, amount
616                FROM blinded_messages
617                WHERE quote_id = :quote_id
618                "#,
619            )?
620            .bind("quote_id", quote_id.to_string())
621            .fetch_all(&self.inner)
622            .await?;
623
624            let blinded_messages: Result<Vec<BlindedMessage>, Error> = blinded_messages_rows
625                .into_iter()
626                .map(|row| -> Result<BlindedMessage, Error> {
627                    let blinded_message_key =
628                        column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice);
629                    let keyset_id = column_as_string!(&row[1], Id::from_str, Id::from_bytes);
630                    let amount: u64 = column_as_number!(row[2].clone());
631
632                    Ok(BlindedMessage {
633                        blinded_secret: blinded_message_key,
634                        keyset_id,
635                        amount: Amount::from(amount),
636                        witness: None, // Not storing witness in database currently
637                    })
638                })
639                .collect();
640            let blinded_messages = blinded_messages?;
641
642            Ok(Some(database::mint::MeltRequestInfo {
643                inputs_amount: Amount::from(inputs_amount),
644                inputs_fee: Amount::from(inputs_fee),
645                change_outputs: blinded_messages,
646            }))
647        } else {
648            Ok(None)
649        }
650    }
651
652    async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
653        query(
654            r#"
655            DELETE FROM melt_request
656            WHERE quote_id = :quote_id
657            "#,
658        )?
659        .bind("quote_id", quote_id.to_string())
660        .execute(&self.inner)
661        .await?;
662
663        Ok(())
664    }
665
666    #[instrument(skip(self))]
667    async fn increment_mint_quote_amount_paid(
668        &mut self,
669        quote_id: &QuoteId,
670        amount_paid: Amount,
671        payment_id: String,
672    ) -> Result<Amount, Self::Err> {
673        if amount_paid == Amount::ZERO {
674            tracing::warn!("Amount payments of zero amount should not be recorded.");
675            return Err(Error::Duplicate);
676        }
677
678        // Check if payment_id already exists in mint_quote_payments
679        let exists = query(
680            r#"
681            SELECT payment_id
682            FROM mint_quote_payments
683            WHERE payment_id = :payment_id
684            FOR UPDATE
685            "#,
686        )?
687        .bind("payment_id", payment_id.clone())
688        .fetch_one(&self.inner)
689        .await?;
690
691        if exists.is_some() {
692            tracing::error!("Payment ID already exists: {}", payment_id);
693            return Err(database::Error::Duplicate);
694        }
695
696        // Get current amount_paid from quote
697        let current_amount = query(
698            r#"
699            SELECT amount_paid
700            FROM mint_quote
701            WHERE id = :quote_id
702            FOR UPDATE
703            "#,
704        )?
705        .bind("quote_id", quote_id.to_string())
706        .fetch_one(&self.inner)
707        .await
708        .inspect_err(|err| {
709            tracing::error!("SQLite could not get mint quote amount_paid: {}", err);
710        })?;
711
712        let current_amount_paid = if let Some(current_amount) = current_amount {
713            let amount: u64 = column_as_number!(current_amount[0].clone());
714            Amount::from(amount)
715        } else {
716            Amount::ZERO
717        };
718
719        // Calculate new amount_paid with overflow check
720        let new_amount_paid = current_amount_paid
721            .checked_add(amount_paid)
722            .ok_or_else(|| database::Error::AmountOverflow)?;
723
724        tracing::debug!(
725            "Mint quote {} amount paid was {} is now {}.",
726            quote_id,
727            current_amount_paid,
728            new_amount_paid
729        );
730
731        // Update the amount_paid
732        query(
733            r#"
734            UPDATE mint_quote
735            SET amount_paid = :amount_paid
736            WHERE id = :quote_id
737            "#,
738        )?
739        .bind("amount_paid", new_amount_paid.to_i64())
740        .bind("quote_id", quote_id.to_string())
741        .execute(&self.inner)
742        .await
743        .inspect_err(|err| {
744            tracing::error!("SQLite could not update mint quote amount_paid: {}", err);
745        })?;
746
747        // Add payment_id to mint_quote_payments table
748        query(
749            r#"
750            INSERT INTO mint_quote_payments
751            (quote_id, payment_id, amount, timestamp)
752            VALUES (:quote_id, :payment_id, :amount, :timestamp)
753            "#,
754        )?
755        .bind("quote_id", quote_id.to_string())
756        .bind("payment_id", payment_id)
757        .bind("amount", amount_paid.to_i64())
758        .bind("timestamp", unix_time() as i64)
759        .execute(&self.inner)
760        .await
761        .map_err(|err| {
762            tracing::error!("SQLite could not insert payment ID: {}", err);
763            err
764        })?;
765
766        Ok(new_amount_paid)
767    }
768
769    #[instrument(skip_all)]
770    async fn increment_mint_quote_amount_issued(
771        &mut self,
772        quote_id: &QuoteId,
773        amount_issued: Amount,
774    ) -> Result<Amount, Self::Err> {
775        // Get current amount_issued from quote
776        let current_amounts = query(
777            r#"
778            SELECT amount_issued, amount_paid
779            FROM mint_quote
780            WHERE id = :quote_id
781            FOR UPDATE
782            "#,
783        )?
784        .bind("quote_id", quote_id.to_string())
785        .fetch_one(&self.inner)
786        .await
787        .inspect_err(|err| {
788            tracing::error!("SQLite could not get mint quote amount_issued: {}", err);
789        })?
790        .ok_or(Error::QuoteNotFound)?;
791
792        let new_amount_issued = {
793            // Make sure the db protects issuing not paid quotes
794            unpack_into!(
795                let (current_amount_issued, current_amount_paid) = current_amounts
796            );
797
798            let current_amount_issued: u64 = column_as_number!(current_amount_issued);
799            let current_amount_paid: u64 = column_as_number!(current_amount_paid);
800
801            let current_amount_issued = Amount::from(current_amount_issued);
802            let current_amount_paid = Amount::from(current_amount_paid);
803
804            // Calculate new amount_issued with overflow check
805            let new_amount_issued = current_amount_issued
806                .checked_add(amount_issued)
807                .ok_or_else(|| database::Error::AmountOverflow)?;
808
809            current_amount_paid
810                .checked_sub(new_amount_issued)
811                .ok_or(Error::Internal("Over-issued not allowed".to_owned()))?;
812
813            new_amount_issued
814        };
815
816        // Update the amount_issued
817        query(
818            r#"
819            UPDATE mint_quote
820            SET amount_issued = :amount_issued
821            WHERE id = :quote_id
822            "#,
823        )?
824        .bind("amount_issued", new_amount_issued.to_i64())
825        .bind("quote_id", quote_id.to_string())
826        .execute(&self.inner)
827        .await
828        .inspect_err(|err| {
829            tracing::error!("SQLite could not update mint quote amount_issued: {}", err);
830        })?;
831
832        let current_time = unix_time();
833
834        query(
835            r#"
836INSERT INTO mint_quote_issued
837(quote_id, amount, timestamp)
838VALUES (:quote_id, :amount, :timestamp);
839            "#,
840        )?
841        .bind("quote_id", quote_id.to_string())
842        .bind("amount", amount_issued.to_i64())
843        .bind("timestamp", current_time as i64)
844        .execute(&self.inner)
845        .await?;
846
847        Ok(new_amount_issued)
848    }
849
850    #[instrument(skip_all)]
851    async fn add_mint_quote(&mut self, quote: MintQuote) -> Result<(), Self::Err> {
852        query(
853            r#"
854                INSERT INTO mint_quote (
855                id, amount, unit, request, expiry, request_lookup_id, pubkey, created_time, payment_method, request_lookup_id_kind
856                )
857                VALUES (
858                :id, :amount, :unit, :request, :expiry, :request_lookup_id, :pubkey, :created_time, :payment_method, :request_lookup_id_kind
859                )
860            "#,
861        )?
862        .bind("id", quote.id.to_string())
863        .bind("amount", quote.amount.map(|a| a.to_i64()))
864        .bind("unit", quote.unit.to_string())
865        .bind("request", quote.request)
866        .bind("expiry", quote.expiry as i64)
867        .bind(
868            "request_lookup_id",
869            quote.request_lookup_id.to_string(),
870        )
871        .bind("pubkey", quote.pubkey.map(|p| p.to_string()))
872        .bind("created_time", quote.created_time as i64)
873        .bind("payment_method", quote.payment_method.to_string())
874        .bind("request_lookup_id_kind", quote.request_lookup_id.kind())
875        .execute(&self.inner)
876        .await?;
877
878        Ok(())
879    }
880
881    async fn remove_mint_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
882        query(r#"DELETE FROM mint_quote WHERE id=:id"#)?
883            .bind("id", quote_id.to_string())
884            .execute(&self.inner)
885            .await?;
886        Ok(())
887    }
888
889    async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
890        // Now insert the new quote
891        query(
892            r#"
893            INSERT INTO melt_quote
894            (
895                id, unit, amount, request, fee_reserve, state,
896                expiry, payment_preimage, request_lookup_id,
897                created_time, paid_time, options, request_lookup_id_kind, payment_method
898            )
899            VALUES
900            (
901                :id, :unit, :amount, :request, :fee_reserve, :state,
902                :expiry, :payment_preimage, :request_lookup_id,
903                :created_time, :paid_time, :options, :request_lookup_id_kind, :payment_method
904            )
905        "#,
906        )?
907        .bind("id", quote.id.to_string())
908        .bind("unit", quote.unit.to_string())
909        .bind("amount", quote.amount.to_i64())
910        .bind("request", serde_json::to_string(&quote.request)?)
911        .bind("fee_reserve", quote.fee_reserve.to_i64())
912        .bind("state", quote.state.to_string())
913        .bind("expiry", quote.expiry as i64)
914        .bind("payment_preimage", quote.payment_preimage)
915        .bind(
916            "request_lookup_id",
917            quote.request_lookup_id.as_ref().map(|id| id.to_string()),
918        )
919        .bind("created_time", quote.created_time as i64)
920        .bind("paid_time", quote.paid_time.map(|t| t as i64))
921        .bind(
922            "options",
923            quote.options.map(|o| serde_json::to_string(&o).ok()),
924        )
925        .bind(
926            "request_lookup_id_kind",
927            quote.request_lookup_id.map(|id| id.kind()),
928        )
929        .bind("payment_method", quote.payment_method.to_string())
930        .execute(&self.inner)
931        .await?;
932
933        Ok(())
934    }
935
936    async fn update_melt_quote_request_lookup_id(
937        &mut self,
938        quote_id: &QuoteId,
939        new_request_lookup_id: &PaymentIdentifier,
940    ) -> Result<(), Self::Err> {
941        query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
942            .bind("new_req_id", new_request_lookup_id.to_string())
943            .bind("new_kind",new_request_lookup_id.kind() )
944            .bind("id", quote_id.to_string())
945            .execute(&self.inner)
946            .await?;
947        Ok(())
948    }
949
950    async fn update_melt_quote_state(
951        &mut self,
952        quote_id: &QuoteId,
953        state: MeltQuoteState,
954        payment_proof: Option<String>,
955    ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err> {
956        let mut quote = query(
957            r#"
958            SELECT
959                id,
960                unit,
961                amount,
962                request,
963                fee_reserve,
964                expiry,
965                state,
966                payment_preimage,
967                request_lookup_id,
968                created_time,
969                paid_time,
970                payment_method,
971                options,
972                request_lookup_id_kind
973            FROM
974                melt_quote
975            WHERE
976                id=:id
977                AND state != :state
978            "#,
979        )?
980        .bind("id", quote_id.to_string())
981        .bind("state", state.to_string())
982        .fetch_one(&self.inner)
983        .await?
984        .map(sql_row_to_melt_quote)
985        .transpose()?
986        .ok_or(Error::QuoteNotFound)?;
987
988        let rec = if state == MeltQuoteState::Paid {
989            let current_time = unix_time();
990            query(r#"UPDATE melt_quote SET state = :state, paid_time = :paid_time, payment_preimage = :payment_preimage WHERE id = :id"#)?
991                .bind("state", state.to_string())
992                .bind("paid_time", current_time as i64)
993                .bind("payment_preimage", payment_proof)
994                .bind("id", quote_id.to_string())
995                .execute(&self.inner)
996                .await
997        } else {
998            query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
999                .bind("state", state.to_string())
1000                .bind("id", quote_id.to_string())
1001                .execute(&self.inner)
1002                .await
1003        };
1004
1005        match rec {
1006            Ok(_) => {}
1007            Err(err) => {
1008                tracing::error!("SQLite Could not update melt quote");
1009                return Err(err);
1010            }
1011        };
1012
1013        let old_state = quote.state;
1014        quote.state = state;
1015
1016        if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
1017            self.delete_melt_request(quote_id).await?;
1018        }
1019
1020        Ok((old_state, quote))
1021    }
1022
1023    async fn remove_melt_quote(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
1024        query(
1025            r#"
1026            DELETE FROM melt_quote
1027            WHERE id=:id
1028            "#,
1029        )?
1030        .bind("id", quote_id.to_string())
1031        .execute(&self.inner)
1032        .await?;
1033
1034        Ok(())
1035    }
1036
1037    async fn get_mint_quote(&mut self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
1038        let payments = get_mint_quote_payments(&self.inner, quote_id).await?;
1039        let issuance = get_mint_quote_issuance(&self.inner, quote_id).await?;
1040
1041        Ok(query(
1042            r#"
1043            SELECT
1044                id,
1045                amount,
1046                unit,
1047                request,
1048                expiry,
1049                request_lookup_id,
1050                pubkey,
1051                created_time,
1052                amount_paid,
1053                amount_issued,
1054                payment_method,
1055                request_lookup_id_kind
1056            FROM
1057                mint_quote
1058            WHERE id = :id
1059            FOR UPDATE
1060            "#,
1061        )?
1062        .bind("id", quote_id.to_string())
1063        .fetch_one(&self.inner)
1064        .await?
1065        .map(|row| sql_row_to_mint_quote(row, payments, issuance))
1066        .transpose()?)
1067    }
1068
1069    async fn get_melt_quote(
1070        &mut self,
1071        quote_id: &QuoteId,
1072    ) -> Result<Option<mint::MeltQuote>, Self::Err> {
1073        Ok(query(
1074            r#"
1075            SELECT
1076                id,
1077                unit,
1078                amount,
1079                request,
1080                fee_reserve,
1081                expiry,
1082                state,
1083                payment_preimage,
1084                request_lookup_id,
1085                created_time,
1086                paid_time,
1087                payment_method,
1088                options,
1089                request_lookup_id
1090            FROM
1091                melt_quote
1092            WHERE
1093                id=:id
1094            "#,
1095        )?
1096        .bind("id", quote_id.to_string())
1097        .fetch_one(&self.inner)
1098        .await?
1099        .map(sql_row_to_melt_quote)
1100        .transpose()?)
1101    }
1102
1103    async fn get_mint_quote_by_request(
1104        &mut self,
1105        request: &str,
1106    ) -> Result<Option<MintQuote>, Self::Err> {
1107        let mut mint_quote = query(
1108            r#"
1109            SELECT
1110                id,
1111                amount,
1112                unit,
1113                request,
1114                expiry,
1115                request_lookup_id,
1116                pubkey,
1117                created_time,
1118                amount_paid,
1119                amount_issued,
1120                payment_method,
1121                request_lookup_id_kind
1122            FROM
1123                mint_quote
1124            WHERE request = :request
1125            FOR UPDATE
1126            "#,
1127        )?
1128        .bind("request", request.to_string())
1129        .fetch_one(&self.inner)
1130        .await?
1131        .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1132        .transpose()?;
1133
1134        if let Some(quote) = mint_quote.as_mut() {
1135            let payments = get_mint_quote_payments(&self.inner, &quote.id).await?;
1136            let issuance = get_mint_quote_issuance(&self.inner, &quote.id).await?;
1137            quote.issuance = issuance;
1138            quote.payments = payments;
1139        }
1140
1141        Ok(mint_quote)
1142    }
1143
1144    async fn get_mint_quote_by_request_lookup_id(
1145        &mut self,
1146        request_lookup_id: &PaymentIdentifier,
1147    ) -> Result<Option<MintQuote>, Self::Err> {
1148        let mut mint_quote = query(
1149            r#"
1150            SELECT
1151                id,
1152                amount,
1153                unit,
1154                request,
1155                expiry,
1156                request_lookup_id,
1157                pubkey,
1158                created_time,
1159                amount_paid,
1160                amount_issued,
1161                payment_method,
1162                request_lookup_id_kind
1163            FROM
1164                mint_quote
1165            WHERE request_lookup_id = :request_lookup_id
1166            AND request_lookup_id_kind = :request_lookup_id_kind
1167            FOR UPDATE
1168            "#,
1169        )?
1170        .bind("request_lookup_id", request_lookup_id.to_string())
1171        .bind("request_lookup_id_kind", request_lookup_id.kind())
1172        .fetch_one(&self.inner)
1173        .await?
1174        .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1175        .transpose()?;
1176
1177        if let Some(quote) = mint_quote.as_mut() {
1178            let payments = get_mint_quote_payments(&self.inner, &quote.id).await?;
1179            let issuance = get_mint_quote_issuance(&self.inner, &quote.id).await?;
1180            quote.issuance = issuance;
1181            quote.payments = payments;
1182        }
1183
1184        Ok(mint_quote)
1185    }
1186}
1187
1188#[async_trait]
1189impl<RM> MintQuotesDatabase for SQLMintDatabase<RM>
1190where
1191    RM: DatabasePool + 'static,
1192{
1193    type Err = Error;
1194
1195    async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
1196        #[cfg(feature = "prometheus")]
1197        METRICS.inc_in_flight_requests("get_mint_quote");
1198
1199        #[cfg(feature = "prometheus")]
1200        let start_time = std::time::Instant::now();
1201        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1202
1203        let result = async {
1204            let payments = get_mint_quote_payments(&*conn, quote_id).await?;
1205            let issuance = get_mint_quote_issuance(&*conn, quote_id).await?;
1206
1207            query(
1208                r#"
1209                SELECT
1210                    id,
1211                    amount,
1212                    unit,
1213                    request,
1214                    expiry,
1215                    request_lookup_id,
1216                    pubkey,
1217                    created_time,
1218                    amount_paid,
1219                    amount_issued,
1220                    payment_method,
1221                    request_lookup_id_kind
1222                FROM
1223                    mint_quote
1224                WHERE id = :id"#,
1225            )?
1226            .bind("id", quote_id.to_string())
1227            .fetch_one(&*conn)
1228            .await?
1229            .map(|row| sql_row_to_mint_quote(row, payments, issuance))
1230            .transpose()
1231        }
1232        .await;
1233
1234        #[cfg(feature = "prometheus")]
1235        {
1236            let success = result.is_ok();
1237
1238            METRICS.record_mint_operation("get_mint_quote", success);
1239            METRICS.record_mint_operation_histogram(
1240                "get_mint_quote",
1241                success,
1242                start_time.elapsed().as_secs_f64(),
1243            );
1244            METRICS.dec_in_flight_requests("get_mint_quote");
1245        }
1246
1247        result
1248    }
1249
1250    async fn get_mint_quote_by_request(
1251        &self,
1252        request: &str,
1253    ) -> Result<Option<MintQuote>, Self::Err> {
1254        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1255        let mut mint_quote = query(
1256            r#"
1257            SELECT
1258                id,
1259                amount,
1260                unit,
1261                request,
1262                expiry,
1263                request_lookup_id,
1264                pubkey,
1265                created_time,
1266                amount_paid,
1267                amount_issued,
1268                payment_method,
1269                request_lookup_id_kind
1270            FROM
1271                mint_quote
1272            WHERE request = :request"#,
1273        )?
1274        .bind("request", request.to_owned())
1275        .fetch_one(&*conn)
1276        .await?
1277        .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1278        .transpose()?;
1279
1280        if let Some(quote) = mint_quote.as_mut() {
1281            let payments = get_mint_quote_payments(&*conn, &quote.id).await?;
1282            let issuance = get_mint_quote_issuance(&*conn, &quote.id).await?;
1283            quote.issuance = issuance;
1284            quote.payments = payments;
1285        }
1286
1287        Ok(mint_quote)
1288    }
1289
1290    async fn get_mint_quote_by_request_lookup_id(
1291        &self,
1292        request_lookup_id: &PaymentIdentifier,
1293    ) -> Result<Option<MintQuote>, Self::Err> {
1294        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1295        let mut mint_quote = query(
1296            r#"
1297            SELECT
1298                id,
1299                amount,
1300                unit,
1301                request,
1302                expiry,
1303                request_lookup_id,
1304                pubkey,
1305                created_time,
1306                amount_paid,
1307                amount_issued,
1308                payment_method,
1309                request_lookup_id_kind
1310            FROM
1311                mint_quote
1312            WHERE request_lookup_id = :request_lookup_id
1313            AND request_lookup_id_kind = :request_lookup_id_kind
1314            "#,
1315        )?
1316        .bind("request_lookup_id", request_lookup_id.to_string())
1317        .bind("request_lookup_id_kind", request_lookup_id.kind())
1318        .fetch_one(&*conn)
1319        .await?
1320        .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1321        .transpose()?;
1322
1323        // TODO: these should use an sql join so they can be done in one query
1324        if let Some(quote) = mint_quote.as_mut() {
1325            let payments = get_mint_quote_payments(&*conn, &quote.id).await?;
1326            let issuance = get_mint_quote_issuance(&*conn, &quote.id).await?;
1327            quote.issuance = issuance;
1328            quote.payments = payments;
1329        }
1330
1331        Ok(mint_quote)
1332    }
1333
1334    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
1335        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1336        let mut mint_quotes = query(
1337            r#"
1338            SELECT
1339                id,
1340                amount,
1341                unit,
1342                request,
1343                expiry,
1344                request_lookup_id,
1345                pubkey,
1346                created_time,
1347                amount_paid,
1348                amount_issued,
1349                payment_method,
1350                request_lookup_id_kind
1351            FROM
1352                mint_quote
1353            "#,
1354        )?
1355        .fetch_all(&*conn)
1356        .await?
1357        .into_iter()
1358        .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1359        .collect::<Result<Vec<_>, _>>()?;
1360
1361        for quote in mint_quotes.as_mut_slice() {
1362            let payments = get_mint_quote_payments(&*conn, &quote.id).await?;
1363            let issuance = get_mint_quote_issuance(&*conn, &quote.id).await?;
1364            quote.issuance = issuance;
1365            quote.payments = payments;
1366        }
1367
1368        Ok(mint_quotes)
1369    }
1370
1371    async fn get_melt_quote(
1372        &self,
1373        quote_id: &QuoteId,
1374    ) -> Result<Option<mint::MeltQuote>, Self::Err> {
1375        #[cfg(feature = "prometheus")]
1376        METRICS.inc_in_flight_requests("get_melt_quote");
1377
1378        #[cfg(feature = "prometheus")]
1379        let start_time = std::time::Instant::now();
1380        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1381
1382        let result = async {
1383            query(
1384                r#"
1385                SELECT
1386                    id,
1387                    unit,
1388                    amount,
1389                    request,
1390                    fee_reserve,
1391                    expiry,
1392                    state,
1393                    payment_preimage,
1394                    request_lookup_id,
1395                    created_time,
1396                    paid_time,
1397                    payment_method,
1398                    options,
1399                    request_lookup_id_kind
1400                FROM
1401                    melt_quote
1402                WHERE
1403                    id=:id
1404                "#,
1405            )?
1406            .bind("id", quote_id.to_string())
1407            .fetch_one(&*conn)
1408            .await?
1409            .map(sql_row_to_melt_quote)
1410            .transpose()
1411        }
1412        .await;
1413
1414        #[cfg(feature = "prometheus")]
1415        {
1416            let success = result.is_ok();
1417
1418            METRICS.record_mint_operation("get_melt_quote", success);
1419            METRICS.record_mint_operation_histogram(
1420                "get_melt_quote",
1421                success,
1422                start_time.elapsed().as_secs_f64(),
1423            );
1424            METRICS.dec_in_flight_requests("get_melt_quote");
1425        }
1426
1427        result
1428    }
1429
1430    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
1431        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1432        Ok(query(
1433            r#"
1434            SELECT
1435                id,
1436                unit,
1437                amount,
1438                request,
1439                fee_reserve,
1440                expiry,
1441                state,
1442                payment_preimage,
1443                request_lookup_id,
1444                created_time,
1445                paid_time,
1446                payment_method,
1447                options,
1448                request_lookup_id_kind
1449            FROM
1450                melt_quote
1451            "#,
1452        )?
1453        .fetch_all(&*conn)
1454        .await?
1455        .into_iter()
1456        .map(sql_row_to_melt_quote)
1457        .collect::<Result<Vec<_>, _>>()?)
1458    }
1459}
1460
1461#[async_trait]
1462impl<RM> MintProofsDatabase for SQLMintDatabase<RM>
1463where
1464    RM: DatabasePool + 'static,
1465{
1466    type Err = Error;
1467
1468    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
1469        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1470        let mut proofs = query(
1471            r#"
1472            SELECT
1473                amount,
1474                keyset_id,
1475                secret,
1476                c,
1477                witness,
1478                y
1479            FROM
1480                proof
1481            WHERE
1482                y IN (:ys)
1483            "#,
1484        )?
1485        .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
1486        .fetch_all(&*conn)
1487        .await?
1488        .into_iter()
1489        .map(|mut row| {
1490            Ok((
1491                column_as_string!(
1492                    row.pop().ok_or(Error::InvalidDbResponse)?,
1493                    PublicKey::from_hex,
1494                    PublicKey::from_slice
1495                ),
1496                sql_row_to_proof(row)?,
1497            ))
1498        })
1499        .collect::<Result<HashMap<_, _>, Error>>()?;
1500
1501        Ok(ys.iter().map(|y| proofs.remove(y)).collect())
1502    }
1503
1504    async fn get_proof_ys_by_quote_id(
1505        &self,
1506        quote_id: &QuoteId,
1507    ) -> Result<Vec<PublicKey>, Self::Err> {
1508        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1509        Ok(query(
1510            r#"
1511            SELECT
1512                amount,
1513                keyset_id,
1514                secret,
1515                c,
1516                witness
1517            FROM
1518                proof
1519            WHERE
1520                quote_id = :quote_id
1521            "#,
1522        )?
1523        .bind("quote_id", quote_id.to_string())
1524        .fetch_all(&*conn)
1525        .await?
1526        .into_iter()
1527        .map(sql_row_to_proof)
1528        .collect::<Result<Vec<Proof>, _>>()?
1529        .ys()?)
1530    }
1531
1532    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
1533        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1534        let mut current_states = get_current_states(&*conn, ys).await?;
1535
1536        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1537    }
1538
1539    async fn get_proofs_by_keyset_id(
1540        &self,
1541        keyset_id: &Id,
1542    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err> {
1543        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1544        Ok(query(
1545            r#"
1546            SELECT
1547               keyset_id,
1548               amount,
1549               secret,
1550               c,
1551               witness,
1552               state
1553            FROM
1554                proof
1555            WHERE
1556                keyset_id=:keyset_id
1557            "#,
1558        )?
1559        .bind("keyset_id", keyset_id.to_string())
1560        .fetch_all(&*conn)
1561        .await?
1562        .into_iter()
1563        .map(sql_row_to_proof_with_state)
1564        .collect::<Result<Vec<_>, _>>()?
1565        .into_iter()
1566        .unzip())
1567    }
1568}
1569
1570#[async_trait]
1571impl<RM> MintSignatureTransaction<'_> for SQLTransaction<RM>
1572where
1573    RM: DatabasePool + 'static,
1574{
1575    type Err = Error;
1576
1577    async fn add_blind_signatures(
1578        &mut self,
1579        blinded_messages: &[PublicKey],
1580        blind_signatures: &[BlindSignature],
1581        quote_id: Option<QuoteId>,
1582    ) -> Result<(), Self::Err> {
1583        let current_time = unix_time();
1584
1585        for (message, signature) in blinded_messages.iter().zip(blind_signatures) {
1586            query(
1587                r#"
1588                    INSERT INTO blind_signature
1589                    (blinded_message, amount, keyset_id, c, quote_id, dleq_e, dleq_s, created_time)
1590                    VALUES
1591                    (:blinded_message, :amount, :keyset_id, :c, :quote_id, :dleq_e, :dleq_s, :created_time)
1592                "#,
1593            )?
1594            .bind("blinded_message", message.to_bytes().to_vec())
1595            .bind("amount", u64::from(signature.amount) as i64)
1596            .bind("keyset_id", signature.keyset_id.to_string())
1597            .bind("c", signature.c.to_bytes().to_vec())
1598            .bind("quote_id", quote_id.as_ref().map(|q| match q {
1599                QuoteId::BASE64(s) => s.to_string(),
1600                QuoteId::UUID(u) => u.hyphenated().to_string(),
1601            }))
1602            .bind(
1603                "dleq_e",
1604                signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()),
1605            )
1606            .bind(
1607                "dleq_s",
1608                signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()),
1609            )
1610            .bind("created_time", current_time as i64)
1611            .execute(&self.inner)
1612            .await?;
1613        }
1614
1615        Ok(())
1616    }
1617
1618    async fn get_blind_signatures(
1619        &mut self,
1620        blinded_messages: &[PublicKey],
1621    ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
1622        let mut blinded_signatures = query(
1623            r#"SELECT
1624                keyset_id,
1625                amount,
1626                c,
1627                dleq_e,
1628                dleq_s,
1629                blinded_message
1630            FROM
1631                blind_signature
1632            WHERE blinded_message IN (:y)
1633            "#,
1634        )?
1635        .bind_vec(
1636            "y",
1637            blinded_messages
1638                .iter()
1639                .map(|y| y.to_bytes().to_vec())
1640                .collect(),
1641        )
1642        .fetch_all(&self.inner)
1643        .await?
1644        .into_iter()
1645        .map(|mut row| {
1646            Ok((
1647                column_as_string!(
1648                    &row.pop().ok_or(Error::InvalidDbResponse)?,
1649                    PublicKey::from_hex,
1650                    PublicKey::from_slice
1651                ),
1652                sql_row_to_blind_signature(row)?,
1653            ))
1654        })
1655        .collect::<Result<HashMap<_, _>, Error>>()?;
1656        Ok(blinded_messages
1657            .iter()
1658            .map(|y| blinded_signatures.remove(y))
1659            .collect())
1660    }
1661}
1662
1663#[async_trait]
1664impl<RM> MintSignaturesDatabase for SQLMintDatabase<RM>
1665where
1666    RM: DatabasePool + 'static,
1667{
1668    type Err = Error;
1669
1670    async fn get_blind_signatures(
1671        &self,
1672        blinded_messages: &[PublicKey],
1673    ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
1674        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1675        let mut blinded_signatures = query(
1676            r#"SELECT
1677                keyset_id,
1678                amount,
1679                c,
1680                dleq_e,
1681                dleq_s,
1682                blinded_message
1683            FROM
1684                blind_signature
1685            WHERE blinded_message IN (:blinded_message)
1686            "#,
1687        )?
1688        .bind_vec(
1689            "blinded_message",
1690            blinded_messages
1691                .iter()
1692                .map(|b_| b_.to_bytes().to_vec())
1693                .collect(),
1694        )
1695        .fetch_all(&*conn)
1696        .await?
1697        .into_iter()
1698        .map(|mut row| {
1699            Ok((
1700                column_as_string!(
1701                    &row.pop().ok_or(Error::InvalidDbResponse)?,
1702                    PublicKey::from_hex,
1703                    PublicKey::from_slice
1704                ),
1705                sql_row_to_blind_signature(row)?,
1706            ))
1707        })
1708        .collect::<Result<HashMap<_, _>, Error>>()?;
1709        Ok(blinded_messages
1710            .iter()
1711            .map(|y| blinded_signatures.remove(y))
1712            .collect())
1713    }
1714
1715    async fn get_blind_signatures_for_keyset(
1716        &self,
1717        keyset_id: &Id,
1718    ) -> Result<Vec<BlindSignature>, Self::Err> {
1719        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1720        Ok(query(
1721            r#"
1722            SELECT
1723                keyset_id,
1724                amount,
1725                c,
1726                dleq_e,
1727                dleq_s
1728            FROM
1729                blind_signature
1730            WHERE
1731                keyset_id=:keyset_id
1732            "#,
1733        )?
1734        .bind("keyset_id", keyset_id.to_string())
1735        .fetch_all(&*conn)
1736        .await?
1737        .into_iter()
1738        .map(sql_row_to_blind_signature)
1739        .collect::<Result<Vec<BlindSignature>, _>>()?)
1740    }
1741
1742    /// Get [`BlindSignature`]s for quote
1743    async fn get_blind_signatures_for_quote(
1744        &self,
1745        quote_id: &QuoteId,
1746    ) -> Result<Vec<BlindSignature>, Self::Err> {
1747        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1748        Ok(query(
1749            r#"
1750            SELECT
1751                keyset_id,
1752                amount,
1753                c,
1754                dleq_e,
1755                dleq_s
1756            FROM
1757                blind_signature
1758            WHERE
1759                quote_id=:quote_id
1760            "#,
1761        )?
1762        .bind("quote_id", quote_id.to_string())
1763        .fetch_all(&*conn)
1764        .await?
1765        .into_iter()
1766        .map(sql_row_to_blind_signature)
1767        .collect::<Result<Vec<BlindSignature>, _>>()?)
1768    }
1769}
1770
1771#[async_trait]
1772impl<RM> database::MintKVStoreTransaction<'_, Error> for SQLTransaction<RM>
1773where
1774    RM: DatabasePool + 'static,
1775{
1776    async fn kv_read(
1777        &mut self,
1778        primary_namespace: &str,
1779        secondary_namespace: &str,
1780        key: &str,
1781    ) -> Result<Option<Vec<u8>>, Error> {
1782        // Validate parameters according to KV store requirements
1783        validate_kvstore_params(primary_namespace, secondary_namespace, key)?;
1784        Ok(query(
1785            r#"
1786            SELECT value
1787            FROM kv_store
1788            WHERE primary_namespace = :primary_namespace
1789            AND secondary_namespace = :secondary_namespace
1790            AND key = :key
1791            "#,
1792        )?
1793        .bind("primary_namespace", primary_namespace.to_owned())
1794        .bind("secondary_namespace", secondary_namespace.to_owned())
1795        .bind("key", key.to_owned())
1796        .pluck(&self.inner)
1797        .await?
1798        .and_then(|col| match col {
1799            Column::Blob(data) => Some(data),
1800            _ => None,
1801        }))
1802    }
1803
1804    async fn kv_write(
1805        &mut self,
1806        primary_namespace: &str,
1807        secondary_namespace: &str,
1808        key: &str,
1809        value: &[u8],
1810    ) -> Result<(), Error> {
1811        // Validate parameters according to KV store requirements
1812        validate_kvstore_params(primary_namespace, secondary_namespace, key)?;
1813
1814        let current_time = unix_time();
1815
1816        query(
1817            r#"
1818            INSERT INTO kv_store
1819            (primary_namespace, secondary_namespace, key, value, created_time, updated_time)
1820            VALUES (:primary_namespace, :secondary_namespace, :key, :value, :created_time, :updated_time)
1821            ON CONFLICT(primary_namespace, secondary_namespace, key)
1822            DO UPDATE SET
1823                value = excluded.value,
1824                updated_time = excluded.updated_time
1825            "#,
1826        )?
1827        .bind("primary_namespace", primary_namespace.to_owned())
1828        .bind("secondary_namespace", secondary_namespace.to_owned())
1829        .bind("key", key.to_owned())
1830        .bind("value", value.to_vec())
1831        .bind("created_time", current_time as i64)
1832        .bind("updated_time", current_time as i64)
1833        .execute(&self.inner)
1834        .await?;
1835
1836        Ok(())
1837    }
1838
1839    async fn kv_remove(
1840        &mut self,
1841        primary_namespace: &str,
1842        secondary_namespace: &str,
1843        key: &str,
1844    ) -> Result<(), Error> {
1845        // Validate parameters according to KV store requirements
1846        validate_kvstore_params(primary_namespace, secondary_namespace, key)?;
1847        query(
1848            r#"
1849            DELETE FROM kv_store
1850            WHERE primary_namespace = :primary_namespace
1851            AND secondary_namespace = :secondary_namespace
1852            AND key = :key
1853            "#,
1854        )?
1855        .bind("primary_namespace", primary_namespace.to_owned())
1856        .bind("secondary_namespace", secondary_namespace.to_owned())
1857        .bind("key", key.to_owned())
1858        .execute(&self.inner)
1859        .await?;
1860
1861        Ok(())
1862    }
1863
1864    async fn kv_list(
1865        &mut self,
1866        primary_namespace: &str,
1867        secondary_namespace: &str,
1868    ) -> Result<Vec<String>, Error> {
1869        // Validate namespace parameters according to KV store requirements
1870        cdk_common::database::mint::validate_kvstore_string(primary_namespace)?;
1871        cdk_common::database::mint::validate_kvstore_string(secondary_namespace)?;
1872
1873        // Check empty namespace rules
1874        if primary_namespace.is_empty() && !secondary_namespace.is_empty() {
1875            return Err(Error::KVStoreInvalidKey(
1876                "If primary_namespace is empty, secondary_namespace must also be empty".to_string(),
1877            ));
1878        }
1879        Ok(query(
1880            r#"
1881            SELECT key
1882            FROM kv_store
1883            WHERE primary_namespace = :primary_namespace
1884            AND secondary_namespace = :secondary_namespace
1885            ORDER BY key
1886            "#,
1887        )?
1888        .bind("primary_namespace", primary_namespace.to_owned())
1889        .bind("secondary_namespace", secondary_namespace.to_owned())
1890        .fetch_all(&self.inner)
1891        .await?
1892        .into_iter()
1893        .map(|row| Ok(column_as_string!(&row[0])))
1894        .collect::<Result<Vec<_>, Error>>()?)
1895    }
1896}
1897
1898#[async_trait]
1899impl<RM> database::MintKVStoreDatabase for SQLMintDatabase<RM>
1900where
1901    RM: DatabasePool + 'static,
1902{
1903    type Err = Error;
1904
1905    async fn kv_read(
1906        &self,
1907        primary_namespace: &str,
1908        secondary_namespace: &str,
1909        key: &str,
1910    ) -> Result<Option<Vec<u8>>, Error> {
1911        // Validate parameters according to KV store requirements
1912        validate_kvstore_params(primary_namespace, secondary_namespace, key)?;
1913
1914        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1915        Ok(query(
1916            r#"
1917            SELECT value
1918            FROM kv_store
1919            WHERE primary_namespace = :primary_namespace
1920            AND secondary_namespace = :secondary_namespace
1921            AND key = :key
1922            "#,
1923        )?
1924        .bind("primary_namespace", primary_namespace.to_owned())
1925        .bind("secondary_namespace", secondary_namespace.to_owned())
1926        .bind("key", key.to_owned())
1927        .pluck(&*conn)
1928        .await?
1929        .and_then(|col| match col {
1930            Column::Blob(data) => Some(data),
1931            _ => None,
1932        }))
1933    }
1934
1935    async fn kv_list(
1936        &self,
1937        primary_namespace: &str,
1938        secondary_namespace: &str,
1939    ) -> Result<Vec<String>, Error> {
1940        // Validate namespace parameters according to KV store requirements
1941        cdk_common::database::mint::validate_kvstore_string(primary_namespace)?;
1942        cdk_common::database::mint::validate_kvstore_string(secondary_namespace)?;
1943
1944        // Check empty namespace rules
1945        if primary_namespace.is_empty() && !secondary_namespace.is_empty() {
1946            return Err(Error::KVStoreInvalidKey(
1947                "If primary_namespace is empty, secondary_namespace must also be empty".to_string(),
1948            ));
1949        }
1950
1951        let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1952        Ok(query(
1953            r#"
1954            SELECT key
1955            FROM kv_store
1956            WHERE primary_namespace = :primary_namespace
1957            AND secondary_namespace = :secondary_namespace
1958            ORDER BY key
1959            "#,
1960        )?
1961        .bind("primary_namespace", primary_namespace.to_owned())
1962        .bind("secondary_namespace", secondary_namespace.to_owned())
1963        .fetch_all(&*conn)
1964        .await?
1965        .into_iter()
1966        .map(|row| Ok(column_as_string!(&row[0])))
1967        .collect::<Result<Vec<_>, Error>>()?)
1968    }
1969}
1970
1971#[async_trait]
1972impl<RM> database::MintKVStore for SQLMintDatabase<RM>
1973where
1974    RM: DatabasePool + 'static,
1975{
1976    async fn begin_transaction<'a>(
1977        &'a self,
1978    ) -> Result<Box<dyn database::MintKVStoreTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>
1979    {
1980        Ok(Box::new(SQLTransaction {
1981            inner: ConnectionWithTransaction::new(
1982                self.pool.get().map_err(|e| Error::Database(Box::new(e)))?,
1983            )
1984            .await?,
1985        }))
1986    }
1987}
1988
1989#[async_trait]
1990impl<RM> MintDatabase<Error> for SQLMintDatabase<RM>
1991where
1992    RM: DatabasePool + 'static,
1993{
1994    async fn begin_transaction<'a>(
1995        &'a self,
1996    ) -> Result<Box<dyn database::MintTransaction<'a, Error> + Send + Sync + 'a>, Error> {
1997        let tx = SQLTransaction {
1998            inner: ConnectionWithTransaction::new(
1999                self.pool.get().map_err(|e| Error::Database(Box::new(e)))?,
2000            )
2001            .await?,
2002        };
2003
2004        Ok(Box::new(tx))
2005    }
2006}
2007
2008fn sql_row_to_keyset_info(row: Vec<Column>) -> Result<MintKeySetInfo, Error> {
2009    unpack_into!(
2010        let (
2011            id,
2012            unit,
2013            active,
2014            valid_from,
2015            valid_to,
2016            derivation_path,
2017            derivation_path_index,
2018            max_order,
2019            amounts,
2020            row_keyset_ppk
2021        ) = row
2022    );
2023
2024    let max_order: u8 = column_as_number!(max_order);
2025    let amounts = column_as_nullable_string!(amounts)
2026        .and_then(|str| serde_json::from_str(&str).ok())
2027        .unwrap_or_else(|| (0..max_order).map(|m| 2u64.pow(m.into())).collect());
2028
2029    Ok(MintKeySetInfo {
2030        id: column_as_string!(id, Id::from_str, Id::from_bytes),
2031        unit: column_as_string!(unit, CurrencyUnit::from_str),
2032        active: matches!(active, Column::Integer(1)),
2033        valid_from: column_as_number!(valid_from),
2034        derivation_path: column_as_string!(derivation_path, DerivationPath::from_str),
2035        derivation_path_index: column_as_nullable_number!(derivation_path_index),
2036        max_order,
2037        amounts,
2038        input_fee_ppk: column_as_number!(row_keyset_ppk),
2039        final_expiry: column_as_nullable_number!(valid_to),
2040    })
2041}
2042
2043#[instrument(skip_all)]
2044fn sql_row_to_mint_quote(
2045    row: Vec<Column>,
2046    payments: Vec<IncomingPayment>,
2047    issueances: Vec<Issuance>,
2048) -> Result<MintQuote, Error> {
2049    unpack_into!(
2050        let (
2051            id, amount, unit, request, expiry, request_lookup_id,
2052            pubkey, created_time, amount_paid, amount_issued, payment_method, request_lookup_id_kind
2053        ) = row
2054    );
2055
2056    let request_str = column_as_string!(&request);
2057    let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
2058        Bolt11Invoice::from_str(&request_str)
2059            .map(|invoice| invoice.payment_hash().to_string())
2060            .unwrap_or_else(|_| request_str.clone())
2061    });
2062    let request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
2063
2064    let pubkey = column_as_nullable_string!(&pubkey)
2065        .map(|pk| PublicKey::from_hex(&pk))
2066        .transpose()?;
2067
2068    let id = column_as_string!(id);
2069    let amount: Option<u64> = column_as_nullable_number!(amount);
2070    let amount_paid: u64 = column_as_number!(amount_paid);
2071    let amount_issued: u64 = column_as_number!(amount_issued);
2072    let payment_method = column_as_string!(payment_method, PaymentMethod::from_str);
2073
2074    Ok(MintQuote::new(
2075        Some(QuoteId::from_str(&id)?),
2076        request_str,
2077        column_as_string!(unit, CurrencyUnit::from_str),
2078        amount.map(Amount::from),
2079        column_as_number!(expiry),
2080        PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
2081            .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
2082        pubkey,
2083        amount_paid.into(),
2084        amount_issued.into(),
2085        payment_method,
2086        column_as_number!(created_time),
2087        payments,
2088        issueances,
2089    ))
2090}
2091
2092fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
2093    unpack_into!(
2094        let (
2095                id,
2096                unit,
2097                amount,
2098                request,
2099                fee_reserve,
2100                expiry,
2101                state,
2102                payment_preimage,
2103                request_lookup_id,
2104                created_time,
2105                paid_time,
2106                payment_method,
2107                options,
2108                request_lookup_id_kind
2109        ) = row
2110    );
2111
2112    let id = column_as_string!(id);
2113    let amount: u64 = column_as_number!(amount);
2114    let fee_reserve: u64 = column_as_number!(fee_reserve);
2115
2116    let expiry = column_as_number!(expiry);
2117    let payment_preimage = column_as_nullable_string!(payment_preimage);
2118    let options = column_as_nullable_string!(options);
2119    let options = options.and_then(|o| serde_json::from_str(&o).ok());
2120    let created_time: i64 = column_as_number!(created_time);
2121    let paid_time = column_as_nullable_number!(paid_time);
2122    let payment_method = PaymentMethod::from_str(&column_as_string!(payment_method))?;
2123
2124    let state =
2125        MeltQuoteState::from_str(&column_as_string!(&state)).map_err(ConversionError::from)?;
2126
2127    let unit = column_as_string!(unit);
2128    let request = column_as_string!(request);
2129
2130    let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
2131
2132    let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
2133        Bolt11Invoice::from_str(&request)
2134            .ok()
2135            .map(|invoice| invoice.payment_hash().to_string())
2136    });
2137
2138    let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
2139        (request_lookup_id_kind, request_lookup_id)
2140    {
2141        Some(
2142            PaymentIdentifier::new(&id_kind, &request_lookup_id)
2143                .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
2144        )
2145    } else {
2146        None
2147    };
2148
2149    let request = match serde_json::from_str(&request) {
2150        Ok(req) => req,
2151        Err(err) => {
2152            tracing::debug!(
2153                "Melt quote from pre migrations defaulting to bolt11 {}.",
2154                err
2155            );
2156            let bolt11 = Bolt11Invoice::from_str(&request).unwrap();
2157            MeltPaymentRequest::Bolt11 { bolt11 }
2158        }
2159    };
2160
2161    Ok(MeltQuote {
2162        id: QuoteId::from_str(&id)?,
2163        unit: CurrencyUnit::from_str(&unit)?,
2164        amount: Amount::from(amount),
2165        request,
2166        fee_reserve: Amount::from(fee_reserve),
2167        state,
2168        expiry,
2169        payment_preimage,
2170        request_lookup_id,
2171        options,
2172        created_time: created_time as u64,
2173        paid_time,
2174        payment_method,
2175    })
2176}
2177
2178fn sql_row_to_proof(row: Vec<Column>) -> Result<Proof, Error> {
2179    unpack_into!(
2180        let (
2181            amount,
2182            keyset_id,
2183            secret,
2184            c,
2185            witness
2186        ) = row
2187    );
2188
2189    let amount: u64 = column_as_number!(amount);
2190    Ok(Proof {
2191        amount: Amount::from(amount),
2192        keyset_id: column_as_string!(keyset_id, Id::from_str),
2193        secret: column_as_string!(secret, Secret::from_str),
2194        c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
2195        witness: column_as_nullable_string!(witness).and_then(|w| serde_json::from_str(&w).ok()),
2196        dleq: None,
2197    })
2198}
2199
2200fn sql_row_to_proof_with_state(row: Vec<Column>) -> Result<(Proof, Option<State>), Error> {
2201    unpack_into!(
2202        let (
2203            keyset_id, amount, secret, c, witness, state
2204        ) = row
2205    );
2206
2207    let amount: u64 = column_as_number!(amount);
2208    let state = column_as_nullable_string!(state).and_then(|s| State::from_str(&s).ok());
2209
2210    Ok((
2211        Proof {
2212            amount: Amount::from(amount),
2213            keyset_id: column_as_string!(keyset_id, Id::from_str, Id::from_bytes),
2214            secret: column_as_string!(secret, Secret::from_str),
2215            c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
2216            witness: column_as_nullable_string!(witness)
2217                .and_then(|w| serde_json::from_str(&w).ok()),
2218            dleq: None,
2219        },
2220        state,
2221    ))
2222}
2223
2224fn sql_row_to_blind_signature(row: Vec<Column>) -> Result<BlindSignature, Error> {
2225    unpack_into!(
2226        let (
2227            keyset_id, amount, c, dleq_e, dleq_s
2228        ) = row
2229    );
2230
2231    let dleq = match (
2232        column_as_nullable_string!(dleq_e),
2233        column_as_nullable_string!(dleq_s),
2234    ) {
2235        (Some(e), Some(s)) => Some(BlindSignatureDleq {
2236            e: SecretKey::from_hex(e)?,
2237            s: SecretKey::from_hex(s)?,
2238        }),
2239        _ => None,
2240    };
2241
2242    let amount: u64 = column_as_number!(amount);
2243
2244    Ok(BlindSignature {
2245        amount: Amount::from(amount),
2246        keyset_id: column_as_string!(keyset_id, Id::from_str, Id::from_bytes),
2247        c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
2248        dleq,
2249    })
2250}
2251
2252#[cfg(test)]
2253mod test {
2254    use super::*;
2255
2256    mod max_order_to_amounts_migrations {
2257        use super::*;
2258
2259        #[test]
2260        fn legacy_payload() {
2261            let result = sql_row_to_keyset_info(vec![
2262                Column::Text("0083a60439303340".to_owned()),
2263                Column::Text("sat".to_owned()),
2264                Column::Integer(1),
2265                Column::Integer(1749844864),
2266                Column::Null,
2267                Column::Text("0'/0'/0'".to_owned()),
2268                Column::Integer(0),
2269                Column::Integer(32),
2270                Column::Null,
2271                Column::Integer(0),
2272            ]);
2273            assert!(result.is_ok());
2274        }
2275
2276        #[test]
2277        fn migrated_payload() {
2278            let legacy = sql_row_to_keyset_info(vec![
2279                Column::Text("0083a60439303340".to_owned()),
2280                Column::Text("sat".to_owned()),
2281                Column::Integer(1),
2282                Column::Integer(1749844864),
2283                Column::Null,
2284                Column::Text("0'/0'/0'".to_owned()),
2285                Column::Integer(0),
2286                Column::Integer(32),
2287                Column::Null,
2288                Column::Integer(0),
2289            ]);
2290            assert!(legacy.is_ok());
2291
2292            let amounts = (0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>();
2293            let migrated = sql_row_to_keyset_info(vec![
2294                Column::Text("0083a60439303340".to_owned()),
2295                Column::Text("sat".to_owned()),
2296                Column::Integer(1),
2297                Column::Integer(1749844864),
2298                Column::Null,
2299                Column::Text("0'/0'/0'".to_owned()),
2300                Column::Integer(0),
2301                Column::Integer(32),
2302                Column::Text(serde_json::to_string(&amounts).expect("valid json")),
2303                Column::Integer(0),
2304            ]);
2305            assert!(migrated.is_ok());
2306            assert_eq!(legacy.unwrap(), migrated.unwrap());
2307        }
2308
2309        #[test]
2310        fn amounts_over_max_order() {
2311            let legacy = sql_row_to_keyset_info(vec![
2312                Column::Text("0083a60439303340".to_owned()),
2313                Column::Text("sat".to_owned()),
2314                Column::Integer(1),
2315                Column::Integer(1749844864),
2316                Column::Null,
2317                Column::Text("0'/0'/0'".to_owned()),
2318                Column::Integer(0),
2319                Column::Integer(32),
2320                Column::Null,
2321                Column::Integer(0),
2322            ]);
2323            assert!(legacy.is_ok());
2324
2325            let amounts = (0..16).map(|x| 2u64.pow(x)).collect::<Vec<_>>();
2326            let migrated = sql_row_to_keyset_info(vec![
2327                Column::Text("0083a60439303340".to_owned()),
2328                Column::Text("sat".to_owned()),
2329                Column::Integer(1),
2330                Column::Integer(1749844864),
2331                Column::Null,
2332                Column::Text("0'/0'/0'".to_owned()),
2333                Column::Integer(0),
2334                Column::Integer(32),
2335                Column::Text(serde_json::to_string(&amounts).expect("valid json")),
2336                Column::Integer(0),
2337            ]);
2338            assert!(migrated.is_ok());
2339            let migrated = migrated.unwrap();
2340            assert_ne!(legacy.unwrap(), migrated);
2341            assert_eq!(migrated.amounts.len(), 16);
2342        }
2343    }
2344}