mod.rs 37 KB

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