Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Dec 5, 2023
1 parent 5e53dbe commit ac19ef5
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 152 deletions.
83 changes: 60 additions & 23 deletions backend/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ func sortAccounts(accounts []accounts.Interface) {

// filterAccounts fetches all persisted accounts that pass the provided filter. Testnet/regtest
// accounts are not loaded in mainnet and vice versa.
func (backend *Backend) filterAccounts(accountsConfig *config.AccountsConfig, filter func(*config.Account) bool) []*config.Account {
// The keystore in the filter function can be nil if no keystore has been persisted yet.
func (backend *Backend) filterAccounts(accountsConfig *config.AccountsConfig, filter func(*config.Keystore, *config.Account) bool) []*config.Account {
var accounts []*config.Account
for _, account := range accountsConfig.Accounts {
if !backend.arguments.Regtest() {
Expand All @@ -151,7 +152,17 @@ func (backend *Backend) filterAccounts(accountsConfig *config.AccountsConfig, fi
continue
}

if !filter(account) {
rootFingerprint, err := account.SigningConfigurations.RootFingerprint()
if err != nil {
backend.log.Errorf("filterAccounts: skipping account %s/%s, could not retrieve root fingerprint",
account.CoinCode, account.Code)
continue
}
keystore, err := accountsConfig.LookupKeystore(rootFingerprint)
if err != nil {
keystore = nil
}
if !filter(keystore, account) {
continue
}
accounts = append(accounts, account)
Expand Down Expand Up @@ -406,9 +417,15 @@ func (backend *Backend) CreateAndPersistAccountConfig(
if hiddenAccount != nil {
hiddenAccount.HiddenBecauseUnused = false
hiddenAccount.Name = name

rootFingerprint, err := keystore.RootFingerprint()
if err != nil {
return err
}

// We only really show the account to the user now, so this is the moment to set the
// watchonly flag on it if the user has the global watchonly setting enabled.
if backend.config.AppConfig().Backend.Watchonly {
if accountsConfig.IsWatchonly(rootFingerprint) {
t := true
hiddenAccount.Watch = &t
}
Expand Down Expand Up @@ -766,7 +783,7 @@ func (backend *Backend) persistBTCAccountConfig(
// If the account was added in the background as part of scanning, we don't mark it watchonly.
// Otherwise the account would appear automatically once it received funds, even if it was not
// visible before and the keystore is never connected again.
if !hiddenBecauseUnused && backend.config.AppConfig().Backend.Watchonly {
if !hiddenBecauseUnused && accountsConfig.IsWatchonly(rootFingerprint) {
t := true
accountWatch = &t
}
Expand Down Expand Up @@ -868,7 +885,7 @@ func (backend *Backend) persistETHAccountConfig(
}

var accountWatch *bool
if !hiddenBecauseUnused && backend.config.AppConfig().Backend.Watchonly {
if !hiddenBecauseUnused && accountsConfig.IsWatchonly(rootFingerprint) {
t := true
accountWatch = &t
}
Expand All @@ -887,8 +904,8 @@ func (backend *Backend) persistETHAccountConfig(
// The accountsAndKeystoreLock must be held when calling this function.
func (backend *Backend) initPersistedAccounts() {
// Only load accounts which belong to connected keystores or for which watchonly is enabled.
keystoreConnectedOrWatch := func(account *config.Account) bool {
if account.IsWatch(backend.config.AppConfig().Backend.Watchonly) {
keystoreConnectedOrWatch := func(keystore *config.Keystore, account *config.Account) bool {
if account.IsWatch(keystore.Watchonly) {
return true
}

Expand Down Expand Up @@ -924,17 +941,24 @@ outer:
// Watch-only accounts are loaded regardless, and if later e.g. a BitBox02 BTC-only is
// inserted with the same seed as a Multi, we will need to catch that mismatch when the
// keystore will be used to e.g. display an Ethereum address etc.
if backend.keystore != nil && !account.IsWatch(backend.config.AppConfig().Backend.Watchonly) {
switch coin.(type) {
case *btc.Coin:
for _, cfg := range account.SigningConfigurations {
if !backend.keystore.SupportsAccount(coin, cfg.ScriptType()) {
continue outer
if backend.keystore != nil {
rootFingerprint, err := backend.keystore.RootFingerprint()
if err != nil {
backend.log.WithError(err).Error("Could not retrieve root fingerprint")
continue
}
if !account.IsWatch(persistedAccounts.IsWatchonly(rootFingerprint)) {
switch coin.(type) {
case *btc.Coin:
for _, cfg := range account.SigningConfigurations {
if !backend.keystore.SupportsAccount(coin, cfg.ScriptType()) {
continue outer
}
}
default:
if !backend.keystore.SupportsAccount(coin, nil) {
continue
}
}
default:
if !backend.keystore.SupportsAccount(coin, nil) {
continue
}
}
}
Expand Down Expand Up @@ -1056,15 +1080,19 @@ func (backend *Backend) maybeAddP2TR(keystore keystore.Keystore, accounts []*con
// perform migrations, updates etc. We use it to add taproot subaccounts to Bitcoin accounts that
// were created (persisted) before the introduction of taproot support.
func (backend *Backend) updatePersistedAccounts(
keystore keystore.Keystore, accounts []*config.Account) error {
keystore keystore.Keystore, accountsConfig *config.AccountsConfig) error {

// setWatch, if the global Watchonly flag is enabled, sets the `Watch`
// flag to `true`, turning this account into a watch-only account.
setWatch := func() error {
if !backend.config.AppConfig().Backend.Watchonly {
rootFingerprint, err := keystore.RootFingerprint()
if err != nil {
return err
}
if !accountsConfig.IsWatchonly(rootFingerprint) {
return nil
}
for _, account := range accounts {
for _, account := range accountsConfig.Accounts {
// If the account was added in the background as part of scanning, we don't mark it
// watchonly. Otherwise the account would appear automatically once it received funds,
// even if it was not visible before and the keystore is never connected again.
Expand All @@ -1080,7 +1108,7 @@ func (backend *Backend) updatePersistedAccounts(
return err
}

return backend.maybeAddP2TR(keystore, accounts)
return backend.maybeAddP2TR(keystore, accountsConfig.Accounts)
}

// The accountsAndKeystoreLock must be held when calling this function.
Expand Down Expand Up @@ -1127,7 +1155,12 @@ func (backend *Backend) uninitAccounts(force bool) {
}
}

if !force && (belongsToKeystore || account.Config().Config.IsWatch(backend.config.AppConfig().Backend.Watchonly)) {
isWatchonly, err := backend.config.AccountsConfig().IsWatchonlyAccount(account.Config().Config)
if err != nil {
backend.log.WithError(err).Error("could not retrieve keystore fingerprint")
isWatchonly = false
}
if !force && (belongsToKeystore || isWatchonly) {
// Do not uninit/remove account that is being watched.
keep = append(keep, account)
continue
Expand Down Expand Up @@ -1297,7 +1330,11 @@ func (backend *Backend) checkAccountUsed(account accounts.Interface) {

// We only really show the account to the user now, so this is the moment to set the
// watchonly flag on it if the user has the global watchonly setting enabled.
if backend.config.AppConfig().Backend.Watchonly {
rootFingerprint, err := acct.SigningConfigurations.RootFingerprint()
if err != nil {
return err
}
if accountsConfig.IsWatchonly(rootFingerprint) {
t := true
acct.Watch = &t
}
Expand Down
20 changes: 12 additions & 8 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
Action: action.Reload,
})

belongsToKeystore := func(account *config.Account) bool {
belongsToKeystore := func(_ *config.Keystore, account *config.Account) bool {
return account.SigningConfigurations.ContainsRootFingerprint(fingerprint)
}

Expand All @@ -640,7 +640,7 @@ func (backend *Backend) registerKeystore(keystore keystore.Keystore) {
// needed on the persisted accounts.
accounts := backend.filterAccounts(accountsConfig, belongsToKeystore)
if len(accounts) != 0 {
return backend.updatePersistedAccounts(keystore, accounts)
return backend.updatePersistedAccounts(keystore, accountsConfig)
}
return backend.persistDefaultAccountConfigs(keystore, accountsConfig)
})
Expand Down Expand Up @@ -886,12 +886,16 @@ func (backend *Backend) CancelConnectKeystore() {
backend.connectKeystore.cancel(errUserAbort)
}

// SetWatchonly sets the app config's watchonly flag to `watchonly`.
// When enabling watchonly, all currently loaded accounts are turned into watchonly accounts.
// When disabling watchonly, all persisted accounts's watchonly status is reset.
func (backend *Backend) SetWatchonly(watchonly bool) error {
err := backend.config.ModifyAppConfig(func(config *config.AppConfig) error {
config.Backend.Watchonly = watchonly
// SetWatchonly sets the keystore's watchonly flag to `watchonly`.
// When enabling watchonly, all currently loaded accounts of that keystore are turned into watchonly accounts.
// When disabling watchonly, all the watchonly status of all of the keystore's persisted accounts is reset.
func (backend *Backend) SetWatchonly(rootFingerprint []byte, watchonly bool) error {
err := backend.config.ModifyAccountsConfig(func(config *config.AccountsConfig) error {
ks, err := config.LookupKeystore(rootFingerprint)
if err != nil {
return err
}
ks.Watchonly = watchonly
return nil
})
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions backend/config/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ func (acct *Account) IsWatch(defaultWatchonly bool) bool {

// Keystore holds information related to keystores such as the BitBox02.
type Keystore struct {
// Watchonly determines if accounts of this keystore should be loaded even if the keystore is not connected.
// If false, accounts are only loaded if this keystore is connected.
// If true, they are loaded and displayed when the app launches.
// Individual accounts can be exempt from being loaded even if this flag is true by setting the account's
// `Watch` flag to false.
Watchonly bool `json:"watchonly"`
// The root fingerprint is the first 32 bits of the hash160 of the pubkey at the keypath m/.
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers It serves as
// an identifier for the keystore. Collisions are possible but the chance is very small.
Expand Down Expand Up @@ -129,6 +135,22 @@ func (cfg AccountsConfig) LookupKeystore(rootFingerprint []byte) (*Keystore, err
return nil, errp.Newf("could not retrieve keystore for fingerprint %x", rootFingerprint)
}

func (cfg AccountsConfig) IsWatchonly(rootFingerprint []byte) bool {
ks, err := cfg.LookupKeystore(rootFingerprint)
if err != nil {
return false
}
return ks.Watchonly
}

func (cfg AccountsConfig) IsWatchonlyAccount(account *Account) (bool, error) {
rootFingerprint, err := account.SigningConfigurations.RootFingerprint()
if err != nil {
return false, err
}
return account.IsWatch(cfg.IsWatchonly(rootFingerprint)), nil
}

// GetOrAddKeystore looks up the keystore by root fingerprint. If it does not exist, one is added to
// the list of keystores and the newly created one is returned.
func (cfg *AccountsConfig) GetOrAddKeystore(rootFingerprint []byte) *Keystore {
Expand Down
7 changes: 0 additions & 7 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,6 @@ type Backend struct {

// BtcUnit is the unit used to represent Bitcoin amounts. See `coin.BtcUnit` for details.
BtcUnit coin.BtcUnit `json:"btcUnit"`

// Watchonly determines if accounts should be loaded even if their keystore is not conneced.
// If false, accounts are only loaded if their keystore is connected.
// If true, they are loaded and displayed when the app launches.
// Individual accounts can be exempt from being loaded even if this flag is true by setting the account's
// `Watch` flag to false.
Watchonly bool `json:"watchonly"`
}

// DeprecatedCoinActive returns the Active setting for a coin by code. This call is should not be
Expand Down
11 changes: 7 additions & 4 deletions backend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ type Backend interface {
GetAccountFromCode(code string) (accounts.Interface, error)
HTTPClient() *http.Client
CancelConnectKeystore()
SetWatchonly(watchonly bool) error
SetWatchonly(rootFingerprint []byte, watchonly bool) error
LookupEthAccountCode(address string) (accountsTypes.Code, string, error)
}

Expand Down Expand Up @@ -1354,11 +1354,14 @@ func (handlers *Handlers) postSetWatchonlyHandler(r *http.Request) interface{} {
type response struct {
Success bool `json:"success"`
}
var watchonly bool
if err := json.NewDecoder(r.Body).Decode(&watchonly); err != nil {
var request struct {
RootFingerprint jsonp.HexBytes `json:"rootFingerprint"`
Watchonly bool `json:"watchonly"`
}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return response{Success: false}
}
if err := handlers.backend.SetWatchonly(watchonly); err != nil {
if err := handlers.backend.SetWatchonly([]byte(request.RootFingerprint), request.Watchonly); err != nil {
return response{Success: false}
}
return response{Success: true}
Expand Down
1 change: 1 addition & 0 deletions frontends/web/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface IActiveToken {
}

export type TKeystore = {
watchonly: boolean;
rootFingerprint: string;
name: string;
};
Expand Down
4 changes: 2 additions & 2 deletions frontends/web/src/api/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ export const cancelConnectKeystore = (): Promise<void> => {
return apiPost('cancel-connect-keystore');
};

export const setWatchonly = (watchonly: boolean): Promise<ISuccess> => {
return apiPost('set-watchonly', watchonly);
export const setWatchonly = (rootFingerprint: string, watchonly: boolean): Promise<ISuccess> => {
return apiPost('set-watchonly', { rootFingerprint, watchonly });
};

export const authenticate = (force: boolean = false): Promise<void> => {
Expand Down
2 changes: 0 additions & 2 deletions frontends/web/src/routes/settings/appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { DisplaySatsToggleSetting } from './components/appearance/displaySatsTog
import { LanguageDropdownSetting } from './components/appearance/languageDropdownSetting';
import { ActiveCurrenciesDropdownSettingWithStore } from './components/appearance/activeCurrenciesDropdownSetting';
import { HideAmountsSetting } from './components/appearance/hideAmountsSetting';
import { WatchonlySetting } from './components/appearance/watchonlySetting';
import { WithSettingsTabs } from './components/tabs';
import { MobileHeader } from './components/mobile-header';
import { Guide } from '../../components/guide/guide';
Expand Down Expand Up @@ -53,7 +52,6 @@ export const Appearance = ({ deviceIDs, hasAccounts }: TPagePropsWithSettingsTab
<DarkmodeToggleSetting />
<DisplaySatsToggleSetting />
<HideAmountsSetting />
<WatchonlySetting />
</WithSettingsTabs>
</ViewContent>
</View>
Expand Down
Loading

0 comments on commit ac19ef5

Please sign in to comment.