Skip to content

#2585 Preflight Test Coverage #2641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 64 additions & 7 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2097,6 +2097,26 @@ pub fn expect_channel_ready_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, ex
}
}

#[cfg(any(test, feature = "_test_utils"))]
pub fn expect_probe_successful_events(node: &Node, mut probe_results: Vec<(PaymentHash, PaymentId)>) {
let mut events = node.node.get_and_clear_pending_events();

for event in events.drain(..) {
match event {
Event::ProbeSuccessful { payment_hash: ev_ph, payment_id: ev_pid, ..} => {
let result_idx = probe_results.iter().position(|(payment_hash, payment_id)| *payment_hash == ev_ph && *payment_id == ev_pid);
assert!(result_idx.is_some());

probe_results.remove(result_idx.unwrap());
},
_ => panic!(),
}
};

// Ensure that we received a ProbeSuccessful event for each probe result.
assert!(probe_results.is_empty());
}

pub struct PaymentFailedConditions<'a> {
pub(crate) expected_htlc_error_data: Option<(u16, &'a [u8])>,
pub(crate) expected_blamed_scid: Option<u64>,
Expand Down Expand Up @@ -2234,21 +2254,41 @@ pub fn send_along_route_with_secret<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
payment_id
}

pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, clear_recipient_events: bool, expected_preimage: Option<PaymentPreimage>) -> Option<Event> {
fn fail_payment_along_path<'a, 'b, 'c>(expected_path: &[&Node<'a, 'b, 'c>]) {
let origin_node_id = expected_path[0].node.get_our_node_id();

// iterate from the receiving node to the origin node and handle update fail htlc.
for (&node, &prev_node) in expected_path.iter().rev().zip(expected_path.iter().rev().skip(1)) {
let updates = get_htlc_update_msgs!(node, prev_node.node.get_our_node_id());
prev_node.node.handle_update_fail_htlc(&node.node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(prev_node, 0);

let is_first_hop = origin_node_id == prev_node.node.get_our_node_id();
// We do not want to fail backwards on the first hop. All other hops should fail backwards.
commitment_signed_dance!(prev_node, node, updates.commitment_signed, !is_first_hop);
}
}

pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, clear_recipient_events: bool, expected_preimage: Option<PaymentPreimage>, is_probe: bool) -> Option<Event> {
let mut payment_event = SendEvent::from_event(ev);
let mut prev_node = origin_node;
let mut event = None;

for (idx, &node) in expected_path.iter().enumerate() {
let is_last_hop = idx == expected_path.len() - 1;
assert_eq!(node.node.get_our_node_id(), payment_event.node_id);

node.node.handle_update_add_htlc(&prev_node.node.get_our_node_id(), &payment_event.msgs[0]);
check_added_monitors!(node, 0);
commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, false);

expect_pending_htlcs_forwardable!(node);
if is_last_hop && is_probe {
commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, true, true);
} else {
commitment_signed_dance!(node, prev_node, payment_event.commitment_msg, false);
expect_pending_htlcs_forwardable!(node);
}

if idx == expected_path.len() - 1 && clear_recipient_events {
if is_last_hop && clear_recipient_events {
let events_2 = node.node.get_and_clear_pending_events();
if payment_claimable_expected {
assert_eq!(events_2.len(), 1);
Expand Down Expand Up @@ -2282,7 +2322,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
} else {
assert!(events_2.is_empty());
}
} else if idx != expected_path.len() - 1 {
} else if !is_last_hop {
let mut events_2 = node.node.get_and_clear_pending_msg_events();
assert_eq!(events_2.len(), 1);
check_added_monitors!(node, 1);
Expand All @@ -2296,16 +2336,33 @@ pub fn do_pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_p
}

pub fn pass_along_path<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: Option<PaymentSecret>, ev: MessageSendEvent, payment_claimable_expected: bool, expected_preimage: Option<PaymentPreimage>) -> Option<Event> {
do_pass_along_path(origin_node, expected_path, recv_value, our_payment_hash, our_payment_secret, ev, payment_claimable_expected, true, expected_preimage)
do_pass_along_path(origin_node, expected_path, recv_value, our_payment_hash, our_payment_secret, ev, payment_claimable_expected, true, expected_preimage, false)
}

pub fn send_probe_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]]) {
let mut events = origin_node.node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), expected_route.len());

check_added_monitors!(origin_node, expected_route.len());

for path in expected_route.iter() {
let ev = remove_first_msg_event_to_node(&path[0].node.get_our_node_id(), &mut events);

do_pass_along_path(origin_node, path, 0, PaymentHash([0_u8; 32]), None, ev, false, false, None, true);
let nodes_to_fail_payment: Vec<_> = vec![origin_node].into_iter().chain(path.iter().cloned()).collect();

fail_payment_along_path(nodes_to_fail_payment.as_slice());
}
}

pub fn pass_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&[&Node<'a, 'b, 'c>]], recv_value: u64, our_payment_hash: PaymentHash, our_payment_secret: PaymentSecret) {
let mut events = origin_node.node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), expected_route.len());

for (path_idx, expected_path) in expected_route.iter().enumerate() {
let ev = remove_first_msg_event_to_node(&expected_path[0].node.get_our_node_id(), &mut events);
// Once we've gotten through all the HTLCs, the last one should result in a
// PaymentClaimable (but each previous one should not!), .
// PaymentClaimable (but each previous one should not!).
let expect_payment = path_idx == expected_route.len() - 1;
pass_along_path(origin_node, expected_path, recv_value, our_payment_hash.clone(), Some(our_payment_secret), ev, expect_payment, None);
}
Expand Down
195 changes: 102 additions & 93 deletions lightning/src/ln/payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,48 +1277,17 @@ fn successful_probe_yields_event() {
create_announced_chan_between_nodes(&nodes, 0, 1);
create_announced_chan_between_nodes(&nodes, 1, 2);

let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], 100_000);
let recv_value = 100_000;
let (route, payment_hash, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], recv_value);

let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();
let res = nodes[0].node.send_probe(route.paths[0].clone()).unwrap();

// node[0] -- update_add_htlcs -> node[1]
check_added_monitors!(nodes[0], 1);
let updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
let probe_event = SendEvent::from_commitment_update(nodes[1].node.get_our_node_id(), updates);
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &probe_event.msgs[0]);
check_added_monitors!(nodes[1], 0);
commitment_signed_dance!(nodes[1], nodes[0], probe_event.commitment_msg, false);
expect_pending_htlcs_forwardable!(nodes[1]);
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]];

// node[1] -- update_add_htlcs -> node[2]
check_added_monitors!(nodes[1], 1);
let updates = get_htlc_update_msgs!(nodes[1], nodes[2].node.get_our_node_id());
let probe_event = SendEvent::from_commitment_update(nodes[1].node.get_our_node_id(), updates);
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &probe_event.msgs[0]);
check_added_monitors!(nodes[2], 0);
commitment_signed_dance!(nodes[2], nodes[1], probe_event.commitment_msg, true, true);
send_probe_along_route(&nodes[0], expected_route);

// node[1] <- update_fail_htlcs -- node[2]
let updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
nodes[1].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(nodes[1], 0);
commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, true);

// node[0] <- update_fail_htlcs -- node[1]
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(nodes[0], 0);
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);
expect_probe_successful_events(&nodes[0], vec![res]);

let mut events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events.drain(..).next().unwrap() {
crate::events::Event::ProbeSuccessful { payment_id: ev_pid, payment_hash: ev_ph, .. } => {
assert_eq!(payment_id, ev_pid);
assert_eq!(payment_hash, ev_ph);
},
_ => panic!(),
};
assert!(!nodes[0].node.has_pending_payments());
}

Expand Down Expand Up @@ -1423,6 +1392,94 @@ fn onchain_failed_probe_yields_event() {
assert!(!nodes[0].node.has_pending_payments());
}

#[test]
fn preflight_probes_yield_event_skip_private_hop() {
let chanmon_cfgs = create_chanmon_cfgs(5);
let node_cfgs = create_node_cfgs(5, &chanmon_cfgs);

// We alleviate the HTLC max-in-flight limit, as otherwise we'd always be limited through that.
let mut no_htlc_limit_config = test_default_channel_config();
no_htlc_limit_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;

let user_configs = std::iter::repeat(no_htlc_limit_config).take(5).map(|c| Some(c)).collect::<Vec<Option<UserConfig>>>();
let node_chanmgrs = create_node_chanmgrs(5, &node_cfgs, &user_configs);
let nodes = create_network(5, &node_cfgs, &node_chanmgrs);

// Setup channel topology:
// N0 -(1M:0)- N1 -(1M:0)- N2 -(70k:0)- N3 -(50k:0)- N4

create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 70_000, 0);
create_unannounced_chan_between_nodes_with_value(&nodes, 3, 4, 50_000, 0);

let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_basic_mpp_optional();

let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(invoice_features).unwrap();

let recv_value = 50_000_000;
let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap();

let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2], &nodes[3]]];

assert_eq!(res.len(), expected_route.len());

send_probe_along_route(&nodes[0], expected_route);

expect_probe_successful_events(&nodes[0], res.clone());

assert!(!nodes[0].node.has_pending_payments());
}

#[test]
fn preflight_probes_yield_event() {
let chanmon_cfgs = create_chanmon_cfgs(4);
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);

// We alleviate the HTLC max-in-flight limit, as otherwise we'd always be limited through that.
let mut no_htlc_limit_config = test_default_channel_config();
no_htlc_limit_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;

let user_configs = std::iter::repeat(no_htlc_limit_config).take(4).map(|c| Some(c)).collect::<Vec<Option<UserConfig>>>();
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &user_configs);
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);

// Setup channel topology:
// (1M:0)- N1 -(30k:0)
// / \
// N0 N4
// \ /
// (1M:0)- N2 -(70k:0)
//
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 30_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 70_000, 0);

let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_basic_mpp_optional();

let payment_params = PaymentParameters::from_node_id(nodes[3].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(invoice_features).unwrap();

let recv_value = 50_000_000;
let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap();

let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]];

assert_eq!(res.len(), expected_route.len());

send_probe_along_route(&nodes[0], expected_route);

expect_probe_successful_events(&nodes[0], res.clone());

assert!(!nodes[0].node.has_pending_payments());
}

#[test]
fn preflight_probes_yield_event_and_skip() {
let chanmon_cfgs = create_chanmon_cfgs(5);
Expand All @@ -1443,7 +1500,7 @@ fn preflight_probes_yield_event_and_skip() {
// \ /
// (70k:0)- N3 -(1M:0)
//
let first_chan_update = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0).0;
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 30_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 70_000, 0);
create_announced_chan_between_nodes_with_value(&nodes, 2, 4, 1_000_000, 0);
Expand All @@ -1452,70 +1509,22 @@ fn preflight_probes_yield_event_and_skip() {
let mut invoice_features = Bolt11InvoiceFeatures::empty();
invoice_features.set_basic_mpp_optional();

let mut payment_params = PaymentParameters::from_node_id(nodes[4].node.get_our_node_id(), TEST_FINAL_CLTV)
let payment_params = PaymentParameters::from_node_id(nodes[4].node.get_our_node_id(), TEST_FINAL_CLTV)
.with_bolt11_features(invoice_features).unwrap();

let route_params = RouteParameters::from_payment_params_and_value(payment_params, 80_000_000);
let recv_value = 80_000_000;
let route_params = RouteParameters::from_payment_params_and_value(payment_params, recv_value);
let res = nodes[0].node.send_preflight_probes(route_params, None).unwrap();

let expected_route : &[&[&Node]] = &[&[&nodes[1], &nodes[2], &nodes[4]]];

// We check that only one probe was sent, the other one was skipped due to limited liquidity.
assert_eq!(res.len(), 1);
let log_msg = format!("Skipped sending payment probe to avoid putting channel {} under the liquidity limit.",
first_chan_update.contents.short_channel_id);
node_cfgs[0].logger.assert_log_contains("lightning::ln::channelmanager", &log_msg, 1);

let (payment_hash, payment_id) = res.first().unwrap();
send_probe_along_route(&nodes[0], expected_route);

// node[0] -- update_add_htlcs -> node[1]
check_added_monitors!(nodes[0], 1);
let probe_event = SendEvent::from_node(&nodes[0]);
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &probe_event.msgs[0]);
check_added_monitors!(nodes[1], 0);
commitment_signed_dance!(nodes[1], nodes[0], probe_event.commitment_msg, false);
expect_pending_htlcs_forwardable!(nodes[1]);
expect_probe_successful_events(&nodes[0], res.clone());

// node[1] -- update_add_htlcs -> node[2]
check_added_monitors!(nodes[1], 1);
let probe_event = SendEvent::from_node(&nodes[1]);
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &probe_event.msgs[0]);
check_added_monitors!(nodes[2], 0);
commitment_signed_dance!(nodes[2], nodes[1], probe_event.commitment_msg, false);
expect_pending_htlcs_forwardable!(nodes[2]);

// node[2] -- update_add_htlcs -> node[4]
check_added_monitors!(nodes[2], 1);
let probe_event = SendEvent::from_node(&nodes[2]);
nodes[4].node.handle_update_add_htlc(&nodes[2].node.get_our_node_id(), &probe_event.msgs[0]);
check_added_monitors!(nodes[4], 0);
commitment_signed_dance!(nodes[4], nodes[2], probe_event.commitment_msg, true, true);

// node[2] <- update_fail_htlcs -- node[4]
let updates = get_htlc_update_msgs!(nodes[4], nodes[2].node.get_our_node_id());
nodes[2].node.handle_update_fail_htlc(&nodes[4].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(nodes[2], 0);
commitment_signed_dance!(nodes[2], nodes[4], updates.commitment_signed, true);

// node[1] <- update_fail_htlcs -- node[2]
let updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
nodes[1].node.handle_update_fail_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(nodes[1], 0);
commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, true);

// node[0] <- update_fail_htlcs -- node[1]
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
check_added_monitors!(nodes[0], 0);
commitment_signed_dance!(nodes[0], nodes[1], updates.commitment_signed, false);

let mut events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events.drain(..).next().unwrap() {
crate::events::Event::ProbeSuccessful { payment_id: ev_pid, payment_hash: ev_ph, .. } => {
assert_eq!(*payment_id, ev_pid);
assert_eq!(*payment_hash, ev_ph);
},
_ => panic!(),
};
assert!(!nodes[0].node.has_pending_payments());
}

Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/reload_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,8 +749,8 @@ fn do_test_partial_claim_before_restart(persist_both_monitors: bool) {
assert_eq!(send_events.len(), 2);
let node_1_msgs = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut send_events);
let node_2_msgs = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut send_events);
do_pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_1_msgs, true, false, None);
do_pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_2_msgs, true, false, None);
do_pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_1_msgs, true, false, None, false);
do_pass_along_path(&nodes[0], &[&nodes[2], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), node_2_msgs, true, false, None, false);

// Now that we have an MPP payment pending, get the latest encoded copies of nodes[3]'s
// monitors and ChannelManager, for use later, if we don't want to persist both monitors.
Expand Down