Skip to content

Commit 88bb10d

Browse files
authored
Merge pull request #605 from lightninglabs/accounts
accounts: add label and AccountInfo RPC
2 parents b05e640 + 6c147e9 commit 88bb10d

24 files changed

+1186
-265
lines changed

accounts/checkers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
525525
if len(invoice) > 0 {
526526
payReq, err := zpay32.Decode(invoice, chainParams)
527527
if err != nil {
528-
return fmt.Errorf("error decoding pay req: %v", err)
528+
return fmt.Errorf("error decoding pay req: %w", err)
529529
}
530530

531531
if payReq.MilliSat != nil && *payReq.MilliSat > sendAmt {
@@ -546,7 +546,7 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
546546

547547
err = service.CheckBalance(acct.ID, sendAmt)
548548
if err != nil {
549-
return fmt.Errorf("error validating account balance: %v", err)
549+
return fmt.Errorf("error validating account balance: %w", err)
550550
}
551551

552552
return nil
@@ -609,7 +609,7 @@ func checkSendToRoute(ctx context.Context, service Service,
609609

610610
err = service.CheckBalance(acct.ID, sendAmt)
611611
if err != nil {
612-
return fmt.Errorf("error validating account balance: %v", err)
612+
return fmt.Errorf("error validating account balance: %w", err)
613613
}
614614

615615
return nil

accounts/checkers_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ type mockService struct {
4141
acctBalanceMsat lnwire.MilliSatoshi
4242

4343
trackedInvoices map[lntypes.Hash]AccountID
44-
trackedPayments map[lntypes.Hash]*PaymentEntry
44+
trackedPayments AccountPayments
4545
}
4646

4747
func newMockService() *mockService {
4848
return &mockService{
4949
acctBalanceMsat: 0,
5050
trackedInvoices: make(map[lntypes.Hash]AccountID),
51-
trackedPayments: make(map[lntypes.Hash]*PaymentEntry),
51+
trackedPayments: make(AccountPayments),
5252
}
5353
}
5454

@@ -68,7 +68,7 @@ func (m *mockService) AssociateInvoice(id AccountID, hash lntypes.Hash) error {
6868
return nil
6969
}
7070

71-
func (m *mockService) TrackPayment(id AccountID, hash lntypes.Hash,
71+
func (m *mockService) TrackPayment(_ AccountID, hash lntypes.Hash,
7272
amt lnwire.MilliSatoshi) error {
7373

7474
m.trackedPayments[hash] = &PaymentEntry{
@@ -403,8 +403,8 @@ func TestAccountCheckers(t *testing.T) {
403403
acct := &OffChainBalanceAccount{
404404
ID: testID,
405405
Type: TypeInitialBalance,
406-
Invoices: make(map[lntypes.Hash]struct{}),
407-
Payments: make(map[lntypes.Hash]*PaymentEntry),
406+
Invoices: make(AccountInvoices),
407+
Payments: make(AccountPayments),
408408
}
409409
ctx := AddToContext(
410410
context.Background(), KeyAccount, acct,

accounts/interceptor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func parseRPCMessage(msg *lnrpc.RPCMessage) (proto.Message, error) {
153153
// No, it's a normal message.
154154
parsedMsg, err := mid.ParseProtobuf(msg.TypeName, msg.Serialized)
155155
if err != nil {
156-
return nil, fmt.Errorf("error parsing proto of type %v: %v",
156+
return nil, fmt.Errorf("error parsing proto of type %v: %w",
157157
msg.TypeName, err)
158158
}
159159

accounts/interface.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func ParseAccountID(idStr string) (*AccountID, error) {
4444

4545
idBytes, err := hex.DecodeString(idStr)
4646
if err != nil {
47-
return nil, fmt.Errorf("error decoding account ID: %v", err)
47+
return nil, fmt.Errorf("error decoding account ID: %w", err)
4848
}
4949

5050
var id AccountID
@@ -67,6 +67,12 @@ type PaymentEntry struct {
6767
FullAmount lnwire.MilliSatoshi
6868
}
6969

70+
// AccountInvoices is the set of invoices that are associated with an account.
71+
type AccountInvoices map[lntypes.Hash]struct{}
72+
73+
// AccountPayments is the set of payments that are associated with an account.
74+
type AccountPayments map[lntypes.Hash]*PaymentEntry
75+
7076
// OffChainBalanceAccount holds all information that is needed to keep track of
7177
// a user's off-chain account balance. This balance can only be spent by paying
7278
// invoices.
@@ -99,11 +105,15 @@ type OffChainBalanceAccount struct {
99105

100106
// Invoices is a list of all invoices that are associated with the
101107
// account.
102-
Invoices map[lntypes.Hash]struct{}
108+
Invoices AccountInvoices
103109

104110
// Payments is a list of all payments that are associated with the
105111
// account and the last status we were aware of.
106-
Payments map[lntypes.Hash]*PaymentEntry
112+
Payments AccountPayments
113+
114+
// Label is an optional label that can be set for the account. If it is
115+
// not empty then it must be unique.
116+
Label string
107117
}
108118

109119
// HasExpired returns true if the account has an expiration date set and that
@@ -180,8 +190,8 @@ var (
180190
type Store interface {
181191
// NewAccount creates a new OffChainBalanceAccount with the given
182192
// balance and a randomly chosen ID.
183-
NewAccount(balance lnwire.MilliSatoshi,
184-
expirationDate time.Time) (*OffChainBalanceAccount, error)
193+
NewAccount(balance lnwire.MilliSatoshi, expirationDate time.Time,
194+
label string) (*OffChainBalanceAccount, error)
185195

186196
// UpdateAccount writes an account to the database, overwriting the
187197
// existing one if it exists.

accounts/rpcserver.go

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ func (s *RPCServer) CreateAccount(ctx context.Context,
5050
req *litrpc.CreateAccountRequest) (*litrpc.CreateAccountResponse,
5151
error) {
5252

53-
log.Infof("[createaccount] balance=%d, expiration=%d",
54-
req.AccountBalance, req.ExpirationDate)
53+
log.Infof("[createaccount] label=%v, balance=%d, expiration=%d",
54+
req.Label, req.AccountBalance, req.ExpirationDate)
5555

5656
var (
5757
balanceMsat lnwire.MilliSatoshi
@@ -70,9 +70,11 @@ func (s *RPCServer) CreateAccount(ctx context.Context,
7070
balanceMsat = lnwire.NewMSatFromSatoshis(balance)
7171

7272
// Create the actual account in the macaroon account store.
73-
account, err := s.service.NewAccount(balanceMsat, expirationDate)
73+
account, err := s.service.NewAccount(
74+
balanceMsat, expirationDate, req.Label,
75+
)
7476
if err != nil {
75-
return nil, fmt.Errorf("unable to create account: %v", err)
77+
return nil, fmt.Errorf("unable to create account: %w", err)
7678
}
7779

7880
var rootKeyIdSuffix [4]byte
@@ -91,12 +93,12 @@ func (s *RPCServer) CreateAccount(ctx context.Context,
9193
}},
9294
})
9395
if err != nil {
94-
return nil, fmt.Errorf("error baking account macaroon: %v", err)
96+
return nil, fmt.Errorf("error baking account macaroon: %w", err)
9597
}
9698

9799
macBytes, err := hex.DecodeString(macHex)
98100
if err != nil {
99-
return nil, fmt.Errorf("error decoding account macaroon: %v",
101+
return nil, fmt.Errorf("error decoding account macaroon: %w",
100102
err)
101103
}
102104

@@ -110,16 +112,13 @@ func (s *RPCServer) CreateAccount(ctx context.Context,
110112
func (s *RPCServer) UpdateAccount(_ context.Context,
111113
req *litrpc.UpdateAccountRequest) (*litrpc.Account, error) {
112114

113-
log.Infof("[updateaccount] id=%s, balance=%d, expiration=%d", req.Id,
114-
req.AccountBalance, req.ExpirationDate)
115+
log.Infof("[updateaccount] id=%s, label=%v, balance=%d, expiration=%d",
116+
req.Id, req.Label, req.AccountBalance, req.ExpirationDate)
115117

116-
// Account ID is always a hex string, convert it to our account ID type.
117-
var accountID AccountID
118-
decoded, err := hex.DecodeString(req.Id)
118+
accountID, err := s.findAccount(req.Id, req.Label)
119119
if err != nil {
120-
return nil, fmt.Errorf("error decoding account ID: %v", err)
120+
return nil, err
121121
}
122-
copy(accountID[:], decoded)
123122

124123
// Ask the service to update the account.
125124
account, err := s.service.UpdateAccount(
@@ -142,7 +141,7 @@ func (s *RPCServer) ListAccounts(context.Context,
142141
// Retrieve all accounts from the macaroon account store.
143142
accts, err := s.service.Accounts()
144143
if err != nil {
145-
return nil, fmt.Errorf("unable to list accounts: %v", err)
144+
return nil, fmt.Errorf("unable to list accounts: %w", err)
146145
}
147146

148147
// Map the response into the proper response type and return it.
@@ -158,30 +157,89 @@ func (s *RPCServer) ListAccounts(context.Context,
158157
}, nil
159158
}
160159

160+
// AccountInfo returns the account with the given ID or label.
161+
func (s *RPCServer) AccountInfo(_ context.Context,
162+
req *litrpc.AccountInfoRequest) (*litrpc.Account, error) {
163+
164+
log.Infof("[accountinfo] id=%v, label=%v", req.Id, req.Label)
165+
166+
accountID, err := s.findAccount(req.Id, req.Label)
167+
if err != nil {
168+
return nil, err
169+
}
170+
171+
dbAccount, err := s.service.Account(accountID)
172+
if err != nil {
173+
return nil, fmt.Errorf("error retrieving account: %w", err)
174+
}
175+
176+
return marshalAccount(dbAccount), nil
177+
}
178+
161179
// RemoveAccount removes the given account from the account database.
162180
func (s *RPCServer) RemoveAccount(_ context.Context,
163181
req *litrpc.RemoveAccountRequest) (*litrpc.RemoveAccountResponse,
164182
error) {
165183

166-
log.Infof("[removeaccount] id=%v", req.Id)
184+
log.Infof("[removeaccount] id=%v, label=%v", req.Id, req.Label)
167185

168-
// Account ID is always a hex string, convert it to our account ID type.
169-
var accountID AccountID
170-
decoded, err := hex.DecodeString(req.Id)
186+
accountID, err := s.findAccount(req.Id, req.Label)
171187
if err != nil {
172-
return nil, fmt.Errorf("error decoding account ID: %v", err)
188+
return nil, err
173189
}
174-
copy(accountID[:], decoded)
175190

176191
// Now remove the account.
177192
err = s.service.RemoveAccount(accountID)
178193
if err != nil {
179-
return nil, fmt.Errorf("error removing account: %v", err)
194+
return nil, fmt.Errorf("error removing account: %w", err)
180195
}
181196

182197
return &litrpc.RemoveAccountResponse{}, nil
183198
}
184199

200+
// findAccount finds an account by its ID or label.
201+
func (s *RPCServer) findAccount(id string, label string) (AccountID, error) {
202+
switch {
203+
case id != "" && label != "":
204+
return AccountID{}, fmt.Errorf("either account ID or label " +
205+
"must be specified, not both")
206+
207+
case id != "":
208+
// Account ID is always a hex string, convert it to our account
209+
// ID type.
210+
var accountID AccountID
211+
decoded, err := hex.DecodeString(id)
212+
if err != nil {
213+
return AccountID{}, fmt.Errorf("error decoding "+
214+
"account ID: %w", err)
215+
}
216+
copy(accountID[:], decoded)
217+
218+
return accountID, nil
219+
220+
case label != "":
221+
// We need to find the account by its label.
222+
accounts, err := s.service.Accounts()
223+
if err != nil {
224+
return AccountID{}, fmt.Errorf("unable to list "+
225+
"accounts: %w", err)
226+
}
227+
228+
for _, acct := range accounts {
229+
if acct.Label == label {
230+
return acct.ID, nil
231+
}
232+
}
233+
234+
return AccountID{}, fmt.Errorf("unable to find account "+
235+
"with label '%s'", label)
236+
237+
default:
238+
return AccountID{}, fmt.Errorf("either account ID or label " +
239+
"must be specified")
240+
}
241+
}
242+
185243
// marshalAccount converts an account into its RPC counterpart.
186244
func marshalAccount(acct *OffChainBalanceAccount) *litrpc.Account {
187245
rpcAccount := &litrpc.Account{
@@ -196,6 +254,7 @@ func marshalAccount(acct *OffChainBalanceAccount) *litrpc.Account {
196254
Payments: make(
197255
[]*litrpc.AccountPayment, 0, len(acct.Payments),
198256
),
257+
Label: acct.Label,
199258
}
200259

201260
for hash := range acct.Invoices {

0 commit comments

Comments
 (0)