mod.rs 8.9 KB

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