fix(wallet): added ChainID to balance cache, as now it is shared between

services and contains balances for all addresses and chains.
Made rpc chain client return ChainID property on `NetworkID()` method
This commit is contained in:
Ivan Belyakov 2023-09-06 11:13:46 +02:00 committed by IvanBelyakoff
parent 83d1354845
commit 81b94b7a4e
5 changed files with 131 additions and 92 deletions

View File

@ -498,20 +498,8 @@ func (c *ClientWithFallback) SubscribeNewHead(ctx context.Context, ch chan<- *ty
return sub.(ethereum.Subscription), nil return sub.(ethereum.Subscription), nil
} }
func (c *ClientWithFallback) NetworkID(ctx context.Context) (*big.Int, error) { func (c *ClientWithFallback) NetworkID() uint64 {
rpcstats.CountCall("eth_NetworkID") return c.ChainID
networkID, err := c.makeCallSingleReturn(
func() (any, error) { return c.main.NetworkID(ctx) },
func() (any, error) { return c.fallback.NetworkID(ctx) },
true,
)
if err != nil {
return nil, err
}
return networkID.(*big.Int), nil
} }
func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (c *ClientWithFallback) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {

View File

@ -2,6 +2,7 @@ package balance
import ( import (
"context" "context"
"math"
"math/big" "math/big"
"sort" "sort"
"sync" "sync"
@ -23,6 +24,7 @@ type Reader interface {
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error)
NetworkID() uint64
} }
// Cacher interface for caching balance to BalanceCache. Requires BalanceReader to fetch balance. // Cacher interface for caching balance to BalanceCache. Requires BalanceReader to fetch balance.
@ -35,28 +37,33 @@ type Cacher interface {
// Interface for cache of balances. // Interface for cache of balances.
type CacheIface interface { type CacheIface interface {
GetBalance(account common.Address, blockNumber *big.Int) *big.Int GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int
GetNonce(account common.Address, blockNumber *big.Int) *int64 GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64
AddBalance(account common.Address, blockNumber *big.Int, balance *big.Int) AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int)
AddNonce(account common.Address, blockNumber *big.Int, nonce *int64) AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64)
Clear() Clear()
} }
type balanceCacheType map[common.Address]map[uint64]map[uint64]*big.Int // address->chainID->blockNumber->balance
type nonceCacheType map[common.Address]map[uint64]map[uint64]*int64 // address->chainID->blockNumber->nonce
type nonceRangesCacheType map[common.Address]map[uint64]map[int64]nonceRange // address->chainID->blockNumber->nonceRange
type sortedNonceRangesCacheType map[common.Address]map[uint64][]nonceRange // address->chainID->[]nonceRange
type Cache struct { type Cache 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[uint64]*big.Int // we don't care about block number overflow as we use cache only for comparing balances when fetching, not for UI balances balanceCacheType
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 nonces nonceCacheType
nonceRanges map[common.Address]map[int64]nonceRange nonceRanges nonceRangesCacheType
sortedRanges map[common.Address][]nonceRange sortedRanges sortedNonceRangesCacheType
rw sync.RWMutex rw sync.RWMutex
} }
func NewCache() *Cache { func NewCache() *Cache {
return &Cache{ return &Cache{
balances: make(map[common.Address]map[uint64]*big.Int), balances: make(balanceCacheType),
nonces: make(map[common.Address]map[uint64]*int64), nonces: make(nonceCacheType),
nonceRanges: make(map[common.Address]map[int64]nonceRange), nonceRanges: make(nonceRangesCacheType),
sortedRanges: make(map[common.Address][]nonceRange), sortedRanges: make(sortedNonceRangesCacheType),
} }
} }
@ -64,13 +71,18 @@ func (b *Cache) Clear() {
b.rw.Lock() b.rw.Lock()
defer b.rw.Unlock() defer b.rw.Unlock()
for address, cache := range b.balances { for address, chainCache := range b.balances {
if len(chainCache) == 0 {
continue
}
for chainID, cache := range chainCache {
if len(cache) == 0 { if len(cache) == 0 {
continue continue
} }
var maxBlock uint64 = 0 var maxBlock uint64 = 0
var minBlock uint64 = 18446744073709551615 var minBlock uint64 = math.MaxUint64
for key := range cache { for key := range cache {
if key > maxBlock { if key > maxBlock {
maxBlock = key maxBlock = key
@ -82,15 +94,17 @@ func (b *Cache) Clear() {
newCache := make(map[uint64]*big.Int) newCache := make(map[uint64]*big.Int)
newCache[maxBlock] = cache[maxBlock] newCache[maxBlock] = cache[maxBlock]
newCache[minBlock] = cache[minBlock] newCache[minBlock] = cache[minBlock]
b.balances[address] = newCache b.balances[address][chainID] = newCache
} }
for address, cache := range b.nonces { }
if len(cache) == 0 { for address, chainCache := range b.nonces {
if len(chainCache) == 0 {
continue continue
} }
for chainID, cache := range chainCache {
var maxBlock uint64 = 0 var maxBlock uint64 = 0
var minBlock uint64 = 18446744073709551615 var minBlock uint64 = math.MaxUint64
for key := range cache { for key := range cache {
if key > maxBlock { if key > maxBlock {
maxBlock = key maxBlock = key
@ -102,32 +116,43 @@ func (b *Cache) Clear() {
newCache := make(map[uint64]*int64) newCache := make(map[uint64]*int64)
newCache[maxBlock] = cache[maxBlock] newCache[maxBlock] = cache[maxBlock]
newCache[minBlock] = cache[minBlock] newCache[minBlock] = cache[minBlock]
b.nonces[address] = newCache b.nonces[address][chainID] = newCache
} }
b.nonceRanges = make(map[common.Address]map[int64]nonceRange) }
b.sortedRanges = make(map[common.Address][]nonceRange) b.nonceRanges = make(nonceRangesCacheType)
b.sortedRanges = make(sortedNonceRangesCacheType)
} }
func (b *Cache) GetBalance(account common.Address, blockNumber *big.Int) *big.Int { func (b *Cache) GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int {
b.rw.RLock() b.rw.RLock()
defer b.rw.RUnlock() defer b.rw.RUnlock()
return b.balances[account][blockNumber.Uint64()] if b.balances[account] == nil || b.balances[account][chainID] == nil {
return nil
}
return b.balances[account][chainID][blockNumber.Uint64()]
} }
func (b *Cache) AddBalance(account common.Address, blockNumber *big.Int, balance *big.Int) { func (b *Cache) AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int) {
b.rw.Lock() b.rw.Lock()
defer b.rw.Unlock() defer b.rw.Unlock()
_, exists := b.balances[account] _, exists := b.balances[account]
if !exists { if !exists {
b.balances[account] = make(map[uint64]*big.Int) b.balances[account] = make(map[uint64]map[uint64]*big.Int)
} }
b.balances[account][blockNumber.Uint64()] = balance
_, exists = b.balances[account][chainID]
if !exists {
b.balances[account][chainID] = make(map[uint64]*big.Int)
}
b.balances[account][chainID][blockNumber.Uint64()] = balance
} }
func (b *Cache) BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error) { func (b *Cache) BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error) {
cachedBalance := b.GetBalance(account, blockNumber) cachedBalance := b.GetBalance(account, client.NetworkID(), blockNumber)
if cachedBalance != nil { if cachedBalance != nil {
return cachedBalance, nil return cachedBalance, nil
} }
@ -135,25 +160,28 @@ func (b *Cache) BalanceAt(ctx context.Context, client Reader, account common.Add
if err != nil { if err != nil {
return nil, err return nil, err
} }
b.AddBalance(account, blockNumber, balance) b.AddBalance(account, client.NetworkID(), blockNumber, balance)
return balance, nil return balance, nil
} }
func (b *Cache) GetNonce(account common.Address, blockNumber *big.Int) *int64 { func (b *Cache) GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64 {
b.rw.RLock() b.rw.RLock()
defer b.rw.RUnlock() defer b.rw.RUnlock()
return b.nonces[account][blockNumber.Uint64()] if b.nonces[account] == nil || b.nonces[account][chainID] == nil {
return nil
}
return b.nonces[account][chainID][blockNumber.Uint64()]
} }
func (b *Cache) Cache() CacheIface { func (b *Cache) Cache() CacheIface {
return b return b
} }
func (b *Cache) sortRanges(account common.Address) { func (b *Cache) sortRanges(account common.Address, chainID uint64) {
keys := make([]int, 0, len(b.nonceRanges[account])) keys := make([]int, 0, len(b.nonceRanges[account][chainID]))
for k := range b.nonceRanges[account] { for k := range b.nonceRanges[account][chainID] {
keys = append(keys, int(k)) keys = append(keys, int(k))
} }
@ -161,19 +189,24 @@ func (b *Cache) sortRanges(account common.Address) {
ranges := []nonceRange{} ranges := []nonceRange{}
for _, k := range keys { for _, k := range keys {
r := b.nonceRanges[account][int64(k)] r := b.nonceRanges[account][chainID][int64(k)]
ranges = append(ranges, r) ranges = append(ranges, r)
} }
b.sortedRanges[account] = ranges _, exists := b.sortedRanges[account]
if !exists {
b.sortedRanges[account] = make(map[uint64][]nonceRange)
}
b.sortedRanges[account][chainID] = ranges
} }
func (b *Cache) findNonceInRange(account common.Address, block *big.Int) *int64 { func (b *Cache) findNonceInRange(account common.Address, chainID uint64, block *big.Int) *int64 {
b.rw.RLock() b.rw.RLock()
defer b.rw.RUnlock() defer b.rw.RUnlock()
for k := range b.sortedRanges[account] { for k := range b.sortedRanges[account][chainID] {
nr := b.sortedRanges[account][k] nr := b.sortedRanges[account][chainID][k]
cmpMin := nr.min.Cmp(block) cmpMin := nr.min.Cmp(block)
if cmpMin == 1 { if cmpMin == 1 {
return nil return nil
@ -190,19 +223,24 @@ func (b *Cache) findNonceInRange(account common.Address, block *big.Int) *int64
return nil return nil
} }
func (b *Cache) updateNonceRange(account common.Address, blockNumber *big.Int, nonce *int64) { func (b *Cache) updateNonceRange(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
_, exists := b.nonceRanges[account] _, exists := b.nonceRanges[account]
if !exists { if !exists {
b.nonceRanges[account] = make(map[int64]nonceRange) b.nonceRanges[account] = make(map[uint64]map[int64]nonceRange)
} }
nr, exists := b.nonceRanges[account][*nonce] _, exists = b.nonceRanges[account][chainID]
if !exists {
b.nonceRanges[account][chainID] = make(map[int64]nonceRange)
}
nr, exists := b.nonceRanges[account][chainID][*nonce]
if !exists { if !exists {
r := nonceRange{ r := nonceRange{
max: big.NewInt(0).Set(blockNumber), max: big.NewInt(0).Set(blockNumber),
min: big.NewInt(0).Set(blockNumber), min: big.NewInt(0).Set(blockNumber),
nonce: *nonce, nonce: *nonce,
} }
b.nonceRanges[account][*nonce] = r b.nonceRanges[account][chainID][*nonce] = r
} else { } else {
if nr.max.Cmp(blockNumber) == -1 { if nr.max.Cmp(blockNumber) == -1 {
nr.max.Set(blockNumber) nr.max.Set(blockNumber)
@ -212,29 +250,34 @@ func (b *Cache) updateNonceRange(account common.Address, blockNumber *big.Int, n
nr.min.Set(blockNumber) nr.min.Set(blockNumber)
} }
b.nonceRanges[account][*nonce] = nr b.nonceRanges[account][chainID][*nonce] = nr
b.sortRanges(account) b.sortRanges(account, chainID)
} }
} }
func (b *Cache) AddNonce(account common.Address, blockNumber *big.Int, nonce *int64) { func (b *Cache) AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
b.rw.Lock() b.rw.Lock()
defer b.rw.Unlock() defer b.rw.Unlock()
_, exists := b.nonces[account] _, exists := b.nonces[account]
if !exists { if !exists {
b.nonces[account] = make(map[uint64]*int64) b.nonces[account] = make(map[uint64]map[uint64]*int64)
} }
b.nonces[account][blockNumber.Uint64()] = nonce
b.updateNonceRange(account, blockNumber, nonce) _, exists = b.nonces[account][chainID]
if !exists {
b.nonces[account][chainID] = make(map[uint64]*int64)
}
b.nonces[account][chainID][blockNumber.Uint64()] = nonce
b.updateNonceRange(account, chainID, blockNumber, nonce)
} }
func (b *Cache) NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error) { func (b *Cache) NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error) {
cachedNonce := b.GetNonce(account, blockNumber) cachedNonce := b.GetNonce(account, client.NetworkID(), blockNumber)
if cachedNonce != nil { if cachedNonce != nil {
return cachedNonce, nil return cachedNonce, nil
} }
rangeNonce := b.findNonceInRange(account, blockNumber) rangeNonce := b.findNonceInRange(account, client.NetworkID(), blockNumber)
if rangeNonce != nil { if rangeNonce != nil {
return rangeNonce, nil return rangeNonce, nil
} }
@ -244,7 +287,7 @@ func (b *Cache) NonceAt(ctx context.Context, client Reader, account common.Addre
return nil, err return nil, err
} }
int64Nonce := int64(nonce) int64Nonce := int64(nonce)
b.AddNonce(account, blockNumber, &int64Nonce) b.AddNonce(account, client.NetworkID(), blockNumber, &int64Nonce)
return &int64Nonce, nil return &int64Nonce, nil
} }

View File

@ -82,10 +82,10 @@ func (c *ethHistoricalCommand) Run(ctx context.Context) (err error) {
start := time.Now() start := time.Now()
if c.from.Number != nil && c.from.Balance != nil { if c.from.Number != nil && c.from.Balance != nil {
c.balanceCacher.Cache().AddBalance(c.address, c.from.Number, c.from.Balance) c.balanceCacher.Cache().AddBalance(c.address, c.chainClient.ChainID, c.from.Number, c.from.Balance)
} }
if c.from.Number != nil && c.from.Nonce != nil { if c.from.Number != nil && c.from.Nonce != nil {
c.balanceCacher.Cache().AddNonce(c.address, c.from.Number, c.from.Nonce) c.balanceCacher.Cache().AddNonce(c.address, c.chainClient.ChainID, c.from.Number, c.from.Nonce)
} }
from, headers, startBlock, err := findBlocksWithEthTransfers(ctx, c.chainClient, from, headers, startBlock, err := findBlocksWithEthTransfers(ctx, c.chainClient,
c.balanceCacher, c.address, c.from.Number, c.to, c.noLimit, c.threadLimit) c.balanceCacher, c.address, c.from.Number, c.to, c.noLimit, c.threadLimit)
@ -301,6 +301,7 @@ func (c *controlCommand) Run(parent context.Context) error {
event := walletevent.Event{ event := walletevent.Event{
Type: EventNewTransfers, Type: EventNewTransfers,
Accounts: []common.Address{address}, Accounts: []common.Address{address},
ChainID: c.chainClient.ChainID,
} }
for _, header := range cmnd.foundHeaders[address] { for _, header := range cmnd.foundHeaders[address] {
if event.BlockNumber == nil || header.Number.Cmp(event.BlockNumber) == 1 { if event.BlockNumber == nil || header.Number.Cmp(event.BlockNumber) == 1 {
@ -590,6 +591,7 @@ func (c *transfersCommand) notifyOfNewTransfers(transfers []Transfer) {
c.feed.Send(walletevent.Event{ c.feed.Send(walletevent.Event{
Type: EventNewTransfers, Type: EventNewTransfers,
Accounts: []common.Address{c.address}, Accounts: []common.Address{c.address},
ChainID: c.chainClient.ChainID,
}) })
} }
} }
@ -691,12 +693,13 @@ func (c *findAndCheckBlockRangeCommand) Run(parent context.Context) error {
lastBlockNumber := c.toByAddress[address] lastBlockNumber := c.toByAddress[address]
log.Debug("saving headers", "len", len(uniqHeaders), "lastBlockNumber", lastBlockNumber, log.Debug("saving headers", "len", len(uniqHeaders), "lastBlockNumber", lastBlockNumber,
"balance", c.balanceCacher.Cache().GetBalance(address, lastBlockNumber), "nonce", c.balanceCacher.Cache().GetNonce(address, lastBlockNumber)) "balance", c.balanceCacher.Cache().GetBalance(address, c.chainClient.ChainID, lastBlockNumber),
"nonce", c.balanceCacher.Cache().GetNonce(address, c.chainClient.ChainID, lastBlockNumber))
to := &Block{ to := &Block{
Number: lastBlockNumber, Number: lastBlockNumber,
Balance: c.balanceCacher.Cache().GetBalance(address, lastBlockNumber), Balance: c.balanceCacher.Cache().GetBalance(address, c.chainClient.ChainID, lastBlockNumber),
Nonce: c.balanceCacher.Cache().GetNonce(address, lastBlockNumber), Nonce: c.balanceCacher.Cache().GetNonce(address, c.chainClient.ChainID, lastBlockNumber),
} }
log.Debug("uniqHeaders found for account", "address", address, "uniqHeaders.len", len(uniqHeaders)) log.Debug("uniqHeaders found for account", "address", address, "uniqHeaders.len", len(uniqHeaders))
err = c.db.ProcessBlocks(c.chainClient.ChainID, address, newFromByAddress[address], to, uniqHeaders) err = c.db.ProcessBlocks(c.chainClient.ChainID, address, newFromByAddress[address], to, uniqHeaders)

View File

@ -113,8 +113,8 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
if len(headers) > 0 { if len(headers) > 0 {
log.Debug("findBlocksCommand saving headers", "len", len(headers), "lastBlockNumber", to, log.Debug("findBlocksCommand saving headers", "len", len(headers), "lastBlockNumber", to,
"balance", c.balanceCacher.Cache().GetBalance(c.account, to), "balance", c.balanceCacher.Cache().GetBalance(c.account, c.chainClient.ChainID, to),
"nonce", c.balanceCacher.Cache().GetNonce(c.account, to)) "nonce", c.balanceCacher.Cache().GetNonce(c.account, c.chainClient.ChainID, to))
err = c.db.SaveBlocks(c.chainClient.ChainID, c.account, headers) err = c.db.SaveBlocks(c.chainClient.ChainID, c.account, headers)
if err != nil { if err != nil {
@ -542,6 +542,7 @@ func (c *loadBlocksAndTransfersCommand) notifyHistoryReady() {
c.feed.Send(walletevent.Event{ c.feed.Send(walletevent.Event{
Type: EventRecentHistoryReady, Type: EventRecentHistoryReady,
Accounts: []common.Address{c.account}, Accounts: []common.Address{c.account},
ChainID: c.chainClient.ChainID,
}) })
} }
} }

View File

@ -79,6 +79,10 @@ func (f balancesFixture) HeaderByHash(ctx context.Context, hash common.Hash) (*t
}, nil }, nil
} }
func (f balancesFixture) NetworkID() uint64 {
return 0
}
func (f balancesFixture) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) { func (f balancesFixture) FullTransactionByBlockNumberAndIndex(ctx context.Context, blockNumber *big.Int, index uint) (*chain.FullTransaction, error) {
blockHash := common.HexToHash("0x0") blockHash := common.HexToHash("0x0")
return &chain.FullTransaction{ return &chain.FullTransaction{