//! A bunch of useful utilities for building networks of nodes and exchanging messages between
//! nodes for functional tests.
-use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch, keysinterface::EntropySource};
+use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
+use crate::sign::EntropySource;
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint;
use crate::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, PaymentFailureReason};
use crate::util::ser::{ReadableArgs, Writeable};
use bitcoin::blockdata::block::{Block, BlockHeader};
-use bitcoin::blockdata::constants::genesis_block;
use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::network::constants::Network;
}
impl ConnectStyle {
+ pub fn skips_blocks(&self) -> bool {
+ match self {
+ ConnectStyle::BestBlockFirst => false,
+ ConnectStyle::BestBlockFirstSkippingBlocks => true,
+ ConnectStyle::BestBlockFirstReorgsOnlyTip => true,
+ ConnectStyle::TransactionsFirst => false,
+ ConnectStyle::TransactionsFirstSkippingBlocks => true,
+ ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks => true,
+ ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks => true,
+ ConnectStyle::TransactionsFirstReorgsOnlyTip => true,
+ ConnectStyle::FullBlockViaListen => false,
+ }
+ }
+
+ pub fn updates_best_block_first(&self) -> bool {
+ match self {
+ ConnectStyle::BestBlockFirst => true,
+ ConnectStyle::BestBlockFirstSkippingBlocks => true,
+ ConnectStyle::BestBlockFirstReorgsOnlyTip => true,
+ ConnectStyle::TransactionsFirst => false,
+ ConnectStyle::TransactionsFirstSkippingBlocks => false,
+ ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks => false,
+ ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks => false,
+ ConnectStyle::TransactionsFirstReorgsOnlyTip => false,
+ ConnectStyle::FullBlockViaListen => false,
+ }
+ }
+
fn random_style() -> ConnectStyle {
#[cfg(feature = "std")] {
use core::hash::{BuildHasher, Hasher};
}
pub fn connect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, depth: u32) -> BlockHash {
- let skip_intermediaries = match *node.connect_style.borrow() {
- ConnectStyle::BestBlockFirstSkippingBlocks|ConnectStyle::TransactionsFirstSkippingBlocks|
- ConnectStyle::TransactionsDuplicativelyFirstSkippingBlocks|ConnectStyle::HighlyRedundantTransactionsFirstSkippingBlocks|
- ConnectStyle::BestBlockFirstReorgsOnlyTip|ConnectStyle::TransactionsFirstReorgsOnlyTip => true,
- _ => false,
- };
+ let skip_intermediaries = node.connect_style.borrow().skips_blocks();
let height = node.best_block_info().1 + 1;
let mut block = Block {
#[cfg(feature = "std")] {
eprintln!("Connecting block using Block Connection Style: {:?}", *node.connect_style.borrow());
}
+ // Update the block internally before handing it over to LDK, to ensure our assertions regarding
+ // transaction broadcast are correct.
+ node.blocks.lock().unwrap().push((block.clone(), height));
if !skip_intermediaries {
let txdata: Vec<_> = block.txdata.iter().enumerate().collect();
match *node.connect_style.borrow() {
}
call_claimable_balances(node);
node.node.test_process_background_events();
- node.blocks.lock().unwrap().push((block, height));
}
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
bs_revoke_and_ack
}
};
- ($node_a: expr, $node_b: expr, (), $fail_backwards: expr, true /* skip last step */, true /* return extra message */) => {
- {
- let (extra_msg_option, bs_revoke_and_ack) = $crate::ln::functional_test_utils::do_main_commitment_signed_dance(&$node_a, &$node_b, $fail_backwards);
- $node_a.node.handle_revoke_and_ack(&$node_b.node.get_our_node_id(), &bs_revoke_and_ack);
- $crate::ln::functional_test_utils::check_added_monitors(&$node_a, 1);
- extra_msg_option
- }
- };
($node_a: expr, $node_b: expr, (), $fail_backwards: expr, true /* skip last step */, false /* no extra message */) => {
- assert!(commitment_signed_dance!($node_a, $node_b, (), $fail_backwards, true, true).is_none());
+ assert!($crate::ln::functional_test_utils::commitment_signed_dance_through_cp_raa(&$node_a, &$node_b, $fail_backwards).is_none());
};
($node_a: expr, $node_b: expr, $commitment_signed: expr, $fail_backwards: expr) => {
$crate::ln::functional_test_utils::do_commitment_signed_dance(&$node_a, &$node_b, &$commitment_signed, $fail_backwards, false);
}
}
-
+/// Runs the commitment_signed dance after the initial commitment_signed is delivered through to
+/// the initiator's `revoke_and_ack` response. i.e. [`do_main_commitment_signed_dance`] plus the
+/// `revoke_and_ack` response to it.
+///
+/// Returns any additional message `node_b` generated in addition to the `revoke_and_ack` response.
+pub fn commitment_signed_dance_through_cp_raa(node_a: &Node<'_, '_, '_>, node_b: &Node<'_, '_, '_>, fail_backwards: bool) -> Option<MessageSendEvent> {
+ let (extra_msg_option, bs_revoke_and_ack) = do_main_commitment_signed_dance(node_a, node_b, fail_backwards);
+ node_a.node.handle_revoke_and_ack(&node_b.node.get_our_node_id(), &bs_revoke_and_ack);
+ check_added_monitors(node_a, 1);
+ extra_msg_option
+}
+
+/// Does the main logic in the commitment_signed dance. After the first `commitment_signed` has
+/// been delivered, this method picks up and delivers the response `revoke_and_ack` and
+/// `commitment_signed`, returning the recipient's `revoke_and_ack` and any extra message it may
+/// have included.
pub fn do_main_commitment_signed_dance(node_a: &Node<'_, '_, '_>, node_b: &Node<'_, '_, '_>, fail_backwards: bool) -> (Option<MessageSendEvent>, msgs::RevokeAndACK) {
let (as_revoke_and_ack, as_commitment_signed) = get_revoke_commit_msgs!(node_a, node_b.node.get_our_node_id());
check_added_monitors!(node_b, 0);
(extra_msg_option, bs_revoke_and_ack)
}
+/// Runs a full commitment_signed dance, delivering a commitment_signed, the responding
+/// `revoke_and_ack` and `commitment_signed`, and then the final `revoke_and_ack` response.
+///
+/// If `skip_last_step` is unset, also checks for the payment failure update for the previous hop
+/// on failure or that no new messages are left over on success.
pub fn do_commitment_signed_dance(node_a: &Node<'_, '_, '_>, node_b: &Node<'_, '_, '_>, commitment_signed: &msgs::CommitmentSigned, fail_backwards: bool, skip_last_step: bool) {
check_added_monitors!(node_a, 0);
assert!(node_a.node.get_and_clear_pending_msg_events().is_empty());
}
/// Gets a route from the given sender to the node described in `payment_params`.
-pub fn get_route(send_node: &Node, payment_params: &PaymentParameters, recv_value: u64, final_cltv_expiry_delta: u32) -> Result<Route, msgs::LightningError> {
+pub fn get_route(send_node: &Node, payment_params: &PaymentParameters, recv_value: u64) -> Result<Route, msgs::LightningError> {
let scorer = TestScorer::new();
let keys_manager = TestKeysInterface::new(&[0u8; 32], bitcoin::network::constants::Network::Testnet);
let random_seed_bytes = keys_manager.get_secure_random_bytes();
router::get_route(
&send_node.node.get_our_node_id(), payment_params, &send_node.network_graph.read_only(),
Some(&send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
- recv_value, final_cltv_expiry_delta, send_node.logger, &scorer, &random_seed_bytes
+ recv_value, send_node.logger, &scorer, &random_seed_bytes
)
}
/// Don't use this, use the identically-named function instead.
#[macro_export]
macro_rules! get_route {
- ($send_node: expr, $payment_params: expr, $recv_value: expr, $cltv: expr) => {
- $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value, $cltv)
+ ($send_node: expr, $payment_params: expr, $recv_value: expr) => {
+ $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value)
}
}
($send_node: expr, $recv_node: expr, $recv_value: expr) => {{
let payment_params = $crate::routing::router::PaymentParameters::from_node_id($recv_node.node.get_our_node_id(), TEST_FINAL_CLTV)
.with_features($recv_node.node.invoice_features());
- $crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value, TEST_FINAL_CLTV)
+ $crate::get_route_and_payment_hash!($send_node, $recv_node, payment_params, $recv_value)
}};
- ($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr, $cltv: expr) => {{
+ ($send_node: expr, $recv_node: expr, $payment_params: expr, $recv_value: expr) => {{
let (payment_preimage, payment_hash, payment_secret) =
$crate::ln::functional_test_utils::get_payment_preimage_hash(&$recv_node, Some($recv_value), None);
- let route = $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value, $cltv);
+ let route = $crate::ln::functional_test_utils::get_route(&$send_node, &$payment_params, $recv_value);
(route.unwrap(), payment_hash, payment_preimage, payment_secret)
}}
}
}
}
+pub fn expect_payment_sent<CM: AChannelManager, H: NodeHolder<CM=CM>>(node: &H,
+ expected_payment_preimage: PaymentPreimage, expected_fee_msat_opt: Option<Option<u64>>,
+ expect_per_path_claims: bool,
+) {
+ let events = node.node().get_and_clear_pending_events();
+ let expected_payment_hash = PaymentHash(
+ bitcoin::hashes::sha256::Hash::hash(&expected_payment_preimage.0).into_inner());
+ if expect_per_path_claims {
+ assert!(events.len() > 1);
+ } else {
+ assert_eq!(events.len(), 1);
+ }
+ let expected_payment_id = match events[0] {
+ Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
+ assert_eq!(expected_payment_preimage, *payment_preimage);
+ assert_eq!(expected_payment_hash, *payment_hash);
+ if let Some(expected_fee_msat) = expected_fee_msat_opt {
+ assert_eq!(*fee_paid_msat, expected_fee_msat);
+ } else {
+ assert!(fee_paid_msat.is_some());
+ }
+ payment_id.unwrap()
+ },
+ _ => panic!("Unexpected event"),
+ };
+ if expect_per_path_claims {
+ for i in 1..events.len() {
+ match events[i] {
+ Event::PaymentPathSuccessful { payment_id, payment_hash, .. } => {
+ assert_eq!(payment_id, expected_payment_id);
+ assert_eq!(payment_hash, Some(expected_payment_hash));
+ },
+ _ => panic!("Unexpected event"),
+ }
+ }
+ }
+}
+
#[cfg(test)]
#[macro_export]
macro_rules! expect_payment_sent_without_paths {
($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr) => {
$crate::expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, true);
};
- ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr, $expect_paths: expr) => { {
- use bitcoin::hashes::Hash as _;
- let events = $node.node.get_and_clear_pending_events();
- let expected_payment_hash = $crate::ln::PaymentHash(
- bitcoin::hashes::sha256::Hash::hash(&$expected_payment_preimage.0).into_inner());
- if $expect_paths {
- assert!(events.len() > 1);
- } else {
- assert_eq!(events.len(), 1);
- }
- let expected_payment_id = match events[0] {
- $crate::events::Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
- assert_eq!($expected_payment_preimage, *payment_preimage);
- assert_eq!(expected_payment_hash, *payment_hash);
- assert!(fee_paid_msat.is_some());
- if $expected_fee_msat_opt.is_some() {
- assert_eq!(*fee_paid_msat, $expected_fee_msat_opt);
- }
- payment_id.unwrap()
- },
- _ => panic!("Unexpected event"),
- };
- if $expect_paths {
- for i in 1..events.len() {
- match events[i] {
- $crate::events::Event::PaymentPathSuccessful { payment_id, payment_hash, .. } => {
- assert_eq!(payment_id, expected_payment_id);
- assert_eq!(payment_hash, Some(expected_payment_hash));
- },
- _ => panic!("Unexpected event"),
- }
- }
- }
- } }
+ ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr, $expect_paths: expr) => {
+ $crate::ln::functional_test_utils::expect_payment_sent(&$node, $expected_payment_preimage,
+ $expected_fee_msat_opt.map(|o| Some(o)), $expect_paths);
+ }
}
#[cfg(test)]
let events_2 = node.node.get_and_clear_pending_events();
if payment_claimable_expected {
assert_eq!(events_2.len(), 1);
- match events_2[0] {
- Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat, receiver_node_id, ref via_channel_id, ref via_user_channel_id, claim_deadline } => {
+ match &events_2[0] {
+ Event::PaymentClaimable { ref payment_hash, ref purpose, amount_msat,
+ receiver_node_id, ref via_channel_id, ref via_user_channel_id,
+ claim_deadline, onion_fields,
+ } => {
assert_eq!(our_payment_hash, *payment_hash);
assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
+ assert!(onion_fields.is_some());
match &purpose {
PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
assert_eq!(expected_preimage, *payment_preimage);
assert_eq!(our_payment_secret.unwrap(), *payment_secret);
+ assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
},
PaymentPurpose::SpontaneousPayment(payment_preimage) => {
assert_eq!(expected_preimage.unwrap(), *payment_preimage);
assert!(our_payment_secret.is_none());
},
}
- assert_eq!(amount_msat, recv_value);
+ assert_eq!(*amount_msat, recv_value);
assert!(node.node.list_channels().iter().any(|details| details.channel_id == via_channel_id.unwrap()));
assert!(node.node.list_channels().iter().any(|details| details.user_channel_id == via_user_channel_id.unwrap()));
assert!(claim_deadline.unwrap() > node.best_block_info().1);
pub fn route_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], recv_value: u64) -> (PaymentPreimage, PaymentHash, PaymentSecret) {
let payment_params = PaymentParameters::from_node_id(expected_route.last().unwrap().node.get_our_node_id(), TEST_FINAL_CLTV)
.with_features(expected_route.last().unwrap().node.invoice_features());
- let route = get_route(origin_node, &payment_params, recv_value, TEST_FINAL_CLTV).unwrap();
+ let route = get_route(origin_node, &payment_params, recv_value).unwrap();
assert_eq!(route.paths.len(), 1);
- assert_eq!(route.paths[0].len(), expected_route.len());
- for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
+ assert_eq!(route.paths[0].hops.len(), expected_route.len());
+ for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
assert_eq!(hop.pubkey, node.node.get_our_node_id());
}
let random_seed_bytes = keys_manager.get_secure_random_bytes();
let route = router::get_route(
&origin_node.node.get_our_node_id(), &payment_params, &network_graph,
- None, recv_value, TEST_FINAL_CLTV, origin_node.logger, &scorer, &random_seed_bytes).unwrap();
+ None, recv_value, origin_node.logger, &scorer, &random_seed_bytes).unwrap();
assert_eq!(route.paths.len(), 1);
- assert_eq!(route.paths[0].len(), expected_route.len());
- for (node, hop) in expected_route.iter().zip(route.paths[0].iter()) {
+ assert_eq!(route.paths[0].hops.len(), expected_route.len());
+ for (node, hop) in expected_route.iter().zip(route.paths[0].hops.iter()) {
assert_eq!(hop.pubkey, node.node.get_our_node_id());
}
assert_eq!(payment_hash, our_payment_hash);
assert!(payment_failed_permanently);
for (idx, hop) in expected_route.iter().enumerate() {
- assert_eq!(hop.node.get_our_node_id(), path[idx].pubkey);
+ assert_eq!(hop.node.get_our_node_id(), path.hops[idx].pubkey);
}
payment_id.unwrap()
},
pub fn create_chanmon_cfgs(node_count: usize) -> Vec<TestChanMonCfg> {
let mut chan_mon_cfgs = Vec::new();
for i in 0..node_count {
- let tx_broadcaster = test_utils::TestBroadcaster {
- txn_broadcasted: Mutex::new(Vec::new()),
- blocks: Arc::new(Mutex::new(vec![(genesis_block(Network::Testnet), 0)])),
- };
+ let tx_broadcaster = test_utils::TestBroadcaster::new(Network::Testnet);
let fee_estimator = test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) };
let chain_source = test_utils::TestChainSource::new(Network::Testnet);
let logger = test_utils::TestLogger::with_id(format!("node {}", i));
/// also fail.
pub fn test_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, chan: &(msgs::ChannelUpdate, msgs::ChannelUpdate, [u8; 32], Transaction), commitment_tx: Option<Transaction>, has_htlc_tx: HTLCType) -> Vec<Transaction> {
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
+ let mut txn_seen = HashSet::new();
+ node_txn.retain(|tx| txn_seen.insert(tx.txid()));
assert!(node_txn.len() >= if commitment_tx.is_some() { 0 } else { 1 } + if has_htlc_tx == HTLCType::NONE { 0 } else { 1 });
let mut res = Vec::with_capacity(2);
pub fn check_preimage_claim<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, prev_txn: &Vec<Transaction>) -> Vec<Transaction> {
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
+ let mut txn_seen = HashSet::new();
+ node_txn.retain(|tx| txn_seen.insert(tx.txid()));
- assert!(node_txn.len() >= 1);
- assert_eq!(node_txn[0].input.len(), 1);
let mut found_prev = false;
-
- for tx in prev_txn {
- if node_txn[0].input[0].previous_output.txid == tx.txid() {
- check_spends!(node_txn[0], tx);
- let mut iter = node_txn[0].input[0].witness.iter();
- iter.next().expect("expected 3 witness items");
- iter.next().expect("expected 3 witness items");
- assert!(iter.next().expect("expected 3 witness items").len() > 106); // must spend an htlc output
- assert_eq!(tx.input.len(), 1); // must spend a commitment tx
-
- found_prev = true;
- break;
+ for prev_tx in prev_txn {
+ for tx in &*node_txn {
+ if tx.input[0].previous_output.txid == prev_tx.txid() {
+ check_spends!(tx, prev_tx);
+ let mut iter = tx.input[0].witness.iter();
+ iter.next().expect("expected 3 witness items");
+ iter.next().expect("expected 3 witness items");
+ assert!(iter.next().expect("expected 3 witness items").len() > 106); // must spend an htlc output
+ assert_eq!(tx.input.len(), 1); // must spend a commitment tx
+
+ found_prev = true;
+ break;
+ }
}
}
assert!(found_prev);
}
let mut had_channel_update = false; // ChannelUpdate may be now or later, but not both
- if let Some(&MessageSendEvent::SendChannelUpdate { ref node_id, ref msg }) = msg_events.get(idx) {
+ if let Some(&MessageSendEvent::SendChannelUpdate { ref node_id, .. }) = msg_events.get(idx) {
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
idx += 1;
- assert_eq!(msg.contents.flags & 2, 0); // "disabled" flag must not be set as we just reconnected.
had_channel_update = true;
}
}
}
- if let Some(&MessageSendEvent::SendChannelUpdate { ref node_id, ref msg }) = msg_events.get(idx) {
+ if let Some(&MessageSendEvent::SendChannelUpdate { ref node_id, .. }) = msg_events.get(idx) {
assert_eq!(*node_id, $dst_node.node.get_our_node_id());
idx += 1;
- assert_eq!(msg.contents.flags & 2, 0); // "disabled" flag must not be set as we just reconnected.
assert!(!had_channel_update);
}