Skip to content

Commit 6476b47

Browse files
committed
accounts: add new UpdateAccountBalanceAndExpiry Store method
In this commit, we remove one call to the UpdateAccount store method and replace it with a call to a new UpdateAccountBalanceAndExpiry method which updates an accounts balance and/or expiry fields and finds the account via the given ID. This method signature is more appropriate for a SQL backend than the UpdateAccount method.
1 parent 211865a commit 6476b47

File tree

4 files changed

+131
-11
lines changed

4 files changed

+131
-11
lines changed

accounts/interface.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"time"
99

10+
"github.com/lightningnetwork/lnd/fn"
1011
"github.com/lightningnetwork/lnd/lnrpc"
1112
"github.com/lightningnetwork/lnd/lntypes"
1213
"github.com/lightningnetwork/lnd/lnwire"
@@ -219,6 +220,12 @@ type Store interface {
219220
// Accounts retrieves all accounts from the store and un-marshals them.
220221
Accounts(ctx context.Context) ([]*OffChainBalanceAccount, error)
221222

223+
// UpdateAccountBalanceAndExpiry updates the balance and/or expiry of an
224+
// account.
225+
UpdateAccountBalanceAndExpiry(ctx context.Context, id AccountID,
226+
newBalance fn.Option[int64],
227+
newExpiry fn.Option[time.Time]) error
228+
222229
// RemoveAccount finds an account by its ID and removes it from the¨
223230
// store.
224231
RemoveAccount(ctx context.Context, id AccountID) error

accounts/service.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99

1010
"github.com/btcsuite/btcd/chaincfg"
1111
"github.com/lightninglabs/lndclient"
12-
"github.com/lightninglabs/taproot-assets/fn"
1312
"github.com/lightningnetwork/lnd/channeldb"
13+
"github.com/lightningnetwork/lnd/fn"
1414
invpkg "github.com/lightningnetwork/lnd/invoices"
1515
"github.com/lightningnetwork/lnd/lnrpc"
1616
"github.com/lightningnetwork/lnd/lntypes"
@@ -313,36 +313,35 @@ func (s *InterceptorService) UpdateAccount(ctx context.Context,
313313
return nil, ErrAccountServiceDisabled
314314
}
315315

316-
account, err := s.store.Account(ctx, accountID)
317-
if err != nil {
318-
return nil, fmt.Errorf("error fetching account: %w", err)
319-
}
320-
321316
// If the expiration date was set, parse it as a unix time stamp. A
322317
// value of -1 signals "don't update the expiration date".
318+
var expiry fn.Option[time.Time]
323319
if expirationDate > 0 {
324-
account.ExpirationDate = time.Unix(expirationDate, 0)
320+
expiry = fn.Some(time.Unix(expirationDate, 0))
325321
} else if expirationDate == 0 {
326322
// Setting the expiration to 0 means don't expire in which case
327323
// we use a zero time (zero unix time would still be 1970, so
328324
// that doesn't work for us).
329-
account.ExpirationDate = time.Time{}
325+
expiry = fn.Some(time.Time{})
330326
}
331327

332328
// If the new account balance was set, parse it as millisatoshis. A
333329
// value of -1 signals "don't update the balance".
330+
var balance fn.Option[int64]
334331
if accountBalance >= 0 {
335332
// Convert from satoshis to millisatoshis for storage.
336-
account.CurrentBalance = int64(accountBalance) * 1000
333+
balance = fn.Some(int64(accountBalance) * 1000)
337334
}
338335

339336
// Create the actual account in the macaroon account store.
340-
err = s.store.UpdateAccount(ctx, account)
337+
err := s.store.UpdateAccountBalanceAndExpiry(
338+
ctx, accountID, balance, expiry,
339+
)
341340
if err != nil {
342341
return nil, fmt.Errorf("unable to update account: %w", err)
343342
}
344343

345-
return account, nil
344+
return s.store.Account(ctx, accountID)
346345
}
347346

348347
// Account retrieves an account from the bolt DB and un-marshals it. If the

accounts/store_kvdb.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/btcsuite/btcwallet/walletdb"
14+
"github.com/lightningnetwork/lnd/fn"
1415
"github.com/lightningnetwork/lnd/kvdb"
1516
"github.com/lightningnetwork/lnd/lnwire"
1617
"go.etcd.io/bbolt"
@@ -194,6 +195,51 @@ func (s *BoltStore) UpdateAccount(_ context.Context,
194195
}, func() {})
195196
}
196197

198+
// UpdateAccountBalanceAndExpiry updates the balance and/or expiry of an
199+
// account.
200+
//
201+
// NOTE: This is part of the Store interface.
202+
func (s *BoltStore) UpdateAccountBalanceAndExpiry(_ context.Context,
203+
id AccountID, newBalance fn.Option[int64],
204+
newExpiry fn.Option[time.Time]) error {
205+
206+
update := func(account *OffChainBalanceAccount) error {
207+
newBalance.WhenSome(func(balance int64) {
208+
account.CurrentBalance = balance
209+
})
210+
newExpiry.WhenSome(func(expiry time.Time) {
211+
account.ExpirationDate = expiry
212+
})
213+
214+
return nil
215+
}
216+
217+
return s.updateAccount(id, update)
218+
}
219+
220+
func (s *BoltStore) updateAccount(id AccountID,
221+
updateFn func(*OffChainBalanceAccount) error) error {
222+
223+
return s.db.Update(func(tx kvdb.RwTx) error {
224+
bucket := tx.ReadWriteBucket(accountBucketName)
225+
if bucket == nil {
226+
return ErrAccountBucketNotFound
227+
}
228+
229+
account, err := getAccount(bucket, id)
230+
if err != nil {
231+
return err
232+
}
233+
234+
err = updateFn(account)
235+
if err != nil {
236+
return err
237+
}
238+
239+
return storeAccount(bucket, account)
240+
}, func() {})
241+
}
242+
197243
// storeAccount serializes and writes the given account to the given account
198244
// bucket.
199245
func storeAccount(accountBucket kvdb.RwBucket,
@@ -209,6 +255,19 @@ func storeAccount(accountBucket kvdb.RwBucket,
209255
return accountBucket.Put(account.ID[:], accountBinary)
210256
}
211257

258+
// getAccount retrieves an account from the given account bucket and
259+
// deserializes it.
260+
func getAccount(accountBucket kvdb.RwBucket, id AccountID) (
261+
*OffChainBalanceAccount, error) {
262+
263+
accountBinary := accountBucket.Get(id[:])
264+
if len(accountBinary) == 0 {
265+
return nil, ErrAccNotFound
266+
}
267+
268+
return deserializeAccount(accountBinary)
269+
}
270+
212271
// uniqueRandomAccountID generates a new random ID and makes sure it does not
213272
// yet exist in the DB.
214273
func uniqueRandomAccountID(accountBucket kvdb.RBucket) (AccountID, error) {

accounts/store_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/lightningnetwork/lnd/fn"
89
"github.com/lightningnetwork/lnd/lnrpc"
910
"github.com/lightningnetwork/lnd/lntypes"
1011
"github.com/stretchr/testify/require"
@@ -105,6 +106,60 @@ func assertEqualAccounts(t *testing.T, expected,
105106
actual.LastUpdate = actualUpdate
106107
}
107108

109+
// TestAccountUpdateMethods tests that all the Store methods that update an
110+
// account work correctly.
111+
func TestAccountUpdateMethods(t *testing.T) {
112+
t.Parallel()
113+
ctx := context.Background()
114+
115+
t.Run("UpdateAccountBalanceAndExpiry", func(t *testing.T) {
116+
store := NewTestDB(t)
117+
118+
acct, err := store.NewAccount(ctx, 0, time.Time{}, "foo")
119+
require.NoError(t, err)
120+
121+
assertBalanceAndExpiry := func(balance int64,
122+
expiry time.Time) {
123+
124+
dbAcct, err := store.Account(ctx, acct.ID)
125+
require.NoError(t, err)
126+
require.EqualValues(t, balance, dbAcct.CurrentBalance)
127+
require.Equal(
128+
t, expiry.Unix(), dbAcct.ExpirationDate.Unix(),
129+
)
130+
}
131+
132+
// Get the account from the store and check to see what its
133+
// initial balance and expiry fields are set to.
134+
assertBalanceAndExpiry(0, time.Time{})
135+
136+
// Now, update just the balance of the account.
137+
newBalance := int64(123)
138+
err = store.UpdateAccountBalanceAndExpiry(
139+
ctx, acct.ID, fn.Some(newBalance), fn.None[time.Time](),
140+
)
141+
require.NoError(t, err)
142+
assertBalanceAndExpiry(newBalance, time.Time{})
143+
144+
// Now update just the expiry of the account.
145+
newExpiry := time.Now().Add(time.Hour)
146+
err = store.UpdateAccountBalanceAndExpiry(
147+
ctx, acct.ID, fn.None[int64](), fn.Some(newExpiry),
148+
)
149+
require.NoError(t, err)
150+
assertBalanceAndExpiry(newBalance, newExpiry)
151+
152+
// Finally, update both the balance and expiry of the account.
153+
newBalance = 456
154+
newExpiry = time.Now().Add(2 * time.Hour)
155+
err = store.UpdateAccountBalanceAndExpiry(
156+
ctx, acct.ID, fn.Some(newBalance), fn.Some(newExpiry),
157+
)
158+
require.NoError(t, err)
159+
assertBalanceAndExpiry(newBalance, newExpiry)
160+
})
161+
}
162+
108163
// TestLastInvoiceIndexes makes sure the last known invoice indexes can be
109164
// stored and retrieved correctly.
110165
func TestLastInvoiceIndexes(t *testing.T) {

0 commit comments

Comments
 (0)