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::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#[derive(Debug, Clone)]
43pub struct MintSqliteDatabase {
44 pool: Pool<Sqlite>,
45}
46
47impl MintSqliteDatabase {
48 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 #[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 #[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 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 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 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 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 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 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 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("e_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 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 db.add_proofs(proofs.clone(), None).await.unwrap();
1890
1891 db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1893 .await
1894 .unwrap();
1895
1896 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 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 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 db.add_proofs(proofs.clone(), None).await.unwrap();
1958
1959 db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1961 .await
1962 .unwrap();
1963
1964 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 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}