-
Notifications
You must be signed in to change notification settings - Fork 421
Block RAA ChannelMonitorUpdate
s on PaymentClaimed
events
#3988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Block RAA ChannelMonitorUpdate
s on PaymentClaimed
events
#3988
Conversation
TheBlueMatt
commented
Aug 4, 2025
👋 I see @tankyleo was un-assigned. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3988 +/- ##
==========================================
- Coverage 89.01% 88.93% -0.09%
==========================================
Files 174 174
Lines 124395 124576 +181
Branches 124395 124576 +181
==========================================
+ Hits 110730 110786 +56
- Misses 11187 11292 +105
- Partials 2478 2498 +20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
d0595d8
to
7261fdf
Compare
CI is unhappy. This should be fine but gotta get the stupid multithreaded test to pass. |
7261fdf
to
4d12016
Compare
|
||
let event_node: &'static TestChannelManager<'static, 'static> = | ||
unsafe { std::mem::transmute(nodes[8].node as &TestChannelManager) }; | ||
let thrd_event = std::thread::spawn(move || { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize I also punted on this recently, but maybe we should take the opportunity to revisit using std::thread::scoped
now that we can? If we decide against it, maybe we should drop the TODO comment above and elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, yea, let me do that in a followup.
Labeled for backport, but note that the first two commits should not be backported, so the second two might need some tweaks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, it's nice we had the infra in place already to make this relatively straightforward.
Needs rebase |
let durable_preimage_channel = payment.htlcs.last().map_or(None, |htlc| { | ||
if let Some(node_id) = htlc.prev_hop.counterparty_node_id { | ||
Some((htlc.prev_hop.outpoint, node_id, htlc.prev_hop.channel_id)) | ||
} else { | ||
None | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: don't think it matters but this makes more sense in my brain for not having to think about whether a counterparty node id may not be set for all htlcs:
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 4fe74b3e7..cb4dc94c1 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -1093,12 +1093,10 @@ impl ClaimablePayments {
.or_insert_with(|| {
let htlcs = payment.htlcs.iter().map(events::ClaimedHTLC::from).collect();
let sender_intended_value = payment.htlcs.first().map(|htlc| htlc.total_msat);
- let durable_preimage_channel = payment.htlcs.last().map_or(None, |htlc| {
- if let Some(node_id) = htlc.prev_hop.counterparty_node_id {
- Some((htlc.prev_hop.outpoint, node_id, htlc.prev_hop.channel_id))
- } else {
- None
- }
+ let durable_preimage_channel = payment.htlcs.iter().find_map(|htlc| {
+ if let Some(id) = htlc.prev_hop.counterparty_node_id {
+ Some((htlc.prev_hop.outpoint, id, htlc.prev_hop.channel_id))
+ } else { None }
});
debug_assert!(durable_preimage_channel.is_some());
ClaimingPayment {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're definitely always set. I was gonna even make it required but I think there's cases where we may fail to deserialize an ancient ChannelMonitor
that's just hanging around uselessly if we do :/. Using the last entry here was a almost-stupid micro-optimization, blocking the last channel which is getting an HTLC claimed marginally reduces the chance that we actually end up really blocking, as its gonna be the last one to get an update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the last entry here was a almost-stupid micro-optimization, blocking the last channel which is getting an HTLC claimed marginally reduces the chance that we actually end up really blocking, as its gonna be the last one to get an update.
Let's add this as a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its so marginal I didn't want to bother lol. Also anyone doing high-latency persist should be doing async updates, where it really won't matter, at least after #3986.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is pretty unclear why we're doing it that way when reading the code at the moment, though.
4d12016
to
3887730
Compare
Rebased. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, feel free to squash
In 0.1 we started requiring `counterparty_node_id` to be filled in in various previous-hop datastructures when claiming HTLCs. While we can't switch `HTLCSource`'s `HTLCPreviousHopData::counterparty_node_id` to required (as it might cause us to fail to read old `ChannelMonitor`s which still hold `HTLCSource`s we no longer need to claim), we can at least start requiring the field in `PendingAddHTLCInfo` and `HTLCClaimSource`. This simplifies `claim_mpp_part` marginally.
When we claim a payment, `Event::PaymentClaimed` contains a list of the HTLCs we claimed from as `ClaimedHTLC` objects. While they include a `channel_id` the pyment came to us over, in theory `channel_id`s aren't guaranteed to be unique (though in practice they are in all opened channels aside from 0conf ones with a malicious counterparty). Further, our APIs often require passing both the `counterparty_node_id` and the `channel_id` to do things to chanels. Thus, here we add the missing `counterparty_node-id` to `ClaimedHTLC`.
Historically we indexed channels by `(counterparty_node_id, funding outpoint)` in several pipelines, especially the `ChannelMonitorUpdate` pipeline. This ended up complexifying quite a few things as we always needed to store the full `(counterparty_node_id, funding outpoint, channel_id)` tuple to ensure we can always access a channel no matter its state. Over time we want to move to only the `(counterparty_node_id, channel_id)` tuple as *the* channel index, especially as we move towards V2 channels that have a globally-unique `channel_id` anyway. Here we take one small step towards this, avoiding using the channel funding outpoint in the `EventCompletionAction` pipeline.
Squashed without further changes. |
3887730
to
c1e86fc
Compare
impl_writeable_tlv_based_enum!(EventCompletionAction, | ||
(0, ReleaseRAAChannelMonitorUpdate) => { | ||
(0, channel_funding_outpoint, required), | ||
(0, channel_funding_outpoint, option), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other place I found that could be affected by funding outpoints changing due to splicing is BackgroundEvent::MonitorUpdateRegeneratedOnStartup
. We may also want to revisit the HTLC forwarding pipeline to make sure we're not relying on them there either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, it looks like we should drop that. It should be okay, though, I think - funding_txo
there only goes in to handle_new_monitor_update
which then only uses it to pass back to MonitorUpdateRegeneratedOnStartup
if we are running during startup...should be an easy cleanup as a followup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grr, it is needed in the one spot where we need to add an entry to in_flight_monitor_updates
and we need the OutPoint
to support downgrades to 0.1.
let durable_preimage_channel = payment.htlcs.last().map_or(None, |htlc| { | ||
if let Some(node_id) = htlc.prev_hop.counterparty_node_id { | ||
Some((htlc.prev_hop.outpoint, node_id, htlc.prev_hop.channel_id)) | ||
} else { | ||
None | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the last entry here was a almost-stupid micro-optimization, blocking the last channel which is getting an HTLC claimed marginally reduces the chance that we actually end up really blocking, as its gonna be the last one to get an update.
Let's add this as a comment?
We added the ability to block `ChannelMonitorUpdate`s on receipt of an RAA in order to avoid dropping a payment preimage from a channel that created a `PaymentSent` event in 9ede794. We did not at the time use the same infrastructure for `PaymentClaimed` events, but really should have. While a `PaymentClaimed` event may seem a bit less critical than a `PaymentSent` event (it doesn't contain a payment preimage that the user needs to make sure they store for proof of payment), its still important for users to ensure their payment tracking logic is always correct. Here we take the (relatively straightforward) action of setting a `EventCompletionAction` to block RAA monitor updates on channels which created a `PaymentClaimed` event. Note that we only block one random channel from an MPP paymnet, not all of them, as any single channel should provide enough information for us to recreate the `PaymentClaimed` event on restart.
c1e86fc
to
a80c855
Compare
Added a comment about channel selection: $ git diff-tree -U1 c1e86fcc13 a80c855b21
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b4eb1f39aa..1fd99f8945 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -1095,2 +1095,4 @@ impl ClaimablePayments {
let sender_intended_value = payment.htlcs.first().map(|htlc| htlc.total_msat);
+ // Pick an "arbitrary" channel to block RAAs on until the `PaymentSent`
+ // event is processed, specifically the last channel to get claimed.
let durable_preimage_channel = payment.htlcs.last().map_or(None, |htlc| { |
Backported in #4143 |