diff --git a/.github/workflows/vss-integration.yml b/.github/workflows/vss-integration.yml index 0afba9711..53104afc4 100644 --- a/.github/workflows/vss-integration.yml +++ b/.github/workflows/vss-integration.yml @@ -74,8 +74,8 @@ jobs: run: | cd ldk-node export TEST_VSS_BASE_URL="http://localhost:8080/vss" - RUSTFLAGS="--cfg=vss_test --cfg=vss" cargo build --verbose --color always - RUSTFLAGS="--cfg=vss_test --cfg=vss" cargo test -- --nocapture + RUSTFLAGS="--cfg vss_test --cfg vss" cargo build --verbose --color always + RUSTFLAGS="--cfg vss_test" cargo test --test integration_tests_vss - name: Cleanup run: | diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index dbb120b93..425c9b02b 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -18,29 +18,6 @@ fn channel_full_cycle() { do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false); } -#[test] -#[cfg(vss_test)] -fn channel_full_cycle_with_vss_store() { - let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); - println!("== Node A =="); - let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); - let config_a = random_config(); - let mut builder_a = NodeBuilder::from_config(config_a); - builder_a.set_esplora_server(esplora_url.clone()); - let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); - let node_a = builder_a.build_with_vss_store(&vss_base_url, "node_1_store".to_string()).unwrap(); - node_a.start().unwrap(); - - println!("\n== Node B =="); - let config_b = random_config(); - let mut builder_b = NodeBuilder::from_config(config_b); - builder_b.set_esplora_server(esplora_url); - let node_b = builder_b.build_with_vss_store(&vss_base_url, "node_2_store".to_string()).unwrap(); - node_b.start().unwrap(); - - do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false); -} - #[test] fn channel_full_cycle_0conf() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); diff --git a/tests/common.rs b/tests/common.rs index 6c92c4c1d..f249c048f 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -71,6 +71,26 @@ macro_rules! expect_channel_ready_event { pub(crate) use expect_channel_ready_event; +pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) { + let bitcoind_exe = + env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect( + "you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature", + ); + let mut bitcoind_conf = bitcoind::Conf::default(); + bitcoind_conf.network = "regtest"; + let bitcoind = BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap(); + + let electrs_exe = env::var("ELECTRS_EXE") + .ok() + .or_else(electrsd::downloaded_exe_path) + .expect("you need to provide env var ELECTRS_EXE or specify an electrsd version feature"); + let mut electrsd_conf = electrsd::Conf::default(); + electrsd_conf.http_enabled = true; + electrsd_conf.network = "regtest"; + let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &electrsd_conf).unwrap(); + (bitcoind, electrsd) +} + pub(crate) fn random_storage_path() -> PathBuf { let mut temp_path = std::env::temp_dir(); let mut rng = thread_rng(); @@ -220,3 +240,251 @@ pub(crate) fn premine_and_distribute_funds( generate_blocks_and_wait(bitcoind, electrs, 1); } + + +pub(crate) fn do_channel_full_cycle( + node_a: Node, node_b: Node, bitcoind: &BitcoinD, electrsd: &ElectrsD, allow_0conf: bool, +) { + let addr_a = node_a.new_onchain_address().unwrap(); + let addr_b = node_b.new_onchain_address().unwrap(); + + let premine_amount_sat = 100_000; + + premine_and_distribute_funds( + &bitcoind, + &electrsd, + vec![addr_a, addr_b], + Amount::from_sat(premine_amount_sat), + ); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + assert_eq!(node_a.spendable_onchain_balance_sats().unwrap(), premine_amount_sat); + assert_eq!(node_b.spendable_onchain_balance_sats().unwrap(), premine_amount_sat); + + // Check we haven't got any events yet + assert_eq!(node_a.next_event(), None); + assert_eq!(node_b.next_event(), None); + + println!("\nA -- connect_open_channel -> B"); + let funding_amount_sat = 80_000; + let push_msat = (funding_amount_sat / 2) * 1000; // balance the channel + node_a + .connect_open_channel( + node_b.node_id(), + node_b.listening_addresses().unwrap().first().unwrap().clone(), + funding_amount_sat, + Some(push_msat), + None, + true, + ) + .unwrap(); + + assert_eq!(node_a.list_peers().first().unwrap().node_id, node_b.node_id()); + let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id()); + let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id()); + assert_eq!(funding_txo_a, funding_txo_b); + + wait_for_tx(&electrsd, funding_txo_a.txid); + + if !allow_0conf { + generate_blocks_and_wait(&bitcoind, &electrsd, 6); + } + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + let onchain_fee_buffer_sat = 1500; + let node_a_upper_bound_sat = premine_amount_sat - funding_amount_sat; + let node_a_lower_bound_sat = premine_amount_sat - funding_amount_sat - onchain_fee_buffer_sat; + assert!(node_a.spendable_onchain_balance_sats().unwrap() < node_a_upper_bound_sat); + assert!(node_a.spendable_onchain_balance_sats().unwrap() > node_a_lower_bound_sat); + assert_eq!(node_b.spendable_onchain_balance_sats().unwrap(), premine_amount_sat); + + expect_event!(node_a, ChannelReady); + + let ev = node_b.wait_next_event(); + let channel_id = match ev { + ref e @ Event::ChannelReady { ref channel_id, .. } => { + println!("{} got event {:?}", std::stringify!(node_b), e); + node_b.event_handled(); + channel_id + } + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); + } + }; + + println!("\nB receive_payment"); + let invoice_amount_1_msat = 2500_000; + let invoice = node_b.receive_payment(invoice_amount_1_msat, &"asdf", 9217).unwrap(); + + println!("\nA send_payment"); + let payment_hash = node_a.send_payment(&invoice).unwrap(); + assert_eq!(node_a.send_payment(&invoice), Err(Error::DuplicatePayment)); + + assert_eq!(node_a.list_payments().first().unwrap().hash, payment_hash); + + let outbound_payments_a = + node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); + assert_eq!(outbound_payments_a.len(), 1); + + let inbound_payments_a = + node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound); + assert_eq!(inbound_payments_a.len(), 0); + + let outbound_payments_b = + node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound); + assert_eq!(outbound_payments_b.len(), 0); + + let inbound_payments_b = + node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound); + assert_eq!(inbound_payments_b.len(), 1); + + expect_event!(node_a, PaymentSuccessful); + expect_event!(node_b, PaymentReceived); + assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + + // Assert we fail duplicate outbound payments and check the status hasn't changed. + assert_eq!(Err(Error::DuplicatePayment), node_a.send_payment(&invoice)); + assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(invoice_amount_1_msat)); + + // Test under-/overpayment + let invoice_amount_2_msat = 2500_000; + let invoice = node_b.receive_payment(invoice_amount_2_msat, &"asdf", 9217).unwrap(); + + let underpaid_amount = invoice_amount_2_msat - 1; + assert_eq!( + Err(Error::InvalidAmount), + node_a.send_payment_using_amount(&invoice, underpaid_amount) + ); + + println!("\nB overpaid receive_payment"); + let invoice = node_b.receive_payment(invoice_amount_2_msat, &"asdf", 9217).unwrap(); + let overpaid_amount_msat = invoice_amount_2_msat + 100; + + println!("\nA overpaid send_payment"); + let payment_hash = node_a.send_payment_using_amount(&invoice, overpaid_amount_msat).unwrap(); + expect_event!(node_a, PaymentSuccessful); + let received_amount = match node_b.wait_next_event() { + ref e @ Event::PaymentReceived { amount_msat, .. } => { + println!("{} got event {:?}", std::stringify!(node_b), e); + node_b.event_handled(); + amount_msat + } + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); + } + }; + assert_eq!(received_amount, overpaid_amount_msat); + assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(overpaid_amount_msat)); + assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(overpaid_amount_msat)); + + // Test "zero-amount" invoice payment + println!("\nB receive_variable_amount_payment"); + let variable_amount_invoice = node_b.receive_variable_amount_payment(&"asdf", 9217).unwrap(); + let determined_amount_msat = 2345_678; + assert_eq!(Err(Error::InvalidInvoice), node_a.send_payment(&variable_amount_invoice)); + println!("\nA send_payment_using_amount"); + let payment_hash = + node_a.send_payment_using_amount(&variable_amount_invoice, determined_amount_msat).unwrap(); + + expect_event!(node_a, PaymentSuccessful); + let received_amount = match node_b.wait_next_event() { + ref e @ Event::PaymentReceived { amount_msat, .. } => { + println!("{} got event {:?}", std::stringify!(node_b), e); + node_b.event_handled(); + amount_msat + } + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); + } + }; + assert_eq!(received_amount, determined_amount_msat); + assert_eq!(node_a.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_a.payment(&payment_hash).unwrap().direction, PaymentDirection::Outbound); + assert_eq!(node_a.payment(&payment_hash).unwrap().amount_msat, Some(determined_amount_msat)); + assert_eq!(node_b.payment(&payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&payment_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!(node_b.payment(&payment_hash).unwrap().amount_msat, Some(determined_amount_msat)); + + // Test spontaneous/keysend payments + println!("\nA send_spontaneous_payment"); + let keysend_amount_msat = 2500_000; + let keysend_payment_hash = + node_a.send_spontaneous_payment(keysend_amount_msat, node_b.node_id()).unwrap(); + expect_event!(node_a, PaymentSuccessful); + let received_keysend_amount = match node_b.wait_next_event() { + ref e @ Event::PaymentReceived { amount_msat, .. } => { + println!("{} got event {:?}", std::stringify!(node_b), e); + node_b.event_handled(); + amount_msat + } + ref e => { + panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e); + } + }; + assert_eq!(received_keysend_amount, keysend_amount_msat); + assert_eq!(node_a.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!( + node_a.payment(&keysend_payment_hash).unwrap().direction, + PaymentDirection::Outbound + ); + assert_eq!( + node_a.payment(&keysend_payment_hash).unwrap().amount_msat, + Some(keysend_amount_msat) + ); + assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().status, PaymentStatus::Succeeded); + assert_eq!(node_b.payment(&keysend_payment_hash).unwrap().direction, PaymentDirection::Inbound); + assert_eq!( + node_b.payment(&keysend_payment_hash).unwrap().amount_msat, + Some(keysend_amount_msat) + ); + + println!("\nB close_channel"); + node_b.close_channel(&channel_id, node_a.node_id()).unwrap(); + expect_event!(node_a, ChannelClosed); + expect_event!(node_b, ChannelClosed); + + wait_for_outpoint_spend(&electrsd, funding_txo_b); + + generate_blocks_and_wait(&bitcoind, &electrsd, 1); + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + let sum_of_all_payments_sat = (push_msat + + invoice_amount_1_msat + + overpaid_amount_msat + + determined_amount_msat + + keysend_amount_msat) + / 1000; + let node_a_upper_bound_sat = + (premine_amount_sat - funding_amount_sat) + (funding_amount_sat - sum_of_all_payments_sat); + let node_a_lower_bound_sat = node_a_upper_bound_sat - onchain_fee_buffer_sat; + assert!(node_a.spendable_onchain_balance_sats().unwrap() > node_a_lower_bound_sat); + assert!(node_a.spendable_onchain_balance_sats().unwrap() < node_a_upper_bound_sat); + let expected_final_amount_node_b_sat = premine_amount_sat + sum_of_all_payments_sat; + assert_eq!(node_b.spendable_onchain_balance_sats().unwrap(), expected_final_amount_node_b_sat); + + // Check we handled all events + assert_eq!(node_a.next_event(), None); + assert_eq!(node_b.next_event(), None); + + node_a.stop().unwrap(); + println!("\nA stopped"); + node_b.stop().unwrap(); + println!("\nB stopped"); +} diff --git a/tests/integration_tests_vss.rs b/tests/integration_tests_vss.rs new file mode 100644 index 000000000..474ea886e --- /dev/null +++ b/tests/integration_tests_vss.rs @@ -0,0 +1,27 @@ +#![cfg(vss_test)] + +mod common; + +use ldk_node::Builder; + +#[test] +fn channel_full_cycle_with_vss_store() { + let (bitcoind, electrsd) = common::setup_bitcoind_and_electrsd(); + println!("== Node A =="); + let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); + let config_a = common::random_config(); + let mut builder_a = Builder::from_config(config_a); + builder_a.set_esplora_server(esplora_url.clone()); + let vss_base_url = std::env::var("TEST_VSS_BASE_URL").unwrap(); + let node_a = builder_a.build_with_vss_store(&vss_base_url, "node_1_store".to_string()).unwrap(); + node_a.start().unwrap(); + + println!("\n== Node B =="); + let config_b = common::random_config(); + let mut builder_b = Builder::from_config(config_b); + builder_b.set_esplora_server(esplora_url); + let node_b = builder_b.build_with_vss_store(&vss_base_url, "node_2_store".to_string()).unwrap(); + node_b.start().unwrap(); + + common::do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false); +}