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

hsmd: add preapprove invoice and keysend messages #5821

Conversation

ksedgwic
Copy link
Collaborator

@ksedgwic ksedgwic commented Dec 14, 2022

These messages request payment approval from the hsmd for bolt11 and keysend payments:

  • default implementation automatically approves all payments
  • remote signer might not approve based on velocity controls, allowlist controls, manual approval, ...
  • remote signer will not allow unapproved payments to be made (will refuse to sign commitment)
  • declining a preapproval is much cleaner from a UX standpoint than preventing payment later

@ksedgwic ksedgwic force-pushed the hsmd-preapprove-payments branch 2 times, most recently from ebaee21 to a72e684 Compare December 14, 2022 21:40
@ksedgwic ksedgwic marked this pull request as ready for review December 15, 2022 05:39
@cdecker
Copy link
Member

cdecker commented Dec 16, 2022

Thanks @ksedgwic, just a couple of questions for my own understanding:

  • I'm guessing the pre-approval is there in order to avoid situations where lightningd would go ahead and attempt to pay or keysend, but then the signer would run into a policy violation, refusing to sign. This would then end up in the requesting daemon to hang indefinitely (restart seems like the only option here). Is that interpretation correct?
  • Would pre-approval result in any state changes on the signer? I.e., does the signer remember it approved a send, and would consider that for other decisions as well? Is this persisted?
  • How is concurrency handled? If I have two pay commands, that individually would be ok, but not together (velocity controls for example), how is the following sequence resolved:
    • Preapproval A
    • Preapproval B
    • Signature requests for A
    • Signature requests for B
  • If we track approvals persistently, how are these cleared again? Take for example a pay that was approved, but ultimately fails to complete, when is the approval cleared in order to allow future approvals to tap into the reservation for the failed pay?

On a more design-related thing: I'm assuming that keysend is the reason why we expose the pre-approval on the JSON-RPC. It being a plugin, it can't ask the signer to pre-approve, is that correct?

I'll review the code as soon as possible, but thought I needed a bit of a wider picture of how things are intended to work on a conceptual level 🙂

@cdecker cdecker self-assigned this Dec 16, 2022
@devrandom
Copy link
Contributor

Thanks @ksedgwic, just a couple of questions for my own understanding:

* I'm guessing the pre-approval is there in order to avoid situations where `lightningd` would go ahead and attempt to `pay` or `keysend`, but then the signer would run into a policy violation, refusing to sign. This would then end up in the requesting daemon to hang indefinitely (restart seems like the only option here). Is that interpretation correct?

Right, the other option is to return an error, but that would crash or at least restart the node.

There is another reason. Seeing the invoice allows us to bind HTLCs (the payment hash) to payees (the entity who signed the invoice), which in turn allows payee to be allowlisted.

* Would pre-approval result in any state changes on the signer? I.e., does the signer remember it approved a send, and would consider that for other decisions as well? Is this persisted?

Yes, the signer would start doing the following:

  • track the payment hash, the amount approved and the amount actually sent
  • adjust any velocity control state to account for the amount approved
  • prevent sending more than was approved
* How is concurrency handled? If I have two `pay` commands, that individually would be ok, but not together (velocity controls for example), how is the following sequence resolved:
  
  * Preapproval `A`
  * Preapproval `B`
  * Signature requests for `A`
  * Signature requests for `B`

The signer would serialize the preapprovals internally, so the later request for preapproval would fail if the velocity is exceeded. Note that velocity measures amount approved rather than amount actually sent, so that velocity controls can only fail a payment early - in the preapproval stage.

* If we track approvals persistently, how are these cleared again? Take for example a `pay` that was approved, but ultimately fails to complete, when is the approval cleared in order to allow future approvals to tap into the reservation for the failed `pay`?

We were currently planning to expire these on invoice expiry, but on second thought it would be useful to be able to withdraw a preapproval for a payment we give up on. I suppose it should only succeed in removal if there are no pending HTLCs involving that payment hash.

On a more design-related thing: I'm assuming that keysend is the reason why we expose the pre-approval on the JSON-RPC. It being a plugin, it can't ask the signer to pre-approve, is that correct?

I'll defer to @ksedgwic on this question.

@ksedgwic
Copy link
Collaborator Author

On a more design-related thing: I'm assuming that keysend is the reason why we expose the pre-approval on the JSON-RPC. It being a plugin, it can't ask the signer to pre-approve, is that correct?

Yes, that's right

@ksedgwic
Copy link
Collaborator Author

Would pre-approval result in any state changes on the signer? I.e., does the signer remember it approved a send, and would consider that for other decisions as well? Is this persisted?

Yes, the approval is remembered and persisted. Subsequent preapproval requests for an approved invoice or keysend are also immediately accepted.

@rustyrussell rustyrussell added this to the v23.02 milestone Jan 9, 2023
Copy link
Member

@cdecker cdecker left a comment

Choose a reason for hiding this comment

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

Just a minor thing about the dangers of sync comms when the outcome itself is not critical itself. It's a relatively new consideration, and therefore not consistently applied to the existing code. I'd be more at ease with async comms :-)


u8 *msg = towire_hsmd_preapprove_invoice(NULL, invstring);

if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
Copy link
Member

Choose a reason for hiding this comment

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

Doing synchronous comms here may not be what we want. It ends up blocking the entire lightningd until the signer has responded. This means that if we have a long RTT from lightningd to hsmd we are unresponsive for that roundtrip. Worse, if the signer does not respond we are blocked forever and can't react to on-chain events and peer messsages.

We should always prefer async comms in lightningd.


u8 *msg = towire_hsmd_preapprove_keysend(NULL, destination, payment_hash, *amount);

if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
Copy link
Member

Choose a reason for hiding this comment

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

Same thing about sync comms above.

@cdecker cdecker dismissed their stale review January 25, 2023 09:23

Async comms are not possible at this point in time.

@cdecker
Copy link
Member

cdecker commented Jan 25, 2023

Hi @ksedgwic, sorry for suggesting this without first checking if we do any async comms with the signer. It turns out we don't, and introducing it in this PR would massively expand the scope of this PR. I therefore retract my previous comments, and think the changes should go in as they are.

ACK 31da26c

@cdecker
Copy link
Member

cdecker commented Jan 25, 2023

@endothermicdev would you like me to rebase this to get rid of the kick ci commit at the end? It's not semantically meaningful and was there solely to convince CI to give it another shot.

@ksedgwic if CI gets stuck again, ping me, and I can kick it on the console :-)

@endothermicdev
Copy link
Collaborator

@cdecker yes, I would appreciate that. Thanks for the updating your review as well.

@cdecker
Copy link
Member

cdecker commented Jan 26, 2023

@ksedgwic it seems like the PR disallows maintainers to push to your branch, can you rebase and drop the last commit? Then we'll fold the PR into the release 🎉

…ifier

Changelog-added: hsmd: A new message `hsmd_preapprove_invoice` is added.
Changelog-added: JSON-RPC: A new command `preapproveinvoice` is added.
@devrandom
Copy link
Contributor

I can't change that flag from here, but I was able to rebase and drop the extra commit.

…ifier

Changelog-added: hsmd: A new message `hsmd_preapprove_keysend` is added.
Changelog-added: JSON-RPC: A new command `preapprovekeysend` is added.
@devrandom
Copy link
Contributor

CI fail seems unrelated...

@endothermicdev
Copy link
Collaborator

Yes, unrelated but I haven't identified the fix to that one yet. Thanks!

ACK 0fa933e

@endothermicdev endothermicdev merged commit 7b2c561 into ElementsProject:master Jan 27, 2023
Sign up for free to 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.

5 participants