fake_wallet.rs 12 KB

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