Skip to content

Commit 66c8be9

Browse files
committed
Time out incoming HTLCs when we reach cltv_expiry (+ test)
We only do this for incoming HTLCs directly as we rely on channel closure and HTLC-Timeout broadcast to fail any HTLCs which we relayed onwards where our next-hop doesn't update_fail in time.
1 parent 74002d3 commit 66c8be9

File tree

3 files changed

+105
-3
lines changed

3 files changed

+105
-3
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ enum PendingForwardReceiveHTLCInfo {
7676
},
7777
Receive {
7878
payment_data: Option<msgs::FinalOnionHopData>,
79+
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
7980
},
8081
}
8182

@@ -125,6 +126,7 @@ struct ClaimableHTLC {
125126
src: HTLCPreviousHopData,
126127
value: u64,
127128
payment_data: Option<msgs::FinalOnionHopData>,
129+
cltv_expiry: u32,
128130
}
129131

130132
/// Tracks the inbound corresponding to an outbound HTLC
@@ -1015,7 +1017,10 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
10151017
// delay) once they've send us a commitment_signed!
10161018

10171019
PendingHTLCStatus::Forward(PendingHTLCInfo {
1018-
type_data: PendingForwardReceiveHTLCInfo::Receive { payment_data },
1020+
type_data: PendingForwardReceiveHTLCInfo::Receive {
1021+
payment_data,
1022+
incoming_cltv_expiry: msg.cltv_expiry,
1023+
},
10191024
payment_hash: msg.payment_hash.clone(),
10201025
incoming_shared_secret: shared_secret,
10211026
amt_to_forward: next_hop_data.amt_to_forward,
@@ -1630,7 +1635,7 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
16301635
for forward_info in pending_forwards.drain(..) {
16311636
match forward_info {
16321637
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
1633-
type_data: PendingForwardReceiveHTLCInfo::Receive { payment_data },
1638+
type_data: PendingForwardReceiveHTLCInfo::Receive { payment_data, incoming_cltv_expiry },
16341639
incoming_shared_secret, payment_hash, amt_to_forward, .. }, } => {
16351640
let prev_hop_data = HTLCPreviousHopData {
16361641
short_channel_id: prev_short_channel_id,
@@ -1646,6 +1651,7 @@ impl<ChanSigner: ChannelKeys, M: Deref> ChannelManager<ChanSigner, M> where M::T
16461651
src: prev_hop_data,
16471652
value: amt_to_forward,
16481653
payment_data: payment_data.clone(),
1654+
cltv_expiry: incoming_cltv_expiry,
16491655
});
16501656
if let &Some(ref data) = &payment_data {
16511657
for htlc in htlcs.iter() {
@@ -2801,6 +2807,7 @@ impl<ChanSigner: ChannelKeys, M: Deref + Sync + Send> ChainListener for ChannelM
28012807
log_trace!(self, "Block {} at height {} connected with {} txn matched", header_hash, height, txn_matched.len());
28022808
let _ = self.total_consistency_lock.read().unwrap();
28032809
let mut failed_channels = Vec::new();
2810+
let mut timed_out_htlcs = Vec::new();
28042811
{
28052812
let mut channel_lock = self.channel_state.lock().unwrap();
28062813
let channel_state = &mut *channel_lock;
@@ -2870,10 +2877,29 @@ impl<ChanSigner: ChannelKeys, M: Deref + Sync + Send> ChainListener for ChannelM
28702877
}
28712878
true
28722879
});
2880+
2881+
channel_state.claimable_htlcs.retain(|&(ref payment_hash, _), htlcs| {
2882+
htlcs.retain(|htlc| {
2883+
if height >= htlc.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS {
2884+
timed_out_htlcs.push((HTLCSource::PreviousHopData(htlc.src.clone()), payment_hash.clone(), htlc.value));
2885+
false
2886+
} else { true }
2887+
});
2888+
!htlcs.is_empty()
2889+
});
28732890
}
28742891
for failure in failed_channels.drain(..) {
28752892
self.finish_force_close_channel(failure);
28762893
}
2894+
2895+
for (source, payment_hash, value) in timed_out_htlcs.drain(..) {
2896+
// Call it preimage_unknown as the issue, ultimately, is that the user failed to
2897+
// provide us a preimage within the cltv_expiry time window.
2898+
self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), source, &payment_hash, HTLCFailReason::Reason {
2899+
failure_code: 0x4000 | 15,
2900+
data: byte_utils::be64_to_array(value).to_vec()
2901+
});
2902+
}
28772903
self.latest_block_height.store(height as usize, Ordering::Release);
28782904
*self.last_block_hash.try_lock().expect("block_(dis)connected must not be called in parallel") = header_hash;
28792905
}
@@ -3210,9 +3236,10 @@ impl Writeable for PendingHTLCInfo {
32103236
onion_packet.write(writer)?;
32113237
short_channel_id.write(writer)?;
32123238
},
3213-
&PendingForwardReceiveHTLCInfo::Receive { ref payment_data } => {
3239+
&PendingForwardReceiveHTLCInfo::Receive { ref payment_data, ref incoming_cltv_expiry } => {
32143240
1u8.write(writer)?;
32153241
payment_data.write(writer)?;
3242+
incoming_cltv_expiry.write(writer)?;
32163243
},
32173244
}
32183245
self.incoming_shared_secret.write(writer)?;
@@ -3233,6 +3260,7 @@ impl<R: ::std::io::Read> Readable<R> for PendingHTLCInfo {
32333260
},
32343261
1u8 => PendingForwardReceiveHTLCInfo::Receive {
32353262
payment_data: Readable::read(reader)?,
3263+
incoming_cltv_expiry: Readable::read(reader)?,
32363264
},
32373265
_ => return Err(DecodeError::InvalidValue),
32383266
},
@@ -3442,6 +3470,7 @@ impl<ChanSigner: ChannelKeys + Writeable, M: Deref> Writeable for ChannelManager
34423470
htlc.src.write(writer)?;
34433471
htlc.value.write(writer)?;
34443472
htlc.payment_data.write(writer)?;
3473+
htlc.cltv_expiry.write(writer)?;
34453474
}
34463475
}
34473476

@@ -3587,6 +3616,7 @@ impl<'a, R : ::std::io::Read, ChanSigner: ChannelKeys + Readable<R>, M: Deref> R
35873616
src: Readable::read(reader)?,
35883617
value: Readable::read(reader)?,
35893618
payment_data: Readable::read(reader)?,
3619+
cltv_expiry: Readable::read(reader)?,
35903620
});
35913621
}
35923622
claimable_htlcs.insert(payment_hash, previous_hops);

lightning/src/ln/functional_tests.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,15 @@ fn claim_htlc_outputs_single_tx() {
21872187
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
21882188
nodes[0].block_notifier.block_connected(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 200);
21892189
nodes[1].block_notifier.block_connected(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 200);
2190+
2191+
// Expect pending failures, but we don't bother trying to update the channel state with them.
2192+
let events = nodes[0].node.get_and_clear_pending_events();
2193+
assert_eq!(events.len(), 1);
2194+
match events[0] {
2195+
Event::PendingHTLCsForwardable { .. } => { },
2196+
_ => panic!("Unexpected event"),
2197+
};
2198+
21902199
connect_blocks(&nodes[1].block_notifier, ANTI_REORG_DELAY - 1, 200, true, header.bitcoin_hash());
21912200

21922201
let events = nodes[1].node.get_and_clear_pending_events();
@@ -3441,6 +3450,48 @@ fn test_drop_messages_peer_disconnect_dual_htlc() {
34413450
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_2, 1_000_000);
34423451
}
34433452

3453+
#[test]
3454+
fn test_htlc_timeout() {
3455+
// If the user fails to claim/fail an HTLC within the HTLC CLTV timeout we fail it for them
3456+
// to avoid our counterparty failing the channel.
3457+
let node_cfgs = create_node_cfgs(2);
3458+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
3459+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3460+
3461+
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::supported(), InitFeatures::supported());
3462+
let (_, our_payment_hash) = route_payment(&nodes[0], &[&nodes[1]], 100000);
3463+
3464+
let mut header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
3465+
nodes[0].block_notifier.block_connected_checked(&header, 101, &[], &[]);
3466+
nodes[1].block_notifier.block_connected_checked(&header, 101, &[], &[]);
3467+
for i in 102..TEST_FINAL_CLTV + 100 + 1 - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS {
3468+
header.prev_blockhash = header.bitcoin_hash();
3469+
nodes[0].block_notifier.block_connected_checked(&header, i, &[], &[]);
3470+
nodes[1].block_notifier.block_connected_checked(&header, i, &[], &[]);
3471+
}
3472+
3473+
expect_pending_htlcs_forwardable!(nodes[1]);
3474+
3475+
check_added_monitors!(nodes[1], 1);
3476+
let htlc_timeout_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
3477+
assert!(htlc_timeout_updates.update_add_htlcs.is_empty());
3478+
assert_eq!(htlc_timeout_updates.update_fail_htlcs.len(), 1);
3479+
assert!(htlc_timeout_updates.update_fail_malformed_htlcs.is_empty());
3480+
assert!(htlc_timeout_updates.update_fee.is_none());
3481+
3482+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &htlc_timeout_updates.update_fail_htlcs[0]);
3483+
commitment_signed_dance!(nodes[0], nodes[1], htlc_timeout_updates.commitment_signed, false);
3484+
let events = nodes[0].node.get_and_clear_pending_events();
3485+
match events[0] {
3486+
Event::PaymentFailed { payment_hash, rejected_by_dest, error_code } => {
3487+
assert_eq!(payment_hash, our_payment_hash);
3488+
assert!(rejected_by_dest);
3489+
assert_eq!(error_code.unwrap(), 0x4000 | 15);
3490+
},
3491+
_ => panic!("Unexpected event"),
3492+
}
3493+
}
3494+
34443495
#[test]
34453496
fn test_invalid_channel_announcement() {
34463497
//Test BOLT 7 channel_announcement msg requirement for final node, gather data to build customed channel_announcement msgs
@@ -6698,6 +6749,15 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
66986749

66996750
// Broadcast set of revoked txn on A
67006751
let header_128 = connect_blocks(&nodes[0].block_notifier, 128, 0, true, header.bitcoin_hash());
6752+
6753+
// Expect pending failures, but we don't bother trying to update the channel state with them.
6754+
let events = nodes[0].node.get_and_clear_pending_events();
6755+
assert_eq!(events.len(), 1);
6756+
match events[0] {
6757+
Event::PendingHTLCsForwardable { .. } => { },
6758+
_ => panic!("Unexpected event"),
6759+
};
6760+
67016761
let header_129 = BlockHeader { version: 0x20000000, prev_blockhash: header_128, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
67026762
nodes[0].block_notifier.block_connected(&Block { header: header_129, txdata: vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone(), revoked_htlc_txn[1].clone()] }, 129);
67036763
let first;
@@ -7055,6 +7115,15 @@ fn test_bump_txn_sanitize_tracking_maps() {
70557115

70567116
// Broadcast set of revoked txn on A
70577117
let header_128 = connect_blocks(&nodes[0].block_notifier, 128, 0, false, Default::default());
7118+
7119+
// Expect pending failures, but we don't bother trying to update the channel state with them.
7120+
let events = nodes[0].node.get_and_clear_pending_events();
7121+
assert_eq!(events.len(), 1);
7122+
match events[0] {
7123+
Event::PendingHTLCsForwardable { .. } => { },
7124+
_ => panic!("Unexpected event"),
7125+
};
7126+
70587127
let header_129 = BlockHeader { version: 0x20000000, prev_blockhash: header_128, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
70597128
nodes[0].block_notifier.block_connected(&Block { header: header_129, txdata: vec![revoked_local_txn[0].clone()] }, 129);
70607129
check_closed_broadcast!(nodes[0], false);

lightning/src/util/events.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub enum Event {
5757
/// ChannelManager::fail_htlc_backwards to free up resources for this HTLC.
5858
/// The amount paid should be considered 'incorrect' when it is less than or more than twice
5959
/// the amount expected.
60+
/// If you fail to call either ChannelManager::claim_funds of
61+
/// ChannelManager::fail_htlc_backwards within the HTLC's timeout, the HTLC will be
62+
/// automatically failed with PaymentFailReason::PreimageUnknown.
6063
PaymentReceived {
6164
/// The hash for which the preimage should be handed to the ChannelManager.
6265
payment_hash: PaymentHash,

0 commit comments

Comments
 (0)