Skip to content

Commit 0838859

Browse files
Persistent mon events for off-chain outbound claims
Currently, the resolution of HTLCs (and decisions on when HTLCs can be forwarded) is the responsibility of Channel objects (a part of ChannelManager) until the channel is closed, and then the ChannelMonitor thereafter. This leads to some complexity around race conditions for HTLCs right around channel closure. Additionally, there is lots of complexity reconstructing the state of all HTLCs in the ChannelManager deserialization/loading logic. Instead, we want to do all resolution in ChannelMonitors (in response to ChannelMonitorUpdates) and pass them back to ChannelManager in the form of MonitorEvents (similar to how HTLCs are resolved after channels are closed). In order to have reliable resolution, we'll need to keep MonitorEvents around in the ChannelMonitor until the ChannelManager has finished processing them. This will simplify things - on restart instead of examining the set of HTLCs in monitors we can simply replay all the pending MonitorEvents. In recent work, we added support for keeping monitor events around until they are explicitly acked by the ChannelManager, but would always ack monitor events immediately, which preserved the previous behavior and didn't break any tests. Up until this point, we only generated HTLC monitor events when a payment was claimed/failed on-chain. In this commit, we start generating persistent monitor events whenever a payment is claimed *off*-chain, specifically when new latest holder commitment data is provided to the monitor. For the purpose of making incremental progress on this feature, these events will be a no-op and/or continue to be acked immediately except in the narrow case of an off-chain outbound payment claim. HTLC forward claim monitor events will be a no-op, and on-chain outbound payment claim events continue to be acked immediately. Off-chain outbound payment claims, however, now have monitor events generated for them that will not be acked by the ChannelManager until the PaymentSent event is processed by the user. This also allows us to stop blocking the RAA monitor update that removes the preimage, because the purpose of that behavior was to ensure the user got a PaymentSent event and the monitor event now serves that purpose instead.
1 parent 143a56f commit 0838859

13 files changed

Lines changed: 233 additions & 71 deletions

lightning/src/chain/channelmonitor.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
22322232
self.inner.lock().unwrap().persistent_events_enabled = enabled;
22332233
}
22342234

2235+
pub(crate) fn has_pending_event_for_htlc(&self, source: &HTLCSource) -> bool {
2236+
self.inner.lock().unwrap().has_pending_event_for_htlc(source)
2237+
}
2238+
22352239
/// Copies [`MonitorEvent`] state from `other` into `self`.
22362240
/// Used in tests to align transient runtime state before equality comparison after a
22372241
/// serialization round-trip.
@@ -3825,20 +3829,31 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
38253829
self.prev_holder_htlc_data = Some(htlc_data);
38263830

38273831
for (claimed_htlc_id, claimed_preimage) in claimed_htlcs {
3828-
#[cfg(debug_assertions)]
3829-
{
3830-
let cur_counterparty_htlcs = self
3831-
.funding
3832-
.counterparty_claimable_outpoints
3833-
.get(&self.funding.current_counterparty_commitment_txid.unwrap())
3834-
.unwrap();
3835-
assert!(cur_counterparty_htlcs.iter().any(|(_, source_opt)| {
3832+
let htlc_opt = self
3833+
.funding
3834+
.counterparty_claimable_outpoints
3835+
.get(&self.funding.current_counterparty_commitment_txid.unwrap())
3836+
.unwrap()
3837+
.iter()
3838+
.find_map(|(htlc, source_opt)| {
38363839
if let Some(source) = source_opt {
3837-
SentHTLCId::from_source(source) == *claimed_htlc_id
3838-
} else {
3839-
false
3840+
if SentHTLCId::from_source(source) == *claimed_htlc_id {
3841+
return Some((htlc, source));
3842+
}
38403843
}
3841-
}));
3844+
None
3845+
});
3846+
debug_assert!(htlc_opt.is_some());
3847+
if self.persistent_events_enabled {
3848+
if let Some((htlc, source)) = htlc_opt {
3849+
self.push_monitor_event(MonitorEvent::HTLCEvent(HTLCUpdate {
3850+
payment_hash: htlc.payment_hash,
3851+
payment_preimage: Some(*claimed_preimage),
3852+
source: *source.clone(),
3853+
htlc_value_satoshis: Some(htlc.amount_msat),
3854+
user_channel_id: self.user_channel_id,
3855+
}));
3856+
}
38423857
}
38433858
self.counterparty_fulfilled_htlcs.insert(*claimed_htlc_id, *claimed_preimage);
38443859
}
@@ -4656,6 +4671,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
46564671
self.pending_monitor_events.retain(|(id, _)| *id != event_id);
46574672
}
46584673

4674+
fn has_pending_event_for_htlc(&self, htlc: &HTLCSource) -> bool {
4675+
let htlc_id = SentHTLCId::from_source(htlc);
4676+
self.pending_monitor_events.iter().any(|(_, ev)| {
4677+
if let MonitorEvent::HTLCEvent(upd) = ev {
4678+
return htlc_id == SentHTLCId::from_source(&upd.source);
4679+
}
4680+
false
4681+
})
4682+
}
4683+
46594684
fn get_and_clear_pending_monitor_events(&mut self) -> Vec<(u64, MonitorEvent)> {
46604685
if self.persistent_events_enabled {
46614686
let mut ret = Vec::new();

lightning/src/ln/blinded_payment_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) {
854854
do_claim_payment_along_route(
855855
ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1], &nodes[2]]], payment_preimage)
856856
);
857-
expect_payment_sent(&nodes[0], payment_preimage, Some(Some(1000)), true, true);
857+
expect_payment_sent!(nodes[0], payment_preimage, Some(1000));
858858
}
859859

860860
#[test]
@@ -1399,7 +1399,7 @@ fn conditionally_round_fwd_amt() {
13991399
let mut args = ClaimAlongRouteArgs::new(&nodes[0], &expected_route[..], payment_preimage)
14001400
.allow_1_msat_fee_overpay();
14011401
let expected_fee = pass_claimed_payment_along_route(args);
1402-
expect_payment_sent(&nodes[0], payment_preimage, Some(Some(expected_fee)), true, true);
1402+
expect_payment_sent!(nodes[0], payment_preimage, Some(expected_fee));
14031403
}
14041404

14051405
#[test]

lightning/src/ln/chanmon_update_fail_tests.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,7 +2108,7 @@ fn monitor_update_claim_fail_no_response() {
21082108
let mut bs_updates = get_htlc_update_msgs(&nodes[1], &node_a_id);
21092109
nodes[0].node.handle_update_fulfill_htlc(node_b_id, bs_updates.update_fulfill_htlcs.remove(0));
21102110
do_commitment_signed_dance(&nodes[0], &nodes[1], &bs_updates.commitment_signed, false, false);
2111-
expect_payment_sent!(nodes[0], payment_preimage_1);
2111+
expect_payment_sent!(&nodes[0], payment_preimage_1);
21122112

21132113
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage_2);
21142114
}
@@ -3450,7 +3450,10 @@ fn do_test_blocked_chan_preimage_release(completion_mode: BlockedUpdateComplMode
34503450
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
34513451
let persister;
34523452
let new_chain_mon;
3453-
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
3453+
let mut cfg = test_default_channel_config();
3454+
// If persistent_monitor_events is enabed, monitor updates will never be blocked.
3455+
cfg.override_persistent_monitor_events = Some(false);
3456+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(cfg.clone()), Some(cfg)]);
34543457
let nodes_1_reload;
34553458
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
34563459

@@ -3763,7 +3766,7 @@ fn do_test_inverted_mon_completion_order(
37633766
);
37643767

37653768
// Finally, check that the payment was, ultimately, seen as sent by node A.
3766-
expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
3769+
expect_payment_sent!(&nodes[0], payment_preimage);
37673770
}
37683771

37693772
#[test]
@@ -4256,7 +4259,7 @@ fn do_test_glacial_peer_cant_hang(hold_chan_a: bool) {
42564259
nodes[0].node.handle_update_fulfill_htlc(node_b_id, update_fulfill);
42574260
let commitment = &a_update[0].commitment_signed;
42584261
do_commitment_signed_dance(&nodes[0], &nodes[1], commitment, false, false);
4259-
expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
4262+
expect_payment_sent!(nodes[0], payment_preimage);
42604263
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
42614264

42624265
pass_along_path(
@@ -4936,6 +4939,7 @@ fn native_async_persist() {
49364939
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
49374940
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
49384941
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
4942+
let persistent_monitor_events = nodes[0].node.test_persistent_monitor_events_enabled();
49394943

49404944
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
49414945

@@ -5081,7 +5085,16 @@ fn native_async_persist() {
50815085
assert_eq!(update_status, ChannelMonitorUpdateStatus::InProgress);
50825086

50835087
persist_futures.poll_futures();
5084-
assert_eq!(async_chain_monitor.release_pending_monitor_events().len(), 0);
5088+
let events = async_chain_monitor.release_pending_monitor_events();
5089+
if persistent_monitor_events {
5090+
// With persistent monitor events, the LatestHolderCommitmentTXInfo update containing
5091+
// claimed_htlcs generates an HTLCEvent with the preimage.
5092+
assert_eq!(events.len(), 1);
5093+
assert_eq!(events[0].2.len(), 1);
5094+
assert!(matches!(events[0].2[0].1, MonitorEvent::HTLCEvent(..)));
5095+
} else {
5096+
assert!(events.is_empty());
5097+
}
50855098

50865099
let pending_writes = kv_store.list_pending_async_writes(
50875100
CHANNEL_MONITOR_UPDATE_PERSISTENCE_PRIMARY_NAMESPACE,
@@ -5223,7 +5236,7 @@ fn test_mpp_claim_to_holding_cell() {
52235236
let claims = vec![(b_claim_msgs, node_b_id), (c_claim_msgs, node_c_id)];
52245237
pass_claimed_payment_along_route_from_ev(250_000, claims, args);
52255238

5226-
expect_payment_sent(&nodes[0], preimage_1, None, true, true);
5239+
expect_payment_sent!(nodes[0], preimage_1);
52275240

52285241
expect_and_process_pending_htlcs(&nodes[3], false);
52295242
expect_payment_claimable!(nodes[3], paymnt_hash_2, payment_secret_2, 400_000);

lightning/src/ln/channelmanager.rs

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10019,11 +10019,17 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1001910019
};
1002010020
Some(EventCompletionAction::ReleasePaymentCompleteChannelMonitorUpdate(release))
1002110021
} else {
10022-
Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate {
10023-
channel_funding_outpoint: Some(next_channel_outpoint),
10024-
channel_id: next_channel_id,
10025-
counterparty_node_id: path.hops[0].pubkey,
10026-
})
10022+
if self.persistent_monitor_events {
10023+
monitor_event_id.map(|event_id| EventCompletionAction::AckMonitorEvent {
10024+
event_id: MonitorEventSource { channel_id: next_channel_id, event_id },
10025+
})
10026+
} else {
10027+
Some(EventCompletionAction::ReleaseRAAChannelMonitorUpdate {
10028+
channel_funding_outpoint: Some(next_channel_outpoint),
10029+
channel_id: next_channel_id,
10030+
counterparty_node_id: path.hops[0].pubkey,
10031+
})
10032+
}
1002710033
};
1002810034
let logger = WithContext::for_payment(
1002910035
&self.logger,
@@ -10045,6 +10051,25 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1004510051
&self.pending_events,
1004610052
&logger,
1004710053
);
10054+
10055+
if matches!(
10056+
ev_completion_action,
10057+
Some(EventCompletionAction::AckMonitorEvent { .. })
10058+
) {
10059+
// If the `PaymentSent` for this redundant claim is still pending, add the event
10060+
// completion action here to ensure the `PaymentSent` will always be regenerated until it
10061+
// is processed by the user -- as long as the monitor event corresponding to this
10062+
// completion action is not acked, it will continue to be re-provided on startup.
10063+
let mut pending_events = self.pending_events.lock().unwrap();
10064+
for (ev, act_opt) in pending_events.iter_mut() {
10065+
let found_payment_sent = matches!(ev, Event::PaymentSent { payment_id: Some(id), .. } if *id == payment_id);
10066+
if found_payment_sent && act_opt.is_none() {
10067+
*act_opt = ev_completion_action.take();
10068+
break;
10069+
}
10070+
}
10071+
}
10072+
1004810073
// If an event was generated, `claim_htlc` set `ev_completion_action` to None, if
1004910074
// not, we should go ahead and run it now (as the claim was duplicative), at least
1005010075
// if a PaymentClaimed event with the same action isn't already pending.
@@ -12879,6 +12904,13 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1287912904
})
1288012905
}
1288112906

12907+
/// Returns `true` if `ChannelManager::persistent_monitor_events` is enabled. This flag will only
12908+
/// be set randomly in tests for now.
12909+
#[cfg(any(test, feature = "_test_utils"))]
12910+
pub fn test_persistent_monitor_events_enabled(&self) -> bool {
12911+
self.persistent_monitor_events
12912+
}
12913+
1288212914
#[cfg(any(test, feature = "_test_utils"))]
1288312915
pub(crate) fn test_raa_monitor_updates_held(
1288412916
&self, counterparty_node_id: PublicKey, channel_id: ChannelId,
@@ -13585,22 +13617,29 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1358513617
.channel_by_id
1358613618
.contains_key(&channel_id)
1358713619
});
13588-
// Claim the funds from the previous hop, if there is one. Because this is in response to a
13589-
// chain event, no attribution data is available.
13590-
self.claim_funds_internal(
13591-
htlc_update.source,
13592-
preimage,
13593-
htlc_update.htlc_value_satoshis.map(|v| v * 1000),
13594-
None,
13595-
from_onchain,
13596-
counterparty_node_id,
13597-
funding_outpoint,
13598-
channel_id,
13599-
htlc_update.user_channel_id,
13600-
None,
13601-
None,
13602-
Some(event_id),
13603-
);
13620+
let we_are_sender =
13621+
matches!(htlc_update.source, HTLCSource::OutboundRoute { .. });
13622+
if from_onchain | we_are_sender {
13623+
// Claim the funds from the previous hop, if there is one. Because this is in response to a
13624+
// chain event, no attribution data is available.
13625+
self.claim_funds_internal(
13626+
htlc_update.source,
13627+
preimage,
13628+
htlc_update.htlc_value_satoshis.map(|v| v * 1000),
13629+
None,
13630+
from_onchain,
13631+
counterparty_node_id,
13632+
funding_outpoint,
13633+
channel_id,
13634+
htlc_update.user_channel_id,
13635+
None,
13636+
None,
13637+
Some(event_id),
13638+
);
13639+
}
13640+
if from_onchain | !we_are_sender {
13641+
self.chain_monitor.ack_monitor_event(monitor_event_source);
13642+
}
1360413643
} else {
1360513644
log_trace!(logger, "Failing HTLC from our monitor");
1360613645
let failure_reason = LocalHTLCFailureReason::OnChainTimeout;
@@ -13620,8 +13659,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1362013659
failure_type,
1362113660
completion_update,
1362213661
);
13662+
self.chain_monitor.ack_monitor_event(monitor_event_source);
1362313663
}
13624-
self.chain_monitor.ack_monitor_event(monitor_event_source);
1362513664
},
1362613665
MonitorEvent::HolderForceClosed(_)
1362713666
| MonitorEvent::HolderForceClosedWithInfo { .. } => {
@@ -19052,6 +19091,14 @@ impl<
1905219091
break;
1905319092
}
1905419093
}
19094+
if persistent_monitor_events {
19095+
// This will not be necessary once we have persistent events for HTLC failures, we
19096+
// can delete this whole loop and wait to re-process the pending monitor events
19097+
// rather than failing them proactively below.
19098+
if monitor.has_pending_event_for_htlc(&channel_htlc_source) {
19099+
found_htlc = true;
19100+
}
19101+
}
1905519102
if !found_htlc {
1905619103
// If we have some HTLCs in the channel which are not present in the newer
1905719104
// ChannelMonitor, they have been removed and should be failed back to

lightning/src/ln/functional_test_utils.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3074,7 +3074,7 @@ macro_rules! expect_payment_sent {
30743074
$expected_payment_preimage,
30753075
$expected_fee_msat_opt.map(|o| Some(o)),
30763076
$expect_paths,
3077-
true,
3077+
if $node.node.test_persistent_monitor_events_enabled() { false } else { true },
30783078
)
30793079
};
30803080
}
@@ -4235,7 +4235,15 @@ pub fn claim_payment_along_route(
42354235
do_claim_payment_along_route(args) + expected_extra_total_fees_msat;
42364236

42374237
if !skip_last {
4238-
expect_payment_sent!(origin_node, payment_preimage, Some(expected_total_fee_msat))
4238+
let expect_post_ev_mon_update =
4239+
if origin_node.node.test_persistent_monitor_events_enabled() { false } else { true };
4240+
expect_payment_sent(
4241+
origin_node,
4242+
payment_preimage,
4243+
Some(Some(expected_total_fee_msat)),
4244+
true,
4245+
expect_post_ev_mon_update,
4246+
)
42394247
} else {
42404248
(None, Vec::new())
42414249
}

lightning/src/ln/functional_tests.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,10 @@ pub fn do_test_multiple_package_conflicts(p2a_anchor: bool) {
13581358
};
13591359
assert_eq!(updates.update_fulfill_htlcs.len(), 1);
13601360
nodes[0].node.handle_update_fulfill_htlc(node_b_id, updates.update_fulfill_htlcs.remove(0));
1361+
if nodes[0].node.test_persistent_monitor_events_enabled() {
1362+
// If persistent_monitor_events is enabled, the RAA monitor update is not blocked.
1363+
check_added_monitors(&nodes[0], 1);
1364+
}
13611365
do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
13621366
expect_payment_sent!(nodes[0], preimage_2);
13631367

@@ -2672,7 +2676,10 @@ pub fn test_simple_peer_disconnect() {
26722676
_ => panic!("Unexpected event"),
26732677
}
26742678
}
2675-
check_added_monitors(&nodes[0], 1);
2679+
check_added_monitors(
2680+
&nodes[0],
2681+
if nodes[0].node.test_persistent_monitor_events_enabled() { 0 } else { 1 },
2682+
);
26762683

26772684
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage_4);
26782685
fail_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_hash_6);
@@ -4295,7 +4302,7 @@ pub fn test_duplicate_payment_hash_one_failure_one_success() {
42954302

42964303
nodes[0].node.handle_update_fulfill_htlc(node_b_id, updates.update_fulfill_htlcs.remove(0));
42974304
do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false);
4298-
expect_payment_sent(&nodes[0], our_payment_preimage, None, true, true);
4305+
expect_payment_sent!(&nodes[0], our_payment_preimage);
42994306
}
43004307

43014308
#[xtest(feature = "_externalize_tests")]
@@ -8574,7 +8581,9 @@ pub fn test_inconsistent_mpp_params() {
85748581
pass_along_path(&nodes[0], path_b, real_amt, hash, Some(payment_secret), event, true, None);
85758582

85768583
do_claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], &[path_a, path_b], preimage));
8577-
expect_payment_sent(&nodes[0], preimage, Some(None), true, true);
8584+
let expect_post_ev_mon_update =
8585+
if nodes[0].node.test_persistent_monitor_events_enabled() { false } else { true };
8586+
expect_payment_sent(&nodes[0], preimage, Some(None), true, expect_post_ev_mon_update);
85788587
}
85798588

85808589
#[xtest(feature = "_externalize_tests")]
@@ -9932,7 +9941,10 @@ fn do_test_multi_post_event_actions(do_reload: bool) {
99329941
let chanmon_cfgs = create_chanmon_cfgs(3);
99339942
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
99349943
let (persister, chain_monitor);
9935-
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
9944+
let mut cfg = test_default_channel_config();
9945+
// If persistent_monitor_events is enabled, RAAs will not be blocked on events.
9946+
cfg.override_persistent_monitor_events = Some(false);
9947+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(cfg), None, None]);
99369948
let node_a_reload;
99379949
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
99389950

lightning/src/ln/invoice_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1344,7 +1344,7 @@ mod test {
13441344
&[&[&nodes[fwd_idx]]],
13451345
payment_preimage,
13461346
));
1347-
expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
1347+
expect_payment_sent!(&nodes[0], payment_preimage);
13481348
}
13491349

13501350
#[test]

0 commit comments

Comments
 (0)