Skip to content

Commit 37afbe4

Browse files
committed
Add MPP receive timeout handling
1 parent 0e0aabe commit 37afbe4

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 33 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+
ticks: u8,
444445
}
445446

446447
/// A payment identifier used to uniquely identify a payment to LDK.
@@ -1134,6 +1135,9 @@ const CHECK_CLTV_EXPIRY_SANITY_2: u32 = MIN_CLTV_EXPIRY_DELTA as u32 - LATENCY_G
11341135
/// pending HTLCs in flight.
11351136
pub(crate) const PAYMENT_EXPIRY_BLOCKS: u32 = 3;
11361137

1138+
/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until expiry of incomplete MPPs
1139+
pub(crate) const MPP_TIMEOUT_TICKS: u8 = 3;
1140+
11371141
/// Information needed for constructing an invoice route hint for this channel.
11381142
#[derive(Clone, Debug, PartialEq)]
11391143
pub struct CounterpartyForwardingInfo {
@@ -3234,6 +3238,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
32343238
phantom_shared_secret,
32353239
},
32363240
value: amt_to_forward,
3241+
ticks: 0,
32373242
cltv_expiry,
32383243
onion_payload,
32393244
};
@@ -3528,6 +3533,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
35283533
let new_feerate = self.fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
35293534

35303535
let mut handle_errors = Vec::new();
3536+
let mut timed_out_mpp_htlcs = Vec::new();
35313537
{
35323538
let mut channel_state_lock = self.channel_state.lock().unwrap();
35333539
let channel_state = &mut *channel_state_lock;
@@ -3576,6 +3582,32 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
35763582

35773583
true
35783584
});
3585+
3586+
let mut timed_out_payment_hashes: Vec<PaymentHash> = vec![];
3587+
3588+
for (payment_hash, htlcs) in &mut channel_state.claimable_htlcs {
3589+
'htlcs: for mut htlc in htlcs {
3590+
if let OnionPayload::Invoice(ref final_hop_data) = htlc.onion_payload {
3591+
if htlc.value < final_hop_data.total_msat {
3592+
htlc.ticks += 1;
3593+
if htlc.ticks >= MPP_TIMEOUT_TICKS {
3594+
timed_out_payment_hashes.push(*payment_hash);
3595+
break 'htlcs;
3596+
}
3597+
}
3598+
}
3599+
}
3600+
}
3601+
3602+
for payment_hash in &mut timed_out_payment_hashes {
3603+
for htlc in channel_state.claimable_htlcs.remove(payment_hash).unwrap() {
3604+
timed_out_mpp_htlcs.push((htlc.prev_hop, payment_hash.clone()));
3605+
}
3606+
}
3607+
}
3608+
3609+
for htlc_source in timed_out_mpp_htlcs.drain(..) {
3610+
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() });
35793611
}
35803612

35813613
for (err, counterparty_node_id) in handle_errors.drain(..) {
@@ -6146,6 +6178,7 @@ impl Readable for ClaimableHTLC {
61466178
};
61476179
Ok(Self {
61486180
prev_hop: prev_hop.0.unwrap(),
6181+
ticks: 0,
61496182
value,
61506183
onion_payload,
61516184
cltv_expiry,

lightning/src/ln/functional_tests.rs

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

59+
use crate::ln::channelmanager::MPP_TIMEOUT_TICKS;
60+
5961
#[test]
6062
fn test_insane_channel_opens() {
6163
// Stand up a network of 2 nodes
@@ -9913,3 +9915,46 @@ fn test_max_dust_htlc_exposure() {
99139915
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
99149916
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
99159917
}
9918+
9919+
#[test]
9920+
fn test_mpp_receive_timeout() {
9921+
let chanmon_cfgs = create_chanmon_cfgs(2);
9922+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
9923+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
9924+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
9925+
9926+
let chan_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
9927+
9928+
let (mut route, payment_hash, _payment_preimage, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], 100000);
9929+
let path = route.paths[0].clone();
9930+
route.paths.push(path);
9931+
route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
9932+
route.paths[0][0].short_channel_id = chan_id;
9933+
9934+
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.
9935+
let payment_id = PaymentId([42; 32]);
9936+
9937+
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();
9938+
check_added_monitors!(nodes[0], 1);
9939+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
9940+
assert_eq!(events.len(), 1);
9941+
9942+
// Now do the relevant commitment_signed/RAA dances along the path, noting that the final
9943+
// hop should *not* yet generate any PaymentReceived event(s).
9944+
pass_along_path(&nodes[0], &[&nodes[1]], 100000, payment_hash, Some(payment_secret), events.drain(..).next().unwrap(), false, None);
9945+
9946+
for _ in 0..MPP_TIMEOUT_TICKS {
9947+
nodes[1].node.timer_tick_occurred();
9948+
}
9949+
9950+
expect_pending_htlcs_forwardable!(nodes[1]);
9951+
9952+
let htlc_fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
9953+
assert_eq!(htlc_fail_updates.update_fail_htlcs.len(), 1);
9954+
9955+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_fail_updates.update_fail_htlcs[0]);
9956+
check_added_monitors!(nodes[1], 1);
9957+
9958+
commitment_signed_dance!(nodes[0], nodes[1], htlc_fail_updates.commitment_signed, true, true);
9959+
expect_payment_failed!(nodes[0], payment_hash, false, 23, &[][..]);
9960+
}

0 commit comments

Comments
 (0)