Skip to content

Commit 64606f0

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 64606f0

File tree

4 files changed

+155
-11
lines changed

4 files changed

+155
-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[lnwire.MilliSatoshi],
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[lnwire.MilliSatoshi]
334331
if accountBalance >= 0 {
335332
// Convert from satoshis to millisatoshis for storage.
336-
account.CurrentBalance = int64(accountBalance) * 1000
333+
balance = fn.Some(lnwire.MilliSatoshi(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: 64 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,56 @@ 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[lnwire.MilliSatoshi],
204+
newExpiry fn.Option[time.Time]) error {
205+
206+
update := func(account *OffChainBalanceAccount) error {
207+
newBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
208+
account.CurrentBalance = int64(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 fmt.Errorf("error fetching account, %w", err)
232+
}
233+
234+
err = updateFn(account)
235+
if err != nil {
236+
return fmt.Errorf("error updating account, %w", err)
237+
}
238+
239+
err = storeAccount(bucket, account)
240+
if err != nil {
241+
return fmt.Errorf("error storing account, %w", err)
242+
}
243+
244+
return nil
245+
}, func() {})
246+
}
247+
197248
// storeAccount serializes and writes the given account to the given account
198249
// bucket.
199250
func storeAccount(accountBucket kvdb.RwBucket,
@@ -209,6 +260,19 @@ func storeAccount(accountBucket kvdb.RwBucket,
209260
return accountBucket.Put(account.ID[:], accountBinary)
210261
}
211262

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

accounts/store_test.go

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

8+
"github.com/lightningnetwork/lnd/fn"
89
"github.com/lightningnetwork/lnd/lnrpc"
910
"github.com/lightningnetwork/lnd/lntypes"
11+
"github.com/lightningnetwork/lnd/lnwire"
1012
"github.com/stretchr/testify/require"
1113
)
1214

@@ -105,6 +107,78 @@ func assertEqualAccounts(t *testing.T, expected,
105107
actual.LastUpdate = actualUpdate
106108
}
107109

110+
// TestAccountUpdateMethods tests that all the Store methods that update an
111+
// account work correctly.
112+
func TestAccountUpdateMethods(t *testing.T) {
113+
t.Parallel()
114+
ctx := context.Background()
115+
116+
t.Run("UpdateAccountBalanceAndExpiry", func(t *testing.T) {
117+
store := NewTestDB(t)
118+
119+
// Ensure that the function errors out if we try update an
120+
// account that does not exist.
121+
err := store.UpdateAccountBalanceAndExpiry(
122+
ctx, AccountID{}, fn.None[lnwire.MilliSatoshi](),
123+
fn.None[time.Time](),
124+
)
125+
require.ErrorIs(t, err, ErrAccNotFound)
126+
127+
acct, err := store.NewAccount(ctx, 0, time.Time{}, "foo")
128+
require.NoError(t, err)
129+
130+
assertBalanceAndExpiry := func(balance lnwire.MilliSatoshi,
131+
expiry time.Time) {
132+
133+
dbAcct, err := store.Account(ctx, acct.ID)
134+
require.NoError(t, err)
135+
require.EqualValues(t, balance, dbAcct.CurrentBalance)
136+
require.WithinDuration(
137+
t, expiry, dbAcct.ExpirationDate, 0,
138+
)
139+
}
140+
141+
// Get the account from the store and check to see what its
142+
// initial balance and expiry fields are set to.
143+
assertBalanceAndExpiry(0, time.Time{})
144+
145+
// Now, update just the balance of the account.
146+
newBalance := lnwire.MilliSatoshi(123)
147+
err = store.UpdateAccountBalanceAndExpiry(
148+
ctx, acct.ID, fn.Some(newBalance), fn.None[time.Time](),
149+
)
150+
require.NoError(t, err)
151+
assertBalanceAndExpiry(newBalance, time.Time{})
152+
153+
// Now update just the expiry of the account.
154+
newExpiry := time.Now().Add(time.Hour)
155+
err = store.UpdateAccountBalanceAndExpiry(
156+
ctx, acct.ID, fn.None[lnwire.MilliSatoshi](),
157+
fn.Some(newExpiry),
158+
)
159+
require.NoError(t, err)
160+
assertBalanceAndExpiry(newBalance, newExpiry)
161+
162+
// Update both the balance and expiry of the account.
163+
newBalance = 456
164+
newExpiry = time.Now().Add(2 * time.Hour)
165+
err = store.UpdateAccountBalanceAndExpiry(
166+
ctx, acct.ID, fn.Some(newBalance), fn.Some(newExpiry),
167+
)
168+
require.NoError(t, err)
169+
assertBalanceAndExpiry(newBalance, newExpiry)
170+
171+
// Finally, test an update that has no net changes to the
172+
// balance or expiry.
173+
err = store.UpdateAccountBalanceAndExpiry(
174+
ctx, acct.ID, fn.None[lnwire.MilliSatoshi](),
175+
fn.None[time.Time](),
176+
)
177+
require.NoError(t, err)
178+
assertBalanceAndExpiry(newBalance, newExpiry)
179+
})
180+
}
181+
108182
// TestLastInvoiceIndexes makes sure the last known invoice indexes can be
109183
// stored and retrieved correctly.
110184
func TestLastInvoiceIndexes(t *testing.T) {

0 commit comments

Comments
 (0)