Skip to content

Commit c6b3f8a

Browse files
committed
fix duplicate HTLC fail-back on stale force-close
1 parent ecce859 commit c6b3f8a

File tree

2 files changed

+119
-5
lines changed

2 files changed

+119
-5
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12871287
/// a downstream channel force-close remaining unconfirmed by the time the upstream timeout
12881288
/// expires. This is used to tell us we already generated an event to fail this HTLC back
12891289
/// during a previous block scan.
1290-
failed_back_htlc_ids: HashSet<SentHTLCId>,
1290+
failed_back_htlc_ids: HashMap<SentHTLCId, u64>,
12911291

12921292
// The auxiliary HTLC data associated with a holder commitment transaction. This includes
12931293
// non-dust HTLC sources, along with dust HTLCs and their sources. Note that this assumes any
@@ -1885,7 +1885,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
18851885
initial_counterparty_commitment_tx: None,
18861886
balances_empty_height: None,
18871887

1888-
failed_back_htlc_ids: new_hash_set(),
1888+
failed_back_htlc_ids: new_hash_map(),
18891889

18901890
// There are never any HTLCs in the initial commitment transaction
18911891
current_holder_htlc_data: CommitmentHTLCData::new(),
@@ -5435,7 +5435,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
54355435
let mut matured_htlcs = Vec::new();
54365436

54375437
// Produce actionable events from on-chain events having reached their threshold.
5438-
for entry in onchain_events_reaching_threshold_conf {
5438+
for entry in onchain_events_reaching_threshold_conf.clone() {
54395439
match entry.event {
54405440
OnchainEvent::HTLCUpdate { source, payment_hash, htlc_value_satoshis, commitment_tx_output_idx } => {
54415441
// Check for duplicate HTLC resolutions.
@@ -5502,6 +5502,81 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55025502
}
55035503
}
55045504

5505+
// Immediate fail-back on stale force-close, regardless of expiry or whether we're allowed to send further updates.
5506+
let current_counterparty_htlcs = if let Some(txid) = self.funding.current_counterparty_commitment_txid {
5507+
if let Some(htlc_outputs) = self.funding.counterparty_claimable_outpoints.get(&txid) {
5508+
Some(htlc_outputs.iter().map(|&(ref a, ref b)| (a, b.as_ref().map(|boxed| &**boxed))))
5509+
} else { None }
5510+
} else { None }.into_iter().flatten();
5511+
5512+
let prev_counterparty_htlcs = if let Some(txid) = self.funding.prev_counterparty_commitment_txid {
5513+
if let Some(htlc_outputs) = self.funding.counterparty_claimable_outpoints.get(&txid) {
5514+
Some(htlc_outputs.iter().map(|&(ref a, ref b)| (a, b.as_ref().map(|boxed| &**boxed))))
5515+
} else { None }
5516+
} else { None }.into_iter().flatten();
5517+
5518+
let htlcs = holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES)
5519+
.chain(current_counterparty_htlcs)
5520+
.chain(prev_counterparty_htlcs);
5521+
5522+
// To correctly handle duplicate HTLCs, we first count all expected instances from
5523+
// the commitment transactions.
5524+
let mut expected_htlc_counts: HashMap<SentHTLCId, u64> = new_hash_map();
5525+
for (_, source_opt) in htlcs.clone() {
5526+
if let Some(source) = source_opt {
5527+
*expected_htlc_counts.entry(SentHTLCId::from_source(source)).or_default() += 1;
5528+
}
5529+
}
5530+
5531+
// Get a lookup of all HTLCs the monitor is currently tracking on-chain.
5532+
let monitor_htlc_sources: HashSet<&HTLCSource> = onchain_events_reaching_threshold_conf
5533+
.iter()
5534+
.filter_map(|event_entry| match &event_entry.event {
5535+
OnchainEvent::HTLCUpdate { source, .. } => Some(source),
5536+
_ => None,
5537+
})
5538+
.collect();
5539+
5540+
// Group all in-flight HTLCs by payment hash to handle duplicates correctly.
5541+
let mut htlcs_by_hash: HashMap<PaymentHash, Vec<(&HTLCOutputInCommitment, &HTLCSource)>> = new_hash_map();
5542+
for (htlc, source_opt) in htlcs {
5543+
if let Some(source) = source_opt {
5544+
htlcs_by_hash.entry(htlc.payment_hash).or_default().push((htlc, source));
5545+
}
5546+
}
5547+
5548+
for (payment_hash, htlc_group) in htlcs_by_hash {
5549+
// If any HTLC in this group is missing from the monitor's on-chain view,
5550+
// it indicates a stale state was used. We must fail back the entire group.
5551+
let is_any_htlc_missing = htlc_group
5552+
.iter()
5553+
.any(|(_, source)| !monitor_htlc_sources.contains(source));
5554+
if is_any_htlc_missing {
5555+
log_info!(logger,
5556+
"Detected stale force-close. Failing back HTLCs for hash {}.",
5557+
&payment_hash);
5558+
for (htlc, source) in htlc_group {
5559+
let htlc_id = SentHTLCId::from_source(source);
5560+
let already_failed_count = *self.failed_back_htlc_ids.get(&htlc_id).unwrap_or(&0);
5561+
let expected_count = *expected_htlc_counts.get(&htlc_id).unwrap_or(&0);
5562+
5563+
// Only fail back if we haven't already failed all expected instances.
5564+
if already_failed_count < expected_count {
5565+
log_error!(logger,
5566+
"Failing back HTLC for payment {} due to stale close.",
5567+
log_bytes!(payment_hash.0));
5568+
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
5569+
source: source.clone(),
5570+
payment_preimage: None,
5571+
payment_hash,
5572+
htlc_value_satoshis: Some(htlc.amount_msat / 1000),
5573+
}));
5574+
*self.failed_back_htlc_ids.entry(htlc_id).or_default() += 1;
5575+
}
5576+
}
5577+
}
5578+
}
5579+
55055580
if self.no_further_updates_allowed() {
55065581
// Fail back HTLCs on backwards channels if they expire within
55075582
// `LATENCY_GRACE_PERIOD_BLOCKS` blocks and the channel is closed (i.e. we're at a
@@ -5547,7 +5622,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55475622
if duplicate_event {
55485623
continue;
55495624
}
5550-
if !self.failed_back_htlc_ids.insert(SentHTLCId::from_source(source)) {
5625+
let htlc_id = SentHTLCId::from_source(source);
5626+
if *self.failed_back_htlc_ids.get(&htlc_id).unwrap_or(&0) > 0 {
55515627
continue;
55525628
}
55535629
if !duplicate_event {
@@ -5560,6 +5636,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55605636
payment_hash: htlc.payment_hash,
55615637
htlc_value_satoshis: Some(htlc.amount_msat / 1000),
55625638
}));
5639+
*self.failed_back_htlc_ids.entry(htlc_id).or_default() += 1;
55635640
}
55645641
}
55655642
}
@@ -6546,7 +6623,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
65466623
initial_counterparty_commitment_info,
65476624
initial_counterparty_commitment_tx,
65486625
balances_empty_height,
6549-
failed_back_htlc_ids: new_hash_set(),
6626+
failed_back_htlc_ids: new_hash_map(),
65506627

65516628
current_holder_htlc_data,
65526629
prev_holder_htlc_data,

lightning/src/ln/functional_tests.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9680,3 +9680,40 @@ pub fn test_multi_post_event_actions() {
96809680
do_test_multi_post_event_actions(true);
96819681
do_test_multi_post_event_actions(false);
96829682
}
9683+
9684+
#[xtest(feature = "_externalize_tests")]
9685+
fn test_stale_force_close_with_identical_htlcs() {
9686+
// Test that when two identical HTLCs are relayed and force-closes
9687+
// with a stale state, that we fail both HTLCs back immediately.
9688+
let chanmon_cfgs = create_chanmon_cfgs(4);
9689+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
9690+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
9691+
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
9692+
9693+
let chan_a_b = create_announced_chan_between_nodes(&nodes, 0, 1);
9694+
let _chan_b_c = create_announced_chan_between_nodes(&nodes, 1, 2);
9695+
let _chan_b_d = create_announced_chan_between_nodes(&nodes, 1, 3);
9696+
9697+
// Capture a stale state snapshot before adding any HTLCs
9698+
let stale_tx = get_local_commitment_txn!(nodes[0], chan_a_b.2)[0].clone();
9699+
9700+
// Create two identical HTLCs
9701+
let (payment_preimage, payment_hash, ..) =
9702+
route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 10_000);
9703+
9704+
*nodes[0].network_payment_count.borrow_mut() -= 1;
9705+
let (payment_preimage_2, payment_hash_2, ..) = route_payment(&nodes[1], &[&nodes[3]], 10_000);
9706+
9707+
assert_eq!(payment_hash, payment_hash_2);
9708+
assert_eq!(payment_preimage, payment_preimage_2);
9709+
9710+
mine_transaction(&nodes[1], &stale_tx);
9711+
9712+
let events = nodes[1].node.get_and_clear_pending_events();
9713+
let failed_events_count =
9714+
events.iter().filter(|e| matches!(e, Event::HTLCHandlingFailed { .. })).count();
9715+
assert_eq!(failed_events_count, 2);
9716+
9717+
check_added_monitors!(&nodes[1], 1);
9718+
nodes[1].node.get_and_clear_pending_msg_events();
9719+
}

0 commit comments

Comments
 (0)