fix(desktop/wallet): fix bug in balance_cache - balances and nonces (#3509)

were stored in cache by pointers, which caused falsy cache hits in loop
because pointers with same address were created for different block
numbers. Now cache uses block numbers of uint64 as key, which can
overflow but it is not a problem since we use this cache for values
comparison, not as user data.
Fix crash on nil pointer in log.
Remove some unused code.
Protect nonceRanges with mutex while reading.

Updates #10246
This commit is contained in:
IvanBelyakoff 2023-05-19 13:46:54 +02:00 committed by GitHub
parent 94c7cd32af
commit 17aaaf1dca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 33 additions and 28 deletions

View File

@ -17,8 +17,8 @@ type nonceRange struct {
type balanceCache struct { type balanceCache struct {
// balances maps an address to a map of a block number and the balance of this particular address // balances maps an address to a map of a block number and the balance of this particular address
balances map[common.Address]map[*big.Int]*big.Int balances map[common.Address]map[uint64]*big.Int // we don't care about block number overflow as we use cache only for comparing balances when fetching, not for UI
nonces map[common.Address]map[*big.Int]*int64 nonces map[common.Address]map[uint64]*int64 // we don't care about block number overflow as we use cache only for comparing balances when fetching, not for UI
nonceRanges map[common.Address]map[int64]nonceRange nonceRanges map[common.Address]map[int64]nonceRange
sortedRanges map[common.Address][]nonceRange sortedRanges map[common.Address][]nonceRange
rw sync.RWMutex rw sync.RWMutex
@ -33,7 +33,7 @@ func (b *balanceCache) ReadCachedBalance(account common.Address, blockNumber *bi
b.rw.RLock() b.rw.RLock()
defer b.rw.RUnlock() defer b.rw.RUnlock()
return b.balances[account][blockNumber] return b.balances[account][blockNumber.Uint64()]
} }
func (b *balanceCache) addBalanceToCache(account common.Address, blockNumber *big.Int, balance *big.Int) { func (b *balanceCache) addBalanceToCache(account common.Address, blockNumber *big.Int, balance *big.Int) {
@ -42,9 +42,9 @@ func (b *balanceCache) addBalanceToCache(account common.Address, blockNumber *bi
_, exists := b.balances[account] _, exists := b.balances[account]
if !exists { if !exists {
b.balances[account] = make(map[*big.Int]*big.Int) b.balances[account] = make(map[uint64]*big.Int)
} }
b.balances[account][blockNumber] = balance b.balances[account][blockNumber.Uint64()] = balance
} }
func (b *balanceCache) BalanceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (b *balanceCache) BalanceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*big.Int, error) {
@ -65,7 +65,7 @@ func (b *balanceCache) ReadCachedNonce(account common.Address, blockNumber *big.
b.rw.RLock() b.rw.RLock()
defer b.rw.RUnlock() defer b.rw.RUnlock()
return b.nonces[account][blockNumber] return b.nonces[account][blockNumber.Uint64()]
} }
func (b *balanceCache) sortRanges(account common.Address) { func (b *balanceCache) sortRanges(account common.Address) {
@ -74,7 +74,7 @@ func (b *balanceCache) sortRanges(account common.Address) {
keys = append(keys, int(k)) keys = append(keys, int(k))
} }
sort.Ints(keys) sort.Ints(keys) // This will not work for keys > 2^31
ranges := []nonceRange{} ranges := []nonceRange{}
for _, k := range keys { for _, k := range keys {
@ -86,6 +86,9 @@ func (b *balanceCache) sortRanges(account common.Address) {
} }
func (b *balanceCache) findNonceInRange(account common.Address, block *big.Int) *int64 { func (b *balanceCache) findNonceInRange(account common.Address, block *big.Int) *int64 {
b.rw.RLock()
defer b.rw.RUnlock()
for k := range b.sortedRanges[account] { for k := range b.sortedRanges[account] {
nr := b.sortedRanges[account][k] nr := b.sortedRanges[account][k]
cmpMin := nr.min.Cmp(block) cmpMin := nr.min.Cmp(block)
@ -137,9 +140,9 @@ func (b *balanceCache) addNonceToCache(account common.Address, blockNumber *big.
_, exists := b.nonces[account] _, exists := b.nonces[account]
if !exists { if !exists {
b.nonces[account] = make(map[*big.Int]*int64) b.nonces[account] = make(map[uint64]*int64)
} }
b.nonces[account][blockNumber] = nonce b.nonces[account][blockNumber.Uint64()] = nonce
b.updateNonceRange(account, blockNumber, nonce) b.updateNonceRange(account, blockNumber, nonce)
} }
@ -166,8 +169,8 @@ func (b *balanceCache) NonceAt(ctx context.Context, client BalanceReader, accoun
func newBalanceCache() *balanceCache { func newBalanceCache() *balanceCache {
return &balanceCache{ return &balanceCache{
balances: make(map[common.Address]map[*big.Int]*big.Int), balances: make(map[common.Address]map[uint64]*big.Int),
nonces: make(map[common.Address]map[*big.Int]*int64), nonces: make(map[common.Address]map[uint64]*int64),
nonceRanges: make(map[common.Address]map[int64]nonceRange), nonceRanges: make(map[common.Address]map[int64]nonceRange),
sortedRanges: make(map[common.Address][]nonceRange), sortedRanges: make(map[common.Address][]nonceRange),
} }

View File

@ -78,7 +78,8 @@ func (c *ethHistoricalCommand) Run(ctx context.Context) (err error) {
if c.from.Number != nil && c.from.Nonce != nil { if c.from.Number != nil && c.from.Nonce != nil {
c.balanceCache.addNonceToCache(c.address, c.from.Number, c.from.Nonce) c.balanceCache.addNonceToCache(c.address, c.from.Number, c.from.Nonce)
} }
from, headers, startBlock, err := findBlocksWithEthTransfers(ctx, c.chainClient, c.balanceCache, c.eth, c.address, c.from.Number, c.to, c.noLimit, c.threadLimit) from, headers, startBlock, err := findBlocksWithEthTransfers(ctx, c.chainClient,
c.balanceCache, c.eth, c.address, c.from.Number, c.to, c.noLimit, c.threadLimit)
if err != nil { if err != nil {
c.error = err c.error = err
@ -387,6 +388,15 @@ func (c *transfersCommand) Run(ctx context.Context) (err error) {
log.Error("SaveTransfers error", "error", err) log.Error("SaveTransfers error", "error", err)
return err return err
} }
} else {
// If no transfers found, that is suspecting, because downloader returned this block as containing transfers
log.Error("no transfers found in block", "chain", c.chainClient.ChainID, "address", c.address, "block", c.blockNum)
err = markBlocksAsLoaded(c.chainClient.ChainID, c.db.client, c.address, []*big.Int{c.blockNum})
if err != nil {
log.Error("Mark blocks loaded error", "error", err)
return err
}
} }
c.fetchedTransfers = allTransfers c.fetchedTransfers = allTransfers
@ -496,7 +506,8 @@ func (c *findAndCheckBlockRangeCommand) Run(parent context.Context) (err error)
foundHeaders[address] = uniqHeaders foundHeaders[address] = uniqHeaders
lastBlockNumber := c.toByAddress[address] lastBlockNumber := c.toByAddress[address]
log.Debug("saving headers", "len", len(uniqHeaders), "lastBlockNumber", lastBlockNumber, "balance", c.balanceCache.ReadCachedBalance(address, lastBlockNumber), "nonce", c.balanceCache.ReadCachedNonce(address, lastBlockNumber)) log.Debug("saving headers", "len", len(uniqHeaders), "lastBlockNumber", lastBlockNumber,
"balance", c.balanceCache.ReadCachedBalance(address, lastBlockNumber), "nonce", c.balanceCache.ReadCachedNonce(address, lastBlockNumber))
to := &Block{ to := &Block{
Number: lastBlockNumber, Number: lastBlockNumber,
Balance: c.balanceCache.ReadCachedBalance(address, lastBlockNumber), Balance: c.balanceCache.ReadCachedBalance(address, lastBlockNumber),

View File

@ -3,7 +3,6 @@ package transfer
import ( import (
"context" "context"
"math/big" "math/big"
"sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -56,7 +55,7 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
c.error = err c.error = err
} }
return return nil // We break the loop if we fetched all the blocks
} }
var head *types.Header = nil var head *types.Header = nil
@ -93,7 +92,7 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
// 'to' is set to 'head' if 'last' block not found in DB // 'to' is set to 'head' if 'last' block not found in DB
if head != nil && to.Cmp(head.Number) == 0 { if head != nil && to.Cmp(head.Number) == 0 {
log.Info("update blockrange", "head", head.Number, "to", to, "chain", c.chainClient.ChainID, "account", c.account) log.Info("upsert blockrange", "head", head.Number, "to", to, "chain", c.chainClient.ChainID, "account", c.account)
err = c.blockDAO.upsertRange(c.chainClient.ChainID, c.account, c.startBlockNumber, err = c.blockDAO.upsertRange(c.chainClient.ChainID, c.account, c.startBlockNumber,
c.resFromBlock.Number, to) c.resFromBlock.Number, to)
@ -147,13 +146,13 @@ func (c *findBlocksCommand) checkRange(parent context.Context, from *big.Int, to
fromBlock := &Block{Number: from} fromBlock := &Block{Number: from}
newFromBlock, ethHeaders, startBlock, err := c.fastIndex(parent, c.balanceCache, fromBlock, to) newFromBlock, ethHeaders, startBlock, err := c.fastIndex(parent, c.balanceCache, fromBlock, to)
log.Info("findBlocksCommand checkRange", "startBlock", startBlock, "newFromBlock", newFromBlock.Number, "toBlockNumber", to, "noLimit", c.noLimit)
if err != nil { if err != nil {
log.Info("findBlocksCommand checkRange fastIndex", "err", err) log.Info("findBlocksCommand checkRange fastIndex", "err", err)
c.error = err c.error = err
// return err // In case c.noLimit is true, hystrix "max concurrency" may be reached and we will not be able to index ETH transfers // return err // In case c.noLimit is true, hystrix "max concurrency" may be reached and we will not be able to index ETH transfers
return nil, nil return nil, nil
} }
log.Info("findBlocksCommand checkRange", "startBlock", startBlock, "newFromBlock", newFromBlock.Number, "toBlockNumber", to, "noLimit", c.noLimit)
// TODO There should be transfers when either when we have found headers // TODO There should be transfers when either when we have found headers
// or when newFromBlock is different from fromBlock, but if I check for // or when newFromBlock is different from fromBlock, but if I check for
@ -202,10 +201,6 @@ func (c *findBlocksCommand) checkRange(parent context.Context, from *big.Int, to
// return err // return err
return nil, nil return nil, nil
} }
sort.SliceStable(foundHeaders, func(i, j int) bool {
return foundHeaders[i].Number.Cmp(foundHeaders[j].Number) == 1
})
} }
// } // }
@ -348,7 +343,6 @@ type loadBlocksAndTransfersCommand struct {
db *Database db *Database
blockRangeDAO *BlockRangeSequentialDAO blockRangeDAO *BlockRangeSequentialDAO
blockDAO *BlockDAO blockDAO *BlockDAO
erc20 *ERC20TransfersDownloader
chainClient *chain.ClientWithFallback chainClient *chain.ClientWithFallback
feed *event.Feed feed *event.Feed
balanceCache *balanceCache balanceCache *balanceCache

View File

@ -139,7 +139,7 @@ func checkRangesWithStartBlock(parent context.Context, client BalanceReader, cac
return err return err
} }
if lb.Cmp(hb) == 0 { if lb.Cmp(hb) == 0 {
log.Debug("balances are equal", "from", from, "to", to) log.Debug("balances are equal", "from", from, "to", to, "lb", lb, "hb", hb)
hn, err := cache.NonceAt(ctx, client, account, to) hn, err := cache.NonceAt(ctx, client, account, to)
if err != nil { if err != nil {
@ -166,7 +166,7 @@ func checkRangesWithStartBlock(parent context.Context, client BalanceReader, cac
return err return err
} }
if *ln == *hn { if *ln == *hn {
log.Debug("transaction count is also equal", "from", from, "to", to) log.Debug("transaction count is also equal", "from", from, "to", to, "ln", *ln, "hn", *hn)
return nil return nil
} }
} }

View File

@ -20,7 +20,7 @@ const (
ReactorNotStarted string = "reactor not started" ReactorNotStarted string = "reactor not started"
NonArchivalNodeBlockChunkSize = 100 NonArchivalNodeBlockChunkSize = 100
DefaultNodeBlockChunkSize = 10000 DefaultNodeBlockChunkSize = 100000
) )
var errAlreadyRunning = errors.New("already running") var errAlreadyRunning = errors.New("already running")

View File

@ -6,7 +6,6 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/rpc/chain" "github.com/status-im/status-go/rpc/chain"
@ -42,14 +41,12 @@ type SequentialFetchStrategy struct {
func (s *SequentialFetchStrategy) newCommand(chainClient *chain.ClientWithFallback, func (s *SequentialFetchStrategy) newCommand(chainClient *chain.ClientWithFallback,
accounts []common.Address) async.Commander { accounts []common.Address) async.Commander {
signer := types.NewLondonSigner(chainClient.ToBigInt())
ctl := &loadBlocksAndTransfersCommand{ ctl := &loadBlocksAndTransfersCommand{
db: s.db, db: s.db,
chainClient: chainClient, chainClient: chainClient,
accounts: accounts, accounts: accounts,
blockRangeDAO: &BlockRangeSequentialDAO{s.db.client}, blockRangeDAO: &BlockRangeSequentialDAO{s.db.client},
blockDAO: s.blockDAO, blockDAO: s.blockDAO,
erc20: NewERC20TransfersDownloader(chainClient, accounts, signer),
feed: s.feed, feed: s.feed,
errorsCount: 0, errorsCount: 0,
transactionManager: s.transactionManager, transactionManager: s.transactionManager,