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