use bitcoin::blockdata::transaction::{Transaction, TxOut};
use bitcoin::network::constants::Network;
-use bitcoin::hashes::sha256::Hash as Sha256;
-use bitcoin::hashes::Hash;
use bitcoin::hash_types::BlockHash;
use bitcoin::secp256k1::key::PublicKey;
(announcement, as_update, bs_update, channel_id, tx)
}
+#[macro_export]
+/// Gets an RAA and CS which were sent in response to a commitment update
macro_rules! get_revoke_commit_msgs {
($node: expr, $node_id: expr) => {
{
+ use $crate::util::events::MessageSendEvent;
let events = $node.node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
(match events[0] {
}
}
-#[cfg(test)]
+#[macro_export]
+/// Gets an UpdateHTLCs MessageSendEvent
macro_rules! get_htlc_update_msgs {
($node: expr, $node_id: expr) => {
{
let events = $node.node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
match events[0] {
- MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
+ $crate::util::events::MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
assert_eq!(*node_id, $node_id);
(*updates).clone()
},
_ => panic!("Unexpected event"),
}
}
-
-pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64, a_flags: InitFeatures, b_flags: InitFeatures) -> Transaction {
- let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None).unwrap();
- node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id()));
- node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id()));
-
+pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: [u8; 32]) -> Transaction {
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, channel_value, 42);
- assert_eq!(temporary_channel_id, create_chan_id);
+ assert_eq!(temporary_channel_id, expected_temporary_channel_id);
- node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).unwrap();
+ assert!(node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).is_ok());
check_added_monitors!(node_a, 0);
- node_b.node.handle_funding_created(&node_a.node.get_our_node_id(), &get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b.node.get_our_node_id()));
+ let funding_created_msg = get_event_msg!(node_a, MessageSendEvent::SendFundingCreated, node_b.node.get_our_node_id());
+ assert_eq!(funding_created_msg.temporary_channel_id, expected_temporary_channel_id);
+ node_b.node.handle_funding_created(&node_a.node.get_our_node_id(), &funding_created_msg);
{
let mut added_monitors = node_b.chain_monitor.added_monitors.lock().unwrap();
assert_eq!(added_monitors.len(), 1);
assert_eq!(node_a.tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx);
node_a.tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
+ // Ensure that funding_transaction_generated is idempotent.
+ assert!(node_a.node.funding_transaction_generated(&temporary_channel_id, tx.clone()).is_err());
+ assert!(node_a.node.get_and_clear_pending_msg_events().is_empty());
+ check_added_monitors!(node_a, 0);
+
tx
}
+pub fn create_chan_between_nodes_with_value_init<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, push_msat: u64, a_flags: InitFeatures, b_flags: InitFeatures) -> Transaction {
+ let create_chan_id = node_a.node.create_channel(node_b.node.get_our_node_id(), channel_value, push_msat, 42, None).unwrap();
+ let open_channel_msg = get_event_msg!(node_a, MessageSendEvent::SendOpenChannel, node_b.node.get_our_node_id());
+ assert_eq!(open_channel_msg.temporary_channel_id, create_chan_id);
+ node_b.node.handle_open_channel(&node_a.node.get_our_node_id(), a_flags, &open_channel_msg);
+ let accept_channel_msg = get_event_msg!(node_b, MessageSendEvent::SendAcceptChannel, node_a.node.get_our_node_id());
+ assert_eq!(accept_channel_msg.temporary_channel_id, create_chan_id);
+ node_a.node.handle_accept_channel(&node_b.node.get_our_node_id(), b_flags, &accept_channel_msg);
+
+ sign_funding_transaction(node_a, node_b, channel_value, create_chan_id)
+}
+
pub fn create_chan_between_nodes_with_value_confirm_first<'a, 'b, 'c, 'd>(node_recv: &'a Node<'b, 'c, 'c>, node_conf: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) {
confirm_transaction_at(node_conf, tx, conf_height);
connect_blocks(node_conf, CHAN_CONFIRM_DEPTH - 1);
}
}
+#[macro_export]
macro_rules! check_spends {
($tx: expr, $($spends_txn: expr),*) => {
{
+ $(
+ for outp in $spends_txn.output.iter() {
+ assert!(outp.value >= outp.script_pubkey.dust_value().as_sat(), "Input tx output didn't meet dust limit");
+ }
+ )*
+ for outp in $tx.output.iter() {
+ assert!(outp.value >= outp.script_pubkey.dust_value().as_sat(), "Spending tx output didn't meet dust limit");
+ }
let get_output = |out_point: &bitcoin::blockdata::transaction::OutPoint| {
$(
if out_point.txid == $spends_txn.txid() {
#[macro_export]
macro_rules! check_closed_broadcast {
($node: expr, $with_error_msg: expr) => {{
+ use $crate::util::events::MessageSendEvent;
+ use $crate::ln::msgs::ErrorAction;
+
let msg_events = $node.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if $with_error_msg { 2 } else { 1 });
match msg_events[0] {
check_closed_event!($node, $events, $reason, false);
};
($node: expr, $events: expr, $reason: expr, $is_check_discard_funding: expr) => {{
+ use $crate::util::events::Event;
+
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), $events);
let expected_reason = $reason;
}
}
+#[macro_export]
+/// Performs the "commitment signed dance" - the series of message exchanges which occur after a
+/// commitment update.
macro_rules! commitment_signed_dance {
($node_a: expr, $node_b: expr, $commitment_signed: expr, $fail_backwards: expr, true /* skip last step */) => {
{
{
commitment_signed_dance!($node_a, $node_b, $commitment_signed, $fail_backwards, true);
if $fail_backwards {
- expect_pending_htlcs_forwardable!($node_a);
+ $crate::expect_pending_htlcs_forwardable!($node_a);
check_added_monitors!($node_a, 1);
let channel_state = $node_a.node.channel_state.lock().unwrap();
};
($dest_node: expr, $min_value_msat: expr) => {
{
+ use bitcoin::hashes::Hash as _;
let mut payment_count = $dest_node.network_payment_count.borrow_mut();
- let payment_preimage = PaymentPreimage([*payment_count; 32]);
+ let payment_preimage = $crate::ln::PaymentPreimage([*payment_count; 32]);
*payment_count += 1;
- let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
- let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200, 0).unwrap();
+ let payment_hash = $crate::ln::PaymentHash(
+ bitcoin::hashes::sha256::Hash::hash(&payment_preimage.0[..]).into_inner());
+ let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200).unwrap();
(payment_preimage, payment_hash, payment_secret)
}
}
}
#[cfg(test)]
+#[macro_export]
macro_rules! get_route_and_payment_hash {
($send_node: expr, $recv_node: expr, $recv_value: expr) => {{
- get_route_and_payment_hash!($send_node, $recv_node, vec![], $recv_value, TEST_FINAL_CLTV)
+ $crate::get_route_and_payment_hash!($send_node, $recv_node, vec![], $recv_value, TEST_FINAL_CLTV)
}};
($send_node: expr, $recv_node: expr, $last_hops: expr, $recv_value: expr, $cltv: expr) => {{
- let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!($recv_node, Some($recv_value));
+ let (payment_preimage, payment_hash, payment_secret) = $crate::get_payment_preimage_hash!($recv_node, Some($recv_value));
let payee = $crate::routing::router::Payee::from_node_id($recv_node.node.get_our_node_id())
.with_features($crate::ln::features::InvoiceFeatures::known())
.with_route_hints($last_hops);
- let scorer = ::util::test_utils::TestScorer::with_fixed_penalty(0);
- let route = ::routing::router::get_route(
+ let scorer = $crate::util::test_utils::TestScorer::with_fixed_penalty(0);
+ let route = $crate::routing::router::get_route(
&$send_node.node.get_our_node_id(), &payee, $send_node.network_graph,
Some(&$send_node.node.list_usable_channels().iter().collect::<Vec<_>>()),
$recv_value, $cltv, $send_node.logger, &scorer
}}
}
+#[macro_export]
+/// Clears (and ignores) a PendingHTLCsForwardable event
macro_rules! expect_pending_htlcs_forwardable_ignore {
($node: expr) => {{
let events = $node.node.get_and_clear_pending_events();
}}
}
+#[macro_export]
+/// Handles a PendingHTLCsForwardable event
macro_rules! expect_pending_htlcs_forwardable {
($node: expr) => {{
- expect_pending_htlcs_forwardable_ignore!($node);
+ $crate::expect_pending_htlcs_forwardable_ignore!($node);
+ $node.node.process_pending_htlc_forwards();
+
+ // Ensure process_pending_htlc_forwards is idempotent.
$node.node.process_pending_htlc_forwards();
}}
}
};
if $ignore {
$node.node.process_pending_htlc_forwards();
+
+ // Ensure process_pending_htlc_forwards is idempotent.
+ $node.node.process_pending_htlc_forwards();
}
}}
}
}
}
+#[cfg(test)]
+#[macro_export]
+macro_rules! expect_payment_sent_without_paths {
+ ($node: expr, $expected_payment_preimage: expr) => {
+ expect_payment_sent!($node, $expected_payment_preimage, None::<u64>, false);
+ };
+ ($node: expr, $expected_payment_preimage: expr, $expected_fee_msat_opt: expr) => {
+ expect_payment_sent!($node, $expected_payment_preimage, $expected_fee_msat_opt, false);
+ }
+}
+
+#[macro_export]
macro_rules! expect_payment_sent {
($node: expr, $expected_payment_preimage: expr) => {
- expect_payment_sent!($node, $expected_payment_preimage, None::<u64>);
+ $crate::expect_payment_sent!($node, $expected_payment_preimage, None::<u64>, true);
};
($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 = PaymentHash(Sha256::hash(&$expected_payment_preimage.0).into_inner());
- assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentSent { payment_id: _, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
+ 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::util::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::util::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"),
+ }
+ }
+ }
+ } }
+}
+
+#[cfg(test)]
+#[macro_export]
+macro_rules! expect_payment_path_successful {
+ ($node: expr) => {
+ let events = $node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ $crate::util::events::Event::PaymentPathSuccessful { .. } => {},
+ _ => panic!("Unexpected event"),
}
}
}
}
}
+pub struct PaymentFailedConditions<'a> {
+ pub(crate) expected_htlc_error_data: Option<(u16, &'a [u8])>,
+ pub(crate) expected_blamed_scid: Option<u64>,
+ pub(crate) expected_blamed_chan_closed: Option<bool>,
+ pub(crate) expected_mpp_parts_remain: bool,
+}
+
+impl<'a> PaymentFailedConditions<'a> {
+ pub fn new() -> Self {
+ Self {
+ expected_htlc_error_data: None,
+ expected_blamed_scid: None,
+ expected_blamed_chan_closed: None,
+ expected_mpp_parts_remain: false,
+ }
+ }
+ pub fn mpp_parts_remain(mut self) -> Self {
+ self.expected_mpp_parts_remain = true;
+ self
+ }
+ pub fn blamed_scid(mut self, scid: u64) -> Self {
+ self.expected_blamed_scid = Some(scid);
+ self
+ }
+ pub fn blamed_chan_closed(mut self, closed: bool) -> Self {
+ self.expected_blamed_chan_closed = Some(closed);
+ self
+ }
+ pub fn expected_htlc_error_data(mut self, code: u16, data: &'a [u8]) -> Self {
+ self.expected_htlc_error_data = Some((code, data));
+ self
+ }
+}
+
#[cfg(test)]
macro_rules! expect_payment_failed_with_update {
($node: expr, $expected_payment_hash: expr, $rejected_by_dest: expr, $scid: expr, $chan_closed: expr) => {
- let events = $node.node.get_and_clear_pending_events();
- assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentPathFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data, ref path, ref retry, .. } => {
- assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
- assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
- assert!(retry.is_some(), "expected retry.is_some()");
- assert_eq!(retry.as_ref().unwrap().final_value_msat, path.last().unwrap().fee_msat, "Retry amount should match last hop in path");
- assert_eq!(retry.as_ref().unwrap().payee.pubkey, path.last().unwrap().pubkey, "Retry payee node_id should match last hop in path");
- assert!(error_code.is_some(), "expected error_code.is_some() = true");
- assert!(error_data.is_some(), "expected error_data.is_some() = true");
- match network_update {
- &Some(NetworkUpdate::ChannelUpdateMessage { ref msg }) if !$chan_closed => {
- assert_eq!(msg.contents.short_channel_id, $scid);
- assert_eq!(msg.contents.flags & 2, 0);
- },
- &Some(NetworkUpdate::ChannelClosed { short_channel_id, is_permanent }) if $chan_closed => {
- assert_eq!(short_channel_id, $scid);
- assert!(is_permanent);
- },
- Some(_) => panic!("Unexpected update type"),
- None => panic!("Expected update"),
- }
- },
- _ => panic!("Unexpected event"),
- }
+ expect_payment_failed_conditions!($node, $expected_payment_hash, $rejected_by_dest,
+ $crate::ln::functional_test_utils::PaymentFailedConditions::new().blamed_scid($scid).blamed_chan_closed($chan_closed));
}
}
#[cfg(test)]
macro_rules! expect_payment_failed {
($node: expr, $expected_payment_hash: expr, $rejected_by_dest: expr $(, $expected_error_code: expr, $expected_error_data: expr)*) => {
+ #[allow(unused_mut)]
+ let mut conditions = $crate::ln::functional_test_utils::PaymentFailedConditions::new();
+ $(
+ conditions = conditions.expected_htlc_error_data($expected_error_code, &$expected_error_data);
+ )*
+ expect_payment_failed_conditions!($node, $expected_payment_hash, $rejected_by_dest, conditions);
+ };
+}
+
+#[cfg(test)]
+macro_rules! expect_payment_failed_conditions {
+ ($node: expr, $expected_payment_hash: expr, $rejected_by_dest: expr, $conditions: expr) => {
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentPathFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data, ref path, ref retry, .. } => {
+ let expected_payment_id = match events[0] {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest, ref error_code, ref error_data, ref path, ref retry, ref payment_id, ref network_update, .. } => {
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
assert!(retry.is_some(), "expected retry.is_some()");
assert_eq!(retry.as_ref().unwrap().final_value_msat, path.last().unwrap().fee_msat, "Retry amount should match last hop in path");
assert_eq!(retry.as_ref().unwrap().payee.pubkey, path.last().unwrap().pubkey, "Retry payee node_id should match last hop in path");
+
assert!(error_code.is_some(), "expected error_code.is_some() = true");
assert!(error_data.is_some(), "expected error_data.is_some() = true");
- $(
- assert_eq!(error_code.unwrap(), $expected_error_code, "unexpected error code");
- assert_eq!(&error_data.as_ref().unwrap()[..], $expected_error_data, "unexpected error data");
- )*
+ if let Some((code, data)) = $conditions.expected_htlc_error_data {
+ assert_eq!(error_code.unwrap(), code, "unexpected error code");
+ assert_eq!(&error_data.as_ref().unwrap()[..], data, "unexpected error data");
+ }
+
+ if let Some(chan_closed) = $conditions.expected_blamed_chan_closed {
+ match network_update {
+ &Some($crate::routing::network_graph::NetworkUpdate::ChannelUpdateMessage { ref msg }) if !chan_closed => {
+ if let Some(scid) = $conditions.expected_blamed_scid {
+ assert_eq!(msg.contents.short_channel_id, scid);
+ }
+ assert_eq!(msg.contents.flags & 2, 0);
+ },
+ &Some($crate::routing::network_graph::NetworkUpdate::ChannelClosed { short_channel_id, is_permanent }) if chan_closed => {
+ if let Some(scid) = $conditions.expected_blamed_scid {
+ assert_eq!(short_channel_id, scid);
+ }
+ assert!(is_permanent);
+ },
+ Some(_) => panic!("Unexpected update type"),
+ None => panic!("Expected update"),
+ }
+ }
+
+ payment_id.unwrap()
},
_ => panic!("Unexpected event"),
+ };
+ if !$conditions.expected_mpp_parts_remain {
+ $node.node.abandon_payment(expected_payment_id);
+ let events = $node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { ref payment_hash, ref payment_id } => {
+ assert_eq!(*payment_hash, $expected_payment_hash, "unexpected second payment_hash");
+ assert_eq!(*payment_id, expected_payment_id);
+ }
+ _ => panic!("Unexpected second event"),
+ }
}
}
}
last_update_fulfill_dance!(origin_node, expected_route.first().unwrap());
}
}
+
+ // Ensure that claim_funds is idempotent.
+ assert!(!expected_paths[0].last().unwrap().node.claim_funds(our_payment_preimage));
+ assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events().is_empty());
+ check_added_monitors!(expected_paths[0].last().unwrap(), 0);
+
expected_total_fee_msat
}
pub fn claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_preimage: PaymentPreimage) {
commitment_signed_dance!(origin_node, prev_node, next_msgs.as_ref().unwrap().1, false);
let events = origin_node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- match events[0] {
- Event::PaymentPathFailed { payment_hash, rejected_by_dest, all_paths_failed, ref path, .. } => {
+ let expected_payment_id = match events[0] {
+ Event::PaymentPathFailed { payment_hash, rejected_by_dest, all_paths_failed, ref path, ref payment_id, .. } => {
assert_eq!(payment_hash, our_payment_hash);
assert!(rejected_by_dest);
assert_eq!(all_paths_failed, i == expected_paths.len() - 1);
for (idx, hop) in expected_route.iter().enumerate() {
assert_eq!(hop.node.get_our_node_id(), path[idx].pubkey);
}
+ payment_id.unwrap()
},
_ => panic!("Unexpected event"),
+ };
+ if i == expected_paths.len() - 1 {
+ origin_node.node.abandon_payment(expected_payment_id);
+ let events = origin_node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::PaymentFailed { ref payment_hash, ref payment_id } => {
+ assert_eq!(*payment_hash, our_payment_hash, "unexpected second payment_hash");
+ assert_eq!(*payment_id, expected_payment_id);
+ }
+ _ => panic!("Unexpected second event"),
+ }
}
}
}
+
+ // Ensure that fail_htlc_backwards is idempotent.
+ assert!(!expected_paths[0].last().unwrap().node.fail_htlc_backwards(&our_payment_hash));
+ assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_events().is_empty());
+ assert!(expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events().is_empty());
+ check_added_monitors!(expected_paths[0].last().unwrap(), 0);
}
pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], our_payment_hash: PaymentHash) {