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