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