Skip to content

Commit c7c87ac

Browse files
authored
Merge pull request #3984 from TheBlueMatt/2025-07-mon-event-failures
Correctly handle lost `MonitorEvent`s
2 parents c71334e + 77026c9 commit c7c87ac

9 files changed

+451
-201
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 51 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,20 @@ pub(crate) enum ChannelMonitorUpdateStep {
693693
RenegotiatedFundingLocked {
694694
funding_txid: Txid,
695695
},
696+
/// When a payment is finally resolved by the user handling an [`Event::PaymentSent`] or
697+
/// [`Event::PaymentFailed`] event, the `ChannelManager` no longer needs to hear about it on
698+
/// startup (which would cause it to re-hydrate the payment information even though the user
699+
/// already learned about the payment's result).
700+
///
701+
/// This will remove the HTLC from [`ChannelMonitor::get_all_current_outbound_htlcs`] and
702+
/// [`ChannelMonitor::get_onchain_failed_outbound_htlcs`].
703+
///
704+
/// Note that this is only generated for closed channels as this is implicit in the
705+
/// [`Self::CommitmentSecret`] update which clears the payment information from all un-revoked
706+
/// counterparty commitment transactions.
707+
ReleasePaymentComplete {
708+
htlc: SentHTLCId,
709+
},
696710
}
697711

698712
impl ChannelMonitorUpdateStep {
@@ -709,6 +723,7 @@ impl ChannelMonitorUpdateStep {
709723
ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript",
710724
ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding",
711725
ChannelMonitorUpdateStep::RenegotiatedFundingLocked { .. } => "RenegotiatedFundingLocked",
726+
ChannelMonitorUpdateStep::ReleasePaymentComplete { .. } => "ReleasePaymentComplete",
712727
}
713728
}
714729
}
@@ -747,6 +762,9 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
747762
(1, commitment_txs, required_vec),
748763
(3, htlc_data, required),
749764
},
765+
(7, ReleasePaymentComplete) => {
766+
(1, htlc, required),
767+
},
750768
(8, LatestHolderCommitment) => {
751769
(1, commitment_txs, required_vec),
752770
(3, htlc_data, required),
@@ -1288,6 +1306,14 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12881306
/// spending CSV for revocable outputs).
12891307
htlcs_resolved_on_chain: Vec<IrrevocablyResolvedHTLC>,
12901308

1309+
/// When a payment is resolved through an on-chain transaction, we tell the `ChannelManager`
1310+
/// about this via [`ChannelMonitor::get_onchain_failed_outbound_htlcs`] and
1311+
/// [`ChannelMonitor::get_all_current_outbound_htlcs`] at startup. We'll keep repeating the
1312+
/// same payments until they're eventually fully resolved by the user processing a
1313+
/// `PaymentSent` or `PaymentFailed` event, at which point the `ChannelManager` will inform of
1314+
/// this and we'll store the set of fully resolved payments here.
1315+
htlcs_resolved_to_user: HashSet<SentHTLCId>,
1316+
12911317
/// The set of `SpendableOutput` events which we have already passed upstream to be claimed.
12921318
/// These are tracked explicitly to ensure that we don't generate the same events redundantly
12931319
/// if users duplicatively confirm old transactions. Specifically for transactions claiming a
@@ -1697,6 +1723,7 @@ pub(crate) fn write_chanmon_internal<Signer: EcdsaChannelSigner, W: Writer>(
16971723
(29, channel_monitor.initial_counterparty_commitment_tx, option),
16981724
(31, channel_monitor.funding.channel_parameters, required),
16991725
(32, channel_monitor.pending_funding, optional_vec),
1726+
(33, channel_monitor.htlcs_resolved_to_user, required),
17001727
(34, channel_monitor.alternative_funding_confirmed, option),
17011728
});
17021729

@@ -1915,6 +1942,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19151942
funding_spend_confirmed: None,
19161943
confirmed_commitment_tx_counterparty_output: None,
19171944
htlcs_resolved_on_chain: Vec::new(),
1945+
htlcs_resolved_to_user: new_hash_set(),
19181946
spendable_txids_confirmed: Vec::new(),
19191947

19201948
best_block,
@@ -3074,10 +3102,6 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
30743102
/// Gets the set of outbound HTLCs which can be (or have been) resolved by this
30753103
/// `ChannelMonitor`. This is used to determine if an HTLC was removed from the channel prior
30763104
/// to the `ChannelManager` having been persisted.
3077-
///
3078-
/// This is similar to [`Self::get_pending_or_resolved_outbound_htlcs`] except it includes
3079-
/// HTLCs which were resolved on-chain (i.e. where the final HTLC resolution was done by an
3080-
/// event from this `ChannelMonitor`).
30813105
pub(crate) fn get_all_current_outbound_htlcs(
30823106
&self,
30833107
) -> HashMap<HTLCSource, (HTLCOutputInCommitment, Option<PaymentPreimage>)> {
@@ -3090,8 +3114,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
30903114
for &(ref htlc, ref source_option) in latest_outpoints.iter() {
30913115
if let &Some(ref source) = source_option {
30923116
let htlc_id = SentHTLCId::from_source(source);
3093-
let preimage_opt = us.counterparty_fulfilled_htlcs.get(&htlc_id).cloned();
3094-
res.insert((**source).clone(), (htlc.clone(), preimage_opt));
3117+
if !us.htlcs_resolved_to_user.contains(&htlc_id) {
3118+
let preimage_opt =
3119+
us.counterparty_fulfilled_htlcs.get(&htlc_id).cloned();
3120+
res.insert((**source).clone(), (htlc.clone(), preimage_opt));
3121+
}
30953122
}
30963123
}
30973124
}
@@ -3147,6 +3174,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
31473174
} else {
31483175
continue;
31493176
};
3177+
let htlc_id = SentHTLCId::from_source(source);
3178+
if us.htlcs_resolved_to_user.contains(&htlc_id) {
3179+
continue;
3180+
}
3181+
31503182
let confirmed = $htlc_iter.find(|(_, conf_src)| Some(source) == *conf_src);
31513183
if let Some((confirmed_htlc, _)) = confirmed {
31523184
let filter = |v: &&IrrevocablyResolvedHTLC| {
@@ -3219,96 +3251,6 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
32193251
res
32203252
}
32213253

3222-
/// Gets the set of outbound HTLCs which are pending resolution in this channel or which were
3223-
/// resolved with a preimage from our counterparty.
3224-
///
3225-
/// This is used to reconstruct pending outbound payments on restart in the ChannelManager.
3226-
///
3227-
/// Currently, the preimage is unused, however if it is present in the relevant internal state
3228-
/// an HTLC is always included even if it has been resolved.
3229-
#[rustfmt::skip]
3230-
pub(crate) fn get_pending_or_resolved_outbound_htlcs(&self) -> HashMap<HTLCSource, (HTLCOutputInCommitment, Option<PaymentPreimage>)> {
3231-
let us = self.inner.lock().unwrap();
3232-
// We're only concerned with the confirmation count of HTLC transactions, and don't
3233-
// actually care how many confirmations a commitment transaction may or may not have. Thus,
3234-
// we look for either a FundingSpendConfirmation event or a funding_spend_confirmed.
3235-
let confirmed_txid = us.funding_spend_confirmed.or_else(|| {
3236-
us.onchain_events_awaiting_threshold_conf.iter().find_map(|event| {
3237-
if let OnchainEvent::FundingSpendConfirmation { .. } = event.event {
3238-
Some(event.txid)
3239-
} else { None }
3240-
})
3241-
});
3242-
3243-
if confirmed_txid.is_none() {
3244-
// If we have not seen a commitment transaction on-chain (ie the channel is not yet
3245-
// closed), just get the full set.
3246-
mem::drop(us);
3247-
return self.get_all_current_outbound_htlcs();
3248-
}
3249-
3250-
let mut res = new_hash_map();
3251-
macro_rules! walk_htlcs {
3252-
($holder_commitment: expr, $htlc_iter: expr) => {
3253-
for (htlc, source) in $htlc_iter {
3254-
if us.htlcs_resolved_on_chain.iter().any(|v| v.commitment_tx_output_idx == htlc.transaction_output_index) {
3255-
// We should assert that funding_spend_confirmed is_some() here, but we
3256-
// have some unit tests which violate HTLC transaction CSVs entirely and
3257-
// would fail.
3258-
// TODO: Once tests all connect transactions at consensus-valid times, we
3259-
// should assert here like we do in `get_claimable_balances`.
3260-
} else if htlc.offered == $holder_commitment {
3261-
// If the payment was outbound, check if there's an HTLCUpdate
3262-
// indicating we have spent this HTLC with a timeout, claiming it back
3263-
// and awaiting confirmations on it.
3264-
let htlc_update_confd = us.onchain_events_awaiting_threshold_conf.iter().any(|event| {
3265-
if let OnchainEvent::HTLCUpdate { commitment_tx_output_idx: Some(commitment_tx_output_idx), .. } = event.event {
3266-
// If the HTLC was timed out, we wait for ANTI_REORG_DELAY blocks
3267-
// before considering it "no longer pending" - this matches when we
3268-
// provide the ChannelManager an HTLC failure event.
3269-
Some(commitment_tx_output_idx) == htlc.transaction_output_index &&
3270-
us.best_block.height >= event.height + ANTI_REORG_DELAY - 1
3271-
} else if let OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, .. } = event.event {
3272-
// If the HTLC was fulfilled with a preimage, we consider the HTLC
3273-
// immediately non-pending, matching when we provide ChannelManager
3274-
// the preimage.
3275-
Some(commitment_tx_output_idx) == htlc.transaction_output_index
3276-
} else { false }
3277-
});
3278-
if let Some(source) = source {
3279-
let counterparty_resolved_preimage_opt =
3280-
us.counterparty_fulfilled_htlcs.get(&SentHTLCId::from_source(source)).cloned();
3281-
if !htlc_update_confd || counterparty_resolved_preimage_opt.is_some() {
3282-
res.insert(source.clone(), (htlc.clone(), counterparty_resolved_preimage_opt));
3283-
}
3284-
} else {
3285-
panic!("Outbound HTLCs should have a source");
3286-
}
3287-
}
3288-
}
3289-
}
3290-
}
3291-
3292-
let commitment_txid = confirmed_txid.unwrap();
3293-
let funding_spent = get_confirmed_funding_scope!(us);
3294-
3295-
if Some(commitment_txid) == funding_spent.current_counterparty_commitment_txid || Some(commitment_txid) == funding_spent.prev_counterparty_commitment_txid {
3296-
walk_htlcs!(false, funding_spent.counterparty_claimable_outpoints.get(&commitment_txid).unwrap().iter().filter_map(|(a, b)| {
3297-
if let &Some(ref source) = b {
3298-
Some((a, Some(&**source)))
3299-
} else { None }
3300-
}));
3301-
} else if commitment_txid == funding_spent.current_holder_commitment_tx.trust().txid() {
3302-
walk_htlcs!(true, holder_commitment_htlcs!(us, CURRENT_WITH_SOURCES));
3303-
} else if let Some(prev_commitment_tx) = &funding_spent.prev_holder_commitment_tx {
3304-
if commitment_txid == prev_commitment_tx.trust().txid() {
3305-
walk_htlcs!(true, holder_commitment_htlcs!(us, PREV_WITH_SOURCES).unwrap());
3306-
}
3307-
}
3308-
3309-
res
3310-
}
3311-
33123254
pub(crate) fn get_stored_preimages(
33133255
&self,
33143256
) -> HashMap<PaymentHash, (PaymentPreimage, Vec<PaymentClaimDetails>)> {
@@ -4195,6 +4137,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
41954137
if updates.update_id == LEGACY_CLOSED_CHANNEL_UPDATE_ID || self.lockdown_from_offchain {
41964138
assert_eq!(updates.updates.len(), 1);
41974139
match updates.updates[0] {
4140+
ChannelMonitorUpdateStep::ReleasePaymentComplete { .. } => {},
41984141
ChannelMonitorUpdateStep::ChannelForceClosed { .. } => {},
41994142
// We should have already seen a `ChannelForceClosed` update if we're trying to
42004143
// provide a preimage at this point.
@@ -4322,6 +4265,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
43224265
panic!("Attempted to replace shutdown script {} with {}", shutdown_script, scriptpubkey);
43234266
}
43244267
},
4268+
ChannelMonitorUpdateStep::ReleasePaymentComplete { htlc } => {
4269+
log_trace!(logger, "HTLC {htlc:?} permanently and fully resolved");
4270+
self.htlcs_resolved_to_user.insert(*htlc);
4271+
},
43254272
}
43264273
}
43274274

@@ -4347,11 +4294,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
43474294
|ChannelMonitorUpdateStep::RenegotiatedFundingLocked { .. } =>
43484295
is_pre_close_update = true,
43494296
// After a channel is closed, we don't communicate with our peer about it, so the
4350-
// only things we will update is getting a new preimage (from a different channel)
4351-
// or being told that the channel is closed. All other updates are generated while
4352-
// talking to our peer.
4297+
// only things we will update is getting a new preimage (from a different channel),
4298+
// being told that the channel is closed, or being told a payment which was
4299+
// resolved on-chain has had its resolution communicated to the user. All other
4300+
// updates are generated while talking to our peer.
43534301
ChannelMonitorUpdateStep::PaymentPreimage { .. } => {},
43544302
ChannelMonitorUpdateStep::ChannelForceClosed { .. } => {},
4303+
ChannelMonitorUpdateStep::ReleasePaymentComplete { .. } => {},
43554304
}
43564305
}
43574306

@@ -6447,6 +6396,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
64476396

64486397
let mut funding_spend_confirmed = None;
64496398
let mut htlcs_resolved_on_chain = Some(Vec::new());
6399+
let mut htlcs_resolved_to_user = Some(new_hash_set());
64506400
let mut funding_spend_seen = Some(false);
64516401
let mut counterparty_node_id = None;
64526402
let mut confirmed_commitment_tx_counterparty_output = None;
@@ -6480,6 +6430,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
64806430
(29, initial_counterparty_commitment_tx, option),
64816431
(31, channel_parameters, (option: ReadableArgs, None)),
64826432
(32, pending_funding, optional_vec),
6433+
(33, htlcs_resolved_to_user, option),
64836434
(34, alternative_funding_confirmed, option),
64846435
});
64856436
// Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so
@@ -6642,6 +6593,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
66426593
funding_spend_confirmed,
66436594
confirmed_commitment_tx_counterparty_output,
66446595
htlcs_resolved_on_chain: htlcs_resolved_on_chain.unwrap(),
6596+
htlcs_resolved_to_user: htlcs_resolved_to_user.unwrap(),
66456597
spendable_txids_confirmed: spendable_txids_confirmed.unwrap(),
66466598

66476599
best_block,

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3870,6 +3870,7 @@ fn do_test_durable_preimages_on_closed_channel(
38703870
};
38713871
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id_ab, &node_b_id, err_msg).unwrap();
38723872
check_closed_event(&nodes[0], 1, reason, false, &[node_b_id], 100000);
3873+
check_added_monitors(&nodes[0], 1);
38733874
let as_closing_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
38743875
assert_eq!(as_closing_tx.len(), 1);
38753876

0 commit comments

Comments
 (0)