@@ -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,
0 commit comments