Skip to content

Commit aad518e

Browse files
committed
backend: make changes to accounts atomic
We had an accountsLock, but it not held for the whole duration of initAccounts(), which means that in concurrent calls, the accounts could be uninitialized twice, then all accounts would be added twice, or other such race conditions. This commit only acquires lock in exported functions to make it work with all combinations of account-changing function calls.
1 parent a478d39 commit aad518e

File tree

1 file changed

+45
-10
lines changed

1 file changed

+45
-10
lines changed

backend/backend.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,11 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe
208208

209209
// configureHistoryExchangeRates changes backend.ratesUpdater settings.
210210
// It requires both backend.config to be up-to-date and all accounts initialized.
211+
//
212+
// The accountsLock must be held when calling this function.
211213
func (backend *Backend) configureHistoryExchangeRates() {
212214
var coins []string
213-
for _, acct := range backend.Accounts() {
215+
for _, acct := range backend.accounts {
214216
coins = append(coins, string(acct.Coin().Code()))
215217
}
216218
// No reason continue with ERC20 tokens if Ethereum is inactive.
@@ -227,8 +229,8 @@ func (backend *Backend) configureHistoryExchangeRates() {
227229
}
228230

229231
// addAccount adds the given account to the backend.
232+
// The accountsLock must be held when calling this function.
230233
func (backend *Backend) addAccount(account accounts.Interface) {
231-
defer backend.accountsLock.Lock()()
232234
backend.accounts = append(backend.accounts, account)
233235
account.Observe(backend.Notify)
234236
backend.onAccountInit(account)
@@ -264,9 +266,8 @@ func (backend *Backend) emitAccountsStatusChanged() {
264266
})
265267
}
266268

267-
// CreateAndAddAccount creates an account with the given parameters and adds it to the backend. If
268-
// persist is true, the configuration is fetched and saved in the accounts configuration.
269-
func (backend *Backend) CreateAndAddAccount(
269+
// The accountsLock must be held when calling this function.
270+
func (backend *Backend) createAndAddAccount(
270271
coin coin.Coin,
271272
code string,
272273
name string,
@@ -345,6 +346,20 @@ func (backend *Backend) CreateAndAddAccount(
345346
return nil
346347
}
347348

349+
// CreateAndAddAccount creates an account with the given parameters and adds it to the backend. If
350+
// persist is true, the configuration is fetched and saved in the accounts configuration.
351+
func (backend *Backend) CreateAndAddAccount(
352+
coin coin.Coin,
353+
code string,
354+
name string,
355+
getSigningConfigurations func() (signing.Configurations, error),
356+
persist bool,
357+
emitEvent bool,
358+
) error {
359+
defer backend.accountsLock.Lock()()
360+
return backend.createAndAddAccount(coin, code, name, getSigningConfigurations, persist, emitEvent)
361+
}
362+
348363
type scriptTypeWithKeypath struct {
349364
scriptType signing.ScriptType
350365
keypath signing.AbsoluteKeypath
@@ -364,6 +379,8 @@ func newScriptTypeWithKeypath(scriptType signing.ScriptType, keypath string) scr
364379
// adds a combined BTC account with the given script types. If the keystore requires split accounts
365380
// (bitbox01) or the user configure split accounts in the settings, one account per script type is
366381
// added instead of a combined account.
382+
//
383+
// The accountsLock must be held when calling this function.
367384
func (backend *Backend) createAndAddBTCAccount(
368385
keystore keystore.Keystore,
369386
coin coin.Coin,
@@ -420,7 +437,7 @@ func (backend *Backend) createAndAddBTCAccount(
420437
case signing.ScriptTypeP2WPKH:
421438
suffixedName += ": bech32"
422439
}
423-
err := backend.CreateAndAddAccount(
440+
err := backend.createAndAddAccount(
424441
coin,
425442
fmt.Sprintf("%s-%s", code, cfg.scriptType),
426443
suffixedName,
@@ -443,13 +460,14 @@ func (backend *Backend) createAndAddBTCAccount(
443460
}
444461
return result, nil
445462
}
446-
err := backend.CreateAndAddAccount(coin, code, name, getSigningConfigurations, false, false)
463+
err := backend.createAndAddAccount(coin, code, name, getSigningConfigurations, false, false)
447464
if err != nil {
448465
panic(err)
449466
}
450467
}
451468
}
452469

470+
// The accountsLock must be held when calling this function.
453471
func (backend *Backend) createAndAddETHAccount(
454472
keystore keystore.Keystore,
455473
coin coin.Coin,
@@ -493,7 +511,7 @@ func (backend *Backend) createAndAddETHAccount(
493511
),
494512
}, nil
495513
}
496-
err = backend.CreateAndAddAccount(coin, code, name, getSigningConfigurations, false, false)
514+
err = backend.createAndAddAccount(coin, code, name, getSigningConfigurations, false, false)
497515
if err != nil {
498516
panic(err)
499517
}
@@ -660,6 +678,7 @@ func (backend *Backend) Coin(code coinpkg.Code) (coin.Coin, error) {
660678
return coin, nil
661679
}
662680

681+
// The accountsLock must be held when calling this function.
663682
func (backend *Backend) initPersistedAccounts() {
664683
for _, account := range backend.config.AccountsConfig().Accounts {
665684
account := account
@@ -677,7 +696,7 @@ func (backend *Backend) initPersistedAccounts() {
677696
getSigningConfigurations := func() (signing.Configurations, error) {
678697
return signing.Configurations{account.Configuration}, nil
679698
}
680-
err = backend.CreateAndAddAccount(coin, account.Code, account.Name, getSigningConfigurations, false, false)
699+
err = backend.createAndAddAccount(coin, account.Code, account.Name, getSigningConfigurations, false, false)
681700
if err != nil {
682701
panic(err)
683702
}
@@ -686,6 +705,8 @@ func (backend *Backend) initPersistedAccounts() {
686705

687706
// initDefaultAccounts creates a bunch of default accounts for a set of keystores (not manually
688707
// user-added). Currently the first bip44 account for all supported and active account types.
708+
//
709+
// The accountsLock must be held when calling this function.
689710
func (backend *Backend) initDefaultAccounts() {
690711
if backend.keystores.Count() == 0 {
691712
return
@@ -765,6 +786,7 @@ func (backend *Backend) initDefaultAccounts() {
765786
}
766787
}
767788

789+
// The accountsLock must be held when calling this function.
768790
func (backend *Backend) initAccounts() {
769791
// Since initAccounts replaces all previous accounts, we need to properly close them first.
770792
backend.uninitAccounts()
@@ -785,6 +807,8 @@ func (backend *Backend) initAccounts() {
785807
// if the configuration changed (e.g. which accounts are active). This is a stopgap measure until
786808
// accounts can be added and removed individually.
787809
func (backend *Backend) ReinitializeAccounts() {
810+
defer backend.accountsLock.Lock()()
811+
788812
backend.log.Info("Reinitializing accounts")
789813
backend.initAccounts()
790814
}
@@ -796,6 +820,7 @@ func (backend *Backend) Testing() bool {
796820

797821
// Accounts returns the current accounts of the backend.
798822
func (backend *Backend) Accounts() []accounts.Interface {
823+
defer backend.accountsLock.RLock()()
799824
return backend.accounts
800825
}
801826

@@ -852,6 +877,8 @@ func (backend *Backend) Start() <-chan interface{} {
852877
if backend.arguments.DevMode() {
853878
backend.baseManager.Start()
854879
}
880+
881+
defer backend.accountsLock.Lock()()
855882
backend.initPersistedAccounts()
856883
backend.emitAccountsStatusChanged()
857884

@@ -932,8 +959,8 @@ func (backend *Backend) BitBoxBaseDeregister(bitboxBaseID string) {
932959
backend.events <- backendEvent{Type: "bitboxbases", Data: "registeredChanged"}
933960
}
934961

962+
// The accountsLock must be held when calling this function.
935963
func (backend *Backend) uninitAccounts() {
936-
defer backend.accountsLock.Lock()()
937964
for _, account := range backend.accounts {
938965
account := account
939966
backend.onAccountUninit(account)
@@ -960,6 +987,8 @@ func (backend *Backend) RegisterKeystore(keystore keystore.Keystore) {
960987
if backend.arguments.Multisig() && backend.keystores.Count() != 2 {
961988
return
962989
}
990+
991+
defer backend.accountsLock.Lock()()
963992
backend.initAccounts()
964993
}
965994

@@ -971,6 +1000,9 @@ func (backend *Backend) DeregisterKeystore() {
9711000
Subject: "keystores",
9721001
Action: action.Reload,
9731002
})
1003+
1004+
defer backend.accountsLock.Lock()()
1005+
9741006
backend.uninitAccounts()
9751007
// TODO: classify accounts by keystore, remove only the ones belonging to the deregistered
9761008
// keystore. For now we just remove all, then re-add the rest.
@@ -1152,9 +1184,12 @@ func (backend *Backend) Environment() Environment {
11521184

11531185
// Close shuts down the backend. After this, no other method should be called.
11541186
func (backend *Backend) Close() error {
1187+
defer backend.accountsLock.Lock()()
1188+
11551189
errors := []string{}
11561190

11571191
backend.ratesUpdater.Stop()
1192+
11581193
backend.uninitAccounts()
11591194

11601195
for _, coin := range backend.coins {

0 commit comments

Comments
 (0)