1use std::collections::HashMap;
4use std::str::FromStr;
5
6use async_trait::async_trait;
7use cdk_common::database::mint::Acquired;
8use cdk_common::database::{self, Error, MintProofsDatabase};
9use cdk_common::mint::{Operation, ProofsWithState};
10use cdk_common::nut00::ProofsMethods;
11use cdk_common::quote_id::QuoteId;
12use cdk_common::secret::Secret;
13use cdk_common::util::unix_time;
14use cdk_common::{Amount, Id, Proof, Proofs, PublicKey, State};
15
16use super::{SQLMintDatabase, SQLTransaction};
17use crate::database::DatabaseExecutor;
18use crate::pool::DatabasePool;
19use crate::stmt::{query, Column};
20use crate::{column_as_nullable_string, column_as_number, column_as_string, unpack_into};
21
22pub(super) async fn get_current_states<C>(
23 conn: &C,
24 ys: &[PublicKey],
25 for_update: bool,
26) -> Result<HashMap<PublicKey, State>, Error>
27where
28 C: DatabaseExecutor + Send + Sync,
29{
30 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
31
32 query(&format!(
33 r#"SELECT y, state FROM proof WHERE y IN (:ys) ORDER BY y {}"#,
34 for_update_clause
35 ))?
36 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
37 .fetch_all(conn)
38 .await?
39 .into_iter()
40 .map(|row| {
41 Ok((
42 column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice),
43 column_as_string!(&row[1], State::from_str),
44 ))
45 })
46 .collect::<Result<HashMap<_, _>, _>>()
47}
48
49pub(super) fn sql_row_to_proof(row: Vec<Column>) -> Result<Proof, Error> {
50 unpack_into!(
51 let (
52 amount,
53 keyset_id,
54 secret,
55 c,
56 witness
57 ) = row
58 );
59
60 let amount: u64 = column_as_number!(amount);
61 Ok(Proof {
62 amount: Amount::from(amount),
63 keyset_id: column_as_string!(keyset_id, Id::from_str),
64 secret: column_as_string!(secret, Secret::from_str),
65 c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
66 witness: column_as_nullable_string!(witness).and_then(|w| serde_json::from_str(&w).ok()),
67 dleq: None,
68 p2pk_e: None,
69 })
70}
71
72pub(super) fn sql_row_to_proof_with_state(row: Vec<Column>) -> Result<(Proof, State), Error> {
73 unpack_into!(
74 let (
75 keyset_id, amount, secret, c, witness, state
76 ) = row
77 );
78
79 let amount: u64 = column_as_number!(amount);
80 let state = column_as_nullable_string!(state)
81 .and_then(|s| State::from_str(&s).ok())
82 .unwrap_or(State::Pending);
83
84 Ok((
85 Proof {
86 amount: Amount::from(amount),
87 keyset_id: column_as_string!(keyset_id, Id::from_str, Id::from_bytes),
88 secret: column_as_string!(secret, Secret::from_str),
89 c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
90 witness: column_as_nullable_string!(witness)
91 .and_then(|w| serde_json::from_str(&w).ok()),
92 dleq: None,
93 p2pk_e: None,
94 },
95 state,
96 ))
97}
98
99pub(super) fn sql_row_to_hashmap_amount(row: Vec<Column>) -> Result<(Id, Amount), Error> {
100 unpack_into!(
101 let (
102 keyset_id, amount
103 ) = row
104 );
105
106 let amount: u64 = column_as_number!(amount);
107 Ok((
108 column_as_string!(keyset_id, Id::from_str, Id::from_bytes),
109 Amount::from(amount),
110 ))
111}
112
113#[async_trait]
114impl<RM> database::MintProofsTransaction for SQLTransaction<RM>
115where
116 RM: DatabasePool + 'static,
117{
118 type Err = Error;
119
120 async fn add_proofs(
130 &mut self,
131 proofs: Proofs,
132 quote_id: Option<QuoteId>,
133 operation: &Operation,
134 ) -> Result<Acquired<ProofsWithState>, Self::Err> {
135 let current_time = unix_time();
136
137 let mut ordered_proofs = proofs
138 .iter()
139 .map(|proof| Ok((proof.y()?.to_bytes().to_vec(), proof)))
140 .collect::<Result<Vec<_>, Error>>()?;
141 ordered_proofs.sort_unstable_by(|(left_y, _), (right_y, _)| left_y.cmp(right_y));
142
143 match query(r#"SELECT state FROM proof WHERE y IN (:ys) ORDER BY y LIMIT 1 FOR UPDATE"#)?
146 .bind_vec(
147 "ys",
148 ordered_proofs.iter().map(|(y, _)| y.clone()).collect(),
149 )?
150 .pluck(&self.inner)
151 .await?
152 .map(|state| Ok::<_, Error>(column_as_string!(&state, State::from_str)))
153 .transpose()?
154 {
155 Some(State::Spent) => Err(database::Error::AttemptUpdateSpentProof),
156 Some(_) => Err(database::Error::Duplicate),
157 None => Ok(()), }?;
159
160 for (y, proof) in ordered_proofs {
161 query(
162 r#"
163 INSERT INTO proof
164 (y, amount, keyset_id, secret, c, witness, state, quote_id, created_time, operation_kind, operation_id)
165 VALUES
166 (:y, :amount, :keyset_id, :secret, :c, :witness, :state, :quote_id, :created_time, :operation_kind, :operation_id)
167 "#,
168 )?
169 .bind("y", y)
170 .bind("amount", proof.amount.to_i64())
171 .bind("keyset_id", proof.keyset_id.to_string())
172 .bind("secret", proof.secret.to_string())
173 .bind("c", proof.c.to_bytes().to_vec())
174 .bind(
175 "witness",
176 proof.witness.clone().and_then(|w| serde_json::to_string(&w).inspect_err(|e| tracing::error!("Failed to serialize witness: {:?}", e)).ok()),
177 )
178 .bind("state", "UNSPENT".to_string())
179 .bind("quote_id", quote_id.clone().map(|q| q.to_string()))
180 .bind("created_time", current_time as i64)
181 .bind("operation_kind", operation.kind().to_string())
182 .bind("operation_id", operation.id().to_string())
183 .execute(&self.inner)
184 .await?;
185 }
186
187 Ok(ProofsWithState::new(proofs, State::Unspent).into())
188 }
189
190 async fn update_proofs_state(
203 &mut self,
204 proofs: &mut Acquired<ProofsWithState>,
205 new_state: State,
206 ) -> Result<(), Self::Err> {
207 let ys = proofs.ys()?;
208
209 query(r#"UPDATE proof SET state = :new_state WHERE y IN (:ys)"#)?
210 .bind("new_state", new_state.to_string())
211 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
212 .execute(&self.inner)
213 .await?;
214
215 if new_state == State::Spent {
216 query(
217 r#"
218 INSERT INTO keyset_amounts (keyset_id, total_issued, total_redeemed)
219 SELECT keyset_id, 0, COALESCE(SUM(amount), 0)
220 FROM proof
221 WHERE y IN (:ys)
222 GROUP BY keyset_id
223 ORDER BY keyset_id
224 ON CONFLICT (keyset_id)
225 DO UPDATE SET total_redeemed = keyset_amounts.total_redeemed + EXCLUDED.total_redeemed
226 "#,
227 )?
228 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
229 .execute(&self.inner)
230 .await?;
231 }
232
233 proofs.state = new_state;
234
235 Ok(())
236 }
237
238 async fn remove_proofs(
239 &mut self,
240 ys: &[PublicKey],
241 _quote_id: Option<QuoteId>,
242 ) -> Result<(), Self::Err> {
243 query(
247 r#"
248 SELECT y
249 FROM proof
250 WHERE y IN (:ys) AND state NOT IN (:exclude_state)
251 ORDER BY y
252 FOR UPDATE
253 "#,
254 )?
255 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
256 .bind_vec("exclude_state", vec![State::Spent.to_string()])?
257 .fetch_all(&self.inner)
258 .await?;
259
260 let total_deleted = query(
261 r#"
262 DELETE FROM proof WHERE y IN (:ys) AND state NOT IN (:exclude_state)
263 "#,
264 )?
265 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
266 .bind_vec("exclude_state", vec![State::Spent.to_string()])?
267 .execute(&self.inner)
268 .await?;
269
270 if total_deleted != ys.len() {
271 let current_states = get_current_states(&self.inner, ys, true).await?;
273
274 let missing_count = ys.len() - current_states.len();
275 let spent_count = current_states
276 .values()
277 .filter(|s| **s == State::Spent)
278 .count();
279
280 if missing_count > 0 {
281 tracing::warn!(
282 "remove_proofs: {} of {} proofs do not exist in database (already removed?)",
283 missing_count,
284 ys.len()
285 );
286 }
287
288 if spent_count > 0 {
289 tracing::warn!(
290 "remove_proofs: {} of {} proofs are in Spent state and cannot be removed",
291 spent_count,
292 ys.len()
293 );
294 }
295
296 tracing::debug!(
297 "remove_proofs details: requested={}, deleted={}, missing={}, spent={}",
298 ys.len(),
299 total_deleted,
300 missing_count,
301 spent_count
302 );
303
304 return Err(Self::Err::AttemptRemoveSpentProof);
305 }
306
307 Ok(())
308 }
309
310 async fn get_proof_ys_by_quote_id(
311 &mut self,
312 quote_id: &QuoteId,
313 ) -> Result<Vec<PublicKey>, Self::Err> {
314 Ok(query(
315 r#"
316 SELECT
317 amount,
318 keyset_id,
319 secret,
320 c,
321 witness
322 FROM
323 proof
324 WHERE
325 quote_id = :quote_id
326 ORDER BY y
327 FOR UPDATE
328 "#,
329 )?
330 .bind("quote_id", quote_id.to_string())
331 .fetch_all(&self.inner)
332 .await?
333 .into_iter()
334 .map(sql_row_to_proof)
335 .collect::<Result<Vec<Proof>, _>>()?
336 .ys()?)
337 }
338
339 async fn get_proof_ys_by_operation_id(
340 &mut self,
341 operation_id: &uuid::Uuid,
342 ) -> Result<Vec<PublicKey>, Self::Err> {
343 Ok(query(
344 r#"
345 SELECT
346 y
347 FROM
348 proof
349 WHERE
350 operation_id = :operation_id
351 "#,
352 )?
353 .bind("operation_id", operation_id.to_string())
354 .fetch_all(&self.inner)
355 .await?
356 .into_iter()
357 .map(|row| -> Result<PublicKey, Error> {
358 Ok(column_as_string!(
359 &row[0],
360 PublicKey::from_hex,
361 PublicKey::from_slice
362 ))
363 })
364 .collect::<Result<Vec<_>, _>>()?)
365 }
366
367 async fn get_proofs(
368 &mut self,
369 ys: &[PublicKey],
370 ) -> Result<Acquired<ProofsWithState>, Self::Err> {
371 let rows = query(
372 r#"
373 SELECT
374 keyset_id,
375 amount,
376 secret,
377 c,
378 witness,
379 state
380 FROM
381 proof
382 WHERE
383 y IN (:ys)
384 ORDER BY y
385 FOR UPDATE
386 "#,
387 )?
388 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
389 .fetch_all(&self.inner)
390 .await?;
391
392 if rows.is_empty() || rows.len() != ys.len() {
393 return Err(database::Error::ProofNotFound);
394 }
395
396 let results: Vec<(Proof, State)> = rows
397 .into_iter()
398 .map(sql_row_to_proof_with_state)
399 .collect::<Result<Vec<_>, _>>()?;
400
401 let mut proofs = Vec::with_capacity(results.len());
402 let mut first_state: Option<State> = None;
403
404 for (proof, state) in results {
405 if let Some(first) = first_state {
406 if first != state {
407 return Err(database::Error::Internal(
408 "Proofs have inconsistent states".to_string(),
409 ));
410 }
411 } else {
412 first_state = Some(state);
413 }
414
415 proofs.push(proof);
416 }
417
418 let state = first_state.unwrap_or(State::Unspent);
419 Ok(ProofsWithState::new(proofs, state).into())
420 }
421}
422
423#[async_trait]
424impl<RM> MintProofsDatabase for SQLMintDatabase<RM>
425where
426 RM: DatabasePool + 'static,
427{
428 type Err = Error;
429
430 async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
431 let conn = self
432 .pool
433 .get()
434 .await
435 .map_err(|e| Error::Database(Box::new(e)))?;
436 let mut proofs = query(
437 r#"
438 SELECT
439 amount,
440 keyset_id,
441 secret,
442 c,
443 witness,
444 y
445 FROM
446 proof
447 WHERE
448 y IN (:ys)
449 "#,
450 )?
451 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
452 .fetch_all(&*conn)
453 .await?
454 .into_iter()
455 .map(|mut row| {
456 Ok((
457 column_as_string!(
458 row.pop().ok_or(Error::InvalidDbResponse)?,
459 PublicKey::from_hex,
460 PublicKey::from_slice
461 ),
462 sql_row_to_proof(row)?,
463 ))
464 })
465 .collect::<Result<HashMap<_, _>, Error>>()?;
466
467 Ok(ys.iter().map(|y| proofs.remove(y)).collect())
468 }
469
470 async fn get_proof_ys_by_quote_id(
471 &self,
472 quote_id: &QuoteId,
473 ) -> Result<Vec<PublicKey>, Self::Err> {
474 let conn = self
475 .pool
476 .get()
477 .await
478 .map_err(|e| Error::Database(Box::new(e)))?;
479 Ok(query(
480 r#"
481 SELECT
482 amount,
483 keyset_id,
484 secret,
485 c,
486 witness
487 FROM
488 proof
489 WHERE
490 quote_id = :quote_id
491 "#,
492 )?
493 .bind("quote_id", quote_id.to_string())
494 .fetch_all(&*conn)
495 .await?
496 .into_iter()
497 .map(sql_row_to_proof)
498 .collect::<Result<Vec<Proof>, _>>()?
499 .ys()?)
500 }
501
502 async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
503 let conn = self
504 .pool
505 .get()
506 .await
507 .map_err(|e| Error::Database(Box::new(e)))?;
508 let mut current_states = get_current_states(&*conn, ys, false).await?;
509
510 Ok(ys.iter().map(|y| current_states.remove(y)).collect())
511 }
512
513 async fn get_proofs_by_keyset_id(
514 &self,
515 keyset_id: &Id,
516 ) -> Result<(Proofs, Vec<Option<State>>), Self::Err> {
517 let conn = self
518 .pool
519 .get()
520 .await
521 .map_err(|e| Error::Database(Box::new(e)))?;
522
523 let (proofs, states): (Vec<Proof>, Vec<State>) = query(
524 r#"
525 SELECT
526 keyset_id,
527 amount,
528 secret,
529 c,
530 witness,
531 state
532 FROM
533 proof
534 WHERE
535 keyset_id=:keyset_id
536 "#,
537 )?
538 .bind("keyset_id", keyset_id.to_string())
539 .fetch_all(&*conn)
540 .await?
541 .into_iter()
542 .map(sql_row_to_proof_with_state)
543 .collect::<Result<Vec<_>, _>>()?
544 .into_iter()
545 .unzip();
546
547 Ok((proofs, states.into_iter().map(Some).collect()))
548 }
549
550 async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err> {
552 let conn = self
553 .pool
554 .get()
555 .await
556 .map_err(|e| Error::Database(Box::new(e)))?;
557 query(
558 r#"
559 SELECT
560 keyset_id,
561 total_redeemed as amount
562 FROM
563 keyset_amounts
564 "#,
565 )?
566 .fetch_all(&*conn)
567 .await?
568 .into_iter()
569 .map(sql_row_to_hashmap_amount)
570 .collect()
571 }
572
573 async fn get_proof_ys_by_operation_id(
574 &self,
575 operation_id: &uuid::Uuid,
576 ) -> Result<Vec<PublicKey>, Self::Err> {
577 let conn = self
578 .pool
579 .get()
580 .await
581 .map_err(|e| Error::Database(Box::new(e)))?;
582 query(
583 r#"
584 SELECT
585 y
586 FROM
587 proof
588 WHERE
589 operation_id = :operation_id
590 "#,
591 )?
592 .bind("operation_id", operation_id.to_string())
593 .fetch_all(&*conn)
594 .await?
595 .into_iter()
596 .map(|row| -> Result<PublicKey, Error> {
597 Ok(column_as_string!(
598 &row[0],
599 PublicKey::from_hex,
600 PublicKey::from_slice
601 ))
602 })
603 .collect::<Result<Vec<_>, _>>()
604 }
605}