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