Skip to content

Commit 005565d

Browse files
committed
Merge branch 'etherscanAPIv2'
2 parents 827c578 + e7260d9 commit 005565d

File tree

7 files changed

+257
-79
lines changed

7 files changed

+257
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Added a banner to remind user to backup their seed phrase when an account reaches a certain threshold.
77
- Gracefully shut down Electrum connections upon closing the app
88
- Show the selected coin's address on the confirmation screen
9+
- Fetch balances of multiple ETH accounts at the same time, instead of one by one.
910

1011
## v4.48.3
1112
- Linux: fix compatiblity with some versions of Mesa also when using the AppImage

backend/accounts_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,13 @@ func TestSortAccounts(t *testing.T) {
179179
{Code: "acct-tbtc", CoinCode: coinpkg.CodeTBTC},
180180
}
181181
backend := newBackend(t, testnetDisabled, regtestDisabled)
182+
unlockFN := backend.accountsAndKeystoreLock.Lock()
182183
for i := range accountConfigs {
183184
c, err := backend.Coin(accountConfigs[i].CoinCode)
184185
require.NoError(t, err)
185186
backend.createAndAddAccount(c, accountConfigs[i])
186187
}
188+
unlockFN()
187189

188190
expectedOrder := []accountsTypes.Code{
189191
"acct-btc-1",
@@ -747,6 +749,7 @@ func TestCreateAndAddAccount(t *testing.T) {
747749
// Add a Bitcoin account.
748750
coin, err := b.Coin(coinpkg.CodeBTC)
749751
require.NoError(t, err)
752+
unlockFN := b.accountsAndKeystoreLock.Lock()
750753
b.createAndAddAccount(
751754
coin,
752755
&config.Account{
@@ -757,6 +760,7 @@ func TestCreateAndAddAccount(t *testing.T) {
757760
},
758761
},
759762
)
763+
unlockFN()
760764
require.Len(t, b.Accounts(), 1)
761765
// Check some properties of the newly added account.
762766
acct := b.Accounts()[0]
@@ -767,6 +771,8 @@ func TestCreateAndAddAccount(t *testing.T) {
767771
// Add a Litecoin account.
768772
coin, err = b.Coin(coinpkg.CodeLTC)
769773
require.NoError(t, err)
774+
775+
unlockFN = b.accountsAndKeystoreLock.Lock()
770776
b.createAndAddAccount(coin,
771777
&config.Account{
772778
Code: "test-ltc-account-code",
@@ -776,6 +782,7 @@ func TestCreateAndAddAccount(t *testing.T) {
776782
},
777783
},
778784
)
785+
unlockFN()
779786
require.Len(t, b.Accounts(), 2)
780787
// Check some properties of the newly added account.
781788
acct = b.Accounts()[1]
@@ -786,6 +793,7 @@ func TestCreateAndAddAccount(t *testing.T) {
786793
// Add an Ethereum account with some active ERC20 tokens.
787794
coin, err = b.Coin(coinpkg.CodeETH)
788795
require.NoError(t, err)
796+
unlockFN = b.accountsAndKeystoreLock.Lock()
789797
b.createAndAddAccount(coin,
790798
&config.Account{
791799
Code: "test-eth-account-code",
@@ -796,6 +804,7 @@ func TestCreateAndAddAccount(t *testing.T) {
796804
ActiveTokens: []string{"eth-erc20-mkr"},
797805
},
798806
)
807+
unlockFN()
799808
// 2 more accounts: the added ETH account plus the active token for the ETH account.
800809
require.Len(t, b.Accounts(), 4)
801810
// Check some properties of the newly added account.
@@ -812,6 +821,7 @@ func TestCreateAndAddAccount(t *testing.T) {
812821
// Add another Ethereum account with some active ERC20 tokens.
813822
coin, err = b.Coin(coinpkg.CodeETH)
814823
require.NoError(t, err)
824+
unlockFN = b.accountsAndKeystoreLock.Lock()
815825
b.createAndAddAccount(coin,
816826
&config.Account{
817827
Code: "test-eth-account-code-2",
@@ -823,6 +833,7 @@ func TestCreateAndAddAccount(t *testing.T) {
823833
ActiveTokens: []string{"eth-erc20-usdt", "eth-erc20-bat"},
824834
},
825835
)
836+
unlockFN()
826837
// 3 more accounts: the added ETH account plus the two active tokens for the ETH account.
827838
require.Len(t, b.Accounts(), 7)
828839
// Check some properties of the newly added accounts.

backend/backend.go

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import (
6161
"github.com/btcsuite/btcd/chaincfg"
6262
"github.com/ethereum/go-ethereum/params"
6363
"github.com/sirupsen/logrus"
64+
"golang.org/x/time/rate"
6465
)
6566

6667
func init() {
@@ -225,10 +226,11 @@ type Backend struct {
225226

226227
socksProxy socksproxy.SocksProxy
227228
// can be a regular or, if Tor is enabled in the config, a SOCKS5 proxy client.
228-
httpClient *http.Client
229-
etherScanHTTPClient *http.Client
230-
ratesUpdater *rates.RateUpdater
231-
banners *banners.Banners
229+
httpClient *http.Client
230+
etherScanHTTPClient *http.Client
231+
etherScanRateLimiter *rate.Limiter
232+
ratesUpdater *rates.RateUpdater
233+
banners *banners.Banners
232234

233235
// For unit tests, called when `backend.checkAccountUsed()` is called.
234236
tstCheckAccountUsed func(accounts.Interface) bool
@@ -240,6 +242,15 @@ type Backend struct {
240242

241243
// isOnline indicates whether the backend is online, i.e. able to connect to the internet.
242244
isOnline atomic.Bool
245+
246+
// quit is used to indicate to running goroutines that they should stop as the backend is being closed
247+
quit chan struct{}
248+
249+
// enqueueUpdateForAccount is used to enqueue an update for an account.
250+
enqueueUpdateForAccount chan *eth.Account
251+
252+
// updateETHAccountsCh is used to trigger an update of all ETH accounts.
253+
updateETHAccountsCh chan struct{}
243254
}
244255

245256
// NewBackend creates a new backend with the given arguments.
@@ -262,11 +273,13 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe
262273
return nil, err
263274
}
264275

276+
accountUpdate := make(chan *eth.Account)
277+
265278
backend := &Backend{
266279
arguments: arguments,
267280
environment: environment,
268281
config: backendConfig,
269-
events: make(chan interface{}, 1000),
282+
events: make(chan interface{}),
270283

271284
devices: map[string]device.Interface{},
272285
coins: map[coinpkg.Code]coinpkg.Coin{},
@@ -276,12 +289,15 @@ func NewBackend(arguments *arguments.Arguments, environment Environment) (*Backe
276289
return btc.NewAccount(config, coin, gapLimits, getAddress, log, hclient)
277290
},
278291
makeEthAccount: func(config *accounts.AccountConfig, coin *eth.Coin, httpClient *http.Client, log *logrus.Entry) accounts.Interface {
279-
return eth.NewAccount(config, coin, httpClient, log)
292+
return eth.NewAccount(config, coin, httpClient, log, accountUpdate)
280293
},
281294

282295
log: log,
283296

284-
testing: backendConfig.AppConfig().Backend.StartInTestnet || arguments.Testing(),
297+
testing: backendConfig.AppConfig().Backend.StartInTestnet || arguments.Testing(),
298+
quit: make(chan struct{}),
299+
etherScanRateLimiter: rate.NewLimiter(rate.Limit(etherscan.CallsPerSec), 1),
300+
enqueueUpdateForAccount: accountUpdate,
285301
}
286302
// TODO: remove when connectivity check is present on all platforms
287303
backend.isOnline.Store(true)
@@ -529,19 +545,19 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
529545
coin = btc.NewCoin(coinpkg.CodeLTC, "Litecoin", "LTC", coinpkg.BtcUnitDefault, &ltc.MainNetParams, dbFolder, servers,
530546
"https://blockchair.com/litecoin/transaction/", backend.socksProxy)
531547
case code == coinpkg.CodeETH:
532-
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient)
548+
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
533549
coin = eth.NewCoin(etherScan, code, "Ethereum", "ETH", "ETH", params.MainnetChainConfig,
534550
"https://etherscan.io/tx/",
535551
etherScan,
536552
nil)
537553
case code == coinpkg.CodeSEPETH:
538-
etherScan := etherscan.NewEtherScan("11155111", backend.etherScanHTTPClient)
554+
etherScan := etherscan.NewEtherScan("11155111", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
539555
coin = eth.NewCoin(etherScan, code, "Ethereum Sepolia", "SEPETH", "SEPETH", params.SepoliaChainConfig,
540556
"https://sepolia.etherscan.io/tx/",
541557
etherScan,
542558
nil)
543559
case erc20Token != nil:
544-
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient)
560+
etherScan := etherscan.NewEtherScan("1", backend.etherScanHTTPClient, backend.etherScanRateLimiter)
545561
coin = eth.NewCoin(etherScan, erc20Token.code, erc20Token.name, erc20Token.unit, "ETH", params.MainnetChainConfig,
546562
"https://etherscan.io/tx/",
547563
etherScan,
@@ -555,6 +571,74 @@ func (backend *Backend) Coin(code coinpkg.Code) (coinpkg.Coin, error) {
555571
return coin, nil
556572
}
557573

574+
func (backend *Backend) pollETHAccounts() {
575+
timer := time.After(0)
576+
577+
updateAll := func() {
578+
if err := backend.updateETHAccounts(); err != nil {
579+
backend.log.WithError(err).Error("could not update ETH accounts")
580+
}
581+
}
582+
583+
for {
584+
select {
585+
case <-backend.quit:
586+
return
587+
default:
588+
select {
589+
case <-backend.quit:
590+
return
591+
case account := <-backend.enqueueUpdateForAccount:
592+
go func() {
593+
// A single ETH accounts needs an update.
594+
ethCoin, ok := account.Coin().(*eth.Coin)
595+
if !ok {
596+
backend.log.WithField("account", account.Config().Config.Name).Errorf("expected ETH account to have ETH coin, got %T", account.Coin())
597+
}
598+
etherScanClient := etherscan.NewEtherScan(ethCoin.ChainIDstr(), backend.etherScanHTTPClient, backend.etherScanRateLimiter)
599+
if err := eth.UpdateBalances([]*eth.Account{account}, etherScanClient); err != nil {
600+
backend.log.WithError(err).Errorf("could not update account %s", account.Config().Config.Name)
601+
}
602+
}()
603+
case <-backend.updateETHAccountsCh:
604+
go updateAll()
605+
timer = time.After(eth.PollInterval)
606+
case <-timer:
607+
go updateAll()
608+
timer = time.After(eth.PollInterval)
609+
}
610+
}
611+
}
612+
}
613+
614+
func (backend *Backend) updateETHAccounts() error {
615+
defer backend.accountsAndKeystoreLock.RLock()()
616+
backend.log.Debug("Updating ETH accounts balances")
617+
618+
accountsChainID := map[string][]*eth.Account{}
619+
for _, account := range backend.accounts {
620+
ethAccount, ok := account.(*eth.Account)
621+
if ok {
622+
ethCoin, ok := ethAccount.Coin().(*eth.Coin)
623+
if !ok {
624+
return errp.Newf("expected ETH account to have ETH coin, got %T", ethAccount.Coin())
625+
}
626+
chainID := ethCoin.ChainIDstr()
627+
accountsChainID[chainID] = append(accountsChainID[chainID], ethAccount)
628+
}
629+
630+
}
631+
632+
for chainID, ethAccounts := range accountsChainID {
633+
etherScanClient := etherscan.NewEtherScan(chainID, backend.etherScanHTTPClient, backend.etherScanRateLimiter)
634+
if err := eth.UpdateBalances(ethAccounts, etherScanClient); err != nil {
635+
backend.log.WithError(err).Errorf("could not update ETH accounts for chain ID %s", chainID)
636+
}
637+
}
638+
639+
return nil
640+
}
641+
558642
// ManualReconnect triggers reconnecting to Electrum servers if their connection is down.
559643
// Only coin connections that were previously established are reconnected.
560644
// Calling this is a no-op for coins that are already connected.
@@ -592,13 +676,7 @@ func (backend *Backend) ManualReconnect(reconnectETH bool) {
592676
}
593677
if reconnectETH {
594678
backend.log.Info("Reconnecting ETH accounts")
595-
for _, account := range backend.accounts {
596-
ethAccount, ok := account.(*eth.Account)
597-
if !ok {
598-
continue
599-
}
600-
ethAccount.EnqueueUpdate()
601-
}
679+
backend.updateETHAccountsCh <- struct{}{}
602680
}
603681
}
604682

@@ -661,6 +739,8 @@ func (backend *Backend) Start() <-chan interface{} {
661739

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

742+
go backend.pollETHAccounts()
743+
664744
if backend.config.AppConfig().Backend.StartInTestnet {
665745
if err := backend.config.ModifyAppConfig(func(c *config.AppConfig) error { c.Backend.StartInTestnet = false; return nil }); err != nil {
666746
backend.log.WithError(err).Error("Can't set StartInTestnet to false")
@@ -933,6 +1013,8 @@ func (backend *Backend) Close() error {
9331013
if len(errors) > 0 {
9341014
return errp.New(strings.Join(errors, "; "))
9351015
}
1016+
1017+
close(backend.quit)
9361018
return nil
9371019
}
9381020

0 commit comments

Comments
 (0)