Skip to content

Commit 2a5ac7d

Browse files
committed
fix duplicate HTLC fail-back on stale force-close
1 parent 7439528 commit 2a5ac7d

File tree

2 files changed

+232
-7
lines changed

2 files changed

+232
-7
lines changed

lightning/src/chain/channelmonitor.rs

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

13611361
// The auxiliary HTLC data associated with a holder commitment transaction. This includes
13621362
// non-dust HTLC sources, along with dust HTLCs and their sources. Note that this assumes any
@@ -1956,7 +1956,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19561956
initial_counterparty_commitment_tx: None,
19571957
balances_empty_height: None,
19581958

1959-
failed_back_htlc_ids: new_hash_set(),
1959+
failed_back_htlc_ids: new_hash_map(),
19601960

19611961
// There are never any HTLCs in the initial commitment transaction
19621962
current_holder_htlc_data: CommitmentHTLCData::new(),
@@ -5523,7 +5523,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55235523
let mut matured_htlcs = Vec::new();
55245524

55255525
// Produce actionable events from on-chain events having reached their threshold.
5526-
for entry in onchain_events_reaching_threshold_conf {
5526+
for entry in onchain_events_reaching_threshold_conf.clone() {
55275527
match entry.event {
55285528
OnchainEvent::HTLCUpdate { source, payment_hash, htlc_value_satoshis, commitment_tx_output_idx } => {
55295529
// Check for duplicate HTLC resolutions.
@@ -5587,6 +5587,85 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
55875587
});
55885588
}
55895589
}
5590+
5591+
// Only act on stale force-closes where the confirmed funding spend is the
5592+
// previous counterparty commitment transaction.
5593+
let prev_txid = match self.funding.prev_counterparty_commitment_txid {
5594+
Some(txid) => {
5595+
if txid != entry.txid { continue; }
5596+
txid
5597+
},
5598+
None => { continue; }
5599+
};
5600+
5601+
// Process only HTLCs from that previous counterparty commitment.
5602+
let prev_outputs = if let Some(outputs) =
5603+
self.funding.counterparty_claimable_outpoints.get(&prev_txid) {
5604+
outputs
5605+
} else { continue; };
5606+
5607+
// Build the set of HTLC ids present in the stale (previous) commitment.
5608+
let mut prev_ids: HashSet<SentHTLCId> = new_hash_set();
5609+
for &(_, ref source_opt) in prev_outputs.iter() {
5610+
if let Some(source) = source_opt.as_ref() {
5611+
prev_ids.insert(SentHTLCId::from_source(&**source));
5612+
}
5613+
}
5614+
5615+
// Avoid duplicate fail-back if an HTLCUpdate for this source has already been
5616+
// generated (either matured or still awaiting maturity).
5617+
let mut seen_monitor_htlc_ids: HashSet<SentHTLCId> = new_hash_set();
5618+
for e in onchain_events_reaching_threshold_conf.iter() {
5619+
if let OnchainEvent::HTLCUpdate { source, .. } = &e.event {
5620+
seen_monitor_htlc_ids.insert(SentHTLCId::from_source(source));
5621+
}
5622+
}
5623+
for e in self.onchain_events_awaiting_threshold_conf.iter() {
5624+
if let OnchainEvent::HTLCUpdate { source, .. } = &e.event {
5625+
seen_monitor_htlc_ids.insert(SentHTLCId::from_source(source));
5626+
}
5627+
}
5628+
5629+
// Consider current live HTLCs (holder + current counterparty) with sources,
5630+
// and fail back only those NOT present in the stale (previous) commitment.
5631+
let current_counterparty_iter = if let Some(txid) = self.funding.current_counterparty_commitment_txid {
5632+
if let Some(htlc_outputs) = self.funding.counterparty_claimable_outpoints.get(&txid) {
5633+
Some(htlc_outputs.iter().map(|&(ref a, ref b)| (a, b.as_ref().map(|boxed| &**boxed))))
5634+
} else { None }
5635+
} else { None }.into_iter().flatten();
5636+
let current_htlcs = holder_commitment_htlcs!(self, CURRENT_WITH_SOURCES)
5637+
.chain(current_counterparty_iter);
5638+
5639+
// Count expected duplicates per id from the current view to bound emissions.
5640+
let mut expected_current_counts: HashMap<SentHTLCId, u64> = new_hash_map();
5641+
for (_, source_opt) in current_htlcs.clone() {
5642+
if let Some(source) = source_opt { *expected_current_counts.entry(SentHTLCId::from_source(source)).or_default() += 1; }
5643+
}
5644+
5645+
for (htlc, source_opt) in current_htlcs {
5646+
if let Some(source) = source_opt {
5647+
5648+
let sent_id = SentHTLCId::from_source(source);
5649+
if prev_ids.contains(&sent_id) { continue; }
5650+
if seen_monitor_htlc_ids.contains(&sent_id) { continue; }
5651+
5652+
let already_emitted_count = *self.failed_back_htlc_ids.get(&sent_id).unwrap_or(&0);
5653+
let expected_count = *expected_current_counts.get(&sent_id).unwrap_or(&0);
5654+
5655+
if already_emitted_count < expected_count {
5656+
log_info!(logger,
5657+
"Detected stale force-close. Failing back all HTLCs for hash {}.",
5658+
&htlc.payment_hash);
5659+
self.pending_monitor_events.push(MonitorEvent::HTLCEvent(HTLCUpdate {
5660+
source: source.clone(),
5661+
payment_preimage: None,
5662+
payment_hash: htlc.payment_hash,
5663+
htlc_value_satoshis: Some(htlc.amount_msat / 1000),
5664+
}));
5665+
*self.failed_back_htlc_ids.entry(sent_id).or_default() += 1;
5666+
}
5667+
}
5668+
}
55905669
},
55915670
OnchainEvent::AlternativeFundingConfirmation {} => {
55925671
// An alternative funding transaction has irrevocably confirmed and we're no
@@ -5646,7 +5725,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
56465725
if duplicate_event {
56475726
continue;
56485727
}
5649-
if !self.failed_back_htlc_ids.insert(SentHTLCId::from_source(source)) {
5728+
let htlc_id = SentHTLCId::from_source(source);
5729+
if *self.failed_back_htlc_ids.get(&htlc_id).unwrap_or(&0) > 0 {
56505730
continue;
56515731
}
56525732
if !duplicate_event {
@@ -5659,6 +5739,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
56595739
payment_hash: htlc.payment_hash,
56605740
htlc_value_satoshis: Some(htlc.amount_msat / 1000),
56615741
}));
5742+
*self.failed_back_htlc_ids.entry(htlc_id).or_default() += 1;
56625743
}
56635744
}
56645745
}
@@ -6650,7 +6731,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
66506731
initial_counterparty_commitment_info,
66516732
initial_counterparty_commitment_tx,
66526733
balances_empty_height,
6653-
failed_back_htlc_ids: new_hash_set(),
6734+
failed_back_htlc_ids: new_hash_map(),
66546735

66556736
current_holder_htlc_data,
66566737
prev_holder_htlc_data,

lightning/src/ln/functional_tests.rs

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ use crate::ln::channel::{
3333
MIN_CHAN_DUST_LIMIT_SATOSHIS, UNFUNDED_CHANNEL_AGE_LIMIT_TICKS,
3434
};
3535
use crate::ln::channelmanager::{
36-
PaymentId, RAACommitmentOrder, RecipientOnionFields, BREAKDOWN_TIMEOUT, DISABLE_GOSSIP_TICKS,
37-
ENABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA,
36+
PaymentId, RAACommitmentOrder, RecipientOnionFields, Retry, BREAKDOWN_TIMEOUT,
37+
DISABLE_GOSSIP_TICKS, ENABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA,
3838
};
3939
use crate::ln::msgs;
4040
use crate::ln::msgs::{
@@ -9703,3 +9703,147 @@ pub fn test_multi_post_event_actions() {
97039703
do_test_multi_post_event_actions(true);
97049704
do_test_multi_post_event_actions(false);
97059705
}
9706+
9707+
#[xtest(feature = "_externalize_tests")]
9708+
fn test_stale_force_close_with_identical_htlcs() {
9709+
// Test that when two identical HTLCs are relayed and force-closes
9710+
// with a stale state, that we fail both HTLCs back immediately.
9711+
let chanmon_cfgs = create_chanmon_cfgs(4);
9712+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
9713+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
9714+
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
9715+
9716+
let chan_a_b = create_announced_chan_between_nodes(&nodes, 0, 1);
9717+
let _chan_b_c = create_announced_chan_between_nodes(&nodes, 1, 2);
9718+
let _chan_d_b = create_announced_chan_between_nodes(&nodes, 3, 1);
9719+
9720+
let (_payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
9721+
9722+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), 100);
9723+
let scorer = test_utils::TestScorer::new();
9724+
let random_seed_bytes = chanmon_cfgs[1].keys_manager.get_secure_random_bytes();
9725+
let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1_000_000);
9726+
9727+
let route = get_route(
9728+
&nodes[0].node.get_our_node_id(),
9729+
&route_params,
9730+
&nodes[0].network_graph.read_only(),
9731+
None,
9732+
nodes[0].logger,
9733+
&scorer,
9734+
&Default::default(),
9735+
&random_seed_bytes,
9736+
)
9737+
.unwrap();
9738+
9739+
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
9740+
nodes[0]
9741+
.node
9742+
.send_payment(
9743+
payment_hash,
9744+
RecipientOnionFields::secret_only(payment_secret),
9745+
PaymentId([1; 32]),
9746+
route_params.clone(),
9747+
Retry::Attempts(0),
9748+
)
9749+
.unwrap();
9750+
9751+
let ev1 = remove_first_msg_event_to_node(
9752+
&nodes[1].node.get_our_node_id(),
9753+
&mut nodes[0].node.get_and_clear_pending_msg_events(),
9754+
);
9755+
let mut send_ev1 = SendEvent::from_event(ev1);
9756+
9757+
nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_ev1.msgs[0]);
9758+
nodes[1].node.handle_commitment_signed_batch_test(
9759+
nodes[0].node.get_our_node_id(),
9760+
&send_ev1.commitment_msg,
9761+
);
9762+
9763+
let mut b_events = nodes[1].node.get_and_clear_pending_msg_events();
9764+
for ev in b_events.drain(..) {
9765+
match ev {
9766+
MessageSendEvent::SendRevokeAndACK { node_id, msg } => {
9767+
assert_eq!(node_id, nodes[0].node.get_our_node_id());
9768+
nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &msg);
9769+
},
9770+
MessageSendEvent::UpdateHTLCs { node_id, updates, .. } => {
9771+
assert_eq!(node_id, nodes[0].node.get_our_node_id());
9772+
nodes[0].node.handle_commitment_signed_batch_test(
9773+
nodes[1].node.get_our_node_id(),
9774+
&updates.commitment_signed,
9775+
);
9776+
let mut a_events = nodes[0].node.get_and_clear_pending_msg_events();
9777+
for a_ev in a_events.drain(..) {
9778+
if let MessageSendEvent::SendRevokeAndACK { node_id, msg } = a_ev {
9779+
assert_eq!(node_id, nodes[1].node.get_our_node_id());
9780+
nodes[1].node.handle_revoke_and_ack(nodes[0].node.get_our_node_id(), &msg);
9781+
}
9782+
}
9783+
},
9784+
_ => {},
9785+
}
9786+
}
9787+
9788+
nodes[1].node.process_pending_htlc_forwards();
9789+
let _ = nodes[1].node.get_and_clear_pending_msg_events();
9790+
9791+
let stale_commitment_tx = get_local_commitment_txn!(nodes[0], chan_a_b.2)[0].clone();
9792+
9793+
*nodes[0].network_payment_count.borrow_mut() -= 1;
9794+
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
9795+
nodes[0]
9796+
.node
9797+
.send_payment(
9798+
payment_hash,
9799+
RecipientOnionFields::secret_only(payment_secret),
9800+
PaymentId([2; 32]),
9801+
route_params.clone(),
9802+
Retry::Attempts(0),
9803+
)
9804+
.unwrap();
9805+
9806+
let ev2 = remove_first_msg_event_to_node(
9807+
&nodes[1].node.get_our_node_id(),
9808+
&mut nodes[0].node.get_and_clear_pending_msg_events(),
9809+
);
9810+
let mut send_ev2 = SendEvent::from_event(ev2);
9811+
9812+
nodes[1].node.handle_update_add_htlc(nodes[0].node.get_our_node_id(), &send_ev2.msgs[0]);
9813+
nodes[1].node.handle_commitment_signed_batch_test(
9814+
nodes[0].node.get_our_node_id(),
9815+
&send_ev2.commitment_msg,
9816+
);
9817+
9818+
let mut b2_events = nodes[1].node.get_and_clear_pending_msg_events();
9819+
for ev in b2_events.drain(..) {
9820+
match ev {
9821+
MessageSendEvent::SendRevokeAndACK { node_id, msg } => {
9822+
assert_eq!(node_id, nodes[0].node.get_our_node_id());
9823+
nodes[0].node.handle_revoke_and_ack(nodes[1].node.get_our_node_id(), &msg);
9824+
},
9825+
MessageSendEvent::UpdateHTLCs { node_id, updates, .. } => {
9826+
assert_eq!(node_id, nodes[0].node.get_our_node_id());
9827+
nodes[0].node.handle_commitment_signed_batch_test(
9828+
nodes[1].node.get_our_node_id(),
9829+
&updates.commitment_signed,
9830+
);
9831+
},
9832+
_ => {},
9833+
}
9834+
}
9835+
9836+
nodes[1].node.process_pending_htlc_forwards();
9837+
let _ = nodes[1].node.get_and_clear_pending_msg_events();
9838+
9839+
mine_transaction(&nodes[1], &stale_commitment_tx);
9840+
connect_blocks(&nodes[1], ANTI_REORG_DELAY);
9841+
9842+
let events = nodes[1].node.get_and_clear_pending_events();
9843+
let failed_count =
9844+
events.iter().filter(|e| matches!(e, Event::HTLCHandlingFailed { .. })).count();
9845+
assert_eq!(failed_count, 2);
9846+
9847+
check_added_monitors!(&nodes[1], 1);
9848+
nodes[1].node.get_and_clear_pending_msg_events();
9849+
}

0 commit comments

Comments
 (0)