Skip to content

Commit 11c2f12

Browse files
committed
Do additional pre-flight checks before claiming a payment
As additional sanity checks, before claiming a payment, we check that we have the full amount available in `claimable_htlcs` that the payment should be for. Concretely, this prevents one somewhat-absurd edge case where a user may receive an MPP payment, wait many *blocks* before claiming it, allowing us to fail the pending HTLCs and the sender to retry some subset of the payment before we go to claim. More generally, this is just good belt-and-suspenders against any edge cases we may have missed.
1 parent 0e25421 commit 11c2f12

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed

lightning/src/ln/channelmanager.rs

+28
Original file line numberDiff line numberDiff line change
@@ -3825,14 +3825,42 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
38253825
// provide the preimage, so worrying too much about the optimal handling isn't worth
38263826
// it.
38273827
let mut claimable_amt_msat = 0;
3828+
let mut expected_amt_msat = None;
38283829
let mut valid_mpp = true;
38293830
for htlc in sources.iter() {
38303831
if let None = channel_state.as_ref().unwrap().short_to_id.get(&htlc.prev_hop.short_channel_id) {
38313832
valid_mpp = false;
38323833
break;
38333834
}
3835+
if expected_amt_msat.is_some() && expected_amt_msat != Some(htlc.total_msat) {
3836+
log_error!(self.logger, "Somehow ended up with an MPP payment with different total amounts - this should not be reachable!");
3837+
debug_assert!(false);
3838+
valid_mpp = false;
3839+
break;
3840+
}
3841+
expected_amt_msat = Some(htlc.total_msat);
3842+
if let OnionPayload::Spontaneous(_) = &htlc.onion_payload {
3843+
// We don't currently support MPP for spontaneous payments, so just check
3844+
// that there's one payment here and move on.
3845+
if sources.len() != 1 {
3846+
log_error!(self.logger, "Somehow ended up with an MPP spontaneous payment - this should not be reachable!");
3847+
debug_assert!(false);
3848+
valid_mpp = false;
3849+
break;
3850+
}
3851+
}
3852+
38343853
claimable_amt_msat += htlc.value;
38353854
}
3855+
if sources.is_empty() || expected_amt_msat.is_none() {
3856+
log_info!(self.logger, "Attempted to claim an incomplete payment which no longer had any available HTLCs!");
3857+
return;
3858+
}
3859+
if claimable_amt_msat != expected_amt_msat.unwrap() {
3860+
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",
3861+
expected_amt_msat.unwrap(), claimable_amt_msat);
3862+
return;
3863+
}
38363864

38373865
let mut errs = Vec::new();
38383866
let mut claimed_any_htlcs = false;

lightning/src/ln/functional_test_utils.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -1735,13 +1735,18 @@ pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&No
17351735
claim_payment(&origin, expected_route, our_payment_preimage);
17361736
}
17371737

1738-
pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths_slice: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {
1739-
let mut expected_paths: Vec<_> = expected_paths_slice.iter().collect();
1738+
pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {
17401739
for path in expected_paths.iter() {
17411740
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
17421741
}
17431742
assert!(expected_paths[0].last().unwrap().node.fail_htlc_backwards(&our_payment_hash));
17441743
expect_pending_htlcs_forwardable!(expected_paths[0].last().unwrap());
1744+
1745+
pass_failed_payment_back(origin_node, expected_paths, skip_last, our_payment_hash);
1746+
}
1747+
1748+
pub fn pass_failed_payment_back<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths_slice: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {
1749+
let mut expected_paths: Vec<_> = expected_paths_slice.iter().collect();
17451750
check_added_monitors!(expected_paths[0].last().unwrap(), expected_paths.len());
17461751

17471752
let mut per_path_msgs: Vec<((msgs::UpdateFailHTLC, msgs::CommitmentSigned), PublicKey)> = Vec::with_capacity(expected_paths.len());

lightning/src/ln/functional_tests.rs

+51
Original file line numberDiff line numberDiff line change
@@ -9870,6 +9870,57 @@ fn test_keysend_payments_to_private_node() {
98709870
claim_payment(&nodes[0], &path, test_preimage);
98719871
}
98729872

9873+
#[test]
9874+
fn test_double_partial_claim() {
9875+
// Test what happens if a node receives a payment, generates a PaymentReceived event, the HTLCs
9876+
// time out, the sender resends only some of the MPP parts, then the user processes the
9877+
// PaymentReceived event, ensuring they don't inadvertently claim only part of the full payment
9878+
// amount.
9879+
let chanmon_cfgs = create_chanmon_cfgs(4);
9880+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
9881+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
9882+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
9883+
9884+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0, InitFeatures::known(), InitFeatures::known());
9885+
create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100_000, 0, InitFeatures::known(), InitFeatures::known());
9886+
create_announced_chan_between_nodes_with_value(&nodes, 1, 3, 100_000, 0, InitFeatures::known(), InitFeatures::known());
9887+
create_announced_chan_between_nodes_with_value(&nodes, 2, 3, 100_000, 0, InitFeatures::known(), InitFeatures::known());
9888+
9889+
let (mut route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[3], 15_000_000);
9890+
assert_eq!(route.paths.len(), 2);
9891+
route.paths.sort_by(|path_a, _| {
9892+
// Sort the path so that the path through nodes[1] comes first
9893+
if path_a[0].pubkey == nodes[1].node.get_our_node_id() {
9894+
core::cmp::Ordering::Less } else { core::cmp::Ordering::Greater }
9895+
});
9896+
9897+
send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], 15_000_000, payment_hash, payment_secret);
9898+
// nodes[3] has now received a PaymentReceived event...which it will take some (exorbitant)
9899+
// amount of time to respond to.
9900+
9901+
// Connect some blocks to time out the payment
9902+
connect_blocks(&nodes[3], TEST_FINAL_CLTV);
9903+
connect_blocks(&nodes[0], TEST_FINAL_CLTV); // To get the same height for sending later
9904+
9905+
expect_pending_htlcs_forwardable!(nodes[3]);
9906+
9907+
pass_failed_payment_back(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_hash);
9908+
9909+
// nodes[1] now retries one of the two paths...
9910+
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret)).unwrap();
9911+
check_added_monitors!(nodes[0], 2);
9912+
9913+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
9914+
assert_eq!(events.len(), 2);
9915+
pass_along_path(&nodes[0], &[&nodes[1], &nodes[3]], 15_000_000, payment_hash, Some(payment_secret), events.drain(..).next().unwrap(), false, None);
9916+
9917+
// At this point nodes[3] has received one half of the payment, and the user goes to handle
9918+
// that PaymentReceived event they got hours ago and never handled...we should refuse to claim.
9919+
nodes[3].node.claim_funds(payment_preimage);
9920+
check_added_monitors!(nodes[3], 0);
9921+
assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty());
9922+
}
9923+
98739924
fn do_test_partial_claim_before_restart(persist_both_monitors: bool) {
98749925
// Test what happens if a node receives an MPP payment, claims it, but crashes before
98759926
// persisting the ChannelManager. If `persist_both_monitors` is false, also crash after only

0 commit comments

Comments
 (0)