Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Receiver fallbacks #1688

Merged
merged 15 commits into from
Dec 10, 2024
Merged

Receiver fallbacks #1688

merged 15 commits into from
Dec 10, 2024

Conversation

ekzyis
Copy link
Member

@ekzyis ekzyis commented Dec 5, 2024

Description

Receiver fallbacks are implemented by making getInvoiceableWallets aware of previously failed wrapped invoices. If the id of such a failed invoice is passed to it (predecessorId), it will skip any wallets for which a forward was already attempted.

For this, a column predecessorId was added to the Invoice table which points to the previously failed invoice.

With this link back to the "predecessor invoice", we can check which wallet was already tried and skip that wallet in this recursive query.

Since we want to retry with the same sender wallet on receiver errors, the client is made aware of receiver errors via the existing invoice polling. If status is FAILED_FORWARD or the invoice is held + cancelled, the invoice polling will throw WalletReceiverError now. We need to check both since it's dependent on timing what the client will see first (FAILED_FORWARD transitions almost immediately to FAILED).

If there is no more receiver wallet to try, we fallback to an invoice that pays SN and credits the receiver with CCs.

close #1494 based on #1679 based on #1700

TODOs

  • don't log failed forwards as sender errors
  • make sure we avoid infinite retry loops if there ever is a bug?

Tests

  • sender with 1 good wallet and no CCs pays receiver with 2 bad wallets: falls back to CCs
  • sender with 1 bad and 1 good wallet and no CCs pays receiver with 2 bad wallets: falls back to CCs
  • paying with CCs (even though both have wallets attached)
  • sender with 1 bad and 1 good wallet and no CCs pays receiver with 2 good wallets: p2p zap success to first good receiver wallet

Additional Context

  • I am testing failed forward using this patch:
diff --git a/worker/paidAction.js b/worker/paidAction.js
index 70396ddb..9d711b0f 100644
--- a/worker/paidAction.js
+++ b/worker/paidAction.js
@@ -268,7 +268,7 @@ export async function paidActionForwarding ({ data: { invoiceId, ...args }, mode
     payViaPaymentRequest({
       lnd,
       request: bolt11,
-      max_fee_mtokens: String(maxFeeMsats),
+      max_fee_mtokens: String(0),
       pathfinding_timeout: LND_PATHFINDING_TIMEOUT_MS,
       max_timeout_height: maxTimeoutHeight
     }).catch(console.error)
  • The properties wallet, message, reason and invoice of the custom errors can probably be moved into the parent class but that should be refactored in a separate PR imo.

  • Receiver fallbacks #1688 (comment)

Checklist

Are your changes backwards compatible? Please answer below:

yes, this only adds a nullable database column

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:

7. See tests above and I looked very closely at the code for extended periods of time

For frontend changes: Tested on mobile, light and dark mode? Please answer below:

n/a

Did you introduce any new environment variables? If so, call them out explicitly here:

no

@ekzyis ekzyis added feature new product features that weren't there before wallets labels Dec 5, 2024
@ekzyis ekzyis marked this pull request as draft December 5, 2024 17:25
@ekzyis ekzyis force-pushed the receiver-fallbacks branch 5 times, most recently from 8c5be5c to 4fd1859 Compare December 6, 2024 14:11
wallets/server.js Outdated Show resolved Hide resolved
@ekzyis ekzyis force-pushed the receiver-fallbacks branch 3 times, most recently from 674a588 to a34aada Compare December 6, 2024 15:11
@ekzyis ekzyis force-pushed the receiver-fallbacks branch from a34aada to 9a7f9ba Compare December 6, 2024 15:19
@riccardobl
Copy link
Member

riccardobl commented Dec 6, 2024

Won't this cause unwanted CC payments?
I think it should prioritize p2p if the receiver has attached wallets.
If the receiver doesnt' have an attached wallet of if they all failed, it should try to pay with CC -> CC.
And only if CC->CC is not possible it should use LN->CC.
So it doesn't end up locking more and more sats into CCs or prioritizing them against p2p, otherwise i think the system will end up becoming almost CC exclusive.

That's what i did in my original PR.

@ekzyis
Copy link
Member Author

ekzyis commented Dec 8, 2024

I think it should prioritize p2p if the receiver has attached wallets.

I think zaps should be paid in the interest of the sender by default. Therefore, without a setting where the sender can express their preference that depends on if the receiver can receive real sats, I think we should try CCs first.

I think it would also be confusing if we try CCs first for posts and replies but not for zaps.1 Preferring sats over CCs should be opt-in imo so this would need a setting which I think we can add later if there's enough demand from users. We shouldn't assume too much about user preferences and I think receiver fallbacks without this setting are valuable enough by themselves already.

So currently, I think it's not worth it to add the complexity of two different payment flows (one for SN fees, one for zaps2) where one even now also includes a sending dependency on receiving next to the already existing dependency of receiving on sending (a receiver cannot avoid to get paid in CCs if the sender only has CCs). The dependency of the receiver on the sender is already tricky UX-wise (need to explain to them that their wallet isn't broken but what they receive depends on the sender etc) so I'd rather not make sending also depend on receiving unless there is enough user demand for such a thing.

Thanks for flagging though, this wasn't on my mind! I'll look into which changes would be required in theory to accommodate this if/when we need it while I review my implementation.

I am also sorry if I missed some existing discussion between @huumn and you about this. I am pretty sure I did. Could be that I am also disagreeing with @huumn regarding this here 👀

otherwise i think the system will end up becoming almost CC exclusive.

30% of each zap goes into rewards (which become sats) so the half life of a CC is almost exactly two zaps: 1 * 0.7 * 0.7 = 0.49 < 0.50.

Therefore, I think how users zap each other doesn't matter much for the amount of CCs in the system, it matters more how many use attached wallets. If everybody has an attached wallet (send+recv), CCs will cease to exist quickly enough.

Footnotes

  1. I assume for posting fees it's clear that everybody will want to try CCs first and we won't need a setting?

  2. was this handled in your PR?

@huumn
Copy link
Member

huumn commented Dec 8, 2024

#1678 tries p2p first if the sender and receiver has an attached wallet. If the receiver wallet fails, then I think the sender should just do CCs (either those they already have, or jit purchasing them).

Either way, it shouldn't require a massive overhaul to change the priority of payment methods. I'd just get it working however you see fit. Sender and receiver fallbacks should be pretty well encapsulated from payment method fallback, and we can massage it however we need.

@ekzyis ekzyis force-pushed the receiver-fallbacks branch 4 times, most recently from cbf24cd to b5fcf21 Compare December 9, 2024 13:20
components/use-invoice.js Outdated Show resolved Hide resolved
@ekzyis ekzyis marked this pull request as ready for review December 9, 2024 13:52
@ekzyis ekzyis requested a review from huumn December 9, 2024 17:37
@ekzyis ekzyis marked this pull request as draft December 10, 2024 11:08
@ekzyis
Copy link
Member Author

ekzyis commented Dec 10, 2024

Converted to draft because this requires a rebase on #1700

@ekzyis ekzyis force-pushed the receiver-fallbacks branch from b5fcf21 to 6b27112 Compare December 10, 2024 15:30
Copy link

gitguardian bot commented Dec 10, 2024

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
14821997 Triggered Elliptic Curve Private Key 5910f6b docker/lnd/router/tls.key View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@ekzyis ekzyis force-pushed the receiver-fallbacks branch from 6b27112 to 89216dd Compare December 10, 2024 15:31
@ekzyis
Copy link
Member Author

ekzyis commented Dec 10, 2024

Okay, superficially tested again based on #1700 and worked as expected, this is ready for review again.

@ekzyis ekzyis marked this pull request as ready for review December 10, 2024 15:33
"InvoiceForward"."walletId"
FROM "Retries"
JOIN "InvoiceForward" ON "InvoiceForward"."invoiceId" = "Retries"."id"
WHERE "InvoiceForward"."withdrawlId" IS NOT NULL
Copy link
Member Author

@ekzyis ekzyis Dec 10, 2024

Choose a reason for hiding this comment

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

Mhhh, I think WHERE "Retries"."isHeld" = 't' instead of "InvoiceForward"."withdrawlId" IS NOT NULL would also work and would not require a join 🤔

But checking for a withdrawal is safer since isHeld set only means the sender wallet didn't fail but not necessarily that the withdrawal failed. Could be that something in between failed.

However, the client currently considers failed + isHeld as a receiver error.

Basically, the client considers every failure after it was able to pay as a receiver error even though maybe SN failed to do something and here we only consider something as a receiver failure if a withdrawal exists ...

This could be a bug in theory 👀

Copy link
Member

Choose a reason for hiding this comment

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

we can check actionError to be sure

Copy link
Member Author

Choose a reason for hiding this comment

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

It is currently not set for forward failures but I can change that if you meant that

Copy link
Member

Choose a reason for hiding this comment

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

I have a fix I'm working on locally. I just need to make sure I didn't regress it.

Copy link
Member

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 this changed or was always there, but fees are being deducted from p2p zap receives and shouldn't be

Screenshot 2024-12-10 at 1 45 58 PM

Copy link
Member

Choose a reason for hiding this comment

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

Then my notification adds the fee too

Screenshot 2024-12-10 at 1 47 49 PM

Copy link
Member Author

Choose a reason for hiding this comment

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

but fees are being deducted from p2p zap receives and shouldn't be

Mhh, I thought I fixed that at some point. If the wallet created a 70 sat invoice, it should obviously receive 70 sats since senders pay network fees.

Then my notification adds the fee too

This is also wrong, right? Imo, the notification should show how much you received and if you click on it, the fees are shown separately.

So for a 100 sat zap, it should show "70 sats were zapped directly to your attached wallet" and if you click into it, it will show "70 sats with 1 sat routing fee".

Does this make sense / do you agree?

Copy link
Member

Choose a reason for hiding this comment

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

SN pays the fee from SN to the receiver. So it shouldn't show any fee.

Copy link
Member

@huumn huumn left a comment

Choose a reason for hiding this comment

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

I fixed the sender error checking by returning fowardStatus which exposes the status of the withdrawal to the sender.

Other than the logging issues (which I suspect are unrelated) this works well. I'm very happy with predecessor recursive query - it's reliable and simple.

@huumn huumn merged commit a46f81f into master Dec 10, 2024
6 checks passed
@huumn huumn deleted the receiver-fallbacks branch December 10, 2024 20:15
@ekzyis
Copy link
Member Author

ekzyis commented Dec 10, 2024

I fixed the sender error checking by returning fowardStatus which exposes the status of the withdrawal to the sender.

Should we also use that in the recursive query so client and server definitely have the same view about receiver errors?

@huumn
Copy link
Member

huumn commented Dec 10, 2024

Should we also use that in the recursive query so client and server definitely have the same view about receiver errors?

Up to you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature new product features that weren't there before wallets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

attached wallet fallbacks on failure in order of priority
3 participants