cdk_sqlite/mint/
mod.rs

1//! SQLite Mint
2
3use std::collections::{HashMap, HashSet};
4use std::path::Path;
5use std::str::FromStr;
6
7use async_trait::async_trait;
8use bitcoin::bip32::DerivationPath;
9use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
10use cdk_common::database::{
11    self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
12    MintSignaturesDatabase,
13};
14use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
15use cdk_common::nut00::ProofsMethods;
16use cdk_common::nut05::QuoteState;
17use cdk_common::secret::Secret;
18use cdk_common::state::check_state_transition;
19use cdk_common::util::unix_time;
20use cdk_common::{
21    Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltBolt11Request,
22    MeltQuoteState, MintInfo, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey,
23    State,
24};
25use error::Error;
26use lightning_invoice::Bolt11Invoice;
27use sqlx::sqlite::SqliteRow;
28use sqlx::{Executor, Pool, Row, Sqlite};
29use uuid::fmt::Hyphenated;
30use uuid::Uuid;
31
32use crate::common::create_sqlite_pool;
33
34#[cfg(feature = "auth")]
35mod auth;
36pub mod error;
37pub mod memory;
38
39#[cfg(feature = "auth")]
40pub use auth::MintSqliteAuthDatabase;
41
42/// Mint SQLite Database
43#[derive(Debug, Clone)]
44pub struct MintSqliteDatabase {
45    pool: Pool<Sqlite>,
46}
47
48impl MintSqliteDatabase {
49    /// Check if any proofs are spent
50    async fn check_for_spent_proofs<'e, 'c: 'e, E>(
51        &self,
52        transaction: E,
53        ys: &[PublicKey],
54    ) -> Result<bool, database::Error>
55    where
56        E: Executor<'c, Database = Sqlite>,
57    {
58        if ys.is_empty() {
59            return Ok(false);
60        }
61
62        let check_sql = format!(
63            "SELECT state FROM proof WHERE y IN ({}) AND state = 'SPENT'",
64            std::iter::repeat("?")
65                .take(ys.len())
66                .collect::<Vec<_>>()
67                .join(",")
68        );
69
70        let spent_count = ys
71            .iter()
72            .fold(sqlx::query(&check_sql), |query, y| {
73                query.bind(y.to_bytes().to_vec())
74            })
75            .fetch_all(transaction)
76            .await
77            .map_err(Error::from)?
78            .len();
79
80        Ok(spent_count > 0)
81    }
82
83    /// Create new [`MintSqliteDatabase`]
84    #[cfg(not(feature = "sqlcipher"))]
85    pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
86        let db = Self {
87            pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
88        };
89        db.migrate().await?;
90        Ok(db)
91    }
92
93    /// Create new [`MintSqliteDatabase`]
94    #[cfg(feature = "sqlcipher")]
95    pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
96        let db = Self {
97            pool: create_sqlite_pool(
98                path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
99                password,
100            )
101            .await?,
102        };
103        db.migrate().await?;
104        Ok(db)
105    }
106
107    /// Migrate [`MintSqliteDatabase`]
108    async fn migrate(&self) -> Result<(), Error> {
109        sqlx::migrate!("./src/mint/migrations")
110            .run(&self.pool)
111            .await
112            .map_err(|_| Error::CouldNotInitialize)?;
113        Ok(())
114    }
115}
116
117#[async_trait]
118impl MintKeysDatabase for MintSqliteDatabase {
119    type Err = database::Error;
120
121    async fn set_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err> {
122        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
123
124        let update_res = sqlx::query(
125            r#"
126UPDATE keyset
127SET active=FALSE
128WHERE unit IS ?;
129        "#,
130        )
131        .bind(unit.to_string())
132        .execute(&mut *transaction)
133        .await;
134
135        match update_res {
136            Ok(_) => (),
137            Err(err) => {
138                tracing::error!("SQLite Could not update keyset");
139                if let Err(err) = transaction.rollback().await {
140                    tracing::error!("Could not rollback sql transaction: {}", err);
141                }
142
143                return Err(Error::from(err).into());
144            }
145        };
146
147        let update_res = sqlx::query(
148            r#"
149UPDATE keyset
150SET active=TRUE
151WHERE unit IS ?
152AND id IS ?;
153        "#,
154        )
155        .bind(unit.to_string())
156        .bind(id.to_string())
157        .execute(&mut *transaction)
158        .await;
159
160        match update_res {
161            Ok(_) => (),
162            Err(err) => {
163                tracing::error!("SQLite Could not update keyset");
164                if let Err(err) = transaction.rollback().await {
165                    tracing::error!("Could not rollback sql transaction: {}", err);
166                }
167
168                return Err(Error::from(err).into());
169            }
170        };
171
172        transaction.commit().await.map_err(Error::from)?;
173
174        Ok(())
175    }
176
177    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err> {
178        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
179
180        let rec = sqlx::query(
181            r#"
182SELECT id
183FROM keyset
184WHERE active = 1
185AND unit IS ?
186        "#,
187        )
188        .bind(unit.to_string())
189        .fetch_one(&mut *transaction)
190        .await;
191
192        let rec = match rec {
193            Ok(rec) => {
194                transaction.commit().await.map_err(Error::from)?;
195                rec
196            }
197            Err(err) => match err {
198                sqlx::Error::RowNotFound => {
199                    transaction.commit().await.map_err(Error::from)?;
200                    return Ok(None);
201                }
202                _ => {
203                    return {
204                        if let Err(err) = transaction.rollback().await {
205                            tracing::error!("Could not rollback sql transaction: {}", err);
206                        }
207                        Err(Error::SQLX(err).into())
208                    }
209                }
210            },
211        };
212
213        Ok(Some(
214            Id::from_str(rec.try_get("id").map_err(Error::from)?).map_err(Error::from)?,
215        ))
216    }
217
218    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err> {
219        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
220
221        let recs = sqlx::query(
222            r#"
223SELECT id, unit
224FROM keyset
225WHERE active = 1
226        "#,
227        )
228        .fetch_all(&mut *transaction)
229        .await;
230
231        match recs {
232            Ok(recs) => {
233                transaction.commit().await.map_err(Error::from)?;
234
235                let keysets = recs
236                    .iter()
237                    .filter_map(|r| match Id::from_str(r.get("id")) {
238                        Ok(id) => Some((
239                            CurrencyUnit::from_str(r.get::<'_, &str, &str>("unit")).unwrap(),
240                            id,
241                        )),
242                        Err(_) => None,
243                    })
244                    .collect();
245                Ok(keysets)
246            }
247            Err(err) => {
248                tracing::error!("SQLite could not get active keyset");
249                if let Err(err) = transaction.rollback().await {
250                    tracing::error!("Could not rollback sql transaction: {}", err);
251                }
252                Err(Error::from(err).into())
253            }
254        }
255    }
256
257    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err> {
258        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
259        let res = sqlx::query(
260            r#"
261INSERT INTO keyset
262(id, unit, active, valid_from, valid_to, derivation_path, max_order, input_fee_ppk, derivation_path_index)
263VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
264ON CONFLICT(id) DO UPDATE SET
265    unit = excluded.unit,
266    active = excluded.active,
267    valid_from = excluded.valid_from,
268    valid_to = excluded.valid_to,
269    derivation_path = excluded.derivation_path,
270    max_order = excluded.max_order,
271    input_fee_ppk = excluded.input_fee_ppk,
272    derivation_path_index = excluded.derivation_path_index
273        "#,
274        )
275        .bind(keyset.id.to_string())
276        .bind(keyset.unit.to_string())
277        .bind(keyset.active)
278        .bind(keyset.valid_from as i64)
279        .bind(keyset.valid_to.map(|v| v as i64))
280        .bind(keyset.derivation_path.to_string())
281        .bind(keyset.max_order)
282        .bind(keyset.input_fee_ppk as i64)
283            .bind(keyset.derivation_path_index)
284        .execute(&mut *transaction)
285        .await;
286
287        match res {
288            Ok(_) => {
289                transaction.commit().await.map_err(Error::from)?;
290                Ok(())
291            }
292            Err(err) => {
293                tracing::error!("SQLite could not add keyset info");
294                if let Err(err) = transaction.rollback().await {
295                    tracing::error!("Could not rollback sql transaction: {}", err);
296                }
297
298                Err(Error::from(err).into())
299            }
300        }
301    }
302
303    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
304        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
305        let rec = sqlx::query(
306            r#"
307SELECT *
308FROM keyset
309WHERE id=?;
310        "#,
311        )
312        .bind(id.to_string())
313        .fetch_one(&mut *transaction)
314        .await;
315
316        match rec {
317            Ok(rec) => {
318                transaction.commit().await.map_err(Error::from)?;
319                Ok(Some(sqlite_row_to_keyset_info(rec)?))
320            }
321            Err(err) => match err {
322                sqlx::Error::RowNotFound => {
323                    transaction.commit().await.map_err(Error::from)?;
324                    return Ok(None);
325                }
326                _ => {
327                    tracing::error!("SQLite could not get keyset info");
328                    if let Err(err) = transaction.rollback().await {
329                        tracing::error!("Could not rollback sql transaction: {}", err);
330                    }
331                    return Err(Error::SQLX(err).into());
332                }
333            },
334        }
335    }
336
337    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
338        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
339        let recs = sqlx::query(
340            r#"
341SELECT *
342FROM keyset;
343        "#,
344        )
345        .fetch_all(&mut *transaction)
346        .await
347        .map_err(Error::from);
348
349        match recs {
350            Ok(recs) => {
351                transaction.commit().await.map_err(Error::from)?;
352                Ok(recs
353                    .into_iter()
354                    .map(sqlite_row_to_keyset_info)
355                    .collect::<Result<_, _>>()?)
356            }
357            Err(err) => {
358                tracing::error!("SQLite could not get keyset info");
359                if let Err(err) = transaction.rollback().await {
360                    tracing::error!("Could not rollback sql transaction: {}", err);
361                }
362                Err(err.into())
363            }
364        }
365    }
366}
367
368#[async_trait]
369impl MintQuotesDatabase for MintSqliteDatabase {
370    type Err = database::Error;
371
372    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
373        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
374
375        let res = sqlx::query(
376            r#"
377INSERT INTO mint_quote
378(id, amount, unit, request, state, expiry, request_lookup_id, pubkey, created_time, paid_time, issued_time)
379VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
380ON CONFLICT(id) DO UPDATE SET
381    amount = excluded.amount,
382    unit = excluded.unit,
383    request = excluded.request,
384    state = excluded.state,
385    expiry = excluded.expiry,
386    request_lookup_id = excluded.request_lookup_id,
387    created_time = excluded.created_time,
388    paid_time = excluded.paid_time,
389    issued_time = excluded.issued_time
390ON CONFLICT(request_lookup_id) DO UPDATE SET
391    amount = excluded.amount,
392    unit = excluded.unit,
393    request = excluded.request,
394    state = excluded.state,
395    expiry = excluded.expiry,
396    id = excluded.id,
397    created_time = excluded.created_time,
398    paid_time = excluded.paid_time,
399    issued_time = excluded.issued_time
400        "#,
401        )
402        .bind(quote.id.to_string())
403        .bind(u64::from(quote.amount) as i64)
404        .bind(quote.unit.to_string())
405        .bind(quote.request)
406        .bind(quote.state.to_string())
407        .bind(quote.expiry as i64)
408        .bind(quote.request_lookup_id)
409        .bind(quote.pubkey.map(|p| p.to_string()))
410        .bind(quote.created_time as i64)
411        .bind(quote.paid_time.map(|t| t as i64))
412        .bind(quote.issued_time.map(|t| t as i64))
413        .execute(&mut *transaction)
414        .await;
415
416        match res {
417            Ok(_) => {
418                transaction.commit().await.map_err(Error::from)?;
419                Ok(())
420            }
421            Err(err) => {
422                tracing::error!("SQLite Could not update keyset");
423                if let Err(err) = transaction.rollback().await {
424                    tracing::error!("Could not rollback sql transaction: {}", err);
425                }
426
427                Err(Error::from(err).into())
428            }
429        }
430    }
431
432    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
433        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
434        let rec = sqlx::query(
435            r#"
436SELECT *
437FROM mint_quote
438WHERE id=?;
439        "#,
440        )
441        .bind(quote_id.as_hyphenated())
442        .fetch_one(&mut *transaction)
443        .await;
444
445        match rec {
446            Ok(rec) => {
447                transaction.commit().await.map_err(Error::from)?;
448                Ok(Some(sqlite_row_to_mint_quote(rec)?))
449            }
450            Err(err) => match err {
451                sqlx::Error::RowNotFound => {
452                    transaction.commit().await.map_err(Error::from)?;
453                    Ok(None)
454                }
455                _ => {
456                    if let Err(err) = transaction.rollback().await {
457                        tracing::error!("Could not rollback sql transaction: {}", err);
458                    }
459                    Err(Error::SQLX(err).into())
460                }
461            },
462        }
463    }
464
465    async fn get_mint_quote_by_request(
466        &self,
467        request: &str,
468    ) -> Result<Option<MintQuote>, Self::Err> {
469        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
470        let rec = sqlx::query(
471            r#"
472SELECT *
473FROM mint_quote
474WHERE request=?;
475        "#,
476        )
477        .bind(request)
478        .fetch_one(&mut *transaction)
479        .await;
480
481        match rec {
482            Ok(rec) => {
483                transaction.commit().await.map_err(Error::from)?;
484                Ok(Some(sqlite_row_to_mint_quote(rec)?))
485            }
486            Err(err) => match err {
487                sqlx::Error::RowNotFound => {
488                    transaction.commit().await.map_err(Error::from)?;
489                    Ok(None)
490                }
491                _ => {
492                    if let Err(err) = transaction.rollback().await {
493                        tracing::error!("Could not rollback sql transaction: {}", err);
494                    }
495                    Err(Error::SQLX(err).into())
496                }
497            },
498        }
499    }
500
501    async fn get_mint_quote_by_request_lookup_id(
502        &self,
503        request_lookup_id: &str,
504    ) -> Result<Option<MintQuote>, Self::Err> {
505        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
506
507        let rec = sqlx::query(
508            r#"
509SELECT *
510FROM mint_quote
511WHERE request_lookup_id=?;
512        "#,
513        )
514        .bind(request_lookup_id)
515        .fetch_one(&mut *transaction)
516        .await;
517
518        match rec {
519            Ok(rec) => {
520                transaction.commit().await.map_err(Error::from)?;
521
522                Ok(Some(sqlite_row_to_mint_quote(rec)?))
523            }
524            Err(err) => match err {
525                sqlx::Error::RowNotFound => {
526                    transaction.commit().await.map_err(Error::from)?;
527                    Ok(None)
528                }
529                _ => {
530                    if let Err(err) = transaction.rollback().await {
531                        tracing::error!("Could not rollback sql transaction: {}", err);
532                    }
533                    Err(Error::SQLX(err).into())
534                }
535            },
536        }
537    }
538
539    async fn update_mint_quote_state(
540        &self,
541        quote_id: &Uuid,
542        state: MintQuoteState,
543    ) -> Result<MintQuoteState, Self::Err> {
544        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
545
546        let rec = sqlx::query(
547            r#"
548SELECT *
549FROM mint_quote
550WHERE id=?;
551        "#,
552        )
553        .bind(quote_id.as_hyphenated())
554        .fetch_one(&mut *transaction)
555        .await;
556        let quote = match rec {
557            Ok(row) => sqlite_row_to_mint_quote(row)?,
558            Err(err) => {
559                tracing::error!("SQLite Could not update keyset");
560                if let Err(err) = transaction.rollback().await {
561                    tracing::error!("Could not rollback sql transaction: {}", err);
562                }
563
564                return Err(Error::from(err).into());
565            }
566        };
567
568        let update_query = match state {
569            MintQuoteState::Paid => {
570                r#"UPDATE mint_quote SET state = ?, paid_time = ? WHERE id = ?"#
571            }
572            MintQuoteState::Issued => {
573                r#"UPDATE mint_quote SET state = ?, issued_time = ? WHERE id = ?"#
574            }
575            _ => r#"UPDATE mint_quote SET state = ? WHERE id = ?"#,
576        };
577
578        let current_time = unix_time();
579
580        let update = match state {
581            MintQuoteState::Paid => {
582                sqlx::query(update_query)
583                    .bind(state.to_string())
584                    .bind(current_time as i64)
585                    .bind(quote_id.as_hyphenated())
586                    .execute(&mut *transaction)
587                    .await
588            }
589            MintQuoteState::Issued => {
590                sqlx::query(update_query)
591                    .bind(state.to_string())
592                    .bind(current_time as i64)
593                    .bind(quote_id.as_hyphenated())
594                    .execute(&mut *transaction)
595                    .await
596            }
597            _ => {
598                sqlx::query(update_query)
599                    .bind(state.to_string())
600                    .bind(quote_id.as_hyphenated())
601                    .execute(&mut *transaction)
602                    .await
603            }
604        };
605
606        match update {
607            Ok(_) => {
608                transaction.commit().await.map_err(Error::from)?;
609                Ok(quote.state)
610            }
611            Err(err) => {
612                tracing::error!("SQLite Could not update keyset");
613                if let Err(err) = transaction.rollback().await {
614                    tracing::error!("Could not rollback sql transaction: {}", err);
615                }
616
617                return Err(Error::from(err).into());
618            }
619        }
620    }
621
622    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
623        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
624        let rec = sqlx::query(
625            r#"
626SELECT *
627FROM mint_quote
628        "#,
629        )
630        .fetch_all(&mut *transaction)
631        .await;
632
633        match rec {
634            Ok(rows) => {
635                transaction.commit().await.map_err(Error::from)?;
636                let mint_quotes = rows
637                    .into_iter()
638                    .map(sqlite_row_to_mint_quote)
639                    .collect::<Result<Vec<MintQuote>, _>>()?;
640
641                Ok(mint_quotes)
642            }
643            Err(err) => {
644                tracing::error!("SQLite get mint quotes");
645                if let Err(err) = transaction.rollback().await {
646                    tracing::error!("Could not rollback sql transaction: {}", err);
647                }
648
649                return Err(Error::from(err).into());
650            }
651        }
652    }
653
654    async fn get_mint_quotes_with_state(
655        &self,
656        state: MintQuoteState,
657    ) -> Result<Vec<MintQuote>, Self::Err> {
658        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
659        let rec = sqlx::query(
660            r#"
661SELECT *
662FROM mint_quote
663WHERE state = ?
664        "#,
665        )
666        .bind(state.to_string())
667        .fetch_all(&mut *transaction)
668        .await;
669
670        match rec {
671            Ok(rows) => {
672                transaction.commit().await.map_err(Error::from)?;
673                let mint_quotes = rows
674                    .into_iter()
675                    .map(sqlite_row_to_mint_quote)
676                    .collect::<Result<Vec<MintQuote>, _>>()?;
677
678                Ok(mint_quotes)
679            }
680            Err(err) => {
681                tracing::error!("SQLite get mint quotes with state");
682                if let Err(err) = transaction.rollback().await {
683                    tracing::error!("Could not rollback sql transaction: {}", err);
684                }
685
686                return Err(Error::from(err).into());
687            }
688        }
689    }
690
691    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
692        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
693
694        let res = sqlx::query(
695            r#"
696DELETE FROM mint_quote
697WHERE id=?
698        "#,
699        )
700        .bind(quote_id.as_hyphenated())
701        .execute(&mut *transaction)
702        .await;
703
704        match res {
705            Ok(_) => {
706                transaction.commit().await.map_err(Error::from)?;
707
708                Ok(())
709            }
710            Err(err) => {
711                tracing::error!("SQLite Could not remove mint quote");
712                if let Err(err) = transaction.rollback().await {
713                    tracing::error!("Could not rollback sql transaction: {}", err);
714                }
715
716                Err(Error::from(err).into())
717            }
718        }
719    }
720
721    async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
722        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
723        let res = sqlx::query(
724            r#"
725INSERT INTO melt_quote
726(id, unit, amount, request, fee_reserve, state, expiry, payment_preimage, request_lookup_id, msat_to_pay, created_time, paid_time)
727VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
728ON CONFLICT(id) DO UPDATE SET
729    unit = excluded.unit,
730    amount = excluded.amount,
731    request = excluded.request,
732    fee_reserve = excluded.fee_reserve,
733    state = excluded.state,
734    expiry = excluded.expiry,
735    payment_preimage = excluded.payment_preimage,
736    request_lookup_id = excluded.request_lookup_id,
737    msat_to_pay = excluded.msat_to_pay,
738    created_time = excluded.created_time,
739    paid_time = excluded.paid_time
740ON CONFLICT(request_lookup_id) DO UPDATE SET
741    unit = excluded.unit,
742    amount = excluded.amount,
743    request = excluded.request,
744    fee_reserve = excluded.fee_reserve,
745    state = excluded.state,
746    expiry = excluded.expiry,
747    payment_preimage = excluded.payment_preimage,
748    id = excluded.id,
749    created_time = excluded.created_time,
750    paid_time = excluded.paid_time;
751        "#,
752        )
753        .bind(quote.id.to_string())
754        .bind(quote.unit.to_string())
755        .bind(u64::from(quote.amount) as i64)
756        .bind(quote.request)
757        .bind(u64::from(quote.fee_reserve) as i64)
758        .bind(quote.state.to_string())
759        .bind(quote.expiry as i64)
760        .bind(quote.payment_preimage)
761        .bind(quote.request_lookup_id)
762        .bind(quote.msat_to_pay.map(|a| u64::from(a) as i64))
763        .bind(quote.created_time as i64)
764        .bind(quote.paid_time.map(|t| t as i64))
765        .execute(&mut *transaction)
766        .await;
767
768        match res {
769            Ok(_) => {
770                transaction.commit().await.map_err(Error::from)?;
771
772                Ok(())
773            }
774            Err(err) => {
775                tracing::error!("SQLite Could not remove mint quote");
776                if let Err(err) = transaction.rollback().await {
777                    tracing::error!("Could not rollback sql transaction: {}", err);
778                }
779
780                Err(Error::from(err).into())
781            }
782        }
783    }
784    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
785        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
786        let rec = sqlx::query(
787            r#"
788SELECT *
789FROM melt_quote
790WHERE id=?;
791        "#,
792        )
793        .bind(quote_id.as_hyphenated())
794        .fetch_one(&mut *transaction)
795        .await;
796
797        match rec {
798            Ok(rec) => {
799                transaction.commit().await.map_err(Error::from)?;
800
801                Ok(Some(sqlite_row_to_melt_quote(rec)?))
802            }
803            Err(err) => match err {
804                sqlx::Error::RowNotFound => {
805                    transaction.commit().await.map_err(Error::from)?;
806                    Ok(None)
807                }
808                _ => {
809                    if let Err(err) = transaction.rollback().await {
810                        tracing::error!("Could not rollback sql transaction: {}", err);
811                    }
812
813                    Err(Error::SQLX(err).into())
814                }
815            },
816        }
817    }
818
819    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
820        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
821        let rec = sqlx::query(
822            r#"
823SELECT *
824FROM melt_quote
825        "#,
826        )
827        .fetch_all(&mut *transaction)
828        .await
829        .map_err(Error::from);
830
831        match rec {
832            Ok(rec) => {
833                let melt_quotes = rec
834                    .into_iter()
835                    .map(sqlite_row_to_melt_quote)
836                    .collect::<Result<Vec<mint::MeltQuote>, _>>()?;
837                Ok(melt_quotes)
838            }
839            Err(err) => {
840                if let Err(err) = transaction.rollback().await {
841                    tracing::error!("Could not rollback sql transaction: {}", err);
842                }
843
844                Err(err.into())
845            }
846        }
847    }
848
849    async fn update_melt_quote_state(
850        &self,
851        quote_id: &Uuid,
852        state: MeltQuoteState,
853    ) -> Result<MeltQuoteState, Self::Err> {
854        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
855
856        let rec = sqlx::query(
857            r#"
858SELECT *
859FROM melt_quote
860WHERE id=?;
861        "#,
862        )
863        .bind(quote_id.as_hyphenated())
864        .fetch_one(&mut *transaction)
865        .await;
866
867        let quote = match rec {
868            Ok(rec) => sqlite_row_to_melt_quote(rec)?,
869            Err(err) => {
870                tracing::error!("SQLite Could not update keyset");
871                if let Err(err) = transaction.rollback().await {
872                    tracing::error!("Could not rollback sql transaction: {}", err);
873                }
874
875                return Err(Error::from(err).into());
876            }
877        };
878
879        let update_query = if state == MeltQuoteState::Paid {
880            r#"UPDATE melt_quote SET state = ?, paid_time = ? WHERE id = ?"#
881        } else {
882            r#"UPDATE melt_quote SET state = ? WHERE id = ?"#
883        };
884
885        let current_time = unix_time();
886
887        let rec = if state == MeltQuoteState::Paid {
888            sqlx::query(update_query)
889                .bind(state.to_string())
890                .bind(current_time as i64)
891                .bind(quote_id.as_hyphenated())
892                .execute(&mut *transaction)
893                .await
894        } else {
895            sqlx::query(update_query)
896                .bind(state.to_string())
897                .bind(quote_id.as_hyphenated())
898                .execute(&mut *transaction)
899                .await
900        };
901
902        match rec {
903            Ok(_) => {
904                transaction.commit().await.map_err(Error::from)?;
905            }
906            Err(err) => {
907                tracing::error!("SQLite Could not update melt quote");
908                if let Err(err) = transaction.rollback().await {
909                    tracing::error!("Could not rollback sql transaction: {}", err);
910                }
911
912                return Err(Error::from(err).into());
913            }
914        };
915
916        Ok(quote.state)
917    }
918
919    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
920        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
921        let res = sqlx::query(
922            r#"
923DELETE FROM melt_quote
924WHERE id=?
925        "#,
926        )
927        .bind(quote_id.as_hyphenated())
928        .execute(&mut *transaction)
929        .await;
930
931        match res {
932            Ok(_) => {
933                transaction.commit().await.map_err(Error::from)?;
934                Ok(())
935            }
936            Err(err) => {
937                tracing::error!("SQLite Could not update melt quote");
938                if let Err(err) = transaction.rollback().await {
939                    tracing::error!("Could not rollback sql transaction: {}", err);
940                }
941
942                Err(Error::from(err).into())
943            }
944        }
945    }
946
947    async fn add_melt_request(
948        &self,
949        melt_request: MeltBolt11Request<Uuid>,
950        ln_key: PaymentProcessorKey,
951    ) -> Result<(), Self::Err> {
952        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
953
954        let res = sqlx::query(
955            r#"
956INSERT INTO melt_request
957(id, inputs, outputs, method, unit)
958VALUES (?, ?, ?, ?, ?)
959ON CONFLICT(id) DO UPDATE SET
960    inputs = excluded.inputs,
961    outputs = excluded.outputs,
962    method = excluded.method,
963    unit = excluded.unit
964        "#,
965        )
966        .bind(melt_request.quote())
967        .bind(serde_json::to_string(&melt_request.inputs())?)
968        .bind(serde_json::to_string(&melt_request.outputs())?)
969        .bind(ln_key.method.to_string())
970        .bind(ln_key.unit.to_string())
971        .execute(&mut *transaction)
972        .await;
973
974        match res {
975            Ok(_) => {
976                transaction.commit().await.map_err(Error::from)?;
977                Ok(())
978            }
979            Err(err) => {
980                tracing::error!("SQLite Could not update keyset");
981                if let Err(err) = transaction.rollback().await {
982                    tracing::error!("Could not rollback sql transaction: {}", err);
983                }
984
985                Err(Error::from(err).into())
986            }
987        }
988    }
989
990    async fn get_melt_request(
991        &self,
992        quote_id: &Uuid,
993    ) -> Result<Option<(MeltBolt11Request<Uuid>, PaymentProcessorKey)>, Self::Err> {
994        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
995
996        let rec = sqlx::query(
997            r#"
998SELECT *
999FROM melt_request
1000WHERE id=?;
1001        "#,
1002        )
1003        .bind(quote_id.as_hyphenated())
1004        .fetch_one(&mut *transaction)
1005        .await;
1006
1007        match rec {
1008            Ok(rec) => {
1009                transaction.commit().await.map_err(Error::from)?;
1010
1011                let (request, key) = sqlite_row_to_melt_request(rec)?;
1012
1013                Ok(Some((request, key)))
1014            }
1015            Err(err) => match err {
1016                sqlx::Error::RowNotFound => {
1017                    transaction.commit().await.map_err(Error::from)?;
1018                    return Ok(None);
1019                }
1020                _ => {
1021                    return {
1022                        if let Err(err) = transaction.rollback().await {
1023                            tracing::error!("Could not rollback sql transaction: {}", err);
1024                        }
1025                        Err(Error::SQLX(err).into())
1026                    }
1027                }
1028            },
1029        }
1030    }
1031}
1032
1033#[async_trait]
1034impl MintProofsDatabase for MintSqliteDatabase {
1035    type Err = database::Error;
1036
1037    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
1038        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1039        let current_time = unix_time();
1040
1041        for proof in proofs {
1042            let result = sqlx::query(
1043                r#"
1044INSERT OR IGNORE INTO proof
1045(y, amount, keyset_id, secret, c, witness, state, quote_id, created_time)
1046VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
1047        "#,
1048            )
1049            .bind(proof.y()?.to_bytes().to_vec())
1050            .bind(u64::from(proof.amount) as i64)
1051            .bind(proof.keyset_id.to_string())
1052            .bind(proof.secret.to_string())
1053            .bind(proof.c.to_bytes().to_vec())
1054            .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap()))
1055            .bind("UNSPENT")
1056            .bind(quote_id.map(|q| q.hyphenated()))
1057            .bind(current_time as i64)
1058            .execute(&mut *transaction)
1059            .await;
1060
1061            // We still need to check for foreign key constraint errors
1062            if let Err(err) = result {
1063                if let sqlx::Error::Database(db_err) = &err {
1064                    if db_err.message().contains("FOREIGN KEY constraint failed") {
1065                        tracing::error!(
1066                            "Foreign key constraint failed when adding proof: {:?}",
1067                            err
1068                        );
1069                        transaction.rollback().await.map_err(Error::from)?;
1070                        return Err(database::Error::InvalidKeysetId);
1071                    }
1072                }
1073
1074                // For any other error, roll back and return the error
1075                tracing::error!("Error adding proof: {:?}", err);
1076                transaction.rollback().await.map_err(Error::from)?;
1077                return Err(Error::from(err).into());
1078            }
1079        }
1080        transaction.commit().await.map_err(Error::from)?;
1081
1082        Ok(())
1083    }
1084
1085    async fn remove_proofs(
1086        &self,
1087        ys: &[PublicKey],
1088        _quote_id: Option<Uuid>,
1089    ) -> Result<(), Self::Err> {
1090        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1091
1092        if self.check_for_spent_proofs(&mut *transaction, ys).await? {
1093            transaction.rollback().await.map_err(Error::from)?;
1094            return Err(Self::Err::AttemptRemoveSpentProof);
1095        }
1096
1097        // If no proofs are spent, proceed with deletion
1098        let delete_sql = format!(
1099            "DELETE FROM proof WHERE y IN ({})",
1100            std::iter::repeat("?")
1101                .take(ys.len())
1102                .collect::<Vec<_>>()
1103                .join(",")
1104        );
1105
1106        ys.iter()
1107            .fold(sqlx::query(&delete_sql), |query, y| {
1108                query.bind(y.to_bytes().to_vec())
1109            })
1110            .execute(&mut *transaction)
1111            .await
1112            .map_err(Error::from)?;
1113
1114        transaction.commit().await.map_err(Error::from)?;
1115        Ok(())
1116    }
1117
1118    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
1119        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1120
1121        let sql = format!(
1122            "SELECT * FROM proof WHERE y IN ({})",
1123            "?,".repeat(ys.len()).trim_end_matches(',')
1124        );
1125
1126        let mut proofs = ys
1127            .iter()
1128            .fold(sqlx::query(&sql), |query, y| {
1129                query.bind(y.to_bytes().to_vec())
1130            })
1131            .fetch_all(&mut *transaction)
1132            .await
1133            .map_err(|err| {
1134                tracing::error!("SQLite could not get state of proof: {err:?}");
1135                Error::SQLX(err)
1136            })?
1137            .into_iter()
1138            .map(|row| {
1139                PublicKey::from_slice(row.get("y"))
1140                    .map_err(Error::from)
1141                    .and_then(|y| sqlite_row_to_proof(row).map(|proof| (y, proof)))
1142            })
1143            .collect::<Result<HashMap<_, _>, _>>()?;
1144
1145        Ok(ys.iter().map(|y| proofs.remove(y)).collect())
1146    }
1147
1148    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
1149        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1150
1151        let rec = sqlx::query(
1152            r#"
1153SELECT *
1154FROM proof
1155WHERE quote_id=?;
1156        "#,
1157        )
1158        .bind(quote_id.as_hyphenated())
1159        .fetch_all(&mut *transaction)
1160        .await;
1161
1162        let ys = match rec {
1163            Ok(rec) => {
1164                transaction.commit().await.map_err(Error::from)?;
1165
1166                let proofs = rec
1167                    .into_iter()
1168                    .map(sqlite_row_to_proof)
1169                    .collect::<Result<Vec<Proof>, _>>()?;
1170
1171                proofs.ys()?
1172            }
1173            Err(err) => match err {
1174                sqlx::Error::RowNotFound => {
1175                    transaction.commit().await.map_err(Error::from)?;
1176
1177                    vec![]
1178                }
1179                _ => {
1180                    if let Err(err) = transaction.rollback().await {
1181                        tracing::error!("Could not rollback sql transaction: {}", err);
1182                    }
1183                    return Err(Error::SQLX(err).into());
1184                }
1185            },
1186        };
1187
1188        Ok(ys)
1189    }
1190
1191    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
1192        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1193
1194        let sql = format!(
1195            "SELECT y, state FROM proof WHERE y IN ({})",
1196            "?,".repeat(ys.len()).trim_end_matches(',')
1197        );
1198
1199        let mut current_states = ys
1200            .iter()
1201            .fold(sqlx::query(&sql), |query, y| {
1202                query.bind(y.to_bytes().to_vec())
1203            })
1204            .fetch_all(&mut *transaction)
1205            .await
1206            .map_err(|err| {
1207                tracing::error!("SQLite could not get state of proof: {err:?}");
1208                Error::SQLX(err)
1209            })?
1210            .into_iter()
1211            .map(|row| {
1212                PublicKey::from_slice(row.get("y"))
1213                    .map_err(Error::from)
1214                    .and_then(|y| {
1215                        let state: String = row.get("state");
1216                        State::from_str(&state)
1217                            .map_err(Error::from)
1218                            .map(|state| (y, state))
1219                    })
1220            })
1221            .collect::<Result<HashMap<_, _>, _>>()?;
1222
1223        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1224    }
1225
1226    async fn get_proofs_by_keyset_id(
1227        &self,
1228        keyset_id: &Id,
1229    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err> {
1230        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1231        let rec = sqlx::query(
1232            r#"
1233SELECT *
1234FROM proof
1235WHERE keyset_id=?;
1236        "#,
1237        )
1238        .bind(keyset_id.to_string())
1239        .fetch_all(&mut *transaction)
1240        .await;
1241
1242        match rec {
1243            Ok(rec) => {
1244                transaction.commit().await.map_err(Error::from)?;
1245                let mut proofs_for_id = vec![];
1246                let mut states = vec![];
1247
1248                for row in rec {
1249                    let (proof, state) = sqlite_row_to_proof_with_state(row)?;
1250
1251                    proofs_for_id.push(proof);
1252                    states.push(state);
1253                }
1254
1255                Ok((proofs_for_id, states))
1256            }
1257            Err(err) => {
1258                tracing::error!("SQLite could not get proofs by keysets id");
1259                if let Err(err) = transaction.rollback().await {
1260                    tracing::error!("Could not rollback sql transaction: {}", err);
1261                }
1262
1263                return Err(Error::from(err).into());
1264            }
1265        }
1266    }
1267
1268    async fn update_proofs_states(
1269        &self,
1270        ys: &[PublicKey],
1271        proofs_state: State,
1272    ) -> Result<Vec<Option<State>>, Self::Err> {
1273        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1274
1275        let sql = format!(
1276            "SELECT y, state FROM proof WHERE y IN ({})",
1277            "?,".repeat(ys.len()).trim_end_matches(',')
1278        );
1279
1280        let rows = ys
1281            .iter()
1282            .fold(sqlx::query(&sql), |query, y| {
1283                query.bind(y.to_bytes().to_vec())
1284            })
1285            .fetch_all(&mut *transaction)
1286            .await
1287            .map_err(|err| {
1288                tracing::error!("SQLite could not get state of proof: {err:?}");
1289                Error::SQLX(err)
1290            })?;
1291
1292        // Check if all proofs exist
1293        if rows.len() != ys.len() {
1294            transaction.rollback().await.map_err(Error::from)?;
1295            tracing::warn!("Attempted to update state of non-existent proof");
1296            return Err(database::Error::ProofNotFound);
1297        }
1298
1299        let mut current_states = rows
1300            .into_iter()
1301            .map(|row| {
1302                PublicKey::from_slice(row.get("y"))
1303                    .map_err(Error::from)
1304                    .and_then(|y| {
1305                        let state: String = row.get("state");
1306                        State::from_str(&state)
1307                            .map_err(Error::from)
1308                            .map(|state| (y, state))
1309                    })
1310            })
1311            .collect::<Result<HashMap<_, _>, _>>()?;
1312
1313        let states = current_states.values().collect::<HashSet<_>>();
1314
1315        for state in states {
1316            check_state_transition(*state, proofs_state)?;
1317        }
1318
1319        // If no proofs are spent, proceed with update
1320        let update_sql = format!(
1321            "UPDATE proof SET state = ? WHERE y IN ({})",
1322            "?,".repeat(ys.len()).trim_end_matches(',')
1323        );
1324
1325        ys.iter()
1326            .fold(
1327                sqlx::query(&update_sql).bind(proofs_state.to_string()),
1328                |query, y| query.bind(y.to_bytes().to_vec()),
1329            )
1330            .execute(&mut *transaction)
1331            .await
1332            .map_err(|err| {
1333                tracing::error!("SQLite could not update proof state: {err:?}");
1334                Error::SQLX(err)
1335            })?;
1336
1337        transaction.commit().await.map_err(Error::from)?;
1338
1339        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1340    }
1341}
1342
1343#[async_trait]
1344impl MintSignaturesDatabase for MintSqliteDatabase {
1345    type Err = database::Error;
1346
1347    async fn add_blind_signatures(
1348        &self,
1349        blinded_messages: &[PublicKey],
1350        blinded_signatures: &[BlindSignature],
1351        quote_id: Option<Uuid>,
1352    ) -> Result<(), Self::Err> {
1353        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1354        let current_time = unix_time();
1355
1356        for (message, signature) in blinded_messages.iter().zip(blinded_signatures) {
1357            let res = sqlx::query(
1358                r#"
1359INSERT INTO blind_signature
1360(y, amount, keyset_id, c, quote_id, dleq_e, dleq_s, created_time)
1361VALUES (?, ?, ?, ?, ?, ?, ?, ?);
1362        "#,
1363            )
1364            .bind(message.to_bytes().to_vec())
1365            .bind(u64::from(signature.amount) as i64)
1366            .bind(signature.keyset_id.to_string())
1367            .bind(signature.c.to_bytes().to_vec())
1368            .bind(quote_id.map(|q| q.hyphenated()))
1369            .bind(signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()))
1370            .bind(signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()))
1371            .bind(current_time as i64)
1372            .execute(&mut *transaction)
1373            .await;
1374
1375            if let Err(err) = res {
1376                tracing::error!("SQLite could not add blind signature");
1377                if let Err(err) = transaction.rollback().await {
1378                    tracing::error!("Could not rollback sql transaction: {}", err);
1379                }
1380                return Err(Error::SQLX(err).into());
1381            }
1382        }
1383
1384        transaction.commit().await.map_err(Error::from)?;
1385
1386        Ok(())
1387    }
1388
1389    async fn get_blind_signatures(
1390        &self,
1391        blinded_messages: &[PublicKey],
1392    ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
1393        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1394
1395        let sql = format!(
1396            "SELECT * FROM blind_signature WHERE y IN ({})",
1397            "?,".repeat(blinded_messages.len()).trim_end_matches(',')
1398        );
1399
1400        let mut blinded_signatures = blinded_messages
1401            .iter()
1402            .fold(sqlx::query(&sql), |query, y| {
1403                query.bind(y.to_bytes().to_vec())
1404            })
1405            .fetch_all(&mut *transaction)
1406            .await
1407            .map_err(|err| {
1408                tracing::error!("SQLite could not get state of proof: {err:?}");
1409                Error::SQLX(err)
1410            })?
1411            .into_iter()
1412            .map(|row| {
1413                PublicKey::from_slice(row.get("y"))
1414                    .map_err(Error::from)
1415                    .and_then(|y| sqlite_row_to_blind_signature(row).map(|blinded| (y, blinded)))
1416            })
1417            .collect::<Result<HashMap<_, _>, _>>()?;
1418
1419        Ok(blinded_messages
1420            .iter()
1421            .map(|y| blinded_signatures.remove(y))
1422            .collect())
1423    }
1424
1425    async fn get_blind_signatures_for_keyset(
1426        &self,
1427        keyset_id: &Id,
1428    ) -> Result<Vec<BlindSignature>, Self::Err> {
1429        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1430
1431        let rec = sqlx::query(
1432            r#"
1433SELECT *
1434FROM blind_signature
1435WHERE keyset_id=?;
1436        "#,
1437        )
1438        .bind(keyset_id.to_string())
1439        .fetch_all(&mut *transaction)
1440        .await;
1441
1442        match rec {
1443            Ok(rec) => {
1444                transaction.commit().await.map_err(Error::from)?;
1445                let sigs = rec
1446                    .into_iter()
1447                    .map(sqlite_row_to_blind_signature)
1448                    .collect::<Result<Vec<BlindSignature>, _>>()?;
1449
1450                Ok(sigs)
1451            }
1452            Err(err) => {
1453                tracing::error!("SQLite could not get vlinf signatures for keyset");
1454                if let Err(err) = transaction.rollback().await {
1455                    tracing::error!("Could not rollback sql transaction: {}", err);
1456                }
1457
1458                return Err(Error::from(err).into());
1459            }
1460        }
1461    }
1462
1463    /// Get [`BlindSignature`]s for quote
1464    async fn get_blind_signatures_for_quote(
1465        &self,
1466        quote_id: &Uuid,
1467    ) -> Result<Vec<BlindSignature>, Self::Err> {
1468        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1469
1470        let recs = sqlx::query(
1471            r#"
1472SELECT *
1473FROM blind_signature
1474WHERE quote_id=?;
1475        "#,
1476        )
1477        .bind(quote_id.as_hyphenated())
1478        .fetch_all(&mut *transaction)
1479        .await;
1480
1481        match recs {
1482            Ok(recs) => {
1483                transaction.commit().await.map_err(Error::from)?;
1484
1485                let keysets = recs
1486                    .into_iter()
1487                    .map(sqlite_row_to_blind_signature)
1488                    .collect::<Result<Vec<_>, _>>()?;
1489                Ok(keysets)
1490            }
1491            Err(err) => {
1492                tracing::error!("SQLite could not get active keyset");
1493                if let Err(err) = transaction.rollback().await {
1494                    tracing::error!("Could not rollback sql transaction: {}", err);
1495                }
1496                Err(Error::from(err).into())
1497            }
1498        }
1499    }
1500}
1501
1502#[async_trait]
1503impl MintDatabase<database::Error> for MintSqliteDatabase {
1504    async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), database::Error> {
1505        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1506
1507        let res = sqlx::query(
1508            r#"
1509INSERT INTO config
1510(id, value)
1511VALUES (?, ?)
1512ON CONFLICT(id) DO UPDATE SET
1513    value = excluded.value
1514;
1515        "#,
1516        )
1517        .bind("mint_info")
1518        .bind(serde_json::to_string(&mint_info)?)
1519        .execute(&mut *transaction)
1520        .await;
1521
1522        match res {
1523            Ok(_) => {
1524                transaction.commit().await.map_err(Error::from)?;
1525                Ok(())
1526            }
1527            Err(err) => {
1528                tracing::error!("SQLite Could not update mint info");
1529                if let Err(err) = transaction.rollback().await {
1530                    tracing::error!("Could not rollback sql transaction: {}", err);
1531                }
1532
1533                Err(Error::from(err).into())
1534            }
1535        }
1536    }
1537    async fn get_mint_info(&self) -> Result<MintInfo, database::Error> {
1538        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1539
1540        let rec = sqlx::query(
1541            r#"
1542SELECT *
1543FROM config
1544WHERE id=?;
1545        "#,
1546        )
1547        .bind("mint_info")
1548        .fetch_one(&mut *transaction)
1549        .await;
1550
1551        match rec {
1552            Ok(rec) => {
1553                transaction.commit().await.map_err(Error::from)?;
1554
1555                let value: String = rec.try_get("value").map_err(Error::from)?;
1556
1557                let mint_info = serde_json::from_str(&value)?;
1558
1559                Ok(mint_info)
1560            }
1561            Err(err) => match err {
1562                sqlx::Error::RowNotFound => {
1563                    transaction.commit().await.map_err(Error::from)?;
1564                    return Err(Error::UnknownMintInfo.into());
1565                }
1566                _ => {
1567                    return {
1568                        if let Err(err) = transaction.rollback().await {
1569                            tracing::error!("Could not rollback sql transaction: {}", err);
1570                        }
1571                        Err(Error::SQLX(err).into())
1572                    }
1573                }
1574            },
1575        }
1576    }
1577
1578    async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), database::Error> {
1579        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1580
1581        let res = sqlx::query(
1582            r#"
1583INSERT INTO config
1584(id, value)
1585VALUES (?, ?)
1586ON CONFLICT(id) DO UPDATE SET
1587    value = excluded.value
1588;
1589        "#,
1590        )
1591        .bind("quote_ttl")
1592        .bind(serde_json::to_string(&quote_ttl)?)
1593        .execute(&mut *transaction)
1594        .await;
1595
1596        match res {
1597            Ok(_) => {
1598                transaction.commit().await.map_err(Error::from)?;
1599                Ok(())
1600            }
1601            Err(err) => {
1602                tracing::error!("SQLite Could not update mint info");
1603                if let Err(err) = transaction.rollback().await {
1604                    tracing::error!("Could not rollback sql transaction: {}", err);
1605                }
1606
1607                Err(Error::from(err).into())
1608            }
1609        }
1610    }
1611    async fn get_quote_ttl(&self) -> Result<QuoteTTL, database::Error> {
1612        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1613
1614        let rec = sqlx::query(
1615            r#"
1616SELECT *
1617FROM config
1618WHERE id=?;
1619        "#,
1620        )
1621        .bind("quote_ttl")
1622        .fetch_one(&mut *transaction)
1623        .await;
1624
1625        match rec {
1626            Ok(rec) => {
1627                transaction.commit().await.map_err(Error::from)?;
1628
1629                let value: String = rec.try_get("value").map_err(Error::from)?;
1630
1631                let quote_ttl = serde_json::from_str(&value)?;
1632
1633                Ok(quote_ttl)
1634            }
1635            Err(err) => match err {
1636                sqlx::Error::RowNotFound => {
1637                    transaction.commit().await.map_err(Error::from)?;
1638                    return Err(Error::UnknownQuoteTTL.into());
1639                }
1640                _ => {
1641                    return {
1642                        if let Err(err) = transaction.rollback().await {
1643                            tracing::error!("Could not rollback sql transaction: {}", err);
1644                        }
1645                        Err(Error::SQLX(err).into())
1646                    }
1647                }
1648            },
1649        }
1650    }
1651}
1652
1653fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
1654    let row_id: String = row.try_get("id").map_err(Error::from)?;
1655    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1656    let row_active: bool = row.try_get("active").map_err(Error::from)?;
1657    let row_valid_from: i64 = row.try_get("valid_from").map_err(Error::from)?;
1658    let row_valid_to: Option<i64> = row.try_get("valid_to").map_err(Error::from)?;
1659    let row_derivation_path: String = row.try_get("derivation_path").map_err(Error::from)?;
1660    let row_max_order: u8 = row.try_get("max_order").map_err(Error::from)?;
1661    let row_keyset_ppk: Option<i64> = row.try_get("input_fee_ppk").ok();
1662    let row_derivation_path_index: Option<i64> =
1663        row.try_get("derivation_path_index").map_err(Error::from)?;
1664
1665    Ok(MintKeySetInfo {
1666        id: Id::from_str(&row_id).map_err(Error::from)?,
1667        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1668        active: row_active,
1669        valid_from: row_valid_from as u64,
1670        valid_to: row_valid_to.map(|v| v as u64),
1671        derivation_path: DerivationPath::from_str(&row_derivation_path).map_err(Error::from)?,
1672        derivation_path_index: row_derivation_path_index.map(|d| d as u32),
1673        max_order: row_max_order,
1674        input_fee_ppk: row_keyset_ppk.unwrap_or(0) as u64,
1675    })
1676}
1677
1678fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
1679    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1680    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1681    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1682    let row_request: String = row.try_get("request").map_err(Error::from)?;
1683    let row_state: String = row.try_get("state").map_err(Error::from)?;
1684    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1685    let row_request_lookup_id: Option<String> =
1686        row.try_get("request_lookup_id").map_err(Error::from)?;
1687    let row_pubkey: Option<String> = row.try_get("pubkey").map_err(Error::from)?;
1688
1689    let row_created_time: i64 = row.try_get("created_time").map_err(Error::from)?;
1690    let row_paid_time: Option<i64> = row.try_get("paid_time").map_err(Error::from)?;
1691    let row_issued_time: Option<i64> = row.try_get("issued_time").map_err(Error::from)?;
1692
1693    let request_lookup_id = match row_request_lookup_id {
1694        Some(id) => id,
1695        None => match Bolt11Invoice::from_str(&row_request) {
1696            Ok(invoice) => invoice.payment_hash().to_string(),
1697            Err(_) => row_request.clone(),
1698        },
1699    };
1700
1701    let pubkey = row_pubkey
1702        .map(|key| PublicKey::from_str(&key))
1703        .transpose()?;
1704
1705    Ok(MintQuote {
1706        id: row_id.into_uuid(),
1707        amount: Amount::from(row_amount as u64),
1708        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1709        request: row_request,
1710        state: MintQuoteState::from_str(&row_state).map_err(Error::from)?,
1711        expiry: row_expiry as u64,
1712        request_lookup_id,
1713        pubkey,
1714        created_time: row_created_time as u64,
1715        paid_time: row_paid_time.map(|p| p as u64),
1716        issued_time: row_issued_time.map(|p| p as u64),
1717    })
1718}
1719
1720fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
1721    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1722    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1723    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1724    let row_request: String = row.try_get("request").map_err(Error::from)?;
1725    let row_fee_reserve: i64 = row.try_get("fee_reserve").map_err(Error::from)?;
1726    let row_state: String = row.try_get("state").map_err(Error::from)?;
1727    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1728    let row_preimage: Option<String> = row.try_get("payment_preimage").map_err(Error::from)?;
1729    let row_request_lookup: Option<String> =
1730        row.try_get("request_lookup_id").map_err(Error::from)?;
1731
1732    let request_lookup_id = row_request_lookup.unwrap_or(row_request.clone());
1733
1734    let row_msat_to_pay: Option<i64> = row.try_get("msat_to_pay").map_err(Error::from)?;
1735
1736    let row_created_time: i64 = row.try_get("created_time").map_err(Error::from)?;
1737    let row_paid_time: Option<i64> = row.try_get("paid_time").map_err(Error::from)?;
1738
1739    Ok(mint::MeltQuote {
1740        id: row_id.into_uuid(),
1741        amount: Amount::from(row_amount as u64),
1742        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1743        request: row_request,
1744        fee_reserve: Amount::from(row_fee_reserve as u64),
1745        state: QuoteState::from_str(&row_state)?,
1746        expiry: row_expiry as u64,
1747        payment_preimage: row_preimage,
1748        request_lookup_id,
1749        msat_to_pay: row_msat_to_pay.map(|a| Amount::from(a as u64)),
1750        created_time: row_created_time as u64,
1751        paid_time: row_paid_time.map(|p| p as u64),
1752    })
1753}
1754
1755fn sqlite_row_to_proof(row: SqliteRow) -> Result<Proof, Error> {
1756    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1757    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1758    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1759    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1760    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1761
1762    Ok(Proof {
1763        amount: Amount::from(row_amount as u64),
1764        keyset_id: Id::from_str(&keyset_id)?,
1765        secret: Secret::from_str(&row_secret)?,
1766        c: PublicKey::from_slice(&row_c)?,
1767        witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1768        dleq: None,
1769    })
1770}
1771
1772fn sqlite_row_to_proof_with_state(row: SqliteRow) -> Result<(Proof, Option<State>), Error> {
1773    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1774    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1775    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1776    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1777    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1778
1779    let row_state: Option<String> = row.try_get("state").map_err(Error::from)?;
1780
1781    let state = row_state.and_then(|s| State::from_str(&s).ok());
1782
1783    Ok((
1784        Proof {
1785            amount: Amount::from(row_amount as u64),
1786            keyset_id: Id::from_str(&keyset_id)?,
1787            secret: Secret::from_str(&row_secret)?,
1788            c: PublicKey::from_slice(&row_c)?,
1789            witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1790            dleq: None,
1791        },
1792        state,
1793    ))
1794}
1795
1796fn sqlite_row_to_blind_signature(row: SqliteRow) -> Result<BlindSignature, Error> {
1797    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1798    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1799    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1800    let row_dleq_e: Option<String> = row.try_get("dleq_e").map_err(Error::from)?;
1801    let row_dleq_s: Option<String> = row.try_get("dleq_s").map_err(Error::from)?;
1802
1803    let dleq = match (row_dleq_e, row_dleq_s) {
1804        (Some(e), Some(s)) => Some(BlindSignatureDleq {
1805            e: SecretKey::from_hex(e)?,
1806            s: SecretKey::from_hex(s)?,
1807        }),
1808        _ => None,
1809    };
1810
1811    Ok(BlindSignature {
1812        amount: Amount::from(row_amount as u64),
1813        keyset_id: Id::from_str(&keyset_id)?,
1814        c: PublicKey::from_slice(&row_c)?,
1815        dleq,
1816    })
1817}
1818
1819fn sqlite_row_to_melt_request(
1820    row: SqliteRow,
1821) -> Result<(MeltBolt11Request<Uuid>, PaymentProcessorKey), Error> {
1822    let quote_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1823    let row_inputs: String = row.try_get("inputs").map_err(Error::from)?;
1824    let row_outputs: Option<String> = row.try_get("outputs").map_err(Error::from)?;
1825    let row_method: String = row.try_get("method").map_err(Error::from)?;
1826    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1827
1828    let melt_request = MeltBolt11Request::new(
1829        quote_id.into_uuid(),
1830        serde_json::from_str(&row_inputs)?,
1831        row_outputs.and_then(|o| serde_json::from_str(&o).ok()),
1832    );
1833
1834    let ln_key = PaymentProcessorKey {
1835        unit: CurrencyUnit::from_str(&row_unit)?,
1836        method: PaymentMethod::from_str(&row_method)?,
1837    };
1838
1839    Ok((melt_request, ln_key))
1840}
1841
1842#[cfg(test)]
1843mod tests {
1844    use cdk_common::mint::MintKeySetInfo;
1845    use cdk_common::{mint_db_test, Amount};
1846
1847    use super::*;
1848
1849    #[tokio::test]
1850    async fn test_remove_spent_proofs() {
1851        let db = memory::empty().await.unwrap();
1852
1853        // Create a keyset and add it to the database
1854        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1855        let keyset_info = MintKeySetInfo {
1856            id: keyset_id,
1857            unit: CurrencyUnit::Sat,
1858            active: true,
1859            valid_from: 0,
1860            valid_to: None,
1861            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1862            derivation_path_index: Some(0),
1863            max_order: 32,
1864            input_fee_ppk: 0,
1865        };
1866        db.add_keyset_info(keyset_info).await.unwrap();
1867
1868        let proofs = vec![
1869            Proof {
1870                amount: Amount::from(100),
1871                keyset_id,
1872                secret: Secret::generate(),
1873                c: SecretKey::generate().public_key(),
1874                witness: None,
1875                dleq: None,
1876            },
1877            Proof {
1878                amount: Amount::from(200),
1879                keyset_id,
1880                secret: Secret::generate(),
1881                c: SecretKey::generate().public_key(),
1882                witness: None,
1883                dleq: None,
1884            },
1885        ];
1886
1887        // Add proofs to database
1888        db.add_proofs(proofs.clone(), None).await.unwrap();
1889
1890        // Mark one proof as spent
1891        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1892            .await
1893            .unwrap();
1894
1895        // Try to remove both proofs - should fail because one is spent
1896        let result = db
1897            .remove_proofs(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()], None)
1898            .await;
1899
1900        assert!(result.is_err());
1901        assert!(matches!(
1902            result.unwrap_err(),
1903            database::Error::AttemptRemoveSpentProof
1904        ));
1905
1906        // Verify both proofs still exist
1907        let states = db
1908            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1909            .await
1910            .unwrap();
1911
1912        assert_eq!(states.len(), 2);
1913        assert_eq!(states[0], Some(State::Spent));
1914        assert_eq!(states[1], Some(State::Unspent));
1915    }
1916
1917    #[tokio::test]
1918    async fn test_update_spent_proofs() {
1919        let db = memory::empty().await.unwrap();
1920
1921        // Create a keyset and add it to the database
1922        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1923        let keyset_info = MintKeySetInfo {
1924            id: keyset_id,
1925            unit: CurrencyUnit::Sat,
1926            active: true,
1927            valid_from: 0,
1928            valid_to: None,
1929            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1930            derivation_path_index: Some(0),
1931            max_order: 32,
1932            input_fee_ppk: 0,
1933        };
1934        db.add_keyset_info(keyset_info).await.unwrap();
1935
1936        let proofs = vec![
1937            Proof {
1938                amount: Amount::from(100),
1939                keyset_id,
1940                secret: Secret::generate(),
1941                c: SecretKey::generate().public_key(),
1942                witness: None,
1943                dleq: None,
1944            },
1945            Proof {
1946                amount: Amount::from(200),
1947                keyset_id,
1948                secret: Secret::generate(),
1949                c: SecretKey::generate().public_key(),
1950                witness: None,
1951                dleq: None,
1952            },
1953        ];
1954
1955        // Add proofs to database
1956        db.add_proofs(proofs.clone(), None).await.unwrap();
1957
1958        // Mark one proof as spent
1959        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1960            .await
1961            .unwrap();
1962
1963        // Try to update both proofs - should fail because one is spent
1964        let result = db
1965            .update_proofs_states(&[proofs[0].y().unwrap()], State::Unspent)
1966            .await;
1967
1968        assert!(result.is_err());
1969        assert!(matches!(
1970            result.unwrap_err(),
1971            database::Error::AttemptUpdateSpentProof
1972        ));
1973
1974        // Verify states haven't changed
1975        let states = db
1976            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1977            .await
1978            .unwrap();
1979
1980        assert_eq!(states.len(), 2);
1981        assert_eq!(states[0], Some(State::Spent));
1982        assert_eq!(states[1], Some(State::Unspent));
1983    }
1984
1985    async fn provide_db() -> MintSqliteDatabase {
1986        memory::empty().await.unwrap()
1987    }
1988
1989    mint_db_test!(provide_db);
1990}