mod.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. //! Macro with default tests
  2. //!
  3. //! This set is generic and checks the default and expected behaviour for a mint database
  4. //! implementation
  5. #![allow(clippy::unwrap_used)]
  6. use std::str::FromStr;
  7. use std::sync::atomic::{AtomicU64, Ordering};
  8. use std::time::{SystemTime, UNIX_EPOCH};
  9. // For derivation path parsing
  10. use bitcoin::bip32::DerivationPath;
  11. use cashu::secret::Secret;
  12. use cashu::{Amount, CurrencyUnit, SecretKey};
  13. use super::*;
  14. use crate::database::MintKVStoreDatabase;
  15. use crate::mint::MintKeySetInfo;
  16. mod keys;
  17. mod kvstore;
  18. mod mint;
  19. mod proofs;
  20. mod saga;
  21. mod signatures;
  22. pub use self::keys::*;
  23. pub use self::mint::*;
  24. pub use self::proofs::*;
  25. pub use self::saga::*;
  26. pub use self::signatures::*;
  27. /// Generate standard keyset amounts as powers of 2
  28. #[inline]
  29. fn standard_keyset_amounts(max_order: u32) -> Vec<u64> {
  30. (0..max_order).map(|n| 2u64.pow(n)).collect()
  31. }
  32. #[inline]
  33. async fn setup_keyset<DB>(db: &DB) -> Id
  34. where
  35. DB: KeysDatabase<Err = crate::database::Error>,
  36. {
  37. let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
  38. let keyset_info = MintKeySetInfo {
  39. id: keyset_id,
  40. unit: CurrencyUnit::Sat,
  41. active: true,
  42. valid_from: 0,
  43. final_expiry: None,
  44. derivation_path: DerivationPath::from_str("m/0'/0'/0'").unwrap(),
  45. derivation_path_index: Some(0),
  46. input_fee_ppk: 0,
  47. amounts: standard_keyset_amounts(32),
  48. };
  49. let mut writer = db.begin_transaction().await.expect("db.begin()");
  50. writer.add_keyset_info(keyset_info).await.unwrap();
  51. writer.commit().await.expect("commit()");
  52. keyset_id
  53. }
  54. /// State transition test
  55. pub async fn state_transition<DB>(db: DB)
  56. where
  57. DB: Database<crate::database::Error> + KeysDatabase<Err = crate::database::Error>,
  58. {
  59. let keyset_id = setup_keyset(&db).await;
  60. let proofs = vec![
  61. Proof {
  62. amount: Amount::from(100),
  63. keyset_id,
  64. secret: Secret::generate(),
  65. c: SecretKey::generate().public_key(),
  66. witness: None,
  67. dleq: None,
  68. },
  69. Proof {
  70. amount: Amount::from(200),
  71. keyset_id,
  72. secret: Secret::generate(),
  73. c: SecretKey::generate().public_key(),
  74. witness: None,
  75. dleq: None,
  76. },
  77. ];
  78. // Add proofs to database
  79. let mut tx = Database::begin_transaction(&db).await.unwrap();
  80. tx.add_proofs(proofs.clone(), None, &Operation::new_swap())
  81. .await
  82. .unwrap();
  83. // Mark one proof as `pending`
  84. assert!(tx
  85. .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
  86. .await
  87. .is_ok());
  88. // Attempt to select the `pending` proof, as `pending` again (which should fail)
  89. assert!(tx
  90. .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
  91. .await
  92. .is_err());
  93. tx.commit().await.unwrap();
  94. }
  95. /// Test KV store functionality including write, read, list, update, and remove operations
  96. pub async fn kvstore_functionality<DB>(db: DB)
  97. where
  98. DB: Database<crate::database::Error> + MintKVStoreDatabase<Err = crate::database::Error>,
  99. {
  100. // Test basic read/write operations in transaction
  101. {
  102. let mut tx = Database::begin_transaction(&db).await.unwrap();
  103. // Write some test data
  104. tx.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
  105. .await
  106. .unwrap();
  107. tx.kv_write("test_namespace", "sub_namespace", "key2", b"value2")
  108. .await
  109. .unwrap();
  110. tx.kv_write("test_namespace", "other_sub", "key3", b"value3")
  111. .await
  112. .unwrap();
  113. // Read back the data in the transaction
  114. let value1 = tx
  115. .kv_read("test_namespace", "sub_namespace", "key1")
  116. .await
  117. .unwrap();
  118. assert_eq!(value1, Some(b"value1".to_vec()));
  119. // List keys in namespace
  120. let keys = tx.kv_list("test_namespace", "sub_namespace").await.unwrap();
  121. assert_eq!(keys, vec!["key1", "key2"]);
  122. // Commit transaction
  123. tx.commit().await.unwrap();
  124. }
  125. // Test read operations after commit
  126. {
  127. let value1 = db
  128. .kv_read("test_namespace", "sub_namespace", "key1")
  129. .await
  130. .unwrap();
  131. assert_eq!(value1, Some(b"value1".to_vec()));
  132. let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
  133. assert_eq!(keys, vec!["key1", "key2"]);
  134. let other_keys = db.kv_list("test_namespace", "other_sub").await.unwrap();
  135. assert_eq!(other_keys, vec!["key3"]);
  136. }
  137. // Test update and remove operations
  138. {
  139. let mut tx = Database::begin_transaction(&db).await.unwrap();
  140. // Update existing key
  141. tx.kv_write("test_namespace", "sub_namespace", "key1", b"updated_value1")
  142. .await
  143. .unwrap();
  144. // Remove a key
  145. tx.kv_remove("test_namespace", "sub_namespace", "key2")
  146. .await
  147. .unwrap();
  148. tx.commit().await.unwrap();
  149. }
  150. // Verify updates
  151. {
  152. let value1 = db
  153. .kv_read("test_namespace", "sub_namespace", "key1")
  154. .await
  155. .unwrap();
  156. assert_eq!(value1, Some(b"updated_value1".to_vec()));
  157. let value2 = db
  158. .kv_read("test_namespace", "sub_namespace", "key2")
  159. .await
  160. .unwrap();
  161. assert_eq!(value2, None);
  162. let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
  163. assert_eq!(keys, vec!["key1"]);
  164. }
  165. }
  166. static COUNTER: AtomicU64 = AtomicU64::new(0);
  167. /// Returns a unique, random-looking Base62 string (no external crates).
  168. /// Not cryptographically secure, but great for ids, keys, temp names, etc.
  169. fn unique_string() -> String {
  170. // 1) high-res timestamp (nanos since epoch)
  171. let now = SystemTime::now()
  172. .duration_since(UNIX_EPOCH)
  173. .unwrap()
  174. .as_nanos();
  175. // 2) per-process monotonic counter to avoid collisions in the same instant
  176. let n = COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
  177. // 3) process id to reduce collision chance across processes
  178. let pid = std::process::id() as u128;
  179. // Mix the components (simple XOR/shift mix; good enough for "random-looking")
  180. let mixed = now ^ (pid << 64) ^ (n << 32);
  181. base62_encode(mixed)
  182. }
  183. fn base62_encode(mut x: u128) -> String {
  184. const ALPHABET: &[u8; 62] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  185. if x == 0 {
  186. return "0".to_string();
  187. }
  188. let mut buf = [0u8; 26]; // enough for base62(u128)
  189. let mut i = buf.len();
  190. while x > 0 {
  191. let rem = (x % 62) as usize;
  192. x /= 62;
  193. i -= 1;
  194. buf[i] = ALPHABET[rem];
  195. }
  196. String::from_utf8_lossy(&buf[i..]).into_owned()
  197. }
  198. /// Unit test that is expected to be passed for a correct database implementation
  199. #[macro_export]
  200. macro_rules! mint_db_test {
  201. ($make_db_fn:ident) => {
  202. mint_db_test!(
  203. $make_db_fn,
  204. state_transition,
  205. add_and_find_proofs,
  206. add_duplicate_proofs,
  207. kvstore_functionality,
  208. add_mint_quote,
  209. add_mint_quote_only_once,
  210. register_payments,
  211. read_mint_from_db_and_tx,
  212. get_proofs_by_keyset_id,
  213. reject_duplicate_payments_same_tx,
  214. reject_duplicate_payments_diff_tx,
  215. reject_over_issue_same_tx,
  216. reject_over_issue_different_tx,
  217. reject_over_issue_with_payment,
  218. reject_over_issue_with_payment_different_tx,
  219. add_melt_request_unique_blinded_messages,
  220. reject_melt_duplicate_blinded_signature,
  221. reject_duplicate_blinded_message_db_constraint,
  222. cleanup_melt_request_after_processing,
  223. add_and_get_melt_quote,
  224. add_melt_quote_only_once,
  225. update_melt_quote_state_transition,
  226. update_melt_quote_request_lookup_id,
  227. get_all_mint_quotes,
  228. get_all_melt_quotes,
  229. get_mint_quote_by_request,
  230. get_mint_quote_by_request_lookup_id,
  231. delete_blinded_messages,
  232. add_and_get_blind_signatures,
  233. get_blind_signatures_for_keyset,
  234. get_blind_signatures_for_quote,
  235. get_total_issued,
  236. get_nonexistent_blind_signatures,
  237. add_duplicate_blind_signatures,
  238. add_and_get_keyset_info,
  239. add_duplicate_keyset_info,
  240. get_all_keyset_infos,
  241. set_and_get_active_keyset,
  242. get_all_active_keysets,
  243. update_active_keyset,
  244. get_nonexistent_keyset_info,
  245. get_active_keyset_when_none_set,
  246. get_proofs_states,
  247. get_nonexistent_proof_states,
  248. get_proofs_by_nonexistent_ys,
  249. proof_transaction_isolation,
  250. proof_rollback,
  251. multiple_proofs_same_keyset,
  252. add_and_get_saga,
  253. add_duplicate_saga,
  254. update_saga_state,
  255. delete_saga,
  256. get_incomplete_swap_sagas,
  257. get_incomplete_melt_sagas,
  258. get_nonexistent_saga,
  259. update_nonexistent_saga,
  260. delete_nonexistent_saga,
  261. saga_with_quote_id,
  262. saga_transaction_rollback,
  263. multiple_sagas_different_states,
  264. increment_mint_quote_amount_paid,
  265. increment_mint_quote_amount_issued,
  266. get_mint_quote_in_transaction,
  267. get_melt_quote_in_transaction,
  268. get_mint_quote_by_request_in_transaction,
  269. get_mint_quote_by_request_lookup_id_in_transaction,
  270. get_blind_signatures_in_transaction
  271. );
  272. };
  273. ($make_db_fn:ident, $($name:ident),+ $(,)?) => {
  274. $(
  275. #[tokio::test]
  276. async fn $name() {
  277. use std::time::{SystemTime, UNIX_EPOCH};
  278. let now = SystemTime::now()
  279. .duration_since(UNIX_EPOCH)
  280. .expect("Time went backwards");
  281. cdk_common::database::mint::test::$name($make_db_fn(format!("test_{}_{}", now.as_nanos(), stringify!($name))).await).await;
  282. }
  283. )+
  284. };
  285. }