Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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" + '
Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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('^' + ".*" + ' Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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('^' + ".*" + ' Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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" + ' Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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('^' + ".*" + ' Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand
, '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); } })(); })(); Rework auto-retry send errors by valentinewallace · Pull Request #2014 · lightningdevkit/rust-lightning · GitHub
Skip to content

Rework auto-retry send errors - #2014

Merged
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail
Feb 23, 2023
Merged

Rework auto-retry send errors #2014
TheBlueMatt merged 8 commits into
lightningdevkit:mainfrom
valentinewallace:2023-02-rework-partial-pmt-fail

Conversation

@valentinewallace

@valentinewallacevalentinewallace commented Feb 6, 2023

Copy link
Copy Markdown
Contributor

Partially addresses #1932. Fixes#1969.

Previously we would return a PaymentSendFailure from auto-retry send_payment methods. This doesn't make sense because they will never return the PartialFailure variant, so we add a new error enum for them here.

We now also generate PaymentPathFailed events for paths that fail on the first hop before they can commit to the htlc (eg if we tried to send more than we have in the channel).

Based on #2008

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking, since we already don't support this in InvoicePayer. I guess I might not be clear on what those events will be used for -- ISTM they're basically just telling the user "your router is buggy"?

@codecov-commenter

codecov-commenter commented Feb 14, 2023

Copy link
Copy Markdown

Codecov Report

Base: 87.20% // Head: 87.89% // Increases project coverage by +0.68% 🎉

Coverage data is based on head (a6e9123) compared to base (558b2f2).
Patch coverage: 78.23% of modified lines in pull request are covered.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2014 +/- ##
==========================================
+ Coverage 87.20% 87.89% +0.68% 
==========================================
Files 101 101 Lines 44175 49323 +5148 Branches 44175 49323 +5148 ==========================================
+ Hits 38525 43351 +4826 - Misses 5650 5972 +322 
Impacted FilesCoverage Δ
lightning-invoice/src/payment.rs75.75% <0.00%> (ø)
lightning/src/ln/outbound_payment.rs79.13% <73.39%> (-0.83%)⬇️
lightning/src/ln/payment_tests.rs95.65% <93.75%> (-0.01%)⬇️
lightning/src/ln/channelmanager.rs89.56% <100.00%> (+3.75%)⬆️
lightning/src/util/time.rs38.29% <0.00%> (-4.56%)⬇️
lightning/src/ln/features.rs92.55% <0.00%> (-0.33%)⬇️
lightning/src/util/errors.rs32.14% <0.00%> (ø)
lightning/src/routing/gossip.rs84.04% <0.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.33% <0.00%> (+0.04%)⬆️
lightning/src/routing/router.rs92.84% <0.00%> (+0.04%)⬆️
... and 13 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 8f1a5db to c331f58CompareFebruary 14, 2023 00:17
@valentinewallacevalentinewallace changed the title WIP: Rework auto-retry send errors Rework auto-retry send errors Feb 14, 2023
Comment threadlightning/src/util/events.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 3 times, most recently from 9946121 to 6513398CompareFebruary 16, 2023 20:53
@valentinewallace
valentinewallace marked this pull request as ready for review February 16, 2023 20:56
Comment threadlightning/src/util/events.rs Outdated
(7, short_channel_id, option),
(9, retry, option),
(11, payment_id, option),
(13, failure_opt, ignorable),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Doesn't the ignorable field imply that if its missing we'll return None for the full event if its missing? Also a bug for the network_update field, right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The expanded macro code looks good to me. I think we'll only return None for the full event if the MaybeReadable::read returns Ok(None), which only happens if the TLV type for the field is present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, hmm, I was thinking of the ignorable case in _init_tlv_based_struct_field. This is confusing - we handle ignorable in the higher-level macros as "implement MaybeReadable and return Ok(None)" but in the lower-level macros as just a regular ol' option. Can we make it consistent - use the new ignorable_option as a regular ol' option but make the ignorable be a required field in the lower-level macros as well, returning Ok(None) in _check_missing_tlv (which implies we're in a MaybeReadable, and maybe renaming the ignorable to ignorable_no_read or whatever?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

maybe renaming the ignorable to ignorable_no_read or whatever?

That all sounds good, though I'd also be in favor of renaming ignorable_* to upgradable_*. Because I'd like to name the above variant ignorable_required, but that sounds like an oxymoron.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/util/events.rs Outdated
pub enum Failure {
/// We failed to initially send the payment and no HTLC was committed to. Contains a relevant
/// error message.
InitialSend(String),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think in ~every case we have exactly one APIError to put here, why not that instead of a String?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It doesn't implement writeable at the moment, though that wouldn't be too hard to add.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's just do that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

FYI, I think we'll need to change the APIError::InvalidRoute to contain a String. I don't think we can implement Readable for a &'static str because it references data owned by the read function.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

@TheBlueMatt I'm wondering if generating PaymentPathFailed events on partial/all-paths failure should be release blocking

Given where other things are it seems we have time? Its not exactly critical, but if I'm tracking failures/successes I'd like to see a failure for every Route path my Router returns, I think.

Yeah agreed, that comment was out of date sorry

Useful for generating Payment(Path)Failed events in this method
Useful for generating Payment(Path)Failed events in this method
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Tacked on a small commit to fix #1969. This PR ended up taking on some unrelated changes, so I'd be happy to split it up if reviewers prefer.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea, let's definitely try to split this up, maybe everything after Fix outdated PendingOutboundPayment::Abandoned docs can go into separate (probably two?) PRs?

let mut payment_hash = PaymentHash([0; 32]);
let mut payment_failed_permanently = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It looks like the backwards compat approach when this was added was that we we didnt support MPP at all so we could assume this was always true. For forwards compat, though, this doesn't make sense - if someone downgrades to 113 from 114 and looks at this field it will always report that all HTLCs have failed, which is obviously nonsense. Its been long enough since we added the abandon flow that we can break forwards compat here, I think, but its a bit safer to write Some(false) rather than letting old clients assume true by writing None. We also need to add a draft release note here that says that the field has been removed and old readers will always see false, and users wishing to support downgrade who still rely on the field should instead first migrate to the abandon-based approach and look at PaymentFailed events and then migrate to 114.

@valentinewallacevalentinewallaceFeb 21, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Will address this in #2043

Edit: addressed

@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Split up the PR, see #2043

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment threadlightning/src/ln/outbound_payment.rs Outdated
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from 2b14430 to ed1ef50CompareFebruary 21, 2023 23:00
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

Why None here?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We could push the failed scids here, but they won't be used because we just use the PartialFailure::failed_paths_retry for resend, which already contains them.

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.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

More generally, I guess it's hard to follow where previously_failed_channels is populated. In this function, it is a side effect of calling push_payment_path_failed_evs, but only for AllFailedResendSafe. Then when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe. Is that expected or am I misreading that? Maybe the second insert is necessary for the backwards compatibility case? Just thinking if there s there any way we could have it added in a single place.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

when looking at fail_htlc for a non-immediate failure, it looks to be added twice. Once when calling insert_previously_failed_scid and another time when determining the failure was not a probe.

So the second add will go away when we get rid of PaymentPathFailed::retry, which as you pointed out should be removed.

But shouldn't the event that we surface also reflect this? Why would we exclude the scid here but include it for the AllFailedResendSafe case?

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

On a related note, on second glance my statement that we push failed scids into failed_paths_retry on PartialFailure was not correct. I think I thought that because the issue says it: #1969 but it isn't in the code. Fixing this.

@valentinewallacevalentinewallaceFeb 22, 2023

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, there was also a rebase error, push_payment_path_failed_evs should be renamed to push_path_failed_evs_and_scids. Will also fix this

Edit: fixed this and above

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

Comment on lines +331 to 343
/// If a payment fails to send with [`ChannelManager::send_payment`], it can be in one of several
/// states. This enum is returned as the Err() type describing which state the payment is in, see
/// the description of individual enum states for more.
///
/// [`ChannelManager::send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
#[derive(Clone, Debug)]
pub enum PaymentSendFailure {

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.

Do we plan to deprecate ChannelManager::send_payment?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I don't know if we've fully decided since it doesn't hurt, but it would definitely clean up this module a bit and the manual route case is already supported by the Router trait anyway.

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from ed1ef50 to 7698078CompareFebruary 22, 2023 17:37
Comment threadlightning/src/ln/outbound_payment.rs
Comment threadlightning/src/ln/outbound_payment.rs Outdated
},
PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. } => {
Self::push_payment_path_failed_evs(payment_id, payment_hash, route.paths, results.into_iter(), pending_events);
Self::push_payment_path_failed_evs(payment_id, payment_hash, None, route.paths, results.into_iter(), pending_events);

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.

We never include failed scids in the event surfaced in handle_pay_route_err, PaymentPathFailed::retry is always None, if I understand you correctly. The scids are being pushed into the route params for immediate retry purposes only.

Ah, right, I missed that. Was thinking it was added from the parameter.

@TheBlueMattTheBlueMatt added this to the 0.0.114 milestone Feb 22, 2023
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch 2 times, most recently from fea92d5 to d086918CompareFebruary 22, 2023 20:50
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Feel free to squash, IMO.

@jkczyz

Copy link
Copy Markdown
Contributor

Feel free to squash, IMO.

Same! LGTM

InvalidRoute is reserved for malformed routes, not routes where a channel or
its peer is unavailable
@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from d086918 to dc05c6dCompareFebruary 23, 2023 04:07
@valentinewallace

Copy link
Copy Markdown
ContributorAuthor

Squashed!

@jkczyzjkczyz 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.

Largely looks good minus a couple things that I noticed.

Comment threadlightning/src/ln/outbound_payment.rs Outdated
Comment on lines +578 to +580
&self, payment_id: PaymentId, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>,
keysend_preimage: Option<PaymentPreimage>, retry_strategy: Retry, route_params: RouteParameters,

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.

Parameters are a little inconsistent here. Should we pass payment_secret by value?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

It's preexisting and I think it would be better to fix it all in one go, since ChannelManager::send_payment and friends also need this fix

@valentinewallace
valentinewallaceforce-pushed the 2023-02-rework-partial-pmt-fail branch from dc05c6d to 6b4ce57CompareFebruary 23, 2023 16:36
jkczyz
jkczyz previously approved these changes Feb 23, 2023
freddiekrugerrand
freddiekrugerrand previously approved these changes Feb 23, 2023

@freddiekrugerrandfreddiekrugerrand 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.

Reviewed to the best of my ability with limited understanding of ldk/rust.
Nice refinement of payment states for retries!

return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
}))
return Err(RetryableSendFailure::PaymentExpired)

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 this check means that the invoice is expired, does it make more sense to rename PaymentExpired -> InvoiceExpired? Assuming that there's also a sender-set time limit on how long we attempt a payment for, I'd think that time running out is a payment expiry.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

PaymentExpired is definitely confusing since we also have Retry::Timeout which is a different thing. Since the PaymentParameters::expiry_time technically doesn't need to be from an invoice (it could even be set for spontaneous payments), I ended up clarifying the docs on this error variant and on Retry::Timeout

Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs()
).map_err(|_| RetryableSendFailure::RouteNotFound)?;

let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret,

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 add_new_pending_payment errors are always mapped to DuplicatePayment, can it be updated to return a RetryableSendFailure instead?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, it isn't always mapped to that, but yeah that would be nicer.

Prior to this, we returned PaymentSendFailure from auto retry send payment
methods. This implied that we might return a PartialFailure from them, which
has never been the case. So it makes sense to rework the errors to be a better
fit for the methods.
We're taking error handling in a totally different direction now to make it
more asynchronous, see send_payment_internal for more information.
Since it's only used one place now
Previously, we could have tried the same failed channels over and over until
retries are exhausted.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix in a followup.

debug_assert_eq!(paths.len(), path_results.len());
for (path, path_res) in paths.into_iter().zip(path_results) {
if let Err(e) = path_res {
let failed_scid = if let APIError::InvalidRoute { .. } = e {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we need to not push a failure event if the error is APIError::MonitorUpdateInProgress.

@TheBlueMatt
TheBlueMatt merged commit 46fd703 into lightningdevkit:mainFeb 23, 2023
PXplod pushed a commit to bitlightlabs/rust-lightning that referenced this pull request Sep 30, 2024
0.0.114 - Mar 3, 2023 - "Faster Async BOLT12 Retries"
API Updates
===========
* `InvoicePayer` has been removed and its features moved directly into
`ChannelManager`. As such it now requires a simplified `Router` and supports
`send_payment_with_retry` (and friends). `ChannelManager::retry_payment` was
removed in favor of the automated retries. Invoice payment utilities in
`lightning-invoice` now call the new code (lightningdevkit#1812, lightningdevkit#1916, lightningdevkit#1929, lightningdevkit#2007, etc).
* `Sign`/`BaseSign` has been renamed `ChannelSigner`, with `EcdsaChannelSigner`
split out in anticipation of future schnorr/taproot support (lightningdevkit#1967).
* The catch-all `KeysInterface` was split into `EntropySource`, `NodeSigner`,
and `SignerProvider`. `KeysManager` implements all three (lightningdevkit#1910, lightningdevkit#1930).
* `KeysInterface::get_node_secret` is now `KeysManager::get_node_secret_key`
and is no longer required for external signers (lightningdevkit#1951, lightningdevkit#2070).
* A `lightning-transaction-sync` crate has been added which implements keeping
LDK in sync with the chain via an esplora server (lightningdevkit#1870). Note that it can
only be used on nodes that *never* ran a previous version of LDK.
* `Score` is updated in `BackgroundProcessor` instead of via `Router` (lightningdevkit#1996).
* `ChainAccess::get_utxo` (now `UtxoAccess`) can now be resolved async (lightningdevkit#1980).
* BOLT12 `Offer`, `InvoiceRequest`, `Invoice` and `Refund` structs as well as
associated builders have been added. Such invoices cannot yet be paid due to
missing support for blinded path payments (lightningdevkit#1927, lightningdevkit#1908, lightningdevkit#1926).
* A `lightning-custom-message` crate has been added to make combining multiple
custom messages into one enum/handler easier (lightningdevkit#1832).
* `Event::PaymentPathFailure` is now generated for failure to send an HTLC
over the first hop on our local channel (lightningdevkit#2014, lightningdevkit#2043).
* `lightning-net-tokio` no longer requires an `Arc` on `PeerManager` (lightningdevkit#1968).
* `ChannelManager::list_recent_payments` was added (lightningdevkit#1873).
* `lightning-background-processor` `std` is now optional in async mode (lightningdevkit#1962).
* `create_phantom_invoice` can now be used in `no-std` (lightningdevkit#1985).
* The required final CLTV delta on inbound payments is now configurable (lightningdevkit#1878)
* bitcoind RPC error code and message are now surfaced in `block-sync` (lightningdevkit#2057).
* Get `historical_estimated_channel_liquidity_probabilities` was added (lightningdevkit#1961).
* `ChannelManager::fail_htlc_backwards_with_reason` was added (lightningdevkit#1948).
* Macros which implement serialization using TLVs or straight writing of struct
fields are now public (lightningdevkit#1823, lightningdevkit#1976, lightningdevkit#1977).
Backwards Compatibility
=======================
* Any inbound payments with a custom final CLTV delta will be rejected by LDK
if you downgrade prior to receipt (lightningdevkit#1878).
* `Event::PaymentPathFailed::network_update` will always be `None` if an
0.0.114-generated event is read by a prior version of LDK (lightningdevkit#2043).
* `Event::PaymentPathFailed::all_paths_removed` will always be false if an
0.0.114-generated event is read by a prior version of LDK. Users who rely on
it to determine payment retries should migrate to `Event::PaymentFailed`, in
a separate release prior to upgrading to LDK 0.0.114 if downgrading is
supported (lightningdevkit#2043).
Performance Improvements
========================
* Channel data is now stored per-peer and channel updates across multiple
peers can be operated on simultaneously (lightningdevkit#1507).
* Routefinding is roughly 1.5x faster (lightningdevkit#1799).
* Deserializing a `NetworkGraph` is roughly 6x faster (lightningdevkit#2016).
* Memory usage for a `NetworkGraph` has been reduced substantially (lightningdevkit#2040).
* `KeysInterface::get_secure_random_bytes` is roughly 200x faster (lightningdevkit#1974).
Bug Fixes
=========
* Fixed a bug where a delay in processing a `PaymentSent` event longer than the
time taken to persist a `ChannelMonitor` update, when occurring immediately
prior to a crash, may result in the `PaymentSent` event being lost (lightningdevkit#2048).
* Fixed spurious rejections of rapid gossip sync data when the graph has been
updated by other means between gossip syncs (lightningdevkit#2046).
* Fixed a panic in `KeysManager` when the high bit of `starting_time_nanos`
is set (lightningdevkit#1935).
* Resolved an issue where the `ChannelManager::get_persistable_update_future`
future would fail to wake until a second notification occurs (lightningdevkit#2064).
* Resolved a memory leak when using `ChannelManager::send_probe` (lightningdevkit#2037).
* Fixed a deadlock on some platforms at least when using async `ChannelMonitor`
updating (lightningdevkit#2006).
* Removed debug-only assertions which were reachable in threaded code (lightningdevkit#1964).
* In some cases when payment sending fails on our local channel retries no
longer take the same path and thus never succeed (lightningdevkit#2014).
* Retries for spontaneous payments have been fixed (lightningdevkit#2002).
* Return an `Err` if `lightning-persister` fails to read the directory listing
rather than panicing (lightningdevkit#1943).
* `peer_disconnected` will now never be called without `peer_connected` (lightningdevkit#2035)
Security
========
0.0.114 fixes several denial-of-service vulnerabilities which are reachable from
untrusted input from channel counterparties or in deployments accepting inbound
connections or channels. It also fixes a denial-of-service vulnerability in rare
cases in the route finding logic.
* The number of pending un-funded channels as well as peers without funded
channels is now limited to avoid denial of service (lightningdevkit#1988).
* A second `channel_ready` message received immediately after the first could
lead to a spurious panic (lightningdevkit#2071). This issue was introduced with 0conf
support in LDK 0.0.107.
* A division-by-zero issue was fixed in the `ProbabilisticScorer` if the amount
being sent (including previous-hop fees) is equal to a channel's capacity
while walking the graph (lightningdevkit#2072). The division-by-zero was introduced with
historical data tracking in LDK 0.0.112.
In total, this release features 130 files changed, 21457 insertions, 10113
deletions in 343 commits from 18 authors, in alphabetical order:
* Alec Chen
* Allan Douglas R. de Oliveira
* Andrei
* Arik Sosman
* Daniel Granhão
* Duncan Dean
* Elias Rohrer
* Jeffrey Czyz
* John Cantrell
* Kurtsley
* Matt Corallo
* Max Fang
* Omer Yacine
* Valentine Wallace
* Viktor Tigerström
* Wilmer Paulino
* benthecarman
* jurvis
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.

Avoid previously failed hops on PaymentFailure::AllFailedResendSafe

5 participants

@valentinewallace@codecov-commenter@TheBlueMatt@jkczyz@freddiekrugerrand