regtest.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. use std::fmt::Debug;
  2. use std::str::FromStr;
  3. use std::sync::Arc;
  4. use std::time::Duration;
  5. use anyhow::{bail, Result};
  6. use bip39::Mnemonic;
  7. use cdk::amount::{Amount, SplitTarget};
  8. use cdk::cdk_database::WalletMemoryDatabase;
  9. use cdk::nuts::{
  10. CurrencyUnit, MeltQuoteState, MintBolt11Request, MintQuoteState, NotificationPayload,
  11. PreMintSecrets, State,
  12. };
  13. use cdk::wallet::client::{HttpClient, HttpClientMethods};
  14. use cdk::wallet::Wallet;
  15. use cdk_integration_tests::init_regtest::{
  16. get_mint_url, get_mint_ws_url, init_cln_client, init_lnd_client,
  17. };
  18. use futures::{SinkExt, StreamExt};
  19. use lightning_invoice::Bolt11Invoice;
  20. use ln_regtest_rs::InvoiceStatus;
  21. use serde_json::json;
  22. use tokio::time::{sleep, timeout};
  23. use tokio_tungstenite::connect_async;
  24. use tokio_tungstenite::tungstenite::protocol::Message;
  25. async fn get_notification<T: StreamExt<Item = Result<Message, E>> + Unpin, E: Debug>(
  26. reader: &mut T,
  27. timeout_to_wait: Duration,
  28. ) -> (String, NotificationPayload) {
  29. let msg = timeout(timeout_to_wait, reader.next())
  30. .await
  31. .expect("timeout")
  32. .unwrap()
  33. .unwrap();
  34. let mut response: serde_json::Value =
  35. serde_json::from_str(msg.to_text().unwrap()).expect("valid json");
  36. let mut params_raw = response
  37. .as_object_mut()
  38. .expect("object")
  39. .remove("params")
  40. .expect("valid params");
  41. let params_map = params_raw.as_object_mut().expect("params is object");
  42. (
  43. params_map
  44. .remove("subId")
  45. .unwrap()
  46. .as_str()
  47. .unwrap()
  48. .to_string(),
  49. serde_json::from_value(params_map.remove("payload").unwrap()).unwrap(),
  50. )
  51. }
  52. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  53. async fn test_regtest_mint_melt_round_trip() -> Result<()> {
  54. let lnd_client = init_lnd_client().await.unwrap();
  55. let wallet = Wallet::new(
  56. &get_mint_url(),
  57. CurrencyUnit::Sat,
  58. Arc::new(WalletMemoryDatabase::default()),
  59. &Mnemonic::generate(12)?.to_seed_normalized(""),
  60. None,
  61. )?;
  62. let (ws_stream, _) = connect_async(get_mint_ws_url())
  63. .await
  64. .expect("Failed to connect");
  65. let (mut write, mut reader) = ws_stream.split();
  66. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  67. lnd_client.pay_invoice(mint_quote.request).await?;
  68. let mint_amount = wallet
  69. .mint(&mint_quote.id, SplitTarget::default(), None)
  70. .await?;
  71. assert!(mint_amount == 100.into());
  72. let invoice = lnd_client.create_invoice(50).await?;
  73. let melt = wallet.melt_quote(invoice, None).await?;
  74. write
  75. .send(Message::Text(serde_json::to_string(&json!({
  76. "jsonrpc": "2.0",
  77. "id": 2,
  78. "method": "subscribe",
  79. "params": {
  80. "kind": "bolt11_melt_quote",
  81. "filters": [
  82. melt.id.clone(),
  83. ],
  84. "subId": "test-sub",
  85. }
  86. }))?))
  87. .await?;
  88. assert_eq!(
  89. reader.next().await.unwrap().unwrap().to_text().unwrap(),
  90. r#"{"jsonrpc":"2.0","result":{"status":"OK","subId":"test-sub"},"id":2}"#
  91. );
  92. let melt_response = wallet.melt(&melt.id).await.unwrap();
  93. assert!(melt_response.preimage.is_some());
  94. assert!(melt_response.state == MeltQuoteState::Paid);
  95. let (sub_id, payload) = get_notification(&mut reader, Duration::from_millis(15000)).await;
  96. // first message is the current state
  97. assert_eq!("test-sub", sub_id);
  98. let payload = match payload {
  99. NotificationPayload::MeltQuoteBolt11Response(melt) => melt,
  100. _ => panic!("Wrong payload"),
  101. };
  102. assert_eq!(payload.amount + payload.fee_reserve, 100.into());
  103. assert_eq!(payload.quote.to_string(), melt.id);
  104. assert_eq!(payload.state, MeltQuoteState::Unpaid);
  105. // get current state
  106. let (sub_id, payload) = get_notification(&mut reader, Duration::from_millis(15000)).await;
  107. assert_eq!("test-sub", sub_id);
  108. let payload = match payload {
  109. NotificationPayload::MeltQuoteBolt11Response(melt) => melt,
  110. _ => panic!("Wrong payload"),
  111. };
  112. assert_eq!(payload.amount + payload.fee_reserve, 100.into());
  113. assert_eq!(payload.quote.to_string(), melt.id);
  114. assert_eq!(payload.state, MeltQuoteState::Paid);
  115. Ok(())
  116. }
  117. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  118. async fn test_regtest_mint_melt() -> Result<()> {
  119. let lnd_client = init_lnd_client().await?;
  120. let wallet = Wallet::new(
  121. &get_mint_url(),
  122. CurrencyUnit::Sat,
  123. Arc::new(WalletMemoryDatabase::default()),
  124. &Mnemonic::generate(12)?.to_seed_normalized(""),
  125. None,
  126. )?;
  127. let mint_amount = Amount::from(100);
  128. let mint_quote = wallet.mint_quote(mint_amount, None).await?;
  129. assert_eq!(mint_quote.amount, mint_amount);
  130. lnd_client.pay_invoice(mint_quote.request).await?;
  131. let mint_amount = wallet
  132. .mint(&mint_quote.id, SplitTarget::default(), None)
  133. .await?;
  134. assert!(mint_amount == 100.into());
  135. Ok(())
  136. }
  137. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  138. async fn test_restore() -> Result<()> {
  139. let lnd_client = init_lnd_client().await?;
  140. let seed = Mnemonic::generate(12)?.to_seed_normalized("");
  141. let wallet = Wallet::new(
  142. &get_mint_url(),
  143. CurrencyUnit::Sat,
  144. Arc::new(WalletMemoryDatabase::default()),
  145. &seed,
  146. None,
  147. )?;
  148. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  149. lnd_client.pay_invoice(mint_quote.request).await?;
  150. let _mint_amount = wallet
  151. .mint(&mint_quote.id, SplitTarget::default(), None)
  152. .await?;
  153. assert!(wallet.total_balance().await? == 100.into());
  154. let wallet_2 = Wallet::new(
  155. &get_mint_url(),
  156. CurrencyUnit::Sat,
  157. Arc::new(WalletMemoryDatabase::default()),
  158. &seed,
  159. None,
  160. )?;
  161. assert!(wallet_2.total_balance().await? == 0.into());
  162. let restored = wallet_2.restore().await?;
  163. let proofs = wallet_2.get_unspent_proofs().await?;
  164. wallet_2
  165. .swap(None, SplitTarget::default(), proofs, None, false)
  166. .await?;
  167. assert!(restored == 100.into());
  168. assert!(wallet_2.total_balance().await? == 100.into());
  169. let proofs = wallet.get_unspent_proofs().await?;
  170. let states = wallet.check_proofs_spent(proofs).await?;
  171. for state in states {
  172. if state.state != State::Spent {
  173. bail!("All proofs should be spent");
  174. }
  175. }
  176. Ok(())
  177. }
  178. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  179. async fn test_pay_invoice_twice() -> Result<()> {
  180. let lnd_client = init_lnd_client().await?;
  181. let seed = Mnemonic::generate(12)?.to_seed_normalized("");
  182. let wallet = Wallet::new(
  183. &get_mint_url(),
  184. CurrencyUnit::Sat,
  185. Arc::new(WalletMemoryDatabase::default()),
  186. &seed,
  187. None,
  188. )?;
  189. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  190. lnd_client.pay_invoice(mint_quote.request).await?;
  191. let mint_amount = wallet
  192. .mint(&mint_quote.id, SplitTarget::default(), None)
  193. .await?;
  194. assert_eq!(mint_amount, 100.into());
  195. let invoice = lnd_client.create_invoice(10).await?;
  196. let melt_quote = wallet.melt_quote(invoice.clone(), None).await?;
  197. let melt = wallet.melt(&melt_quote.id).await.unwrap();
  198. let melt_two = wallet.melt_quote(invoice, None).await?;
  199. let melt_two = wallet.melt(&melt_two.id).await;
  200. match melt_two {
  201. Err(err) => match err {
  202. cdk::Error::RequestAlreadyPaid => (),
  203. _ => {
  204. bail!("Wrong invoice already paid");
  205. }
  206. },
  207. Ok(_) => {
  208. bail!("Should not have allowed second payment");
  209. }
  210. }
  211. let balance = wallet.total_balance().await?;
  212. assert_eq!(balance, (Amount::from(100) - melt.fee_paid - melt.amount));
  213. Ok(())
  214. }
  215. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  216. async fn test_internal_payment() -> Result<()> {
  217. let lnd_client = init_lnd_client().await?;
  218. let seed = Mnemonic::generate(12)?.to_seed_normalized("");
  219. let wallet = Wallet::new(
  220. &get_mint_url(),
  221. CurrencyUnit::Sat,
  222. Arc::new(WalletMemoryDatabase::default()),
  223. &seed,
  224. None,
  225. )?;
  226. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  227. lnd_client.pay_invoice(mint_quote.request).await?;
  228. let _mint_amount = wallet
  229. .mint(&mint_quote.id, SplitTarget::default(), None)
  230. .await?;
  231. assert!(wallet.total_balance().await? == 100.into());
  232. let seed = Mnemonic::generate(12)?.to_seed_normalized("");
  233. let wallet_2 = Wallet::new(
  234. &get_mint_url(),
  235. CurrencyUnit::Sat,
  236. Arc::new(WalletMemoryDatabase::default()),
  237. &seed,
  238. None,
  239. )?;
  240. let mint_quote = wallet_2.mint_quote(10.into(), None).await?;
  241. let melt = wallet.melt_quote(mint_quote.request.clone(), None).await?;
  242. assert_eq!(melt.amount, 10.into());
  243. let _melted = wallet.melt(&melt.id).await.unwrap();
  244. let _wallet_2_mint = wallet_2
  245. .mint(&mint_quote.id, SplitTarget::default(), None)
  246. .await
  247. .unwrap();
  248. let cln_client = init_cln_client().await?;
  249. let payment_hash = Bolt11Invoice::from_str(&mint_quote.request)?;
  250. let check_paid = cln_client
  251. .check_incoming_invoice(payment_hash.payment_hash().to_string())
  252. .await?;
  253. match check_paid {
  254. InvoiceStatus::Unpaid => (),
  255. _ => {
  256. bail!("Invoice has incorrect status: {:?}", check_paid);
  257. }
  258. }
  259. let wallet_2_balance = wallet_2.total_balance().await?;
  260. assert!(wallet_2_balance == 10.into());
  261. let wallet_1_balance = wallet.total_balance().await?;
  262. assert!(wallet_1_balance == 90.into());
  263. Ok(())
  264. }
  265. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  266. async fn test_cached_mint() -> Result<()> {
  267. let lnd_client = init_lnd_client().await.unwrap();
  268. let wallet = Wallet::new(
  269. &get_mint_url(),
  270. CurrencyUnit::Sat,
  271. Arc::new(WalletMemoryDatabase::default()),
  272. &Mnemonic::generate(12)?.to_seed_normalized(""),
  273. None,
  274. )?;
  275. let mint_amount = Amount::from(100);
  276. let quote = wallet.mint_quote(mint_amount, None).await?;
  277. lnd_client.pay_invoice(quote.request).await?;
  278. loop {
  279. let status = wallet.mint_quote_state(&quote.id).await.unwrap();
  280. println!("Quote status: {}", status.state);
  281. if status.state == MintQuoteState::Paid {
  282. break;
  283. }
  284. sleep(Duration::from_secs(5)).await;
  285. }
  286. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  287. let http_client = HttpClient::new();
  288. let premint_secrets =
  289. PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap();
  290. let request = MintBolt11Request {
  291. quote: quote.id,
  292. outputs: premint_secrets.blinded_messages(),
  293. };
  294. let response = http_client
  295. .post_mint(get_mint_url().as_str().parse()?, request.clone())
  296. .await?;
  297. let response1 = http_client
  298. .post_mint(get_mint_url().as_str().parse()?, request)
  299. .await?;
  300. assert!(response == response1);
  301. Ok(())
  302. }