Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Improve Robustness of Inbound MPP Claims Across Restart by TheBlueMatt · Pull Request #1434 · lightningdevkit/rust-lightning · GitHub
Skip to content

Improve Robustness of Inbound MPP Claims Across Restart - #1434

Merged
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims
May 30, 2022
Merged

Improve Robustness of Inbound MPP Claims Across Restart#1434
valentinewallace merged 9 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-04-robust-payment-claims

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This rewrites our claim pipeline to be substantially more robust against particularly poorly timed crashes, including the potentially partial-MPP-claim-causing bug described in Ensure all HTLCs for a claimed payment are claimed on startup!

It also adds some belt-and-suspenders checks when claiming and removes some API bits that I find somewhat confusing and likely hard to use (without replacement, so hopefully users can live without it, otherwise we'll have to revert the last commit and do some really careful documentation).

@valentinewallace

Copy link
Copy Markdown
Contributor

Wanted to throw out the idea of breaking this PR into the first 3 commits and the last 4 (or so)? I think people have mentioned preferring smaller PRs when feasible

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Pulled the first two commits into #1435, the rest are all really just "update claim_funds/fail_htlc and then some tests".

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 721c415 to 49fe7c9CompareApril 20, 2022 19:33
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Remaining issue: (a) claim payment, (b) crash, (c) restart see PaymentReceived + PaymentClaimed events still in processing queue but payment has been claimed (on-chain), (d) sender sends again (e) call claim_funds in response to the dup PaymentRecived claiming the payment duplicatively. Not a big deal but should fix it by dropping the PaymentReceived event from the event queue on startup if we do a re-claim.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 49fe7c9 to 4e24f02CompareApril 21, 2022 20:54
Comment threadlightning/src/chain/channelmonitor.rs Outdated
}
match self.claim_funds_from_hop(channel_state.as_mut().unwrap(), htlc.prev_hop, payment_preimage) {
ClaimFundsFromHop::MonitorUpdateFail(pk, err, _) => {
if let msgs::ErrorAction::IgnoreError = err.err.action {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get a permanent failure here (but claimed_any_htlcs is true), will the permfailed monitor be able to claim the htlc on-chain? Just concerned it won't have learned the preimage in this case

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I mean there's no good answer here (and this PR doesn't change that). TBH I think we need to rethink the whole perm-failure logic a bit. That's kinda the intent behind #1106, but ultimately the "what does perm-failure mean" topic isn't super well-defined currently. Lets leave this for a followup because there's nothing changed here, but, does perm-failure mean "we updated the in-memory copy and nothing else" or "we failed to update anything" needs addressing.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channel.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs
if let Some(channel) = by_id.get_mut(&previous_channel_id) {
channel.claim_htlc_while_disconnected_dropping_mon_update(claimable_htlc.prev_hop.htlc_id, payment_preimage, &args.logger);
}
if let Some(previous_hop_monitor) = args.channel_monitors.get(&claimable_htlc.prev_hop.outpoint) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth checking that this is the same monitor as from L6781, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My line numbers aren't matching up, but I dont think there's any harm in providing extra preimages for monitors in this super-rare case. They'll get pruned anyway, but even if not it wouldn't matter.


nodes[0].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
commitment_signed_dance!(nodes[0], nodes[2], cs_updates.commitment_signed, false, true);
expect_payment_sent!(nodes[0], payment_preimage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda throwing me off that nodes[3] is not claiming the payment for the persisted monitor backwards, only the crashed monitor. To check my understanding, the only difference would be that there would be no PaymentSent generated here, it'd only be generated for the first successful backwards claim, right?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, just on-chain. I didn't bother writing the on-chain eforcement checks cause they're a pain and we have lots of coverage there already.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 4e24f02 to 3c8c013CompareApril 22, 2022 18:06

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through each commit, largely looks good.

Comment threadlightning/src/util/events.rs Outdated
///
/// # Note
/// LDK will not stop an inbound payment from being paid multiple times, so multiple
/// `PaymentReceived` events may be generated for the same payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/PaymentReceived/PaymentClaimed iiuc

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's confusing, I did mean to leave it as-is but added a second sentence.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 3c8c013 to 6c6d939CompareApril 25, 2022 16:41
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
for (_, monitor) in args.channel_monitors.iter() {
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Re-claiming

It took me a second to figure out how we know this is a re-claim, and definitely not a new claim. Is it because the monitor knows the preimage yet the htlc is still in the claimable_htlcs map, so the monitor must be ahead of the ChannelManager?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, yea. Technically its possible its a duplicate payment and the previous payment was claimed but not fully revoked before restart, but its not really worth checking for that case IMO. I updated the log line a bit.

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the API be more ergonomic if we returned a Result<(), Error> from this method, for cases like this?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, basically an error should only occur in a strange restart case of if a user took several blocks to try to claim a payment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like that sends the wrong impression - an Ok(()) does not imply that the payment is fully claimed, which is ultimately why the bool was removed from the return value.

Wouldn't we want to return the Err variant in this case? User attempts to claim a payment that is no longer claimable.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I feel like that's confusing - you call claim_funds and get an Err, a user would reasonably assume that the payment was not claimed/is still pending, but in fact it could have been claimed!

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from b8c99f5 to bf9ec6dCompareApril 28, 2022 02:46
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of #1435.

@valentinewallacevalentinewallace left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK after CI and another reviewer

Comment threadlightning/src/ln/functional_tests.rs Outdated
return;
}
if claimable_amt_msat != expected_amt_msat.unwrap() {
log_info!(self.logger, "Attempted to claim an incomplete payment, expected {} msat, had {} available to claim.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we expect these errors to be super rare? They do seem useful. Returning an Option<Error> is weird though

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 43907a8 to 48e5842CompareMay 4, 2022 18:28
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Ended up having to rewrite this to some extent as we're moving towards removing the FinalOnionHopData from OnionPayload::Invoice which this was relying on. This now starts with two opening commits which extend that migration and adds another field tracking in the claimable HTLCs. See the commit messages on those for more info.

Comment threadlightning/src/util/events.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from 48e5842 to b4cf258CompareMay 5, 2022 19:24
@codecov-commenter

codecov-commenter commented May 5, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1434 (531d6c8) into main (36817e0) will increase coverage by 1.19%.
The diff coverage is 88.71%.

@@ Coverage Diff @@## main #1434 +/- ##
==========================================
+ Coverage 90.90% 92.10% +1.19% 
==========================================
Files 77 80 +3 Lines 42201 53330 +11129 Branches 42201 53330 +11129 ==========================================
+ Hits 38364 49120 +10756 - Misses 3837 4210 +373 
Impacted FilesCoverage Δ
lightning/src/util/events.rs41.22% <26.47%> (+7.32%)⬆️
lightning/src/ln/channelmanager.rs87.92% <75.16%> (+3.25%)⬆️
lightning/src/ln/functional_test_utils.rs97.14% <96.66%> (+1.59%)⬆️
lightning/src/ln/functional_tests.rs98.30% <98.91%> (+1.18%)⬆️
lightning/src/chain/chainmonitor.rs97.96% <100.00%> (+0.04%)⬆️
lightning/src/chain/channelmonitor.rs92.02% <100.00%> (+0.96%)⬆️
lightning/src/ln/chanmon_update_fail_tests.rs97.77% <100.00%> (+0.01%)⬆️
lightning/src/ln/channel.rs91.70% <100.00%> (+3.12%)⬆️
lightning/src/ln/monitor_tests.rs100.00% <100.00%> (ø)
lightning/src/ln/onion_route_tests.rs97.51% <100.00%> (ø)
... and 29 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 36817e0...531d6c8. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from f9515c4 to 7d4faf6CompareMay 25, 2022 23:40
Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch 2 times, most recently from 0fdabf5 to fb7f2b6CompareMay 26, 2022 00:27
While the HTLC-claim process happens across all MPP parts under one
lock, this doesn't imply that they are claimed fully atomically on
disk. Ultimately, an application can crash after persisting one
`ChannelMonitorUpdate` out of multiple monitor updates needed for
the full claim.
Previously, this would leave us in a very bad state - because of
the all-channels-available check in `claim_funds` we'd refuse to
claim the payment again on restart (even though the
`PaymentReceived` event will be passed to the user again), and we'd
end up having partially claimed the payment!
The fix for the consistency part of this issue is pretty
straightforward - just check for this condition on startup and
complete the claim across all channels/`ChannelMonitor`s if we
detect it.
This still leaves us in a confused state from the perspective of
the user, however - we've actually claimed a payment but when they
call `claim_funds` we return `false` indicating it could not be
claimed.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from fb7f2b6 to 319df3aCompareMay 26, 2022 00:53
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without changes:

$ git diff-tree -U1 fb7f2b61 319df3ac
$

valentinewallace
valentinewallace previously approved these changes May 27, 2022
for (payment_hash, payment_preimage) in monitor.get_stored_preimages() {
if let Some(claimable_htlcs) = claimable_htlcs.remove(&payment_hash) {
log_info!(args.logger, "Re-claimaing HTLCs with payment hash {} due to partial-claim.", log_bytes!(payment_hash.0));
log_info!(args.logger, "Re-claiming HTLCs with payment hash {} as we've released the preimage to a ChannelMonitor!", log_bytes!(payment_hash.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is in the wrong commit but wtv

Comment threadlightning/src/ln/channelmanager.rs Outdated
This replaces the return value of `claim_funds` with an event. It
does not yet change behavior in any material way.
If we crashed during a payment claim and then detected a partial
claim on restart, we should ensure the user is aware that the
payment has been claimed. We do so here by using the new
partial-claim detection logic to create a `PaymentClaimed` event.
As additional sanity checks, before claiming a payment, we check
that we have the full amount available in `claimable_htlcs` that
the payment should be for. Concretely, this prevents one
somewhat-absurd edge case where a user may receive an MPP payment,
wait many *blocks* before claiming it, allowing us to fail the
pending HTLCs and the sender to retry some subset of the payment
before we go to claim. More generally, this is just good
belt-and-suspenders against any edge cases we may have missed.
`ChannelManager::fail_htlc_backwards`' bool return value is quite
confusing - just because it returns false doesn't mean the payment
wasn't (already) failed. Worse, in some race cases around shutdown
where a payment was claimed before an unclean shutdown and then
retried on startup, `fail_htlc_backwards` could return true even
though (a duplicate copy of the same payment) was claimed, but the
claim event has not been seen by the user yet.
While its possible to use it correctly, its somewhat confusing to
have a return value at all, and definitely lends itself to misuse.
Instead, we should push users towards a model where they don't care
if `fail_htlc_backwards` succeeds - either they've locally marked
the payment as failed (prior to seeing any `PaymentReceived`
events) and will fail any attempts to pay it, or they have not and
the payment is still receivable until its timeout time is reached.
We can revisit this decision based on user feedback, but will need
to very carefully document the potential failure modes here if we
do.
@TheBlueMatt
TheBlueMattforce-pushed the 2022-04-robust-payment-claims branch from cc25213 to 8a56702CompareMay 28, 2022 00:02
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed, only change since yesterday was taking @jkczyz's suggested wording from #1434 (comment)

Comment threadlightning/src/util/events.rs Outdated
valentinewallace
valentinewallace previously approved these changes May 28, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops lol, duh, force-pushed the last commit to actually change the references to amt not just the definitions.

@valentinewallace
valentinewallace merged commit a534a5e into lightningdevkit:mainMay 30, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@TheBlueMatt@valentinewallace@codecov-commenter@jkczyz