-
Notifications
You must be signed in to change notification settings - Fork 81
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
Add support for custodial off-chain accounts #363
Conversation
This still has a bunch of TODOs and things that can be improved, that's why it's still in draft. But would be nice if you could take a first look at the general architecture, @ellemouton and @Roasbeef. |
yep! currently doing so :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still need to do a more in depth pass. But so far this looks amazing 😱 🔥 leaving a few comments so long
88804ed
to
e4e45cd
Compare
Taking this out of WIP state since this is now a bit better tested and also received its own set of integration tests! The following TODOs are still in place:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing PR!
Rather large, so I did an initial pass through, focusing on internalizing the porposed architecture. To main things popped into my head:
- Why not return the macaroon from the account creation/list calls so the caller can store that along side the account ID?
- What if we instead stored the latest account details in the macaroon itself? This would make the service more stateless as the balances would be encoded in the macaroon itself. The persistence gets a bit simpler, but then we need to sort of burden the user with storing that new updated macaroon. Also this wouldn't really work for the invoices unfortunately, since we'd want to keep the user's macaroon somewhat compact.
rpcmiddleware/interface.go
Outdated
// request and inspects and potentially modifies the response. | ||
func NewResponseRewriter(requestSample proto.Message, | ||
responseSample proto.Message, | ||
typedResponseHandler interface{}) *DefaultChecker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we can dip into a bit of generics usage here? So something like:
type responseHandler[T any] func(T) error
Still getting through the diff, so I don't know if it'll use useful, but just a hunch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that but failed. Maybe I'll try again after I've completed everything else.
accounts/store.go
Outdated
// chargeAccount subtracts the given amount from an account's balance. | ||
func (s *Store) chargeAccount(id AccountID, paymentHash lntypes.Hash, | ||
amount lnwire.MilliSatoshi) error { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to get further in the diff, but in order to prevent race conditions, we may need to actually be holding an internal mutex here. Though I think that things are all serial based on my mental model so far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, a lock was definitely missing. Fixed now.
litrpc/lit-accounts.proto
Outdated
} | ||
|
||
message Account { | ||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also return the raw macaroon here? Otherwise how will it be obtained?
One architecture I have in mind is:
- Service maintains an external DB of the macaroon for each user's accoutn
- Each time a user wants to do something, the services get the macaroon then issues the response as normal
So the service just needs to handle the mapping from user to account macaroon.
One other alternative architecture would be the discard model: the account service actually encodes the latest balance in the macaroon itself. This minimizes the state, as its the clients jobs to maintain this macaroon, and they can't modify it w/ breaking the HAMC check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the baking of an account macaroon to the RPC server and updated the docs.
doc/accounts.md
Outdated
This created a new account (ID ``) with an initial balance of 50k satoshis and | ||
no expiration. | ||
|
||
### Bake a new macaroon for the user |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to my comment above: what if we did this for the user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, great idea. Did that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK 🔥 Leaving some initial comments :)
@guggero, remember to re-request review from reviewers when ready |
!lightninglabs-deploy mute |
64ffcac
to
4fd1e29
Compare
3f18152
to
c2a6484
Compare
10d1a60
to
0f29d10
Compare
Yayy, I was finally able to resolve all TODOs 🚀 |
Gotcha, I think one thing I was overlooking was that (if I'm not mistaken) I wasn't thinking about a mobile setup or anything like that, just about what might happen if there are unexpected outages in an otherwise stable environment. |
Ah yes, that is important to know in this context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking the time to respond to my feedback, this is great stuff and will be very useful I think.
My last handful of thoughts about this PR & feature:
|
Thanks a lot for your feedback! I think both of these features make sense for follow-up PRs (given the added complexity for item 2 and the age of this PR, this has been in the works since late 2018 in some form or another). Feel free to add feature request issues to the repo so they don't drop off the radar. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🌜
/* | ||
The list of invoices created by the account. An invoice created by an | ||
account will credit the account balance if it is settled. | ||
*/ | ||
repeated AccountInvoice invoices = 6; | ||
|
||
/* | ||
The list of payments made by the account. A payment made by an account will | ||
debit the account balance if it is settled. | ||
*/ | ||
repeated AccountPayment payments = 7; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last thought when I was looking through this PR, and also probably something that could be addressed in a follow-up PR, but it looks like these include all payments and invoices ever made by the accounts. Therefore when calling ListAccounts
, you're fetching all payment and invoice history for all accounts across all time. That seems like it could potentially be a very heavy query.
It might make sense to have a ListAccounts
call that just gives you the accounts and balances that exist, and then separate ListInvoices
and ListPayments
calls that work for a single account specified in the request? Something like that, just so that there's a way to list the accounts without needing to fetch all invoice & payment history.
I can put this in an issue as well, just wanted to raise it here first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
With this commit we add a set of utility functions that allow us to store and retrieve an account in/from a context.
This commit adds the checkers component that is responsible for making sure all incoming RPC requests and outgoing responses are tracked and their effects on an account are updated accordingly.
This commit introduces the account interceptor service which makes sure that each request that contains an account macaroon is intercepted and passed through the checkers component to track each RPC call's effects on the account.
Early preview of the LiT custodial off-chain accounts feature.
Depends on #308.Depends on #432.
EDIT: All TODOs have been resolved 🎉