Skip to content

Commit d0369db

Browse files
committed
Add MPP receive timeout handling
1 parent b1cd5a7 commit d0369db

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ struct ClaimableHTLC {
441441
cltv_expiry: u32,
442442
value: u64,
443443
onion_payload: OnionPayload,
444+
timer_ticks: u8,
444445
}
445446

446447
/// A payment identifier used to uniquely identify a payment to LDK.
@@ -1148,6 +1149,9 @@ const CHECK_CLTV_EXPIRY_SANITY_2: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_G
11481149
/// pending HTLCs in flight.
11491150
pub(crate) const PAYMENT_EXPIRY_BLOCKS: u32 = 3;
11501151

1152+
/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until expiry of incomplete MPPs
1153+
pub(crate) const MPP_TIMEOUT_TICKS: u8 = 3;
1154+
11511155
/// Information needed for constructing an invoice route hint for this channel.
11521156
#[derive(Clone, Debug, PartialEq)]
11531157
pub struct CounterpartyForwardingInfo {
@@ -3326,6 +3330,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
33263330
phantom_shared_secret,
33273331
},
33283332
value: amt_to_forward,
3333+
timer_ticks: 0,
33293334
cltv_expiry,
33303335
onion_payload,
33313336
};
@@ -3620,6 +3625,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
36203625
let new_feerate = self.fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
36213626

36223627
let mut handle_errors = Vec::new();
3628+
let mut timed_out_mpp_htlcs = Vec::new();
36233629
{
36243630
let mut channel_state_lock = self.channel_state.lock().unwrap();
36253631
let channel_state = &mut *channel_state_lock;
@@ -3668,6 +3674,28 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
36683674

36693675
true
36703676
});
3677+
3678+
channel_state.claimable_htlcs.retain(|payment_hash, htlcs| {
3679+
if htlcs.is_empty() { return false }
3680+
if let OnionPayload::Invoice(ref final_hop_data) = htlcs[0].onion_payload {
3681+
// Check if we've received all the parts we need for an MPP (the value of the parts adds to total_msat).
3682+
// In this case we're not going to handle any timeouts of the parts here.
3683+
if final_hop_data.total_msat == htlcs.iter().fold(0, |total, htlc| total + htlc.value) {
3684+
return true;
3685+
} else if !htlcs.into_iter().all(|htlc| {
3686+
htlc.timer_ticks += 1;
3687+
return htlc.timer_ticks < MPP_TIMEOUT_TICKS
3688+
}) {
3689+
timed_out_mpp_htlcs.extend(htlcs.into_iter().map(|htlc| (htlc.prev_hop.clone(), payment_hash.clone())));
3690+
return false;
3691+
}
3692+
}
3693+
true
3694+
});
3695+
}
3696+
3697+
for htlc_source in timed_out_mpp_htlcs.drain(..) {
3698+
self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), HTLCSource::PreviousHopData(htlc_source.0), &htlc_source.1, HTLCFailReason::Reason { failure_code: 23, data: Vec::new() });
36713699
}
36723700

36733701
for (err, counterparty_node_id) in handle_errors.drain(..) {
@@ -6233,6 +6261,7 @@ impl Readable for ClaimableHTLC {
62336261
};
62346262
Ok(Self {
62356263
prev_hop: prev_hop.0.unwrap(),
6264+
timer_ticks: 0,
62366265
value,
62376266
onion_payload,
62386267
cltv_expiry,

lightning/src/ln/functional_tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ use sync::{Arc, Mutex};
5555
use ln::functional_test_utils::*;
5656
use ln::chan_utils::CommitmentTransaction;
5757

58+
use crate::ln::channelmanager::MPP_TIMEOUT_TICKS;
59+
5860
#[test]
5961
fn test_insane_channel_opens() {
6062
// Stand up a network of 2 nodes
@@ -9689,3 +9691,46 @@ fn test_max_dust_htlc_exposure() {
96899691
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
96909692
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
96919693
}
9694+
9695+
#[test]
9696+
fn test_mpp_receive_timeout() {
9697+
let chanmon_cfgs = create_chanmon_cfgs(2);
9698+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
9699+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
9700+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
9701+
9702+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
9703+
9704+
let (mut route, payment_hash, _payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], 100000);
9705+
let path = route.paths[0].clone();
9706+
route.paths.push(path);
9707+
route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
9708+
route.paths[0][0].short_channel_id = chan_id;
9709+
9710+
let cur_height = CHAN_CONFIRM_DEPTH + 1; // route_payment calls send_payment, which adds 1 to the current height. So we do the same here to match.
9711+
let payment_id = PaymentId([42; 32]);
9712+
9713+
nodes[0].node.send_payment_along_path(&route.paths[0], &route.payment_params, &payment_hash, &Some(payment_secret), 200000, cur_height, payment_id, &None).unwrap();
9714+
check_added_monitors!(nodes[0], 1);
9715+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
9716+
assert_eq!(events.len(), 1);
9717+
9718+
// Now do the relevant commitment_signed/RAA dances along the path, noting that the final
9719+
// hop should *not* yet generate any PaymentReceived event(s).
9720+
pass_along_path(&nodes[0], &[&nodes[1]], 100000, payment_hash, Some(payment_secret), events.drain(..).next().unwrap(), false, None);
9721+
9722+
for _ in 0..MPP_TIMEOUT_TICKS {
9723+
nodes[1].node.timer_tick_occurred();
9724+
}
9725+
9726+
expect_pending_htlcs_forwardable!(nodes[1]);
9727+
9728+
let htlc_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
9729+
assert_eq!(htlc_fail_updates.update_fail_htlcs.len(), 1);
9730+
9731+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_fail_updates.update_fail_htlcs[0]);
9732+
check_added_monitors!(nodes[1], 1);
9733+
9734+
commitment_signed_dance!(nodes[0], nodes[1], htlc_fail_updates.commitment_signed, true, true);
9735+
expect_payment_failed!(nodes[0], payment_hash, false, 23, &[][..]);
9736+
}

0 commit comments

Comments
 (0)