Skip to content

Commit 73c8b0d

Browse files
committed
backend/accounts: load watch-only accounts at launch
1 parent 118099f commit 73c8b0d

File tree

4 files changed

+121
-25
lines changed

4 files changed

+121
-25
lines changed

backend/accounts.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -758,39 +758,56 @@ func (backend *Backend) persistETHAccountConfig(
758758

759759
// The accountsAndKeystoreLock must be held when calling this function.
760760
func (backend *Backend) initPersistedAccounts() {
761-
if backend.keystore == nil {
762-
return
763-
}
764-
// Only load accounts which belong to connected keystores.
765-
rootFingerprint, err := backend.keystore.RootFingerprint()
766-
if err != nil {
767-
backend.log.WithError(err).Error("Could not retrieve root fingerprint")
768-
return
769-
}
770-
keystoreConnected := func(account *config.Account) bool {
761+
// Only load accounts which belong to connected keystores or for which watchonly is enabled.
762+
keystoreConnectedOrWatch := func(account *config.Account) bool {
763+
if account.IsWatch() {
764+
return true
765+
}
766+
767+
if backend.keystore == nil {
768+
return false
769+
}
770+
rootFingerprint, err := backend.keystore.RootFingerprint()
771+
if err != nil {
772+
backend.log.WithError(err).Error("Could not retrieve root fingerprint")
773+
return false
774+
}
775+
771776
return account.SigningConfigurations.ContainsRootFingerprint(rootFingerprint)
772777
}
773778

774779
persistedAccounts := backend.config.AccountsConfig()
780+
781+
// In this loop, we add all accounts that match the filter, except for the ones whose signing
782+
// configuration is not supported by the connected keystore. The latter can happen for example
783+
// if a user connects a BitBox02 Multi edition first, which persists some altcoin accounts, and
784+
// then connects a BitBox02 BTC-only with the same seed. In that case, the unsupported accounts
785+
// will not be loaded, unless they have been marked as watch-only.
775786
outer:
776-
for _, account := range backend.filterAccounts(&persistedAccounts, keystoreConnected) {
787+
for _, account := range backend.filterAccounts(&persistedAccounts, keystoreConnectedOrWatch) {
777788
account := account
778789
coin, err := backend.Coin(account.CoinCode)
779790
if err != nil {
780791
backend.log.Errorf("skipping persisted account %s/%s, could not find coin",
781792
account.CoinCode, account.Code)
782793
continue
783794
}
784-
switch coin.(type) {
785-
case *btc.Coin:
786-
for _, cfg := range account.SigningConfigurations {
787-
if !backend.keystore.SupportsAccount(coin, cfg.ScriptType()) {
788-
continue outer
795+
796+
// Watch-only accounts are loaded regardless, and if later e.g. a BitBox02 BTC-only is
797+
// inserted with the same seed as a Multi, we will need to catch that mismatch when the
798+
// keystore will be used to e.g. display an Ethereum address etc.
799+
if backend.keystore != nil && !account.IsWatch() {
800+
switch coin.(type) {
801+
case *btc.Coin:
802+
for _, cfg := range account.SigningConfigurations {
803+
if !backend.keystore.SupportsAccount(coin, cfg.ScriptType()) {
804+
continue outer
805+
}
806+
}
807+
default:
808+
if !backend.keystore.SupportsAccount(coin, nil) {
809+
continue
789810
}
790-
}
791-
default:
792-
if !backend.keystore.SupportsAccount(coin, nil) {
793-
continue
794811
}
795812
}
796813

backend/accounts_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,31 @@ func TestAccountSupported(t *testing.T) {
10501050
b.registerKeystore(bb02Multi)
10511051
require.Len(t, b.Accounts(), 3)
10521052
require.Len(t, b.Config().AccountsConfig().Accounts, 3)
1053+
// Mark all as watch-only.
1054+
require.NoError(t, b.config.ModifyAccountsConfig(func(cfg *config.AccountsConfig) error {
1055+
for _, acct := range cfg.Accounts {
1056+
f := true
1057+
acct.Watch = &f
1058+
}
1059+
return nil
1060+
}))
10531061

10541062
b.DeregisterKeystore()
1063+
// Registering a Bitcoin-only like keystore loads also the altcoins that were persisted
1064+
// previously, because they are marked watch-only, so they should be visible.
1065+
b.registerKeystore(bb02BtcOnly)
1066+
require.Len(t, b.Accounts(), 3)
1067+
require.Len(t, b.Config().AccountsConfig().Accounts, 3)
1068+
1069+
b.DeregisterKeystore()
1070+
// If watch-only is disabled, then these will not be loaded if not supported by the keystore.
1071+
require.NoError(t, b.config.ModifyAccountsConfig(func(cfg *config.AccountsConfig) error {
1072+
for _, acct := range cfg.Accounts {
1073+
f := false
1074+
acct.Watch = &f
1075+
}
1076+
return nil
1077+
}))
10551078
// Registering a Bitcoin-only like keystore loads only the Bitcoin account, even though altcoins
10561079
// were persisted previously.
10571080
b.registerKeystore(bb02BtcOnly)

backend/backend_test.go

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
accountsTypes "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts/types"
2323
"github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc"
2424
coinpkg "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin"
25+
"github.com/digitalbitbox/bitbox-wallet-app/backend/config"
2526
keystoremock "github.com/digitalbitbox/bitbox-wallet-app/backend/keystore/mocks"
2627
"github.com/digitalbitbox/bitbox-wallet-app/backend/keystore/software"
2728
"github.com/digitalbitbox/bitbox-wallet-app/backend/signing"
@@ -98,6 +99,9 @@ func TestRegisterKeystore(t *testing.T) {
9899
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-55555555-btc-0"))
99100
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-55555555-ltc-0"))
100101
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-55555555-eth-0"))
102+
require.NotNil(t, b.accounts.lookup("v0-55555555-btc-0"))
103+
require.NotNil(t, b.accounts.lookup("v0-55555555-ltc-0"))
104+
require.NotNil(t, b.accounts.lookup("v0-55555555-eth-0"))
101105
require.Equal(t, "Bitcoin", b.Config().AccountsConfig().Accounts[0].Name)
102106
require.Equal(t, "Litecoin", b.Config().AccountsConfig().Accounts[1].Name)
103107
require.Equal(t, "Ethereum", b.Config().AccountsConfig().Accounts[2].Name)
@@ -113,32 +117,79 @@ func TestRegisterKeystore(t *testing.T) {
113117
// tests, but we check that it was set and recent.
114118
require.True(t, time.Since(b.Config().AccountsConfig().Keystores[0].LastConnected) < 10*time.Second)
115119

116-
// Deregistering the keystore removes the loaded accounts, but not the persisted accounts and
117-
// keystores.
120+
// Deregistering the keystore leaves the loaded accounts (watchonly), and leaves the persisted
121+
// accounts and keystores.
122+
// Mark accounts as watch-only.
123+
require.NoError(t, b.config.ModifyAccountsConfig(func(cfg *config.AccountsConfig) error {
124+
for _, acct := range cfg.Accounts {
125+
f := true
126+
acct.Watch = &f
127+
}
128+
return nil
129+
}))
118130
b.DeregisterKeystore()
119-
require.Len(t, b.Accounts(), 0)
131+
require.Len(t, b.Accounts(), 3)
120132
require.Len(t, b.Config().AccountsConfig().Accounts, 3)
121133
require.Len(t, b.Config().AccountsConfig().Keystores, 1)
122134

123135
// Registering the same keystore again loads the previously persisted accounts and does not
124136
// automatically persist more accounts.
125-
b.DeregisterKeystore()
126137
b.registerKeystore(ks1)
127138
require.Len(t, b.Accounts(), 3)
128139
require.Len(t, b.Config().AccountsConfig().Accounts, 3)
129140
require.Len(t, b.Config().AccountsConfig().Keystores, 1)
130141

131142
// Registering another keystore persists a set of initial default accounts and loads them.
143+
// They are added to the previous set of watchonly accounts
132144
b.DeregisterKeystore()
133145
b.registerKeystore(ks2)
134-
require.Len(t, b.Accounts(), 3)
146+
require.Len(t, b.Accounts(), 6)
135147
require.Len(t, b.Config().AccountsConfig().Accounts, 6)
136148
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-66666666-btc-0"))
137149
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-66666666-ltc-0"))
138150
require.NotNil(t, b.Config().AccountsConfig().Lookup("v0-66666666-eth-0"))
151+
require.NotNil(t, b.accounts.lookup("v0-66666666-btc-0"))
152+
require.NotNil(t, b.accounts.lookup("v0-66666666-ltc-0"))
153+
require.NotNil(t, b.accounts.lookup("v0-66666666-eth-0"))
139154
require.Len(t, b.Config().AccountsConfig().Keystores, 2)
140155
require.Equal(t, "Mock keystore 2", b.Config().AccountsConfig().Keystores[1].Name)
141156
require.Equal(t, rootFingerprint2, []byte(b.Config().AccountsConfig().Keystores[1].RootFingerprint))
157+
158+
b.DeregisterKeystore()
159+
// Enable watch-only for all but two accounts, one of each keystore. Now, all watch-only
160+
// accounts plus the non-watch only accounts of the connected keystore will be loaded.
161+
require.NoError(t, b.config.ModifyAccountsConfig(func(cfg *config.AccountsConfig) error {
162+
for _, acct := range cfg.Accounts {
163+
t := true
164+
acct.Watch = &t
165+
}
166+
167+
f := false
168+
cfg.Lookup("v0-55555555-btc-0").Watch = &f
169+
cfg.Lookup("v0-66666666-ltc-0").Watch = &f
170+
return nil
171+
}))
172+
b.registerKeystore(ks1)
173+
require.Len(t, b.Accounts(), 5)
174+
// v0-55555555-btc-0 loaded even though watch=false, as the keystore is connected.
175+
require.NotNil(t, b.accounts.lookup("v0-55555555-btc-0"))
176+
require.NotNil(t, b.accounts.lookup("v0-55555555-ltc-0"))
177+
require.NotNil(t, b.accounts.lookup("v0-55555555-eth-0"))
178+
require.NotNil(t, b.accounts.lookup("v0-66666666-btc-0"))
179+
// v0-66666666-ltc-0 not loaded (watch=false).
180+
require.Nil(t, b.accounts.lookup("v0-66666666-ltc-0"))
181+
require.NotNil(t, b.accounts.lookup("v0-66666666-eth-0"))
182+
183+
b.DeregisterKeystore()
184+
require.Len(t, b.Accounts(), 4)
185+
// v0-55555555-btc-0 not loaded (watch = false)
186+
require.Nil(t, b.accounts.lookup("v0-55555555-btc-0"))
187+
require.NotNil(t, b.accounts.lookup("v0-55555555-ltc-0"))
188+
require.NotNil(t, b.accounts.lookup("v0-55555555-eth-0"))
189+
require.NotNil(t, b.accounts.lookup("v0-66666666-btc-0"))
190+
// v0-66666666-ltc-0 not loaded (watch=false).
191+
require.Nil(t, b.accounts.lookup("v0-66666666-ltc-0"))
192+
require.NotNil(t, b.accounts.lookup("v0-66666666-eth-0"))
142193
}
143194

144195
func lookup(accts []accounts.Interface, code accountsTypes.Code) accounts.Interface {

backend/config/accounts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func (acct *Account) SetTokenActive(tokenCode string, active bool) error {
7373
return nil
7474
}
7575

76+
// IsWatchOnly returns true if the `Watch` setting is set to true.
77+
func (acct *Account) IsWatch() bool {
78+
return acct.Watch != nil && *acct.Watch
79+
}
80+
7681
// Keystore holds information related to keystores such as the BitBox02.
7782
type Keystore struct {
7883
// The root fingerprint is the first 32 bits of the hash160 of the pubkey at the keypath m/.

0 commit comments

Comments
 (0)