fake_wallet.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. use std::sync::Arc;
  2. use std::time::Duration;
  3. use anyhow::Result;
  4. use bip39::Mnemonic;
  5. use cdk::amount::SplitTarget;
  6. use cdk::cdk_database::WalletMemoryDatabase;
  7. use cdk::nuts::{
  8. CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintQuoteState, PreMintSecrets, State,
  9. };
  10. use cdk::wallet::client::{HttpClient, HttpClientMethods};
  11. use cdk::wallet::Wallet;
  12. use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
  13. use cdk_integration_tests::attempt_to_swap_pending;
  14. use tokio::time::sleep;
  15. const MINT_URL: &str = "http://127.0.0.1:8086";
  16. // If both pay and check return pending input proofs should remain pending
  17. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  18. async fn test_fake_tokens_pending() -> Result<()> {
  19. let wallet = Wallet::new(
  20. MINT_URL,
  21. CurrencyUnit::Sat,
  22. Arc::new(WalletMemoryDatabase::default()),
  23. &Mnemonic::generate(12)?.to_seed_normalized(""),
  24. None,
  25. )?;
  26. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  27. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  28. let _mint_amount = wallet
  29. .mint(&mint_quote.id, SplitTarget::default(), None)
  30. .await?;
  31. let fake_description = FakeInvoiceDescription {
  32. pay_invoice_state: MeltQuoteState::Pending,
  33. check_payment_state: MeltQuoteState::Pending,
  34. pay_err: false,
  35. check_err: false,
  36. };
  37. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  38. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  39. let melt = wallet.melt(&melt_quote.id).await;
  40. assert!(melt.is_err());
  41. attempt_to_swap_pending(&wallet).await?;
  42. Ok(())
  43. }
  44. // If the pay error fails and the check returns unknown or failed
  45. // The inputs proofs should be unset as spending
  46. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  47. async fn test_fake_melt_payment_fail() -> Result<()> {
  48. let wallet = Wallet::new(
  49. MINT_URL,
  50. CurrencyUnit::Sat,
  51. Arc::new(WalletMemoryDatabase::default()),
  52. &Mnemonic::generate(12)?.to_seed_normalized(""),
  53. None,
  54. )?;
  55. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  56. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  57. let _mint_amount = wallet
  58. .mint(&mint_quote.id, SplitTarget::default(), None)
  59. .await?;
  60. let fake_description = FakeInvoiceDescription {
  61. pay_invoice_state: MeltQuoteState::Unknown,
  62. check_payment_state: MeltQuoteState::Unknown,
  63. pay_err: true,
  64. check_err: false,
  65. };
  66. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  67. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  68. // The melt should error at the payment invoice command
  69. let melt = wallet.melt(&melt_quote.id).await;
  70. assert!(melt.is_err());
  71. let fake_description = FakeInvoiceDescription {
  72. pay_invoice_state: MeltQuoteState::Failed,
  73. check_payment_state: MeltQuoteState::Failed,
  74. pay_err: true,
  75. check_err: false,
  76. };
  77. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  78. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  79. // The melt should error at the payment invoice command
  80. let melt = wallet.melt(&melt_quote.id).await;
  81. assert!(melt.is_err());
  82. // The mint should have unset proofs from pending since payment failed
  83. let all_proof = wallet.get_unspent_proofs().await?;
  84. let states = wallet.check_proofs_spent(all_proof).await?;
  85. for state in states {
  86. assert!(state.state == State::Unspent);
  87. }
  88. let wallet_bal = wallet.total_balance().await?;
  89. assert!(wallet_bal == 100.into());
  90. Ok(())
  91. }
  92. // When both the pay_invoice and check_invoice both fail
  93. // the proofs should remain as pending
  94. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  95. async fn test_fake_melt_payment_fail_and_check() -> Result<()> {
  96. let wallet = Wallet::new(
  97. MINT_URL,
  98. CurrencyUnit::Sat,
  99. Arc::new(WalletMemoryDatabase::default()),
  100. &Mnemonic::generate(12)?.to_seed_normalized(""),
  101. None,
  102. )?;
  103. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  104. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  105. let _mint_amount = wallet
  106. .mint(&mint_quote.id, SplitTarget::default(), None)
  107. .await?;
  108. let fake_description = FakeInvoiceDescription {
  109. pay_invoice_state: MeltQuoteState::Unknown,
  110. check_payment_state: MeltQuoteState::Unknown,
  111. pay_err: true,
  112. check_err: true,
  113. };
  114. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  115. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  116. // The melt should error at the payment invoice command
  117. let melt = wallet.melt(&melt_quote.id).await;
  118. assert!(melt.is_err());
  119. let pending = wallet
  120. .localstore
  121. .get_proofs(None, None, Some(vec![State::Pending]), None)
  122. .await?;
  123. assert!(!pending.is_empty());
  124. Ok(())
  125. }
  126. // In the case that the ln backend returns a failed status but does not error
  127. // The mint should do a second check, then remove proofs from pending
  128. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  129. async fn test_fake_melt_payment_return_fail_status() -> Result<()> {
  130. let wallet = Wallet::new(
  131. MINT_URL,
  132. CurrencyUnit::Sat,
  133. Arc::new(WalletMemoryDatabase::default()),
  134. &Mnemonic::generate(12)?.to_seed_normalized(""),
  135. None,
  136. )?;
  137. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  138. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  139. let _mint_amount = wallet
  140. .mint(&mint_quote.id, SplitTarget::default(), None)
  141. .await?;
  142. let fake_description = FakeInvoiceDescription {
  143. pay_invoice_state: MeltQuoteState::Failed,
  144. check_payment_state: MeltQuoteState::Failed,
  145. pay_err: false,
  146. check_err: false,
  147. };
  148. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  149. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  150. // The melt should error at the payment invoice command
  151. let melt = wallet.melt(&melt_quote.id).await;
  152. assert!(melt.is_err());
  153. let fake_description = FakeInvoiceDescription {
  154. pay_invoice_state: MeltQuoteState::Unknown,
  155. check_payment_state: MeltQuoteState::Unknown,
  156. pay_err: false,
  157. check_err: false,
  158. };
  159. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  160. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  161. // The melt should error at the payment invoice command
  162. let melt = wallet.melt(&melt_quote.id).await;
  163. assert!(melt.is_err());
  164. let pending = wallet
  165. .localstore
  166. .get_proofs(None, None, Some(vec![State::Pending]), None)
  167. .await?;
  168. assert!(pending.is_empty());
  169. Ok(())
  170. }
  171. // In the case that the ln backend returns a failed status but does not error
  172. // The mint should do a second check, then remove proofs from pending
  173. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  174. async fn test_fake_melt_payment_error_unknown() -> Result<()> {
  175. let wallet = Wallet::new(
  176. MINT_URL,
  177. CurrencyUnit::Sat,
  178. Arc::new(WalletMemoryDatabase::default()),
  179. &Mnemonic::generate(12)?.to_seed_normalized(""),
  180. None,
  181. )?;
  182. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  183. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  184. let _mint_amount = wallet
  185. .mint(&mint_quote.id, SplitTarget::default(), None)
  186. .await?;
  187. let fake_description = FakeInvoiceDescription {
  188. pay_invoice_state: MeltQuoteState::Failed,
  189. check_payment_state: MeltQuoteState::Unknown,
  190. pay_err: true,
  191. check_err: false,
  192. };
  193. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  194. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  195. // The melt should error at the payment invoice command
  196. let melt = wallet.melt(&melt_quote.id).await;
  197. assert!(melt.is_err());
  198. let fake_description = FakeInvoiceDescription {
  199. pay_invoice_state: MeltQuoteState::Unknown,
  200. check_payment_state: MeltQuoteState::Unknown,
  201. pay_err: true,
  202. check_err: false,
  203. };
  204. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  205. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  206. // The melt should error at the payment invoice command
  207. let melt = wallet.melt(&melt_quote.id).await;
  208. assert!(melt.is_err());
  209. let pending = wallet
  210. .localstore
  211. .get_proofs(None, None, Some(vec![State::Pending]), None)
  212. .await?;
  213. assert!(pending.is_empty());
  214. Ok(())
  215. }
  216. // In the case that the ln backend returns an err
  217. // The mint should do a second check, that returns paid
  218. // Proofs should remain pending
  219. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  220. async fn test_fake_melt_payment_err_paid() -> Result<()> {
  221. let wallet = Wallet::new(
  222. MINT_URL,
  223. CurrencyUnit::Sat,
  224. Arc::new(WalletMemoryDatabase::default()),
  225. &Mnemonic::generate(12)?.to_seed_normalized(""),
  226. None,
  227. )?;
  228. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  229. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  230. let _mint_amount = wallet
  231. .mint(&mint_quote.id, SplitTarget::default(), None)
  232. .await?;
  233. let fake_description = FakeInvoiceDescription {
  234. pay_invoice_state: MeltQuoteState::Failed,
  235. check_payment_state: MeltQuoteState::Paid,
  236. pay_err: true,
  237. check_err: false,
  238. };
  239. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  240. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  241. // The melt should error at the payment invoice command
  242. let melt = wallet.melt(&melt_quote.id).await;
  243. assert!(melt.is_err());
  244. attempt_to_swap_pending(&wallet).await?;
  245. Ok(())
  246. }
  247. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  248. async fn test_fake_melt_change_in_quote() -> Result<()> {
  249. let wallet = Wallet::new(
  250. MINT_URL,
  251. CurrencyUnit::Sat,
  252. Arc::new(WalletMemoryDatabase::default()),
  253. &Mnemonic::generate(12)?.to_seed_normalized(""),
  254. None,
  255. )?;
  256. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  257. wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
  258. let _mint_amount = wallet
  259. .mint(&mint_quote.id, SplitTarget::default(), None)
  260. .await?;
  261. let fake_description = FakeInvoiceDescription::default();
  262. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  263. let proofs = wallet.get_unspent_proofs().await?;
  264. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  265. let keyset = wallet.get_active_mint_keyset().await?;
  266. let premint_secrets = PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default())?;
  267. let client = HttpClient::new();
  268. let melt_request = MeltBolt11Request {
  269. quote: melt_quote.id.clone(),
  270. inputs: proofs.clone(),
  271. outputs: Some(premint_secrets.blinded_messages()),
  272. };
  273. let melt_response = client.post_melt(MINT_URL.parse()?, melt_request).await?;
  274. assert!(melt_response.change.is_some());
  275. let check = wallet.melt_quote_status(&melt_quote.id).await?;
  276. let mut melt_change = melt_response.change.unwrap();
  277. melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
  278. let mut check = check.change.unwrap();
  279. check.sort_by(|a, b| a.amount.cmp(&b.amount));
  280. assert_eq!(melt_change, check);
  281. Ok(())
  282. }
  283. // Keep polling the state of the mint quote id until it's paid
  284. async fn wait_for_mint_to_be_paid(wallet: &Wallet, mint_quote_id: &str) -> Result<()> {
  285. loop {
  286. let status = wallet.mint_quote_state(mint_quote_id).await?;
  287. if status.state == MintQuoteState::Paid {
  288. return Ok(());
  289. }
  290. sleep(Duration::from_millis(5)).await;
  291. }
  292. }