Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 14 additions & 69 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ type Backend struct {
socksProxy socksproxy.SocksProxy
// can be a regular or, if Tor is enabled in the config, a SOCKS5 proxy client.
httpClient *http.Client
etherScanHTTPClient *http.Client
etherScanRateLimiter *rate.Limiter
ratesUpdater *rates.RateUpdater
banners *banners.Banners
Expand All @@ -243,14 +242,8 @@ type Backend struct {
// isOnline indicates whether the backend is online, i.e. able to connect to the internet.
isOnline atomic.Bool

// quit is used to indicate to running goroutines that they should stop as the backend is being closed
quit chan struct{}

// enqueueUpdateForAccount is used to enqueue an update for an account.
enqueueUpdateForAccount chan *eth.Account

// updateETHAccountsCh is used to trigger an update of all ETH accounts.
updateETHAccountsCh chan struct{}
// ethupdater takes care of updating ETH accounts.
ethupdater *eth.Updater
}

// NewBackend creates a new backend with the given arguments.
Expand Down Expand Up @@ -294,10 +287,8 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe

log: log,

testing: backendConfig.AppConfig().Backend.StartInTestnet || arguments.Testing(),
quit: make(chan struct{}),
etherScanRateLimiter: rate.NewLimiter(rate.Limit(etherscan.CallsPerSec), 1),
enqueueUpdateForAccount: accountUpdate,
testing: backendConfig.AppConfig().Backend.StartInTestnet || arguments.Testing(),
etherScanRateLimiter: rate.NewLimiter(rate.Limit(etherscan.CallsPerSec), 1),
}
// TODO: remove when connectivity check is present on all platforms
backend.isOnline.Store(true)
Expand All @@ -309,7 +300,7 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe
backend.notifier = notifier
backend.socksProxy = backendProxy
backend.httpClient = hclient
backend.etherScanHTTPClient = hclient
backend.ethupdater = eth.NewUpdater(accountUpdate, backend.httpClient, backend.etherScanRateLimiter, backend.updateETHAccounts)

ratesCache := filepath.Join(arguments.CacheDirectoryPath(), "exchangerates")
if err := os.MkdirAll(ratesCache, 0700); err != nil {
Expand Down Expand Up @@ -545,19 +536,19 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
case code == coinpkg.CodeETH:
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
etherScan := etherscan.NewEtherScan("1", backend.httpClient, backend.etherScanRateLimiter)
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
"https://etherscan.io/tx/",
etherScan,
nil)
case code == coinpkg.CodeSEPETH:
etherScan := etherscan.NewEtherScan("11155111", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
etherScan := etherscan.NewEtherScan("11155111", backend.httpClient, backend.etherScanRateLimiter)
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
"https://sepolia.etherscan.io/tx/",
etherScan,
nil)
case erc20Token != nil:
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
etherScan := etherscan.NewEtherScan("1", backend.httpClient, backend.etherScanRateLimiter)
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
"https://etherscan.io/tx/",
etherScan,
Expand All @@ -571,46 +562,6 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
return coin, nil
}

func (backend *Backend) pollETHAccounts() {
timer := time.After(0)

updateAll := func() {
if err := backend.updateETHAccounts(); err != nil {
backend.log.WithError(err).Error("could not update ETH accounts")
}
}

for {
select {
case <-backend.quit:
return
default:
select {
case <-backend.quit:
return
case account := <-backend.enqueueUpdateForAccount:
go func() {
// A single ETH accounts needs an update.
ethCoin, ok := account.Coin().(*eth.Coin)
if !ok {
backend.log.WithField("account", account.Config().Config.Name).Errorf("expected ETH account to have ETH coin, got %T", account.Coin())
}
etherScanClient := etherscan.NewEtherScan(ethCoin.ChainIDstr(), backend.etherScanHTTPClient, backend.etherScanRateLimiter)
if err := eth.UpdateBalances([]*eth.Account{account}, etherScanClient); err != nil {
backend.log.WithError(err).Errorf("could not update account %s", account.Config().Config.Name)
}
}()
case <-backend.updateETHAccountsCh:
go updateAll()
timer = time.After(eth.PollInterval)
case <-timer:
go updateAll()
timer = time.After(eth.PollInterval)
}
}
}
}

func (backend *Backend) updateETHAccounts() error {
defer backend.accountsAndKeystoreLock.RLock()()
backend.log.Debug("Updating ETH accounts balances")
Expand All @@ -619,21 +570,15 @@ func (backend *Backend) updateETHAccounts() error {
for _, account := range backend.accounts {
ethAccount, ok := account.(*eth.Account)
if ok {
ethCoin, ok := ethAccount.Coin().(*eth.Coin)
if !ok {
return errp.Newf("expected ETH account to have ETH coin, got %T", ethAccount.Coin())
}
chainID := ethCoin.ChainIDstr()
chainID := ethAccount.ETHCoin().ChainIDstr()
accountsChainID[chainID] = append(accountsChainID[chainID], ethAccount)
}

}

for chainID, ethAccounts := range accountsChainID {
etherScanClient := etherscan.NewEtherScan(chainID, backend.etherScanHTTPClient, backend.etherScanRateLimiter)
if err := eth.UpdateBalances(ethAccounts, etherScanClient); err != nil {
backend.log.WithError(err).Errorf("could not update ETH accounts for chain ID %s", chainID)
}
etherScanClient := etherscan.NewEtherScan(chainID, backend.httpClient, backend.etherScanRateLimiter)
backend.ethupdater.UpdateBalances(ethAccounts, etherScanClient)
}

return nil
Expand Down Expand Up @@ -676,7 +621,7 @@ func (backend *Backend) ManualReconnect(reconnectETH bool) {
}
if reconnectETH {
backend.log.Info("Reconnecting ETH accounts")
backend.updateETHAccountsCh <- struct{}{}
backend.ethupdater.EnqueueUpdateForAllAccounts()
}
}

Expand Down Expand Up @@ -739,7 +684,7 @@ func (backend *Backend) Start() <-chan interface{} {

backend.environment.OnAuthSettingChanged(backend.config.AppConfig().Backend.Authentication)

go backend.pollETHAccounts()
go backend.ethupdater.PollBalances()

if backend.config.AppConfig().Backend.StartInTestnet {
if err := backend.config.ModifyAppConfig(func(c *config.AppConfig) error { c.Backend.StartInTestnet = false; return nil }); err != nil {
Expand Down Expand Up @@ -1014,7 +959,7 @@ func (backend *Backend) Close() error {
return errp.New(strings.Join(errors, "; "))
}

close(backend.quit)
backend.ethupdater.Close()
return nil
}

Expand Down
67 changes: 3 additions & 64 deletions backend/coins/eth/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"path"
"strconv"
"strings"
"time"

"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts/errors"
Expand All @@ -49,9 +48,6 @@ import (
"github.com/sirupsen/logrus"
)

// PollInterval is the interval at which the account is polled for updates.
var PollInterval = 5 * time.Minute

func isMixedCase(s string) bool {
return strings.ToLower(s) != s && strings.ToUpper(s) != s
}
Expand Down Expand Up @@ -1019,64 +1015,7 @@ func (account *Account) EnqueueUpdate() {
account.enqueueUpdateCh <- account
}

// UpdateBalances updates the balances of the accounts in the provided slice.
func UpdateBalances(accounts []*Account, etherScanClient *etherscan.EtherScan) error {
ethNonErc20Addresses := make([]ethcommon.Address, 0, len(accounts))
for _, account := range accounts {
if account.isClosed() {
continue
}
address, err := account.Address()
if err != nil {
account.log.WithError(err).Errorf("Could not get address for account %s", account.Config().Config.Code)
account.SetOffline(err)
continue
}
if account.coin.erc20Token == nil {
ethNonErc20Addresses = append(ethNonErc20Addresses, address.Address)
}
}

balances, err := etherScanClient.Balances(context.TODO(), ethNonErc20Addresses)
if err != nil {
return errp.WithStack(err)
}

for _, account := range accounts {
if account.isClosed() {
continue
}
address, err := account.Address()
if err != nil {
account.log.WithError(err).Errorf("Could not get address for account %s", account.Config().Config.Code)
account.SetOffline(err)
continue
}
var balance *big.Int
if account.coin.erc20Token != nil {
var err error
balance, err = account.coin.client.ERC20Balance(account.address.Address, account.coin.erc20Token)
if err != nil {
account.log.WithError(err).Errorf("Could not get ERC20 balance for address %s", address.Address.Hex())
account.SetOffline(err)
continue
}
} else {
var ok bool
balance, ok = balances[address.Address]
if !ok {
errMsg := fmt.Sprintf("Could not find balance for address %s", address.Address.Hex())
account.log.Error(errMsg)
account.SetOffline(errp.New(errMsg))
continue
}
}
if err := account.Update(balance); err != nil {
account.log.WithError(err).Errorf("Could not update balance for address %s", address.Address.Hex())
account.SetOffline(err)
} else {
account.SetOffline(nil)
}
}
return nil
// ETHCoin returns the eth.Coin of the account.
func (account *Account) ETHCoin() *Coin {
return account.coin
}
84 changes: 84 additions & 0 deletions backend/coins/eth/mocks/balancefetcher.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading