quotes.rs 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. //! Quotes database implementation
  2. use std::str::FromStr;
  3. use async_trait::async_trait;
  4. use cdk_common::database::mint::{Acquired, LockedMeltQuotes};
  5. use cdk_common::database::{
  6. self, ConversionError, Error, MintQuotesDatabase, MintQuotesTransaction,
  7. };
  8. use cdk_common::mint::{
  9. self, IncomingPayment, Issuance, MeltPaymentRequest, MeltQuote, MintQuote, Operation,
  10. };
  11. use cdk_common::payment::PaymentIdentifier;
  12. use cdk_common::quote_id::QuoteId;
  13. use cdk_common::state::check_melt_quote_state_transition;
  14. use cdk_common::util::unix_time;
  15. use cdk_common::{
  16. Amount, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, PaymentMethod, PublicKey,
  17. };
  18. #[cfg(feature = "prometheus")]
  19. use cdk_prometheus::METRICS;
  20. use lightning_invoice::Bolt11Invoice;
  21. use tracing::instrument;
  22. use super::{SQLMintDatabase, SQLTransaction};
  23. use crate::database::DatabaseExecutor;
  24. use crate::pool::DatabasePool;
  25. use crate::stmt::{query, Column};
  26. use crate::{
  27. column_as_nullable_number, column_as_nullable_string, column_as_number, column_as_string,
  28. unpack_into,
  29. };
  30. async fn get_mint_quote_payments<C>(
  31. conn: &C,
  32. quote_id: &QuoteId,
  33. ) -> Result<Vec<IncomingPayment>, Error>
  34. where
  35. C: DatabaseExecutor + Send + Sync,
  36. {
  37. // Get payment IDs and timestamps from the mint_quote_payments table
  38. query(
  39. r#"
  40. SELECT
  41. p.payment_id,
  42. p.timestamp,
  43. p.amount,
  44. q.unit
  45. FROM
  46. mint_quote_payments p
  47. JOIN mint_quote q ON p.quote_id = q.id
  48. WHERE
  49. p.quote_id=:quote_id
  50. "#,
  51. )?
  52. .bind("quote_id", quote_id.to_string())
  53. .fetch_all(conn)
  54. .await?
  55. .into_iter()
  56. .map(|row| {
  57. let amount: u64 = column_as_number!(row[2].clone());
  58. let time: u64 = column_as_number!(row[1].clone());
  59. let unit = column_as_string!(&row[3], CurrencyUnit::from_str);
  60. Ok(IncomingPayment::new(
  61. Amount::from(amount).with_unit(unit),
  62. column_as_string!(&row[0]),
  63. time,
  64. ))
  65. })
  66. .collect()
  67. }
  68. async fn get_mint_quote_issuance<C>(conn: &C, quote_id: &QuoteId) -> Result<Vec<Issuance>, Error>
  69. where
  70. C: DatabaseExecutor + Send + Sync,
  71. {
  72. // Get payment IDs and timestamps from the mint_quote_payments table
  73. query(
  74. r#"
  75. SELECT i.amount, i.timestamp, q.unit
  76. FROM mint_quote_issued i
  77. JOIN mint_quote q ON i.quote_id = q.id
  78. WHERE i.quote_id=:quote_id
  79. "#,
  80. )?
  81. .bind("quote_id", quote_id.to_string())
  82. .fetch_all(conn)
  83. .await?
  84. .into_iter()
  85. .map(|row| {
  86. let time: u64 = column_as_number!(row[1].clone());
  87. let unit = column_as_string!(&row[2], CurrencyUnit::from_str);
  88. Ok(Issuance::new(
  89. Amount::from_i64(column_as_number!(row[0].clone()))
  90. .expect("Is amount when put into db")
  91. .with_unit(unit),
  92. time,
  93. ))
  94. })
  95. .collect()
  96. }
  97. // Inline helper functions that work with both connections and transactions
  98. pub(super) async fn get_mint_quote_inner<T>(
  99. executor: &T,
  100. quote_id: &QuoteId,
  101. for_update: bool,
  102. ) -> Result<Option<MintQuote>, Error>
  103. where
  104. T: DatabaseExecutor,
  105. {
  106. let payments = get_mint_quote_payments(executor, quote_id).await?;
  107. let issuance = get_mint_quote_issuance(executor, quote_id).await?;
  108. let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
  109. let query_str = format!(
  110. r#"
  111. SELECT
  112. id,
  113. amount,
  114. unit,
  115. request,
  116. expiry,
  117. request_lookup_id,
  118. pubkey,
  119. created_time,
  120. amount_paid,
  121. amount_issued,
  122. payment_method,
  123. request_lookup_id_kind
  124. FROM
  125. mint_quote
  126. WHERE id = :id
  127. {for_update_clause}
  128. "#
  129. );
  130. query(&query_str)?
  131. .bind("id", quote_id.to_string())
  132. .fetch_one(executor)
  133. .await?
  134. .map(|row| sql_row_to_mint_quote(row, payments, issuance))
  135. .transpose()
  136. }
  137. pub(super) async fn get_mint_quote_by_request_inner<T>(
  138. executor: &T,
  139. request: &str,
  140. for_update: bool,
  141. ) -> Result<Option<MintQuote>, Error>
  142. where
  143. T: DatabaseExecutor,
  144. {
  145. let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
  146. let query_str = format!(
  147. r#"
  148. SELECT
  149. id,
  150. amount,
  151. unit,
  152. request,
  153. expiry,
  154. request_lookup_id,
  155. pubkey,
  156. created_time,
  157. amount_paid,
  158. amount_issued,
  159. payment_method,
  160. request_lookup_id_kind
  161. FROM
  162. mint_quote
  163. WHERE request = :request
  164. {for_update_clause}
  165. "#
  166. );
  167. let mut mint_quote = query(&query_str)?
  168. .bind("request", request.to_string())
  169. .fetch_one(executor)
  170. .await?
  171. .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
  172. .transpose()?;
  173. if let Some(quote) = mint_quote.as_mut() {
  174. let payments = get_mint_quote_payments(executor, &quote.id).await?;
  175. let issuance = get_mint_quote_issuance(executor, &quote.id).await?;
  176. quote.issuance = issuance;
  177. quote.payments = payments;
  178. }
  179. Ok(mint_quote)
  180. }
  181. pub(super) async fn get_mint_quote_by_request_lookup_id_inner<T>(
  182. executor: &T,
  183. request_lookup_id: &PaymentIdentifier,
  184. for_update: bool,
  185. ) -> Result<Option<MintQuote>, Error>
  186. where
  187. T: DatabaseExecutor,
  188. {
  189. let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
  190. let query_str = format!(
  191. r#"
  192. SELECT
  193. id,
  194. amount,
  195. unit,
  196. request,
  197. expiry,
  198. request_lookup_id,
  199. pubkey,
  200. created_time,
  201. amount_paid,
  202. amount_issued,
  203. payment_method,
  204. request_lookup_id_kind
  205. FROM
  206. mint_quote
  207. WHERE request_lookup_id = :request_lookup_id
  208. AND request_lookup_id_kind = :request_lookup_id_kind
  209. {for_update_clause}
  210. "#
  211. );
  212. let mut mint_quote = query(&query_str)?
  213. .bind("request_lookup_id", request_lookup_id.to_string())
  214. .bind("request_lookup_id_kind", request_lookup_id.kind())
  215. .fetch_one(executor)
  216. .await?
  217. .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
  218. .transpose()?;
  219. if let Some(quote) = mint_quote.as_mut() {
  220. let payments = get_mint_quote_payments(executor, &quote.id).await?;
  221. let issuance = get_mint_quote_issuance(executor, &quote.id).await?;
  222. quote.issuance = issuance;
  223. quote.payments = payments;
  224. }
  225. Ok(mint_quote)
  226. }
  227. pub(super) async fn get_melt_quote_inner<T>(
  228. executor: &T,
  229. quote_id: &QuoteId,
  230. for_update: bool,
  231. ) -> Result<Option<mint::MeltQuote>, Error>
  232. where
  233. T: DatabaseExecutor,
  234. {
  235. let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
  236. let query_str = format!(
  237. r#"
  238. SELECT
  239. id,
  240. unit,
  241. amount,
  242. request,
  243. fee_reserve,
  244. expiry,
  245. state,
  246. payment_preimage,
  247. request_lookup_id,
  248. created_time,
  249. paid_time,
  250. payment_method,
  251. options,
  252. request_lookup_id_kind
  253. FROM
  254. melt_quote
  255. WHERE
  256. id=:id
  257. {for_update_clause}
  258. "#
  259. );
  260. query(&query_str)?
  261. .bind("id", quote_id.to_string())
  262. .fetch_one(executor)
  263. .await?
  264. .map(sql_row_to_melt_quote)
  265. .transpose()
  266. }
  267. pub(super) async fn get_melt_quotes_by_request_lookup_id_inner<T>(
  268. executor: &T,
  269. request_lookup_id: &PaymentIdentifier,
  270. for_update: bool,
  271. ) -> Result<Vec<mint::MeltQuote>, Error>
  272. where
  273. T: DatabaseExecutor,
  274. {
  275. let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
  276. let query_str = format!(
  277. r#"
  278. SELECT
  279. id,
  280. unit,
  281. amount,
  282. request,
  283. fee_reserve,
  284. expiry,
  285. state,
  286. payment_preimage,
  287. request_lookup_id,
  288. created_time,
  289. paid_time,
  290. payment_method,
  291. options,
  292. request_lookup_id_kind
  293. FROM
  294. melt_quote
  295. WHERE
  296. request_lookup_id = :request_lookup_id
  297. AND request_lookup_id_kind = :request_lookup_id_kind
  298. {for_update_clause}
  299. "#
  300. );
  301. query(&query_str)?
  302. .bind("request_lookup_id", request_lookup_id.to_string())
  303. .bind("request_lookup_id_kind", request_lookup_id.kind())
  304. .fetch_all(executor)
  305. .await?
  306. .into_iter()
  307. .map(sql_row_to_melt_quote)
  308. .collect::<Result<Vec<_>, _>>()
  309. }
  310. /// Locks a melt quote and all related quotes atomically to prevent deadlocks.
  311. ///
  312. /// This function acquires all locks in a single query with consistent ordering (by ID),
  313. /// preventing the circular wait condition that can occur when locks are acquired in
  314. /// separate queries.
  315. async fn lock_melt_quote_and_related_inner<T>(
  316. executor: &T,
  317. quote_id: &QuoteId,
  318. ) -> Result<LockedMeltQuotes, Error>
  319. where
  320. T: DatabaseExecutor,
  321. {
  322. // Use a single query with subquery to atomically lock:
  323. // 1. All quotes with the same request_lookup_id as the target quote, OR
  324. // 2. Just the target quote if it has no request_lookup_id
  325. //
  326. // The ORDER BY ensures consistent lock acquisition order across transactions,
  327. // preventing deadlocks.
  328. let query_str = r#"
  329. SELECT
  330. id,
  331. unit,
  332. amount,
  333. request,
  334. fee_reserve,
  335. expiry,
  336. state,
  337. payment_preimage,
  338. request_lookup_id,
  339. created_time,
  340. paid_time,
  341. payment_method,
  342. options,
  343. request_lookup_id_kind
  344. FROM
  345. melt_quote
  346. WHERE
  347. (
  348. request_lookup_id IS NOT NULL
  349. AND request_lookup_id = (SELECT request_lookup_id FROM melt_quote WHERE id = :quote_id)
  350. AND request_lookup_id_kind = (SELECT request_lookup_id_kind FROM melt_quote WHERE id = :quote_id)
  351. )
  352. OR
  353. (
  354. id = :quote_id
  355. AND (SELECT request_lookup_id FROM melt_quote WHERE id = :quote_id) IS NULL
  356. )
  357. ORDER BY id
  358. FOR UPDATE
  359. "#;
  360. let all_quotes: Vec<mint::MeltQuote> = query(query_str)?
  361. .bind("quote_id", quote_id.to_string())
  362. .fetch_all(executor)
  363. .await?
  364. .into_iter()
  365. .map(sql_row_to_melt_quote)
  366. .collect::<Result<Vec<_>, _>>()?;
  367. // Find the target quote from the locked set
  368. let target_quote = all_quotes.iter().find(|q| &q.id == quote_id).cloned();
  369. Ok(LockedMeltQuotes {
  370. target: target_quote.map(|q| q.into()),
  371. all_related: all_quotes.into_iter().map(|q| q.into()).collect(),
  372. })
  373. }
  374. #[instrument(skip_all)]
  375. fn sql_row_to_mint_quote(
  376. row: Vec<Column>,
  377. payments: Vec<IncomingPayment>,
  378. issueances: Vec<Issuance>,
  379. ) -> Result<MintQuote, Error> {
  380. unpack_into!(
  381. let (
  382. id, amount, unit, request, expiry, request_lookup_id,
  383. pubkey, created_time, amount_paid, amount_issued, payment_method, request_lookup_id_kind
  384. ) = row
  385. );
  386. let request_str = column_as_string!(&request);
  387. let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
  388. Bolt11Invoice::from_str(&request_str)
  389. .map(|invoice| invoice.payment_hash().to_string())
  390. .unwrap_or_else(|_| request_str.clone())
  391. });
  392. let request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
  393. let pubkey = column_as_nullable_string!(&pubkey)
  394. .map(|pk| PublicKey::from_hex(&pk))
  395. .transpose()?;
  396. let id = column_as_string!(id);
  397. let amount: Option<u64> = column_as_nullable_number!(amount);
  398. let amount_paid: u64 = column_as_number!(amount_paid);
  399. let amount_issued: u64 = column_as_number!(amount_issued);
  400. let payment_method = column_as_string!(payment_method, PaymentMethod::from_str);
  401. let unit = column_as_string!(unit, CurrencyUnit::from_str);
  402. Ok(MintQuote::new(
  403. Some(QuoteId::from_str(&id)?),
  404. request_str,
  405. unit.clone(),
  406. amount.map(|a| Amount::from(a).with_unit(unit.clone())),
  407. column_as_number!(expiry),
  408. PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
  409. .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
  410. pubkey,
  411. Amount::from(amount_paid).with_unit(unit.clone()),
  412. Amount::from(amount_issued).with_unit(unit),
  413. payment_method,
  414. column_as_number!(created_time),
  415. payments,
  416. issueances,
  417. None,
  418. ))
  419. }
  420. // FIXME: Replace unwrap with proper error handling
  421. fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
  422. unpack_into!(
  423. let (
  424. id,
  425. unit,
  426. amount,
  427. request,
  428. fee_reserve,
  429. expiry,
  430. state,
  431. payment_preimage,
  432. request_lookup_id,
  433. created_time,
  434. paid_time,
  435. payment_method,
  436. options,
  437. request_lookup_id_kind
  438. ) = row
  439. );
  440. let id = column_as_string!(id);
  441. let amount: u64 = column_as_number!(amount);
  442. let fee_reserve: u64 = column_as_number!(fee_reserve);
  443. let expiry = column_as_number!(expiry);
  444. let payment_preimage = column_as_nullable_string!(payment_preimage);
  445. let options = column_as_nullable_string!(options);
  446. let options = options.and_then(|o| serde_json::from_str(&o).ok());
  447. let created_time: i64 = column_as_number!(created_time);
  448. let paid_time = column_as_nullable_number!(paid_time);
  449. let payment_method = PaymentMethod::from_str(&column_as_string!(payment_method))?;
  450. let state =
  451. MeltQuoteState::from_str(&column_as_string!(&state)).map_err(ConversionError::from)?;
  452. let unit = column_as_string!(unit);
  453. let request = column_as_string!(request);
  454. let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
  455. let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
  456. Bolt11Invoice::from_str(&request)
  457. .ok()
  458. .map(|invoice| invoice.payment_hash().to_string())
  459. });
  460. let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
  461. (request_lookup_id_kind, request_lookup_id)
  462. {
  463. Some(
  464. PaymentIdentifier::new(&id_kind, &request_lookup_id)
  465. .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
  466. )
  467. } else {
  468. None
  469. };
  470. let request = match serde_json::from_str(&request) {
  471. Ok(req) => req,
  472. Err(err) => {
  473. tracing::debug!(
  474. "Melt quote from pre migrations defaulting to bolt11 {}.",
  475. err
  476. );
  477. let bolt11 = Bolt11Invoice::from_str(&request)
  478. .map_err(|e| Error::Internal(format!("Could not parse invoice: {e}")))?;
  479. MeltPaymentRequest::Bolt11 { bolt11 }
  480. }
  481. };
  482. let unit = CurrencyUnit::from_str(&unit)?;
  483. Ok(MeltQuote::from_db(
  484. QuoteId::from_str(&id)?,
  485. unit,
  486. request,
  487. amount,
  488. fee_reserve,
  489. state,
  490. expiry,
  491. payment_preimage,
  492. request_lookup_id,
  493. options,
  494. created_time as u64,
  495. paid_time,
  496. payment_method,
  497. ))
  498. }
  499. #[async_trait]
  500. impl<RM> MintQuotesTransaction for SQLTransaction<RM>
  501. where
  502. RM: DatabasePool + 'static,
  503. {
  504. type Err = Error;
  505. async fn add_melt_request(
  506. &mut self,
  507. quote_id: &QuoteId,
  508. inputs_amount: Amount<CurrencyUnit>,
  509. inputs_fee: Amount<CurrencyUnit>,
  510. ) -> Result<(), Self::Err> {
  511. // Insert melt_request
  512. query(
  513. r#"
  514. INSERT INTO melt_request
  515. (quote_id, inputs_amount, inputs_fee)
  516. VALUES
  517. (:quote_id, :inputs_amount, :inputs_fee)
  518. "#,
  519. )?
  520. .bind("quote_id", quote_id.to_string())
  521. .bind("inputs_amount", inputs_amount.to_i64())
  522. .bind("inputs_fee", inputs_fee.to_i64())
  523. .execute(&self.inner)
  524. .await?;
  525. Ok(())
  526. }
  527. async fn add_blinded_messages(
  528. &mut self,
  529. quote_id: Option<&QuoteId>,
  530. blinded_messages: &[BlindedMessage],
  531. operation: &Operation,
  532. ) -> Result<(), Self::Err> {
  533. let current_time = unix_time();
  534. // Insert blinded_messages directly into blind_signature with c = NULL
  535. // Let the database constraint handle duplicate detection
  536. for message in blinded_messages {
  537. match query(
  538. r#"
  539. INSERT INTO blind_signature
  540. (blinded_message, amount, keyset_id, c, quote_id, created_time, operation_kind, operation_id)
  541. VALUES
  542. (:blinded_message, :amount, :keyset_id, NULL, :quote_id, :created_time, :operation_kind, :operation_id)
  543. "#,
  544. )?
  545. .bind(
  546. "blinded_message",
  547. message.blinded_secret.to_bytes().to_vec(),
  548. )
  549. .bind("amount", message.amount.to_i64())
  550. .bind("keyset_id", message.keyset_id.to_string())
  551. .bind("quote_id", quote_id.map(|q| q.to_string()))
  552. .bind("created_time", current_time as i64)
  553. .bind("operation_kind", operation.kind().to_string())
  554. .bind("operation_id", operation.id().to_string())
  555. .execute(&self.inner)
  556. .await
  557. {
  558. Ok(_) => continue,
  559. Err(database::Error::Duplicate) => {
  560. // Primary key constraint violation - blinded message already exists
  561. // This could be either:
  562. // 1. Already signed (c IS NOT NULL) - definitely an error
  563. // 2. Already pending (c IS NULL) - also an error
  564. return Err(database::Error::Duplicate);
  565. }
  566. Err(err) => return Err(err),
  567. }
  568. }
  569. Ok(())
  570. }
  571. async fn delete_blinded_messages(
  572. &mut self,
  573. blinded_secrets: &[PublicKey],
  574. ) -> Result<(), Self::Err> {
  575. if blinded_secrets.is_empty() {
  576. return Ok(());
  577. }
  578. // Delete blinded messages from blind_signature table where c IS NULL
  579. // (only delete unsigned blinded messages)
  580. query(
  581. r#"
  582. DELETE FROM blind_signature
  583. WHERE blinded_message IN (:blinded_secrets) AND c IS NULL
  584. "#,
  585. )?
  586. .bind_vec(
  587. "blinded_secrets",
  588. blinded_secrets
  589. .iter()
  590. .map(|secret| secret.to_bytes().to_vec())
  591. .collect(),
  592. )
  593. .execute(&self.inner)
  594. .await?;
  595. Ok(())
  596. }
  597. async fn get_melt_request_and_blinded_messages(
  598. &mut self,
  599. quote_id: &QuoteId,
  600. ) -> Result<Option<database::mint::MeltRequestInfo>, Self::Err> {
  601. let melt_request_row = query(
  602. r#"
  603. SELECT mr.inputs_amount, mr.inputs_fee, mq.unit
  604. FROM melt_request mr
  605. JOIN melt_quote mq ON mr.quote_id = mq.id
  606. WHERE mr.quote_id = :quote_id
  607. FOR UPDATE
  608. "#,
  609. )?
  610. .bind("quote_id", quote_id.to_string())
  611. .fetch_one(&self.inner)
  612. .await?;
  613. if let Some(row) = melt_request_row {
  614. let inputs_amount: u64 = column_as_number!(row[0].clone());
  615. let inputs_fee: u64 = column_as_number!(row[1].clone());
  616. let unit_str = column_as_string!(&row[2]);
  617. let unit = CurrencyUnit::from_str(&unit_str)?;
  618. // Get blinded messages from blind_signature table where c IS NULL
  619. let blinded_messages_rows = query(
  620. r#"
  621. SELECT blinded_message, keyset_id, amount
  622. FROM blind_signature
  623. WHERE quote_id = :quote_id AND c IS NULL
  624. "#,
  625. )?
  626. .bind("quote_id", quote_id.to_string())
  627. .fetch_all(&self.inner)
  628. .await?;
  629. let blinded_messages: Result<Vec<BlindedMessage>, Error> = blinded_messages_rows
  630. .into_iter()
  631. .map(|row| -> Result<BlindedMessage, Error> {
  632. let blinded_message_key =
  633. column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice);
  634. let keyset_id = column_as_string!(&row[1], Id::from_str, Id::from_bytes);
  635. let amount: u64 = column_as_number!(row[2].clone());
  636. Ok(BlindedMessage {
  637. blinded_secret: blinded_message_key,
  638. keyset_id,
  639. amount: Amount::from(amount),
  640. witness: None, // Not storing witness in database currently
  641. })
  642. })
  643. .collect();
  644. let blinded_messages = blinded_messages?;
  645. Ok(Some(database::mint::MeltRequestInfo {
  646. inputs_amount: Amount::from(inputs_amount).with_unit(unit.clone()),
  647. inputs_fee: Amount::from(inputs_fee).with_unit(unit),
  648. change_outputs: blinded_messages,
  649. }))
  650. } else {
  651. Ok(None)
  652. }
  653. }
  654. async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
  655. // Delete from melt_request table
  656. query(
  657. r#"
  658. DELETE FROM melt_request
  659. WHERE quote_id = :quote_id
  660. "#,
  661. )?
  662. .bind("quote_id", quote_id.to_string())
  663. .execute(&self.inner)
  664. .await?;
  665. // Also delete blinded messages (where c IS NULL) from blind_signature table
  666. query(
  667. r#"
  668. DELETE FROM blind_signature
  669. WHERE quote_id = :quote_id AND c IS NULL
  670. "#,
  671. )?
  672. .bind("quote_id", quote_id.to_string())
  673. .execute(&self.inner)
  674. .await?;
  675. Ok(())
  676. }
  677. async fn update_mint_quote(
  678. &mut self,
  679. quote: &mut Acquired<mint::MintQuote>,
  680. ) -> Result<(), Self::Err> {
  681. let mut changes = if let Some(changes) = quote.take_changes() {
  682. changes
  683. } else {
  684. return Ok(());
  685. };
  686. if changes.issuances.is_none() && changes.payments.is_none() {
  687. return Ok(());
  688. }
  689. for payment in changes.payments.take().unwrap_or_default() {
  690. query(
  691. r#"
  692. INSERT INTO mint_quote_payments
  693. (quote_id, payment_id, amount, timestamp)
  694. VALUES (:quote_id, :payment_id, :amount, :timestamp)
  695. "#,
  696. )?
  697. .bind("quote_id", quote.id.to_string())
  698. .bind("payment_id", payment.payment_id)
  699. .bind("amount", payment.amount.to_i64())
  700. .bind("timestamp", payment.time as i64)
  701. .execute(&self.inner)
  702. .await
  703. .map_err(|err| {
  704. tracing::error!("SQLite could not insert payment ID: {}", err);
  705. err
  706. })?;
  707. }
  708. let current_time = unix_time();
  709. for amount_issued in changes.issuances.take().unwrap_or_default() {
  710. query(
  711. r#"
  712. INSERT INTO mint_quote_issued
  713. (quote_id, amount, timestamp)
  714. VALUES (:quote_id, :amount, :timestamp);
  715. "#,
  716. )?
  717. .bind("quote_id", quote.id.to_string())
  718. .bind("amount", amount_issued.to_i64())
  719. .bind("timestamp", current_time as i64)
  720. .execute(&self.inner)
  721. .await?;
  722. }
  723. query(
  724. r#"
  725. UPDATE
  726. mint_quote
  727. SET
  728. amount_issued = :amount_issued,
  729. amount_paid = :amount_paid
  730. WHERE
  731. id = :quote_id
  732. "#,
  733. )?
  734. .bind("quote_id", quote.id.to_string())
  735. .bind("amount_issued", quote.amount_issued().to_i64())
  736. .bind("amount_paid", quote.amount_paid().to_i64())
  737. .execute(&self.inner)
  738. .await
  739. .inspect_err(|err| {
  740. tracing::error!("SQLite could not update mint quote amount_paid: {}", err);
  741. })?;
  742. Ok(())
  743. }
  744. #[instrument(skip_all)]
  745. async fn add_mint_quote(&mut self, quote: MintQuote) -> Result<Acquired<MintQuote>, Self::Err> {
  746. query(
  747. r#"
  748. INSERT INTO mint_quote (
  749. id, amount, unit, request, expiry, request_lookup_id, pubkey, created_time, payment_method, request_lookup_id_kind
  750. )
  751. VALUES (
  752. :id, :amount, :unit, :request, :expiry, :request_lookup_id, :pubkey, :created_time, :payment_method, :request_lookup_id_kind
  753. )
  754. "#,
  755. )?
  756. .bind("id", quote.id.to_string())
  757. .bind("amount", quote.amount.clone().map(|a| a.to_i64()))
  758. .bind("unit", quote.unit.to_string())
  759. .bind("request", quote.request.clone())
  760. .bind("expiry", quote.expiry as i64)
  761. .bind(
  762. "request_lookup_id",
  763. quote.request_lookup_id.to_string(),
  764. )
  765. .bind("pubkey", quote.pubkey.map(|p| p.to_string()))
  766. .bind("created_time", quote.created_time as i64)
  767. .bind("payment_method", quote.payment_method.to_string())
  768. .bind("request_lookup_id_kind", quote.request_lookup_id.kind())
  769. .execute(&self.inner)
  770. .await?;
  771. Ok(quote.into())
  772. }
  773. async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
  774. // Now insert the new quote
  775. query(
  776. r#"
  777. INSERT INTO melt_quote
  778. (
  779. id, unit, amount, request, fee_reserve, state,
  780. expiry, payment_preimage, request_lookup_id,
  781. created_time, paid_time, options, request_lookup_id_kind, payment_method
  782. )
  783. VALUES
  784. (
  785. :id, :unit, :amount, :request, :fee_reserve, :state,
  786. :expiry, :payment_preimage, :request_lookup_id,
  787. :created_time, :paid_time, :options, :request_lookup_id_kind, :payment_method
  788. )
  789. "#,
  790. )?
  791. .bind("id", quote.id.to_string())
  792. .bind("unit", quote.unit.to_string())
  793. .bind("amount", quote.amount().to_i64())
  794. .bind("request", serde_json::to_string(&quote.request)?)
  795. .bind("fee_reserve", quote.fee_reserve().to_i64())
  796. .bind("state", quote.state.to_string())
  797. .bind("expiry", quote.expiry as i64)
  798. .bind("payment_preimage", quote.payment_preimage)
  799. .bind(
  800. "request_lookup_id",
  801. quote.request_lookup_id.as_ref().map(|id| id.to_string()),
  802. )
  803. .bind("created_time", quote.created_time as i64)
  804. .bind("paid_time", quote.paid_time.map(|t| t as i64))
  805. .bind(
  806. "options",
  807. quote.options.map(|o| serde_json::to_string(&o).ok()),
  808. )
  809. .bind(
  810. "request_lookup_id_kind",
  811. quote.request_lookup_id.map(|id| id.kind()),
  812. )
  813. .bind("payment_method", quote.payment_method.to_string())
  814. .execute(&self.inner)
  815. .await?;
  816. Ok(())
  817. }
  818. async fn update_melt_quote_request_lookup_id(
  819. &mut self,
  820. quote: &mut Acquired<mint::MeltQuote>,
  821. new_request_lookup_id: &PaymentIdentifier,
  822. ) -> Result<(), Self::Err> {
  823. query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
  824. .bind("new_req_id", new_request_lookup_id.to_string())
  825. .bind("new_kind", new_request_lookup_id.kind())
  826. .bind("id", quote.id.to_string())
  827. .execute(&self.inner)
  828. .await?;
  829. quote.request_lookup_id = Some(new_request_lookup_id.clone());
  830. Ok(())
  831. }
  832. async fn update_melt_quote_state(
  833. &mut self,
  834. quote: &mut Acquired<mint::MeltQuote>,
  835. state: MeltQuoteState,
  836. payment_proof: Option<String>,
  837. ) -> Result<MeltQuoteState, Self::Err> {
  838. let old_state = quote.state;
  839. check_melt_quote_state_transition(old_state, state)?;
  840. let rec = if state == MeltQuoteState::Paid {
  841. let current_time = unix_time();
  842. quote.paid_time = Some(current_time);
  843. quote.payment_preimage = payment_proof.clone();
  844. query(r#"UPDATE melt_quote SET state = :state, paid_time = :paid_time, payment_preimage = :payment_preimage WHERE id = :id"#)?
  845. .bind("state", state.to_string())
  846. .bind("paid_time", current_time as i64)
  847. .bind("payment_preimage", payment_proof)
  848. .bind("id", quote.id.to_string())
  849. .execute(&self.inner)
  850. .await
  851. } else {
  852. query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
  853. .bind("state", state.to_string())
  854. .bind("id", quote.id.to_string())
  855. .execute(&self.inner)
  856. .await
  857. };
  858. match rec {
  859. Ok(_) => {}
  860. Err(err) => {
  861. tracing::error!("SQLite Could not update melt quote");
  862. return Err(err);
  863. }
  864. };
  865. quote.state = state;
  866. if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
  867. self.delete_melt_request(&quote.id).await?;
  868. }
  869. Ok(old_state)
  870. }
  871. async fn get_mint_quote(
  872. &mut self,
  873. quote_id: &QuoteId,
  874. ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
  875. get_mint_quote_inner(&self.inner, quote_id, true)
  876. .await
  877. .map(|quote| quote.map(|inner| inner.into()))
  878. }
  879. async fn get_melt_quote(
  880. &mut self,
  881. quote_id: &QuoteId,
  882. ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err> {
  883. get_melt_quote_inner(&self.inner, quote_id, true)
  884. .await
  885. .map(|quote| quote.map(|inner| inner.into()))
  886. }
  887. async fn get_melt_quotes_by_request_lookup_id(
  888. &mut self,
  889. request_lookup_id: &PaymentIdentifier,
  890. ) -> Result<Vec<Acquired<mint::MeltQuote>>, Self::Err> {
  891. get_melt_quotes_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
  892. .await
  893. .map(|quote| quote.into_iter().map(|inner| inner.into()).collect())
  894. }
  895. async fn lock_melt_quote_and_related(
  896. &mut self,
  897. quote_id: &QuoteId,
  898. ) -> Result<LockedMeltQuotes, Self::Err> {
  899. lock_melt_quote_and_related_inner(&self.inner, quote_id).await
  900. }
  901. async fn get_mint_quote_by_request(
  902. &mut self,
  903. request: &str,
  904. ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
  905. get_mint_quote_by_request_inner(&self.inner, request, true)
  906. .await
  907. .map(|quote| quote.map(|inner| inner.into()))
  908. }
  909. async fn get_mint_quote_by_request_lookup_id(
  910. &mut self,
  911. request_lookup_id: &PaymentIdentifier,
  912. ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
  913. get_mint_quote_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
  914. .await
  915. .map(|quote| quote.map(|inner| inner.into()))
  916. }
  917. }
  918. #[async_trait]
  919. impl<RM> MintQuotesDatabase for SQLMintDatabase<RM>
  920. where
  921. RM: DatabasePool + 'static,
  922. {
  923. type Err = Error;
  924. async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
  925. #[cfg(feature = "prometheus")]
  926. METRICS.inc_in_flight_requests("get_mint_quote");
  927. #[cfg(feature = "prometheus")]
  928. let start_time = std::time::Instant::now();
  929. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  930. let result = get_mint_quote_inner(&*conn, quote_id, false).await;
  931. #[cfg(feature = "prometheus")]
  932. {
  933. let success = result.is_ok();
  934. METRICS.record_mint_operation("get_mint_quote", success);
  935. METRICS.record_mint_operation_histogram(
  936. "get_mint_quote",
  937. success,
  938. start_time.elapsed().as_secs_f64(),
  939. );
  940. METRICS.dec_in_flight_requests("get_mint_quote");
  941. }
  942. result
  943. }
  944. async fn get_mint_quote_by_request(
  945. &self,
  946. request: &str,
  947. ) -> Result<Option<MintQuote>, Self::Err> {
  948. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  949. get_mint_quote_by_request_inner(&*conn, request, false).await
  950. }
  951. async fn get_mint_quote_by_request_lookup_id(
  952. &self,
  953. request_lookup_id: &PaymentIdentifier,
  954. ) -> Result<Option<MintQuote>, Self::Err> {
  955. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  956. get_mint_quote_by_request_lookup_id_inner(&*conn, request_lookup_id, false).await
  957. }
  958. async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
  959. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  960. let mut mint_quotes = query(
  961. r#"
  962. SELECT
  963. id,
  964. amount,
  965. unit,
  966. request,
  967. expiry,
  968. request_lookup_id,
  969. pubkey,
  970. created_time,
  971. amount_paid,
  972. amount_issued,
  973. payment_method,
  974. request_lookup_id_kind
  975. FROM
  976. mint_quote
  977. "#,
  978. )?
  979. .fetch_all(&*conn)
  980. .await?
  981. .into_iter()
  982. .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
  983. .collect::<Result<Vec<_>, _>>()?;
  984. for quote in mint_quotes.as_mut_slice() {
  985. let payments = get_mint_quote_payments(&*conn, &quote.id).await?;
  986. let issuance = get_mint_quote_issuance(&*conn, &quote.id).await?;
  987. quote.issuance = issuance;
  988. quote.payments = payments;
  989. }
  990. Ok(mint_quotes)
  991. }
  992. async fn get_melt_quote(
  993. &self,
  994. quote_id: &QuoteId,
  995. ) -> Result<Option<mint::MeltQuote>, Self::Err> {
  996. #[cfg(feature = "prometheus")]
  997. METRICS.inc_in_flight_requests("get_melt_quote");
  998. #[cfg(feature = "prometheus")]
  999. let start_time = std::time::Instant::now();
  1000. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  1001. let result = get_melt_quote_inner(&*conn, quote_id, false).await;
  1002. #[cfg(feature = "prometheus")]
  1003. {
  1004. let success = result.is_ok();
  1005. METRICS.record_mint_operation("get_melt_quote", success);
  1006. METRICS.record_mint_operation_histogram(
  1007. "get_melt_quote",
  1008. success,
  1009. start_time.elapsed().as_secs_f64(),
  1010. );
  1011. METRICS.dec_in_flight_requests("get_melt_quote");
  1012. }
  1013. result
  1014. }
  1015. async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
  1016. let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
  1017. Ok(query(
  1018. r#"
  1019. SELECT
  1020. id,
  1021. unit,
  1022. amount,
  1023. request,
  1024. fee_reserve,
  1025. expiry,
  1026. state,
  1027. payment_preimage,
  1028. request_lookup_id,
  1029. created_time,
  1030. paid_time,
  1031. payment_method,
  1032. options,
  1033. request_lookup_id_kind
  1034. FROM
  1035. melt_quote
  1036. "#,
  1037. )?
  1038. .fetch_all(&*conn)
  1039. .await?
  1040. .into_iter()
  1041. .map(sql_row_to_melt_quote)
  1042. .collect::<Result<Vec<_>, _>>()?)
  1043. }
  1044. }