DedicatedEntropy,
&'node_cfg test_utils::TestKeysInterface,
&'chan_mon_cfg test_utils::TestLogger,
+ &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
&'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>,
&'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
IgnoringMessageHandler,
}}
}
+/// Checks if at least one peer is connected.
+fn is_any_peer_connected(node: &Node) -> bool {
+ let peer_state = node.node.per_peer_state.read().unwrap();
+ for (_, peer_mutex) in peer_state.iter() {
+ let peer = peer_mutex.lock().unwrap();
+ if peer.is_connected { return true; }
+ }
+ false
+}
+
/// Check that a channel's closing channel update has been broadcasted, and optionally
/// check whether an error message event has occurred.
pub fn check_closed_broadcast(node: &Node, num_channels: usize, with_error_msg: bool) -> Vec<msgs::ErrorMessage> {
+ let mut dummy_connected = false;
+ if !is_any_peer_connected(node) {
+ connect_dummy_node(&node);
+ dummy_connected = true;
+ }
let msg_events = node.node.get_and_clear_pending_msg_events();
assert_eq!(msg_events.len(), if with_error_msg { num_channels * 2 } else { num_channels });
+ if dummy_connected {
+ disconnect_dummy_node(&node);
+ }
msg_events.into_iter().filter_map(|msg_event| {
match msg_event {
MessageSendEvent::BroadcastChannelUpdate { ref msg } => {
/// there are any [`Event::HTLCHandlingFailed`] events their [`HTLCDestination`] is included in the
/// `expected_failures` set.
pub fn expect_pending_htlcs_forwardable_conditions(events: Vec<Event>, expected_failures: &[HTLCDestination]) {
- match events[0] {
- Event::PendingHTLCsForwardable { .. } => { },
- _ => panic!("Unexpected event {:?}", events),
- };
-
let count = expected_failures.len() + 1;
assert_eq!(events.len(), count);
-
+ assert!(events.iter().find(|event| matches!(event, Event::PendingHTLCsForwardable { .. })).is_some());
if expected_failures.len() > 0 {
expect_htlc_handling_failed_destinations!(events, expected_failures)
}
assert_eq!(expected_recv_value, *amount_msat);
assert_eq!(expected_receiver_node_id, receiver_node_id.unwrap());
match purpose {
- PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
+ PaymentPurpose::Bolt11InvoicePayment { payment_preimage, payment_secret, .. } => {
+ assert_eq!(&expected_payment_preimage, payment_preimage);
+ assert_eq!(expected_payment_secret, *payment_secret);
+ },
+ PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => {
+ assert_eq!(&expected_payment_preimage, payment_preimage);
+ assert_eq!(expected_payment_secret, *payment_secret);
+ },
+ PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. } => {
assert_eq!(&expected_payment_preimage, payment_preimage);
assert_eq!(expected_payment_secret, *payment_secret);
},
) -> Option<u64> {
match event {
Event::PaymentForwarded {
- total_fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
- outbound_amount_forwarded_msat: _, skimmed_fee_msat
+ prev_channel_id, next_channel_id, prev_user_channel_id, next_user_channel_id,
+ total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx, ..
} => {
if allow_1_msat_fee_overpay {
// Aggregating fees for blinded paths may result in a rounding error, causing slight
assert!(skimmed_fee_msat == expected_extra_fees_msat);
if !upstream_force_closed {
// Is the event prev_channel_id in one of the channels between the two nodes?
- assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap()));
+ assert!(node.node().list_channels().iter().any(|x|
+ x.counterparty.node_id == prev_node.node().get_our_node_id() &&
+ x.channel_id == prev_channel_id.unwrap() &&
+ x.user_channel_id == prev_user_channel_id.unwrap()
+ ));
}
// We check for force closures since a force closed channel is removed from the
// node's channel list
if !downstream_force_closed {
- assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == next_node.node().get_our_node_id() && x.channel_id == next_channel_id.unwrap()));
+ // As documented, `next_user_channel_id` will only be `Some` if we didn't settle via an
+ // onchain transaction, just as the `total_fee_earned_msat` field. Rather than
+ // introducing yet another variable, we use the latter's state as a flag to detect
+ // this and only check if it's `Some`.
+ if total_fee_earned_msat.is_none() {
+ assert!(node.node().list_channels().iter().any(|x|
+ x.counterparty.node_id == next_node.node().get_our_node_id() &&
+ x.channel_id == next_channel_id.unwrap()
+ ));
+ } else {
+ assert!(node.node().list_channels().iter().any(|x|
+ x.counterparty.node_id == next_node.node().get_our_node_id() &&
+ x.channel_id == next_channel_id.unwrap() &&
+ x.user_channel_id == next_user_channel_id.unwrap()
+ ));
+ }
}
assert_eq!(claim_from_onchain_tx, downstream_force_closed);
total_fee_earned_msat
pub clear_recipient_events: bool,
pub expected_preimage: Option<PaymentPreimage>,
pub is_probe: bool,
+ pub custom_tlvs: Vec<(u64, Vec<u8>)>,
}
impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
Self {
origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event,
payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None,
- is_probe: false,
+ is_probe: false, custom_tlvs: Vec::new(),
}
}
pub fn without_clearing_recipient_events(mut self) -> Self {
self.expected_preimage = Some(payment_preimage);
self
}
+ pub fn with_custom_tlvs(mut self, custom_tlvs: Vec<(u64, Vec<u8>)>) -> Self {
+ self.custom_tlvs = custom_tlvs;
+ self
+ }
}
pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event> {
let PassAlongPathArgs {
origin_node, expected_path, recv_value, payment_hash: our_payment_hash,
payment_secret: our_payment_secret, event: ev, payment_claimable_expected,
- clear_recipient_events, expected_preimage, is_probe
+ clear_recipient_events, expected_preimage, is_probe, custom_tlvs
} = args;
let mut payment_event = SendEvent::from_event(ev);
assert_eq!(our_payment_hash, *payment_hash);
assert_eq!(node.node.get_our_node_id(), receiver_node_id.unwrap());
assert!(onion_fields.is_some());
+ assert_eq!(onion_fields.as_ref().unwrap().custom_tlvs, custom_tlvs);
match &purpose {
- PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
+ PaymentPurpose::Bolt11InvoicePayment { 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::Bolt12OfferPayment { 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::Bolt12RefundPayment { 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);
pub expected_min_htlc_overpay: Vec<u32>,
pub skip_last: bool,
pub payment_preimage: PaymentPreimage,
+ // Allow forwarding nodes to have taken 1 msat more fee than expected based on the downstream
+ // fulfill amount.
+ //
+ // Necessary because our test utils calculate the expected fee for an intermediate node based on
+ // the amount was claimed in their downstream peer's fulfill, but blinded intermediate nodes
+ // calculate their fee based on the inbound amount from their upstream peer, causing a difference
+ // in rounding.
+ pub allow_1_msat_fee_overpay: bool,
}
impl<'a, 'b, 'c, 'd> ClaimAlongRouteArgs<'a, 'b, 'c, 'd> {
Self {
origin_node, expected_paths, expected_extra_fees: vec![0; expected_paths.len()],
expected_min_htlc_overpay: vec![0; expected_paths.len()], skip_last: false, payment_preimage,
+ allow_1_msat_fee_overpay: false,
}
}
pub fn skip_last(mut self, skip_last: bool) -> Self {
self.expected_min_htlc_overpay = extra_fees;
self
}
+ pub fn allow_1_msat_fee_overpay(mut self) -> Self {
+ self.allow_1_msat_fee_overpay = true;
+ self
+ }
}
pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArgs) -> u64 {
let ClaimAlongRouteArgs {
origin_node, expected_paths, expected_extra_fees, expected_min_htlc_overpay, skip_last,
- payment_preimage: our_payment_preimage
+ payment_preimage: our_payment_preimage, allow_1_msat_fee_overpay,
} = args;
let claim_event = expected_paths[0].last().unwrap().node.get_and_clear_pending_events();
assert_eq!(claim_event.len(), 1);
let mut fwd_amt_msat = 0;
match claim_event[0] {
Event::PaymentClaimed {
- purpose: PaymentPurpose::SpontaneousPayment(preimage),
+ purpose: PaymentPurpose::SpontaneousPayment(preimage)
+ | PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(preimage), .. }
+ | PaymentPurpose::Bolt12OfferPayment { payment_preimage: Some(preimage), .. }
+ | PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(preimage), .. },
amount_msat,
ref htlcs,
- .. }
- | Event::PaymentClaimed {
- purpose: PaymentPurpose::InvoicePayment { payment_preimage: Some(preimage), ..},
- ref htlcs,
- amount_msat,
..
} => {
assert_eq!(preimage, our_payment_preimage);
fwd_amt_msat = amount_msat;
},
Event::PaymentClaimed {
- purpose: PaymentPurpose::InvoicePayment { .. },
+ purpose: PaymentPurpose::Bolt11InvoicePayment { .. }
+ | PaymentPurpose::Bolt12OfferPayment { .. }
+ | PaymentPurpose::Bolt12RefundPayment { .. },
payment_hash,
amount_msat,
ref htlcs,
}
let mut events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
- Some(fee as u64), expected_extra_fee, false, false, false);
- expected_total_fee_msat += fee as u64;
- fwd_amt_msat += fee as u64;
+ let actual_fee = expect_payment_forwarded(events.pop().unwrap(), *$node, $next_node, $prev_node,
+ Some(fee as u64), expected_extra_fee, false, false, allow_1_msat_fee_overpay);
+ expected_total_fee_msat += actual_fee.unwrap();
+ fwd_amt_msat += actual_fee.unwrap();
check_added_monitors!($node, 1);
let new_next_msgs = if $new_msgs {
let events = $node.node.get_and_clear_pending_msg_events();
for i in 0..node_count {
let dedicated_entropy = DedicatedEntropy(RandomBytes::new([i as u8; 32]));
let onion_messenger = OnionMessenger::new(
- dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &cfgs[i].message_router,
- &chan_mgrs[i], IgnoringMessageHandler {},
+ dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &chan_mgrs[i],
+ &cfgs[i].message_router, &chan_mgrs[i], IgnoringMessageHandler {},
);
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
nodes
}
+pub fn connect_dummy_node<'a, 'b: 'a, 'c: 'b>(node: &Node<'a, 'b, 'c>) {
+ let node_id_dummy = PublicKey::from_slice(&[2; 33]).unwrap();
+
+ let mut dummy_init_features = InitFeatures::empty();
+ dummy_init_features.set_static_remote_key_required();
+
+ let init_dummy = msgs::Init {
+ features: dummy_init_features,
+ networks: None,
+ remote_network_address: None
+ };
+
+ node.node.peer_connected(&node_id_dummy, &init_dummy, true).unwrap();
+ node.onion_messenger.peer_connected(&node_id_dummy, &init_dummy, true).unwrap();
+}
+
+pub fn disconnect_dummy_node<'a, 'b: 'a, 'c: 'b>(node: &Node<'a, 'b, 'c>) {
+ let node_id_dummy = PublicKey::from_slice(&[2; 33]).unwrap();
+ node.node.peer_disconnected(&node_id_dummy);
+ node.onion_messenger.peer_disconnected(&node_id_dummy);
+}
+
// Note that the following only works for CLTV values up to 128
pub const ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 137; // Here we have a diff due to HTLC CLTV expiry being < 2^15 in test
pub const ACCEPTED_HTLC_SCRIPT_WEIGHT_ANCHORS: usize = 140; // Here we have a diff due to HTLC CLTV expiry being < 2^15 in test
}
pub fn handle_announce_close_broadcast_events<'a, 'b, 'c>(nodes: &Vec<Node<'a, 'b, 'c>>, a: usize, b: usize, needs_err_handle: bool, expected_error: &str) {
+ let mut dummy_connected = false;
+ if !is_any_peer_connected(&nodes[a]) {
+ connect_dummy_node(&nodes[a]);
+ dummy_connected = true
+ }
+
let events_1 = nodes[a].node.get_and_clear_pending_msg_events();
assert_eq!(events_1.len(), 2);
- let as_update = match events_1[0] {
+ let as_update = match events_1[1] {
MessageSendEvent::BroadcastChannelUpdate { ref msg } => {
msg.clone()
},
_ => panic!("Unexpected event"),
};
- match events_1[1] {
+ match events_1[0] {
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
assert_eq!(node_id, nodes[b].node.get_our_node_id());
assert_eq!(msg.data, expected_error);
},
_ => panic!("Unexpected event"),
}
-
+ if dummy_connected {
+ disconnect_dummy_node(&nodes[a]);
+ dummy_connected = false;
+ }
+ if !is_any_peer_connected(&nodes[b]) {
+ connect_dummy_node(&nodes[b]);
+ dummy_connected = true;
+ }
let events_2 = nodes[b].node.get_and_clear_pending_msg_events();
assert_eq!(events_2.len(), if needs_err_handle { 1 } else { 2 });
- let bs_update = match events_2[0] {
+ let bs_update = match events_2.last().unwrap() {
MessageSendEvent::BroadcastChannelUpdate { ref msg } => {
msg.clone()
},
_ => panic!("Unexpected event"),
};
if !needs_err_handle {
- match events_2[1] {
+ match events_2[0] {
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
assert_eq!(node_id, nodes[a].node.get_our_node_id());
assert_eq!(msg.data, expected_error);
_ => panic!("Unexpected event"),
}
}
-
+ if dummy_connected {
+ disconnect_dummy_node(&nodes[b]);
+ }
for node in nodes {
node.gossip_sync.handle_channel_update(&as_update).unwrap();
node.gossip_sync.handle_channel_update(&bs_update).unwrap();