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::util::unix_time;
19use cdk_common::{
20    Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltBolt11Request,
21    MeltQuoteState, MintInfo, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey,
22    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: MeltBolt11Request<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<(MeltBolt11Request<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        if states.contains(&State::Spent) {
1315            transaction.rollback().await.map_err(Error::from)?;
1316            tracing::warn!("Attempted to update state of spent proof");
1317            return Err(database::Error::AttemptUpdateSpentProof);
1318        }
1319
1320        // If no proofs are spent, proceed with update
1321        let update_sql = format!(
1322            "UPDATE proof SET state = ? WHERE y IN ({})",
1323            "?,".repeat(ys.len()).trim_end_matches(',')
1324        );
1325
1326        ys.iter()
1327            .fold(
1328                sqlx::query(&update_sql).bind(proofs_state.to_string()),
1329                |query, y| query.bind(y.to_bytes().to_vec()),
1330            )
1331            .execute(&mut *transaction)
1332            .await
1333            .map_err(|err| {
1334                tracing::error!("SQLite could not update proof state: {err:?}");
1335                Error::SQLX(err)
1336            })?;
1337
1338        transaction.commit().await.map_err(Error::from)?;
1339
1340        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1341    }
1342}
1343
1344#[async_trait]
1345impl MintSignaturesDatabase for MintSqliteDatabase {
1346    type Err = database::Error;
1347
1348    async fn add_blind_signatures(
1349        &self,
1350        blinded_messages: &[PublicKey],
1351        blinded_signatures: &[BlindSignature],
1352        quote_id: Option<Uuid>,
1353    ) -> Result<(), Self::Err> {
1354        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1355        let current_time = unix_time();
1356
1357        for (message, signature) in blinded_messages.iter().zip(blinded_signatures) {
1358            let res = sqlx::query(
1359                r#"
1360INSERT INTO blind_signature
1361(y, amount, keyset_id, c, quote_id, dleq_e, dleq_s, created_time)
1362VALUES (?, ?, ?, ?, ?, ?, ?, ?);
1363        "#,
1364            )
1365            .bind(message.to_bytes().to_vec())
1366            .bind(u64::from(signature.amount) as i64)
1367            .bind(signature.keyset_id.to_string())
1368            .bind(signature.c.to_bytes().to_vec())
1369            .bind(quote_id.map(|q| q.hyphenated()))
1370            .bind(signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()))
1371            .bind(signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()))
1372            .bind(current_time as i64)
1373            .execute(&mut *transaction)
1374            .await;
1375
1376            if let Err(err) = res {
1377                tracing::error!("SQLite could not add blind signature");
1378                if let Err(err) = transaction.rollback().await {
1379                    tracing::error!("Could not rollback sql transaction: {}", err);
1380                }
1381                return Err(Error::SQLX(err).into());
1382            }
1383        }
1384
1385        transaction.commit().await.map_err(Error::from)?;
1386
1387        Ok(())
1388    }
1389
1390    async fn get_blind_signatures(
1391        &self,
1392        blinded_messages: &[PublicKey],
1393    ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
1394        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1395
1396        let sql = format!(
1397            "SELECT * FROM blind_signature WHERE y IN ({})",
1398            "?,".repeat(blinded_messages.len()).trim_end_matches(',')
1399        );
1400
1401        let mut blinded_signatures = blinded_messages
1402            .iter()
1403            .fold(sqlx::query(&sql), |query, y| {
1404                query.bind(y.to_bytes().to_vec())
1405            })
1406            .fetch_all(&mut *transaction)
1407            .await
1408            .map_err(|err| {
1409                tracing::error!("SQLite could not get state of proof: {err:?}");
1410                Error::SQLX(err)
1411            })?
1412            .into_iter()
1413            .map(|row| {
1414                PublicKey::from_slice(row.get("y"))
1415                    .map_err(Error::from)
1416                    .and_then(|y| sqlite_row_to_blind_signature(row).map(|blinded| (y, blinded)))
1417            })
1418            .collect::<Result<HashMap<_, _>, _>>()?;
1419
1420        Ok(blinded_messages
1421            .iter()
1422            .map(|y| blinded_signatures.remove(y))
1423            .collect())
1424    }
1425
1426    async fn get_blind_signatures_for_keyset(
1427        &self,
1428        keyset_id: &Id,
1429    ) -> Result<Vec<BlindSignature>, Self::Err> {
1430        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1431
1432        let rec = sqlx::query(
1433            r#"
1434SELECT *
1435FROM blind_signature
1436WHERE keyset_id=?;
1437        "#,
1438        )
1439        .bind(keyset_id.to_string())
1440        .fetch_all(&mut *transaction)
1441        .await;
1442
1443        match rec {
1444            Ok(rec) => {
1445                transaction.commit().await.map_err(Error::from)?;
1446                let sigs = rec
1447                    .into_iter()
1448                    .map(sqlite_row_to_blind_signature)
1449                    .collect::<Result<Vec<BlindSignature>, _>>()?;
1450
1451                Ok(sigs)
1452            }
1453            Err(err) => {
1454                tracing::error!("SQLite could not get vlinf signatures for keyset");
1455                if let Err(err) = transaction.rollback().await {
1456                    tracing::error!("Could not rollback sql transaction: {}", err);
1457                }
1458
1459                return Err(Error::from(err).into());
1460            }
1461        }
1462    }
1463
1464    /// Get [`BlindSignature`]s for quote
1465    async fn get_blind_signatures_for_quote(
1466        &self,
1467        quote_id: &Uuid,
1468    ) -> Result<Vec<BlindSignature>, Self::Err> {
1469        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1470
1471        let recs = sqlx::query(
1472            r#"
1473SELECT *
1474FROM blind_signature
1475WHERE quote_id=?;
1476        "#,
1477        )
1478        .bind(quote_id.as_hyphenated())
1479        .fetch_all(&mut *transaction)
1480        .await;
1481
1482        match recs {
1483            Ok(recs) => {
1484                transaction.commit().await.map_err(Error::from)?;
1485
1486                let keysets = recs
1487                    .into_iter()
1488                    .map(sqlite_row_to_blind_signature)
1489                    .collect::<Result<Vec<_>, _>>()?;
1490                Ok(keysets)
1491            }
1492            Err(err) => {
1493                tracing::error!("SQLite could not get active keyset");
1494                if let Err(err) = transaction.rollback().await {
1495                    tracing::error!("Could not rollback sql transaction: {}", err);
1496                }
1497                Err(Error::from(err).into())
1498            }
1499        }
1500    }
1501}
1502
1503#[async_trait]
1504impl MintDatabase<database::Error> for MintSqliteDatabase {
1505    async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), database::Error> {
1506        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1507
1508        let res = sqlx::query(
1509            r#"
1510INSERT INTO config
1511(id, value)
1512VALUES (?, ?)
1513ON CONFLICT(id) DO UPDATE SET
1514    value = excluded.value
1515;
1516        "#,
1517        )
1518        .bind("mint_info")
1519        .bind(serde_json::to_string(&mint_info)?)
1520        .execute(&mut *transaction)
1521        .await;
1522
1523        match res {
1524            Ok(_) => {
1525                transaction.commit().await.map_err(Error::from)?;
1526                Ok(())
1527            }
1528            Err(err) => {
1529                tracing::error!("SQLite Could not update mint info");
1530                if let Err(err) = transaction.rollback().await {
1531                    tracing::error!("Could not rollback sql transaction: {}", err);
1532                }
1533
1534                Err(Error::from(err).into())
1535            }
1536        }
1537    }
1538    async fn get_mint_info(&self) -> Result<MintInfo, database::Error> {
1539        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1540
1541        let rec = sqlx::query(
1542            r#"
1543SELECT *
1544FROM config
1545WHERE id=?;
1546        "#,
1547        )
1548        .bind("mint_info")
1549        .fetch_one(&mut *transaction)
1550        .await;
1551
1552        match rec {
1553            Ok(rec) => {
1554                transaction.commit().await.map_err(Error::from)?;
1555
1556                let value: String = rec.try_get("value").map_err(Error::from)?;
1557
1558                let mint_info = serde_json::from_str(&value)?;
1559
1560                Ok(mint_info)
1561            }
1562            Err(err) => match err {
1563                sqlx::Error::RowNotFound => {
1564                    transaction.commit().await.map_err(Error::from)?;
1565                    return Err(Error::UnknownMintInfo.into());
1566                }
1567                _ => {
1568                    return {
1569                        if let Err(err) = transaction.rollback().await {
1570                            tracing::error!("Could not rollback sql transaction: {}", err);
1571                        }
1572                        Err(Error::SQLX(err).into())
1573                    }
1574                }
1575            },
1576        }
1577    }
1578
1579    async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), database::Error> {
1580        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1581
1582        let res = sqlx::query(
1583            r#"
1584INSERT INTO config
1585(id, value)
1586VALUES (?, ?)
1587ON CONFLICT(id) DO UPDATE SET
1588    value = excluded.value
1589;
1590        "#,
1591        )
1592        .bind("quote_ttl")
1593        .bind(serde_json::to_string(&quote_ttl)?)
1594        .execute(&mut *transaction)
1595        .await;
1596
1597        match res {
1598            Ok(_) => {
1599                transaction.commit().await.map_err(Error::from)?;
1600                Ok(())
1601            }
1602            Err(err) => {
1603                tracing::error!("SQLite Could not update mint info");
1604                if let Err(err) = transaction.rollback().await {
1605                    tracing::error!("Could not rollback sql transaction: {}", err);
1606                }
1607
1608                Err(Error::from(err).into())
1609            }
1610        }
1611    }
1612    async fn get_quote_ttl(&self) -> Result<QuoteTTL, database::Error> {
1613        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1614
1615        let rec = sqlx::query(
1616            r#"
1617SELECT *
1618FROM config
1619WHERE id=?;
1620        "#,
1621        )
1622        .bind("quote_ttl")
1623        .fetch_one(&mut *transaction)
1624        .await;
1625
1626        match rec {
1627            Ok(rec) => {
1628                transaction.commit().await.map_err(Error::from)?;
1629
1630                let value: String = rec.try_get("value").map_err(Error::from)?;
1631
1632                let quote_ttl = serde_json::from_str(&value)?;
1633
1634                Ok(quote_ttl)
1635            }
1636            Err(err) => match err {
1637                sqlx::Error::RowNotFound => {
1638                    transaction.commit().await.map_err(Error::from)?;
1639                    return Err(Error::UnknownQuoteTTL.into());
1640                }
1641                _ => {
1642                    return {
1643                        if let Err(err) = transaction.rollback().await {
1644                            tracing::error!("Could not rollback sql transaction: {}", err);
1645                        }
1646                        Err(Error::SQLX(err).into())
1647                    }
1648                }
1649            },
1650        }
1651    }
1652}
1653
1654fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
1655    let row_id: String = row.try_get("id").map_err(Error::from)?;
1656    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1657    let row_active: bool = row.try_get("active").map_err(Error::from)?;
1658    let row_valid_from: i64 = row.try_get("valid_from").map_err(Error::from)?;
1659    let row_valid_to: Option<i64> = row.try_get("valid_to").map_err(Error::from)?;
1660    let row_derivation_path: String = row.try_get("derivation_path").map_err(Error::from)?;
1661    let row_max_order: u8 = row.try_get("max_order").map_err(Error::from)?;
1662    let row_keyset_ppk: Option<i64> = row.try_get("input_fee_ppk").ok();
1663    let row_derivation_path_index: Option<i64> =
1664        row.try_get("derivation_path_index").map_err(Error::from)?;
1665
1666    Ok(MintKeySetInfo {
1667        id: Id::from_str(&row_id).map_err(Error::from)?,
1668        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1669        active: row_active,
1670        valid_from: row_valid_from as u64,
1671        valid_to: row_valid_to.map(|v| v as u64),
1672        derivation_path: DerivationPath::from_str(&row_derivation_path).map_err(Error::from)?,
1673        derivation_path_index: row_derivation_path_index.map(|d| d as u32),
1674        max_order: row_max_order,
1675        input_fee_ppk: row_keyset_ppk.unwrap_or(0) as u64,
1676    })
1677}
1678
1679fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
1680    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1681    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1682    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1683    let row_request: String = row.try_get("request").map_err(Error::from)?;
1684    let row_state: String = row.try_get("state").map_err(Error::from)?;
1685    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1686    let row_request_lookup_id: Option<String> =
1687        row.try_get("request_lookup_id").map_err(Error::from)?;
1688    let row_pubkey: Option<String> = row.try_get("pubkey").map_err(Error::from)?;
1689
1690    let row_created_time: i64 = row.try_get("created_time").map_err(Error::from)?;
1691    let row_paid_time: Option<i64> = row.try_get("paid_time").map_err(Error::from)?;
1692    let row_issued_time: Option<i64> = row.try_get("issued_time").map_err(Error::from)?;
1693
1694    let request_lookup_id = match row_request_lookup_id {
1695        Some(id) => id,
1696        None => match Bolt11Invoice::from_str(&row_request) {
1697            Ok(invoice) => invoice.payment_hash().to_string(),
1698            Err(_) => row_request.clone(),
1699        },
1700    };
1701
1702    let pubkey = row_pubkey
1703        .map(|key| PublicKey::from_str(&key))
1704        .transpose()?;
1705
1706    Ok(MintQuote {
1707        id: row_id.into_uuid(),
1708        amount: Amount::from(row_amount as u64),
1709        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1710        request: row_request,
1711        state: MintQuoteState::from_str(&row_state).map_err(Error::from)?,
1712        expiry: row_expiry as u64,
1713        request_lookup_id,
1714        pubkey,
1715        created_time: row_created_time as u64,
1716        paid_time: row_paid_time.map(|p| p as u64),
1717        issued_time: row_issued_time.map(|p| p as u64),
1718    })
1719}
1720
1721fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
1722    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1723    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1724    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1725    let row_request: String = row.try_get("request").map_err(Error::from)?;
1726    let row_fee_reserve: i64 = row.try_get("fee_reserve").map_err(Error::from)?;
1727    let row_state: String = row.try_get("state").map_err(Error::from)?;
1728    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1729    let row_preimage: Option<String> = row.try_get("payment_preimage").map_err(Error::from)?;
1730    let row_request_lookup: Option<String> =
1731        row.try_get("request_lookup_id").map_err(Error::from)?;
1732
1733    let request_lookup_id = row_request_lookup.unwrap_or(row_request.clone());
1734
1735    let row_msat_to_pay: Option<i64> = row.try_get("msat_to_pay").map_err(Error::from)?;
1736
1737    let row_created_time: i64 = row.try_get("created_time").map_err(Error::from)?;
1738    let row_paid_time: Option<i64> = row.try_get("paid_time").map_err(Error::from)?;
1739
1740    Ok(mint::MeltQuote {
1741        id: row_id.into_uuid(),
1742        amount: Amount::from(row_amount as u64),
1743        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1744        request: row_request,
1745        fee_reserve: Amount::from(row_fee_reserve as u64),
1746        state: QuoteState::from_str(&row_state)?,
1747        expiry: row_expiry as u64,
1748        payment_preimage: row_preimage,
1749        request_lookup_id,
1750        msat_to_pay: row_msat_to_pay.map(|a| Amount::from(a as u64)),
1751        created_time: row_created_time as u64,
1752        paid_time: row_paid_time.map(|p| p as u64),
1753    })
1754}
1755
1756fn sqlite_row_to_proof(row: SqliteRow) -> Result<Proof, Error> {
1757    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1758    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1759    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1760    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1761    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1762
1763    Ok(Proof {
1764        amount: Amount::from(row_amount as u64),
1765        keyset_id: Id::from_str(&keyset_id)?,
1766        secret: Secret::from_str(&row_secret)?,
1767        c: PublicKey::from_slice(&row_c)?,
1768        witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1769        dleq: None,
1770    })
1771}
1772
1773fn sqlite_row_to_proof_with_state(row: SqliteRow) -> Result<(Proof, Option<State>), Error> {
1774    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1775    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1776    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1777    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1778    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1779
1780    let row_state: Option<String> = row.try_get("state").map_err(Error::from)?;
1781
1782    let state = row_state.and_then(|s| State::from_str(&s).ok());
1783
1784    Ok((
1785        Proof {
1786            amount: Amount::from(row_amount as u64),
1787            keyset_id: Id::from_str(&keyset_id)?,
1788            secret: Secret::from_str(&row_secret)?,
1789            c: PublicKey::from_slice(&row_c)?,
1790            witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1791            dleq: None,
1792        },
1793        state,
1794    ))
1795}
1796
1797fn sqlite_row_to_blind_signature(row: SqliteRow) -> Result<BlindSignature, Error> {
1798    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1799    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1800    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1801    let row_dleq_e: Option<String> = row.try_get("dleq_e").map_err(Error::from)?;
1802    let row_dleq_s: Option<String> = row.try_get("dleq_s").map_err(Error::from)?;
1803
1804    let dleq = match (row_dleq_e, row_dleq_s) {
1805        (Some(e), Some(s)) => Some(BlindSignatureDleq {
1806            e: SecretKey::from_hex(e)?,
1807            s: SecretKey::from_hex(s)?,
1808        }),
1809        _ => None,
1810    };
1811
1812    Ok(BlindSignature {
1813        amount: Amount::from(row_amount as u64),
1814        keyset_id: Id::from_str(&keyset_id)?,
1815        c: PublicKey::from_slice(&row_c)?,
1816        dleq,
1817    })
1818}
1819
1820fn sqlite_row_to_melt_request(
1821    row: SqliteRow,
1822) -> Result<(MeltBolt11Request<Uuid>, PaymentProcessorKey), Error> {
1823    let quote_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1824    let row_inputs: String = row.try_get("inputs").map_err(Error::from)?;
1825    let row_outputs: Option<String> = row.try_get("outputs").map_err(Error::from)?;
1826    let row_method: String = row.try_get("method").map_err(Error::from)?;
1827    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1828
1829    let melt_request = MeltBolt11Request::new(
1830        quote_id.into_uuid(),
1831        serde_json::from_str(&row_inputs)?,
1832        row_outputs.and_then(|o| serde_json::from_str(&o).ok()),
1833    );
1834
1835    let ln_key = PaymentProcessorKey {
1836        unit: CurrencyUnit::from_str(&row_unit)?,
1837        method: PaymentMethod::from_str(&row_method)?,
1838    };
1839
1840    Ok((melt_request, ln_key))
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845    use cdk_common::mint::MintKeySetInfo;
1846    use cdk_common::Amount;
1847
1848    use super::*;
1849
1850    #[tokio::test]
1851    async fn test_remove_spent_proofs() {
1852        let db = memory::empty().await.unwrap();
1853
1854        // Create a keyset and add it to the database
1855        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1856        let keyset_info = MintKeySetInfo {
1857            id: keyset_id,
1858            unit: CurrencyUnit::Sat,
1859            active: true,
1860            valid_from: 0,
1861            valid_to: None,
1862            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1863            derivation_path_index: Some(0),
1864            max_order: 32,
1865            input_fee_ppk: 0,
1866        };
1867        db.add_keyset_info(keyset_info).await.unwrap();
1868
1869        let proofs = vec![
1870            Proof {
1871                amount: Amount::from(100),
1872                keyset_id,
1873                secret: Secret::generate(),
1874                c: SecretKey::generate().public_key(),
1875                witness: None,
1876                dleq: None,
1877            },
1878            Proof {
1879                amount: Amount::from(200),
1880                keyset_id,
1881                secret: Secret::generate(),
1882                c: SecretKey::generate().public_key(),
1883                witness: None,
1884                dleq: None,
1885            },
1886        ];
1887
1888        // Add proofs to database
1889        db.add_proofs(proofs.clone(), None).await.unwrap();
1890
1891        // Mark one proof as spent
1892        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1893            .await
1894            .unwrap();
1895
1896        // Try to remove both proofs - should fail because one is spent
1897        let result = db
1898            .remove_proofs(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()], None)
1899            .await;
1900
1901        assert!(result.is_err());
1902        assert!(matches!(
1903            result.unwrap_err(),
1904            database::Error::AttemptRemoveSpentProof
1905        ));
1906
1907        // Verify both proofs still exist
1908        let states = db
1909            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1910            .await
1911            .unwrap();
1912
1913        assert_eq!(states.len(), 2);
1914        assert_eq!(states[0], Some(State::Spent));
1915        assert_eq!(states[1], Some(State::Unspent));
1916    }
1917
1918    #[tokio::test]
1919    async fn test_update_spent_proofs() {
1920        let db = memory::empty().await.unwrap();
1921
1922        // Create a keyset and add it to the database
1923        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1924        let keyset_info = MintKeySetInfo {
1925            id: keyset_id,
1926            unit: CurrencyUnit::Sat,
1927            active: true,
1928            valid_from: 0,
1929            valid_to: None,
1930            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1931            derivation_path_index: Some(0),
1932            max_order: 32,
1933            input_fee_ppk: 0,
1934        };
1935        db.add_keyset_info(keyset_info).await.unwrap();
1936
1937        let proofs = vec![
1938            Proof {
1939                amount: Amount::from(100),
1940                keyset_id,
1941                secret: Secret::generate(),
1942                c: SecretKey::generate().public_key(),
1943                witness: None,
1944                dleq: None,
1945            },
1946            Proof {
1947                amount: Amount::from(200),
1948                keyset_id,
1949                secret: Secret::generate(),
1950                c: SecretKey::generate().public_key(),
1951                witness: None,
1952                dleq: None,
1953            },
1954        ];
1955
1956        // Add proofs to database
1957        db.add_proofs(proofs.clone(), None).await.unwrap();
1958
1959        // Mark one proof as spent
1960        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1961            .await
1962            .unwrap();
1963
1964        // Try to update both proofs - should fail because one is spent
1965        let result = db
1966            .update_proofs_states(
1967                &[proofs[0].y().unwrap(), proofs[1].y().unwrap()],
1968                State::Reserved,
1969            )
1970            .await;
1971
1972        assert!(result.is_err());
1973        assert!(matches!(
1974            result.unwrap_err(),
1975            database::Error::AttemptUpdateSpentProof
1976        ));
1977
1978        // Verify states haven't changed
1979        let states = db
1980            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1981            .await
1982            .unwrap();
1983
1984        assert_eq!(states.len(), 2);
1985        assert_eq!(states[0], Some(State::Spent));
1986        assert_eq!(states[1], Some(State::Unspent));
1987    }
1988}