mod.rs 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187
  1. //! SQLite Wallet Database
  2. use std::collections::HashMap;
  3. use std::str::FromStr;
  4. use async_trait::async_trait;
  5. use cdk_common::common::ProofInfo;
  6. use cdk_common::database::{ConversionError, Error, WalletDatabase};
  7. use cdk_common::mint_url::MintUrl;
  8. use cdk_common::nuts::{MeltQuoteState, MintQuoteState};
  9. use cdk_common::secret::Secret;
  10. use cdk_common::wallet::{self, MintQuote, Transaction, TransactionDirection, TransactionId};
  11. use cdk_common::{
  12. database, Amount, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, MintInfo, PaymentMethod, Proof,
  13. ProofDleq, PublicKey, SecretKey, SpendingConditions, State,
  14. };
  15. use tracing::instrument;
  16. use crate::common::migrate;
  17. use crate::database::DatabaseExecutor;
  18. use crate::stmt::{query, Column};
  19. use crate::{
  20. column_as_binary, column_as_nullable_binary, column_as_nullable_number,
  21. column_as_nullable_string, column_as_number, column_as_string, unpack_into,
  22. };
  23. #[rustfmt::skip]
  24. mod migrations;
  25. /// Wallet SQLite Database
  26. #[derive(Debug, Clone)]
  27. pub struct SQLWalletDatabase<T>
  28. where
  29. T: DatabaseExecutor,
  30. {
  31. db: T,
  32. }
  33. impl<DB> SQLWalletDatabase<DB>
  34. where
  35. DB: DatabaseExecutor,
  36. {
  37. /// Creates a new instance
  38. pub async fn new<X>(db: X) -> Result<Self, Error>
  39. where
  40. X: Into<DB>,
  41. {
  42. let db = db.into();
  43. Self::migrate(&db).await?;
  44. Ok(Self { db })
  45. }
  46. /// Migrate [`WalletSqliteDatabase`]
  47. async fn migrate(conn: &DB) -> Result<(), Error> {
  48. migrate(conn, DB::name(), migrations::MIGRATIONS).await?;
  49. // Update any existing keys with missing keyset_u32 values
  50. Self::add_keyset_u32(conn).await?;
  51. Ok(())
  52. }
  53. async fn add_keyset_u32(conn: &DB) -> Result<(), Error> {
  54. // First get the keysets where keyset_u32 on key is null
  55. let keys_without_u32: Vec<Vec<Column>> = query(
  56. r#"
  57. SELECT
  58. id
  59. FROM key
  60. WHERE keyset_u32 IS NULL
  61. "#,
  62. )?
  63. .fetch_all(conn)
  64. .await?;
  65. for id in keys_without_u32 {
  66. let id = column_as_string!(id.first().unwrap());
  67. if let Ok(id) = Id::from_str(&id) {
  68. query(
  69. r#"
  70. UPDATE
  71. key
  72. SET keyset_u32 = :u32_keyset
  73. WHERE id = :keyset_id
  74. "#,
  75. )?
  76. .bind("u32_keyset", u32::from(id))
  77. .bind("keyset_id", id.to_string())
  78. .execute(conn)
  79. .await?;
  80. }
  81. }
  82. // Also update keysets where keyset_u32 is null
  83. let keysets_without_u32: Vec<Vec<Column>> = query(
  84. r#"
  85. SELECT
  86. id
  87. FROM keyset
  88. WHERE keyset_u32 IS NULL
  89. "#,
  90. )?
  91. .fetch_all(conn)
  92. .await?;
  93. for id in keysets_without_u32 {
  94. let id = column_as_string!(id.first().unwrap());
  95. if let Ok(id) = Id::from_str(&id) {
  96. query(
  97. r#"
  98. UPDATE
  99. keyset
  100. SET keyset_u32 = :u32_keyset
  101. WHERE id = :keyset_id
  102. "#,
  103. )?
  104. .bind("u32_keyset", u32::from(id))
  105. .bind("keyset_id", id.to_string())
  106. .execute(conn)
  107. .await?;
  108. }
  109. }
  110. Ok(())
  111. }
  112. }
  113. #[async_trait]
  114. impl<T> WalletDatabase for SQLWalletDatabase<T>
  115. where
  116. T: DatabaseExecutor,
  117. {
  118. type Err = database::Error;
  119. #[instrument(skip(self))]
  120. async fn get_melt_quotes(&self) -> Result<Vec<wallet::MeltQuote>, Self::Err> {
  121. Ok(query(
  122. r#"
  123. SELECT
  124. id,
  125. unit,
  126. amount,
  127. request,
  128. fee_reserve,
  129. state,
  130. expiry,
  131. payment_preimage
  132. FROM
  133. melt_quote
  134. "#,
  135. )?
  136. .fetch_all(&self.db)
  137. .await?
  138. .into_iter()
  139. .map(sql_row_to_melt_quote)
  140. .collect::<Result<_, _>>()?)
  141. }
  142. #[instrument(skip(self, mint_info))]
  143. async fn add_mint(
  144. &self,
  145. mint_url: MintUrl,
  146. mint_info: Option<MintInfo>,
  147. ) -> Result<(), Self::Err> {
  148. let (
  149. name,
  150. pubkey,
  151. version,
  152. description,
  153. description_long,
  154. contact,
  155. nuts,
  156. icon_url,
  157. urls,
  158. motd,
  159. time,
  160. tos_url,
  161. ) = match mint_info {
  162. Some(mint_info) => {
  163. let MintInfo {
  164. name,
  165. pubkey,
  166. version,
  167. description,
  168. description_long,
  169. contact,
  170. nuts,
  171. icon_url,
  172. urls,
  173. motd,
  174. time,
  175. tos_url,
  176. } = mint_info;
  177. (
  178. name,
  179. pubkey.map(|p| p.to_bytes().to_vec()),
  180. version.map(|v| serde_json::to_string(&v).ok()),
  181. description,
  182. description_long,
  183. contact.map(|c| serde_json::to_string(&c).ok()),
  184. serde_json::to_string(&nuts).ok(),
  185. icon_url,
  186. urls.map(|c| serde_json::to_string(&c).ok()),
  187. motd,
  188. time,
  189. tos_url,
  190. )
  191. }
  192. None => (
  193. None, None, None, None, None, None, None, None, None, None, None, None,
  194. ),
  195. };
  196. query(
  197. r#"
  198. INSERT INTO mint
  199. (
  200. mint_url, name, pubkey, version, description, description_long,
  201. contact, nuts, icon_url, urls, motd, mint_time, tos_url
  202. )
  203. VALUES
  204. (
  205. :mint_url, :name, :pubkey, :version, :description, :description_long,
  206. :contact, :nuts, :icon_url, :urls, :motd, :mint_time, :tos_url
  207. )
  208. ON CONFLICT(mint_url) DO UPDATE SET
  209. name = excluded.name,
  210. pubkey = excluded.pubkey,
  211. version = excluded.version,
  212. description = excluded.description,
  213. description_long = excluded.description_long,
  214. contact = excluded.contact,
  215. nuts = excluded.nuts,
  216. icon_url = excluded.icon_url,
  217. urls = excluded.urls,
  218. motd = excluded.motd,
  219. mint_time = excluded.mint_time,
  220. tos_url = excluded.tos_url
  221. ;
  222. "#,
  223. )?
  224. .bind("mint_url", mint_url.to_string())
  225. .bind("name", name)
  226. .bind("pubkey", pubkey)
  227. .bind("version", version)
  228. .bind("description", description)
  229. .bind("description_long", description_long)
  230. .bind("contact", contact)
  231. .bind("nuts", nuts)
  232. .bind("icon_url", icon_url)
  233. .bind("urls", urls)
  234. .bind("motd", motd)
  235. .bind("mint_time", time.map(|v| v as i64))
  236. .bind("tos_url", tos_url)
  237. .execute(&self.db)
  238. .await?;
  239. Ok(())
  240. }
  241. #[instrument(skip(self))]
  242. async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), Self::Err> {
  243. query(r#"DELETE FROM mint WHERE mint_url=:mint_url"#)?
  244. .bind("mint_url", mint_url.to_string())
  245. .execute(&self.db)
  246. .await?;
  247. Ok(())
  248. }
  249. #[instrument(skip(self))]
  250. async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, Self::Err> {
  251. Ok(query(
  252. r#"
  253. SELECT
  254. name,
  255. pubkey,
  256. version,
  257. description,
  258. description_long,
  259. contact,
  260. nuts,
  261. icon_url,
  262. motd,
  263. urls,
  264. mint_time,
  265. tos_url
  266. FROM
  267. mint
  268. WHERE mint_url = :mint_url
  269. "#,
  270. )?
  271. .bind("mint_url", mint_url.to_string())
  272. .fetch_one(&self.db)
  273. .await?
  274. .map(sql_row_to_mint_info)
  275. .transpose()?)
  276. }
  277. #[instrument(skip(self))]
  278. async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, Self::Err> {
  279. Ok(query(
  280. r#"
  281. SELECT
  282. name,
  283. pubkey,
  284. version,
  285. description,
  286. description_long,
  287. contact,
  288. nuts,
  289. icon_url,
  290. motd,
  291. urls,
  292. mint_time,
  293. tos_url,
  294. mint_url
  295. FROM
  296. mint
  297. "#,
  298. )?
  299. .fetch_all(&self.db)
  300. .await?
  301. .into_iter()
  302. .map(|mut row| {
  303. let url = column_as_string!(
  304. row.pop().ok_or(ConversionError::MissingColumn(0, 1))?,
  305. MintUrl::from_str
  306. );
  307. Ok((url, sql_row_to_mint_info(row).ok()))
  308. })
  309. .collect::<Result<HashMap<_, _>, Error>>()?)
  310. }
  311. #[instrument(skip(self))]
  312. async fn update_mint_url(
  313. &self,
  314. old_mint_url: MintUrl,
  315. new_mint_url: MintUrl,
  316. ) -> Result<(), Self::Err> {
  317. let tables = ["mint_quote", "proof"];
  318. for table in &tables {
  319. query(&format!(
  320. r#"
  321. UPDATE {table}
  322. SET mint_url = :new_mint_url
  323. WHERE mint_url = :old_mint_url
  324. "#
  325. ))?
  326. .bind("new_mint_url", new_mint_url.to_string())
  327. .bind("old_mint_url", old_mint_url.to_string())
  328. .execute(&self.db)
  329. .await?;
  330. }
  331. Ok(())
  332. }
  333. #[instrument(skip(self, keysets))]
  334. async fn add_mint_keysets(
  335. &self,
  336. mint_url: MintUrl,
  337. keysets: Vec<KeySetInfo>,
  338. ) -> Result<(), Self::Err> {
  339. for keyset in keysets {
  340. query(
  341. r#"
  342. INSERT INTO keyset
  343. (mint_url, id, unit, active, input_fee_ppk, final_expiry, keyset_u32)
  344. VALUES
  345. (:mint_url, :id, :unit, :active, :input_fee_ppk, :final_expiry, :keyset_u32)
  346. ON CONFLICT(id) DO UPDATE SET
  347. active = excluded.active,
  348. input_fee_ppk = excluded.input_fee_ppk
  349. "#,
  350. )?
  351. .bind("mint_url", mint_url.to_string())
  352. .bind("id", keyset.id.to_string())
  353. .bind("unit", keyset.unit.to_string())
  354. .bind("active", keyset.active)
  355. .bind("input_fee_ppk", keyset.input_fee_ppk as i64)
  356. .bind("final_expiry", keyset.final_expiry.map(|v| v as i64))
  357. .bind("keyset_u32", u32::from(keyset.id))
  358. .execute(&self.db)
  359. .await?;
  360. }
  361. Ok(())
  362. }
  363. #[instrument(skip(self))]
  364. async fn get_mint_keysets(
  365. &self,
  366. mint_url: MintUrl,
  367. ) -> Result<Option<Vec<KeySetInfo>>, Self::Err> {
  368. let keysets = query(
  369. r#"
  370. SELECT
  371. id,
  372. unit,
  373. active,
  374. input_fee_ppk,
  375. final_expiry
  376. FROM
  377. keyset
  378. WHERE mint_url = :mint_url
  379. "#,
  380. )?
  381. .bind("mint_url", mint_url.to_string())
  382. .fetch_all(&self.db)
  383. .await?
  384. .into_iter()
  385. .map(sql_row_to_keyset)
  386. .collect::<Result<Vec<_>, Error>>()?;
  387. match keysets.is_empty() {
  388. false => Ok(Some(keysets)),
  389. true => Ok(None),
  390. }
  391. }
  392. #[instrument(skip(self), fields(keyset_id = %keyset_id))]
  393. async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Self::Err> {
  394. Ok(query(
  395. r#"
  396. SELECT
  397. id,
  398. unit,
  399. active,
  400. input_fee_ppk,
  401. final_expiry
  402. FROM
  403. keyset
  404. WHERE id = :id
  405. "#,
  406. )?
  407. .bind("id", keyset_id.to_string())
  408. .fetch_one(&self.db)
  409. .await?
  410. .map(sql_row_to_keyset)
  411. .transpose()?)
  412. }
  413. #[instrument(skip_all)]
  414. async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
  415. query(
  416. r#"
  417. INSERT INTO mint_quote
  418. (id, mint_url, amount, unit, request, state, expiry, secret_key, payment_method, amount_issued, amount_paid)
  419. VALUES
  420. (:id, :mint_url, :amount, :unit, :request, :state, :expiry, :secret_key, :payment_method, :amount_issued, :amount_paid)
  421. ON CONFLICT(id) DO UPDATE SET
  422. mint_url = excluded.mint_url,
  423. amount = excluded.amount,
  424. unit = excluded.unit,
  425. request = excluded.request,
  426. state = excluded.state,
  427. expiry = excluded.expiry,
  428. secret_key = excluded.secret_key,
  429. payment_method = excluded.payment_method,
  430. amount_issued = excluded.amount_issued,
  431. amount_paid = excluded.amount_paid
  432. ;
  433. "#,
  434. )?
  435. .bind("id", quote.id.to_string())
  436. .bind("mint_url", quote.mint_url.to_string())
  437. .bind("amount", quote.amount.map(|a| a.to_i64()))
  438. .bind("unit", quote.unit.to_string())
  439. .bind("request", quote.request)
  440. .bind("state", quote.state.to_string())
  441. .bind("expiry", quote.expiry as i64)
  442. .bind("secret_key", quote.secret_key.map(|p| p.to_string()))
  443. .bind("payment_method", quote.payment_method.to_string())
  444. .bind("amount_issued", quote.amount_issued.to_i64())
  445. .bind("amount_paid", quote.amount_paid.to_i64())
  446. .execute(&self.db).await?;
  447. Ok(())
  448. }
  449. #[instrument(skip(self))]
  450. async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
  451. Ok(query(
  452. r#"
  453. SELECT
  454. id,
  455. mint_url,
  456. amount,
  457. unit,
  458. request,
  459. state,
  460. expiry,
  461. secret_key,
  462. payment_method,
  463. amount_issued,
  464. amount_paid
  465. FROM
  466. mint_quote
  467. WHERE
  468. id = :id
  469. "#,
  470. )?
  471. .bind("id", quote_id.to_string())
  472. .fetch_one(&self.db)
  473. .await?
  474. .map(sql_row_to_mint_quote)
  475. .transpose()?)
  476. }
  477. #[instrument(skip(self))]
  478. async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
  479. Ok(query(
  480. r#"
  481. SELECT
  482. id,
  483. mint_url,
  484. amount,
  485. unit,
  486. request,
  487. state,
  488. expiry,
  489. secret_key
  490. FROM
  491. mint_quote
  492. "#,
  493. )?
  494. .fetch_all(&self.db)
  495. .await?
  496. .into_iter()
  497. .map(sql_row_to_mint_quote)
  498. .collect::<Result<_, _>>()?)
  499. }
  500. #[instrument(skip(self))]
  501. async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
  502. query(r#"DELETE FROM mint_quote WHERE id=:id"#)?
  503. .bind("id", quote_id.to_string())
  504. .execute(&self.db)
  505. .await?;
  506. Ok(())
  507. }
  508. #[instrument(skip_all)]
  509. async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err> {
  510. query(
  511. r#"
  512. INSERT INTO melt_quote
  513. (id, unit, amount, request, fee_reserve, state, expiry)
  514. VALUES
  515. (:id, :unit, :amount, :request, :fee_reserve, :state, :expiry)
  516. ON CONFLICT(id) DO UPDATE SET
  517. unit = excluded.unit,
  518. amount = excluded.amount,
  519. request = excluded.request,
  520. fee_reserve = excluded.fee_reserve,
  521. state = excluded.state,
  522. expiry = excluded.expiry
  523. ;
  524. "#,
  525. )?
  526. .bind("id", quote.id.to_string())
  527. .bind("unit", quote.unit.to_string())
  528. .bind("amount", u64::from(quote.amount) as i64)
  529. .bind("request", quote.request)
  530. .bind("fee_reserve", u64::from(quote.fee_reserve) as i64)
  531. .bind("state", quote.state.to_string())
  532. .bind("expiry", quote.expiry as i64)
  533. .execute(&self.db)
  534. .await?;
  535. Ok(())
  536. }
  537. #[instrument(skip(self))]
  538. async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<wallet::MeltQuote>, Self::Err> {
  539. Ok(query(
  540. r#"
  541. SELECT
  542. id,
  543. unit,
  544. amount,
  545. request,
  546. fee_reserve,
  547. state,
  548. expiry,
  549. payment_preimage
  550. FROM
  551. melt_quote
  552. WHERE
  553. id=:id
  554. "#,
  555. )?
  556. .bind("id", quote_id.to_owned())
  557. .fetch_one(&self.db)
  558. .await?
  559. .map(sql_row_to_melt_quote)
  560. .transpose()?)
  561. }
  562. #[instrument(skip(self))]
  563. async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
  564. query(r#"DELETE FROM melt_quote WHERE id=:id"#)?
  565. .bind("id", quote_id.to_owned())
  566. .execute(&self.db)
  567. .await?;
  568. Ok(())
  569. }
  570. #[instrument(skip_all)]
  571. async fn add_keys(&self, keyset: KeySet) -> Result<(), Self::Err> {
  572. // Recompute ID for verification
  573. keyset.verify_id()?;
  574. query(
  575. r#"
  576. INSERT INTO key
  577. (id, keys, keyset_u32)
  578. VALUES
  579. (:id, :keys, :keyset_u32)
  580. "#,
  581. )?
  582. .bind("id", keyset.id.to_string())
  583. .bind(
  584. "keys",
  585. serde_json::to_string(&keyset.keys).map_err(Error::from)?,
  586. )
  587. .bind("keyset_u32", u32::from(keyset.id))
  588. .execute(&self.db)
  589. .await?;
  590. Ok(())
  591. }
  592. #[instrument(skip(self), fields(keyset_id = %keyset_id))]
  593. async fn get_keys(&self, keyset_id: &Id) -> Result<Option<Keys>, Self::Err> {
  594. Ok(query(
  595. r#"
  596. SELECT
  597. keys
  598. FROM key
  599. WHERE id = :id
  600. "#,
  601. )?
  602. .bind("id", keyset_id.to_string())
  603. .pluck(&self.db)
  604. .await?
  605. .map(|keys| {
  606. let keys = column_as_string!(keys);
  607. serde_json::from_str(&keys).map_err(Error::from)
  608. })
  609. .transpose()?)
  610. }
  611. #[instrument(skip(self))]
  612. async fn remove_keys(&self, id: &Id) -> Result<(), Self::Err> {
  613. query(r#"DELETE FROM key WHERE id = :id"#)?
  614. .bind("id", id.to_string())
  615. .pluck(&self.db)
  616. .await?;
  617. Ok(())
  618. }
  619. async fn update_proofs(
  620. &self,
  621. added: Vec<ProofInfo>,
  622. removed_ys: Vec<PublicKey>,
  623. ) -> Result<(), Self::Err> {
  624. // TODO: Use a transaction for all these operations
  625. for proof in added {
  626. query(
  627. r#"
  628. INSERT INTO proof
  629. (y, mint_url, state, spending_condition, unit, amount, keyset_id, secret, c, witness, dleq_e, dleq_s, dleq_r)
  630. VALUES
  631. (:y, :mint_url, :state, :spending_condition, :unit, :amount, :keyset_id, :secret, :c, :witness, :dleq_e, :dleq_s, :dleq_r)
  632. ON CONFLICT(y) DO UPDATE SET
  633. mint_url = excluded.mint_url,
  634. state = excluded.state,
  635. spending_condition = excluded.spending_condition,
  636. unit = excluded.unit,
  637. amount = excluded.amount,
  638. keyset_id = excluded.keyset_id,
  639. secret = excluded.secret,
  640. c = excluded.c,
  641. witness = excluded.witness,
  642. dleq_e = excluded.dleq_e,
  643. dleq_s = excluded.dleq_s,
  644. dleq_r = excluded.dleq_r
  645. ;
  646. "#,
  647. )?
  648. .bind("y", proof.y.to_bytes().to_vec())
  649. .bind("mint_url", proof.mint_url.to_string())
  650. .bind("state",proof.state.to_string())
  651. .bind(
  652. "spending_condition",
  653. proof
  654. .spending_condition
  655. .map(|s| serde_json::to_string(&s).ok()),
  656. )
  657. .bind("unit", proof.unit.to_string())
  658. .bind("amount", u64::from(proof.proof.amount) as i64)
  659. .bind("keyset_id", proof.proof.keyset_id.to_string())
  660. .bind("secret", proof.proof.secret.to_string())
  661. .bind("c", proof.proof.c.to_bytes().to_vec())
  662. .bind(
  663. "witness",
  664. proof
  665. .proof
  666. .witness
  667. .map(|w| serde_json::to_string(&w).unwrap()),
  668. )
  669. .bind(
  670. "dleq_e",
  671. proof.proof.dleq.as_ref().map(|dleq| dleq.e.to_secret_bytes().to_vec()),
  672. )
  673. .bind(
  674. "dleq_s",
  675. proof.proof.dleq.as_ref().map(|dleq| dleq.s.to_secret_bytes().to_vec()),
  676. )
  677. .bind(
  678. "dleq_r",
  679. proof.proof.dleq.as_ref().map(|dleq| dleq.r.to_secret_bytes().to_vec()),
  680. )
  681. .execute(&self.db).await?;
  682. }
  683. query(r#"DELETE FROM proof WHERE y IN (:ys)"#)?
  684. .bind_vec(
  685. "ys",
  686. removed_ys.iter().map(|y| y.to_bytes().to_vec()).collect(),
  687. )
  688. .execute(&self.db)
  689. .await?;
  690. Ok(())
  691. }
  692. #[instrument(skip(self, state, spending_conditions))]
  693. async fn get_proofs(
  694. &self,
  695. mint_url: Option<MintUrl>,
  696. unit: Option<CurrencyUnit>,
  697. state: Option<Vec<State>>,
  698. spending_conditions: Option<Vec<SpendingConditions>>,
  699. ) -> Result<Vec<ProofInfo>, Self::Err> {
  700. Ok(query(
  701. r#"
  702. SELECT
  703. amount,
  704. unit,
  705. keyset_id,
  706. secret,
  707. c,
  708. witness,
  709. dleq_e,
  710. dleq_s,
  711. dleq_r,
  712. y,
  713. mint_url,
  714. state,
  715. spending_condition
  716. FROM proof
  717. "#,
  718. )?
  719. .fetch_all(&self.db)
  720. .await?
  721. .into_iter()
  722. .filter_map(|row| {
  723. let row = sql_row_to_proof_info(row).ok()?;
  724. if row.matches_conditions(&mint_url, &unit, &state, &spending_conditions) {
  725. Some(row)
  726. } else {
  727. None
  728. }
  729. })
  730. .collect::<Vec<_>>())
  731. }
  732. async fn update_proofs_state(&self, ys: Vec<PublicKey>, state: State) -> Result<(), Self::Err> {
  733. query("UPDATE proof SET state = :state WHERE y IN (:ys)")?
  734. .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
  735. .bind("state", state.to_string())
  736. .execute(&self.db)
  737. .await?;
  738. Ok(())
  739. }
  740. #[instrument(skip(self), fields(keyset_id = %keyset_id))]
  741. async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err> {
  742. query(
  743. r#"
  744. UPDATE keyset
  745. SET counter=counter+:count
  746. WHERE id=:id
  747. "#,
  748. )?
  749. .bind("count", count)
  750. .bind("id", keyset_id.to_string())
  751. .execute(&self.db)
  752. .await?;
  753. Ok(())
  754. }
  755. #[instrument(skip(self), fields(keyset_id = %keyset_id))]
  756. async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> {
  757. Ok(query(
  758. r#"
  759. SELECT
  760. counter
  761. FROM
  762. keyset
  763. WHERE
  764. id=:id
  765. "#,
  766. )?
  767. .bind("id", keyset_id.to_string())
  768. .pluck(&self.db)
  769. .await?
  770. .map(|n| Ok::<_, Error>(column_as_number!(n)))
  771. .transpose()?)
  772. }
  773. #[instrument(skip(self))]
  774. async fn add_transaction(&self, transaction: Transaction) -> Result<(), Self::Err> {
  775. let mint_url = transaction.mint_url.to_string();
  776. let direction = transaction.direction.to_string();
  777. let unit = transaction.unit.to_string();
  778. let amount = u64::from(transaction.amount) as i64;
  779. let fee = u64::from(transaction.fee) as i64;
  780. let ys = transaction
  781. .ys
  782. .iter()
  783. .flat_map(|y| y.to_bytes().to_vec())
  784. .collect::<Vec<_>>();
  785. query(
  786. r#"
  787. INSERT INTO transactions
  788. (id, mint_url, direction, unit, amount, fee, ys, timestamp, memo, metadata)
  789. VALUES
  790. (:id, :mint_url, :direction, :unit, :amount, :fee, :ys, :timestamp, :memo, :metadata)
  791. ON CONFLICT(id) DO UPDATE SET
  792. mint_url = excluded.mint_url,
  793. direction = excluded.direction,
  794. unit = excluded.unit,
  795. amount = excluded.amount,
  796. fee = excluded.fee,
  797. ys = excluded.ys,
  798. timestamp = excluded.timestamp,
  799. memo = excluded.memo,
  800. metadata = excluded.metadata
  801. ;
  802. "#,
  803. )?
  804. .bind("id", transaction.id().as_slice().to_vec())
  805. .bind("mint_url", mint_url)
  806. .bind("direction", direction)
  807. .bind("unit", unit)
  808. .bind("amount", amount)
  809. .bind("fee", fee)
  810. .bind("ys", ys)
  811. .bind("timestamp", transaction.timestamp as i64)
  812. .bind("memo", transaction.memo)
  813. .bind(
  814. "metadata",
  815. serde_json::to_string(&transaction.metadata).map_err(Error::from)?,
  816. )
  817. .execute(&self.db)
  818. .await?;
  819. Ok(())
  820. }
  821. #[instrument(skip(self))]
  822. async fn get_transaction(
  823. &self,
  824. transaction_id: TransactionId,
  825. ) -> Result<Option<Transaction>, Self::Err> {
  826. Ok(query(
  827. r#"
  828. SELECT
  829. mint_url,
  830. direction,
  831. unit,
  832. amount,
  833. fee,
  834. ys,
  835. timestamp,
  836. memo,
  837. metadata
  838. FROM
  839. transactions
  840. WHERE
  841. id = :id
  842. "#,
  843. )?
  844. .bind("id", transaction_id.as_slice().to_vec())
  845. .fetch_one(&self.db)
  846. .await?
  847. .map(sql_row_to_transaction)
  848. .transpose()?)
  849. }
  850. #[instrument(skip(self))]
  851. async fn list_transactions(
  852. &self,
  853. mint_url: Option<MintUrl>,
  854. direction: Option<TransactionDirection>,
  855. unit: Option<CurrencyUnit>,
  856. ) -> Result<Vec<Transaction>, Self::Err> {
  857. Ok(query(
  858. r#"
  859. SELECT
  860. mint_url,
  861. direction,
  862. unit,
  863. amount,
  864. fee,
  865. ys,
  866. timestamp,
  867. memo,
  868. metadata
  869. FROM
  870. transactions
  871. "#,
  872. )?
  873. .fetch_all(&self.db)
  874. .await?
  875. .into_iter()
  876. .filter_map(|row| {
  877. // TODO: Avoid a table scan by passing the heavy lifting of checking to the DB engine
  878. let transaction = sql_row_to_transaction(row).ok()?;
  879. if transaction.matches_conditions(&mint_url, &direction, &unit) {
  880. Some(transaction)
  881. } else {
  882. None
  883. }
  884. })
  885. .collect::<Vec<_>>())
  886. }
  887. #[instrument(skip(self))]
  888. async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), Self::Err> {
  889. query(r#"DELETE FROM transactions WHERE id=:id"#)?
  890. .bind("id", transaction_id.as_slice().to_vec())
  891. .execute(&self.db)
  892. .await?;
  893. Ok(())
  894. }
  895. }
  896. fn sql_row_to_mint_info(row: Vec<Column>) -> Result<MintInfo, Error> {
  897. unpack_into!(
  898. let (
  899. name,
  900. pubkey,
  901. version,
  902. description,
  903. description_long,
  904. contact,
  905. nuts,
  906. icon_url,
  907. motd,
  908. urls,
  909. mint_time,
  910. tos_url
  911. ) = row
  912. );
  913. Ok(MintInfo {
  914. name: column_as_nullable_string!(&name),
  915. pubkey: column_as_nullable_string!(&pubkey, |v| serde_json::from_str(v).ok(), |v| {
  916. serde_json::from_slice(v).ok()
  917. }),
  918. version: column_as_nullable_string!(&version).and_then(|v| serde_json::from_str(&v).ok()),
  919. description: column_as_nullable_string!(description),
  920. description_long: column_as_nullable_string!(description_long),
  921. contact: column_as_nullable_string!(contact, |v| serde_json::from_str(&v).ok()),
  922. nuts: column_as_nullable_string!(nuts, |v| serde_json::from_str(&v).ok())
  923. .unwrap_or_default(),
  924. urls: column_as_nullable_string!(urls, |v| serde_json::from_str(&v).ok()),
  925. icon_url: column_as_nullable_string!(icon_url),
  926. motd: column_as_nullable_string!(motd),
  927. time: column_as_nullable_number!(mint_time).map(|t| t),
  928. tos_url: column_as_nullable_string!(tos_url),
  929. })
  930. }
  931. #[instrument(skip_all)]
  932. fn sql_row_to_keyset(row: Vec<Column>) -> Result<KeySetInfo, Error> {
  933. unpack_into!(
  934. let (
  935. id,
  936. unit,
  937. active,
  938. input_fee_ppk,
  939. final_expiry
  940. ) = row
  941. );
  942. Ok(KeySetInfo {
  943. id: column_as_string!(id, Id::from_str, Id::from_bytes),
  944. unit: column_as_string!(unit, CurrencyUnit::from_str),
  945. active: matches!(active, Column::Integer(1)),
  946. input_fee_ppk: column_as_nullable_number!(input_fee_ppk).unwrap_or_default(),
  947. final_expiry: column_as_nullable_number!(final_expiry),
  948. })
  949. }
  950. fn sql_row_to_mint_quote(row: Vec<Column>) -> Result<MintQuote, Error> {
  951. unpack_into!(
  952. let (
  953. id,
  954. mint_url,
  955. amount,
  956. unit,
  957. request,
  958. state,
  959. expiry,
  960. secret_key,
  961. row_method,
  962. row_amount_minted,
  963. row_amount_paid
  964. ) = row
  965. );
  966. let amount: Option<i64> = column_as_nullable_number!(amount);
  967. let amount_paid: u64 = column_as_number!(row_amount_paid);
  968. let amount_minted: u64 = column_as_number!(row_amount_minted);
  969. let payment_method =
  970. PaymentMethod::from_str(&column_as_string!(row_method)).map_err(Error::from)?;
  971. Ok(MintQuote {
  972. id: column_as_string!(id),
  973. mint_url: column_as_string!(mint_url, MintUrl::from_str),
  974. amount: amount.and_then(Amount::from_i64),
  975. unit: column_as_string!(unit, CurrencyUnit::from_str),
  976. request: column_as_string!(request),
  977. state: column_as_string!(state, MintQuoteState::from_str),
  978. expiry: column_as_number!(expiry),
  979. secret_key: column_as_nullable_string!(secret_key)
  980. .map(|v| SecretKey::from_str(&v))
  981. .transpose()?,
  982. payment_method,
  983. amount_issued: amount_minted.into(),
  984. amount_paid: amount_paid.into(),
  985. })
  986. }
  987. fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<wallet::MeltQuote, Error> {
  988. unpack_into!(
  989. let (
  990. id,
  991. unit,
  992. amount,
  993. request,
  994. fee_reserve,
  995. state,
  996. expiry,
  997. payment_preimage
  998. ) = row
  999. );
  1000. let amount: u64 = column_as_number!(amount);
  1001. let fee_reserve: u64 = column_as_number!(fee_reserve);
  1002. Ok(wallet::MeltQuote {
  1003. id: column_as_string!(id),
  1004. amount: Amount::from(amount),
  1005. unit: column_as_string!(unit, CurrencyUnit::from_str),
  1006. request: column_as_string!(request),
  1007. fee_reserve: Amount::from(fee_reserve),
  1008. state: column_as_string!(state, MeltQuoteState::from_str),
  1009. expiry: column_as_number!(expiry),
  1010. payment_preimage: column_as_nullable_string!(payment_preimage),
  1011. })
  1012. }
  1013. fn sql_row_to_proof_info(row: Vec<Column>) -> Result<ProofInfo, Error> {
  1014. unpack_into!(
  1015. let (
  1016. amount,
  1017. unit,
  1018. keyset_id,
  1019. secret,
  1020. c,
  1021. witness,
  1022. dleq_e,
  1023. dleq_s,
  1024. dleq_r,
  1025. y,
  1026. mint_url,
  1027. state,
  1028. spending_condition
  1029. ) = row
  1030. );
  1031. let dleq = match (
  1032. column_as_nullable_binary!(dleq_e),
  1033. column_as_nullable_binary!(dleq_s),
  1034. column_as_nullable_binary!(dleq_r),
  1035. ) {
  1036. (Some(e), Some(s), Some(r)) => {
  1037. let e_key = SecretKey::from_slice(&e)?;
  1038. let s_key = SecretKey::from_slice(&s)?;
  1039. let r_key = SecretKey::from_slice(&r)?;
  1040. Some(ProofDleq::new(e_key, s_key, r_key))
  1041. }
  1042. _ => None,
  1043. };
  1044. let amount: u64 = column_as_number!(amount);
  1045. let proof = Proof {
  1046. amount: Amount::from(amount),
  1047. keyset_id: column_as_string!(keyset_id, Id::from_str),
  1048. secret: column_as_string!(secret, Secret::from_str),
  1049. witness: column_as_nullable_string!(witness, |v| { serde_json::from_str(&v).ok() }, |v| {
  1050. serde_json::from_slice(&v).ok()
  1051. }),
  1052. c: column_as_string!(c, PublicKey::from_str, PublicKey::from_slice),
  1053. dleq,
  1054. };
  1055. Ok(ProofInfo {
  1056. proof,
  1057. y: column_as_string!(y, PublicKey::from_str, PublicKey::from_slice),
  1058. mint_url: column_as_string!(mint_url, MintUrl::from_str),
  1059. state: column_as_string!(state, State::from_str),
  1060. spending_condition: column_as_nullable_string!(
  1061. spending_condition,
  1062. |r| { serde_json::from_str(&r).ok() },
  1063. |r| { serde_json::from_slice(&r).ok() }
  1064. ),
  1065. unit: column_as_string!(unit, CurrencyUnit::from_str),
  1066. })
  1067. }
  1068. fn sql_row_to_transaction(row: Vec<Column>) -> Result<Transaction, Error> {
  1069. unpack_into!(
  1070. let (
  1071. mint_url,
  1072. direction,
  1073. unit,
  1074. amount,
  1075. fee,
  1076. ys,
  1077. timestamp,
  1078. memo,
  1079. metadata
  1080. ) = row
  1081. );
  1082. let amount: u64 = column_as_number!(amount);
  1083. let fee: u64 = column_as_number!(fee);
  1084. Ok(Transaction {
  1085. mint_url: column_as_string!(mint_url, MintUrl::from_str),
  1086. direction: column_as_string!(direction, TransactionDirection::from_str),
  1087. unit: column_as_string!(unit, CurrencyUnit::from_str),
  1088. amount: Amount::from(amount),
  1089. fee: Amount::from(fee),
  1090. ys: column_as_binary!(ys)
  1091. .chunks(33)
  1092. .map(PublicKey::from_slice)
  1093. .collect::<Result<Vec<_>, _>>()?,
  1094. timestamp: column_as_number!(timestamp),
  1095. memo: column_as_nullable_string!(memo),
  1096. metadata: column_as_nullable_string!(metadata, |v| serde_json::from_str(&v).ok(), |v| {
  1097. serde_json::from_slice(&v).ok()
  1098. })
  1099. .unwrap_or_default(),
  1100. })
  1101. }