fake_wallet.rs 12 KB

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