mirror of
https://github.com/status-im/status-go.git
synced 2025-01-19 03:04:54 +00:00
a2ff03c79e
Extends wallet module with the history package with the following components: BalanceDB (balance_db.go) - Keeps track of balance information (token count, block, block timestamp) for a token identity (chain, address, currency) - The cached data is stored in `balance_history` table. - Uniqueness constrained is enforced by the `balance_history_identify_entry` UNIQUE index. - Optimal DB fetching is ensured by the `balance_history_filter_entries` index Balance (balance.go) - Provides two stages: - Fetch of balance history using RPC calls (Balance.update function) - Retrieving of cached balance data from the DB it exists (Balance.get function) - Fetching and retrieving of data is done for specific time intervals defined by TimeInterval "enumeration" - Update process is done for a token identity by the Balance.Update function - The granularity of data points returned is defined by the constant increment step define in `timeIntervalToStride` for each time interval. - The `blocksStride` values have a common divisor to have cache hit between time intervals. Service (service.go) - Main APIs - StartBalanceHistory: Regularly updates balance history for all enabled networks, available accounts and provided tokens. - GetBalanceHistory: retrieves cached token count for a token identity (chain, address, currency) for multiple chains - UpdateVisibleTokens: will set the list of tokens to have historical balance fetched. This is a simplification to limit tokens to a small list that make sense Fetch balance history for ECR20 tokens - Add token.Manager.GetTokenBalanceAt to fetch balance of a specific block number of ECR20. - Add tokenChainClientSource concrete implementation of DataSource to fetch balance of ECR20 tokens. - Chose the correct DataSource implementation based on the token "is native" property. Tests Tests are implemented using a mock of `DataSource` interface used to intercept the RPC calls. Notes: - the timestamp used for retrieving block balance is constant Closes status-desktop: #8175, #8226, #8862
175 lines
4.4 KiB
Go
175 lines
4.4 KiB
Go
package transfer
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
)
|
|
|
|
type nonceRange struct {
|
|
nonce int64
|
|
max *big.Int
|
|
min *big.Int
|
|
}
|
|
|
|
type balanceCache struct {
|
|
// 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
|
|
nonces map[common.Address]map[*big.Int]*int64
|
|
nonceRanges map[common.Address]map[int64]nonceRange
|
|
sortedRanges map[common.Address][]nonceRange
|
|
rw sync.RWMutex
|
|
}
|
|
|
|
type BalanceCache interface {
|
|
BalanceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*big.Int, error)
|
|
NonceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*int64, error)
|
|
}
|
|
|
|
func (b *balanceCache) ReadCachedBalance(account common.Address, blockNumber *big.Int) *big.Int {
|
|
b.rw.RLock()
|
|
defer b.rw.RUnlock()
|
|
|
|
return b.balances[account][blockNumber]
|
|
}
|
|
|
|
func (b *balanceCache) addBalanceToCache(account common.Address, blockNumber *big.Int, balance *big.Int) {
|
|
b.rw.Lock()
|
|
defer b.rw.Unlock()
|
|
|
|
_, exists := b.balances[account]
|
|
if !exists {
|
|
b.balances[account] = make(map[*big.Int]*big.Int)
|
|
}
|
|
b.balances[account][blockNumber] = balance
|
|
}
|
|
|
|
func (b *balanceCache) BalanceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
|
cachedBalance := b.ReadCachedBalance(account, blockNumber)
|
|
if cachedBalance != nil {
|
|
return cachedBalance, nil
|
|
}
|
|
balance, err := client.BalanceAt(ctx, account, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.addBalanceToCache(account, blockNumber, balance)
|
|
|
|
return balance, nil
|
|
}
|
|
|
|
func (b *balanceCache) ReadCachedNonce(account common.Address, blockNumber *big.Int) *int64 {
|
|
b.rw.RLock()
|
|
defer b.rw.RUnlock()
|
|
|
|
return b.nonces[account][blockNumber]
|
|
}
|
|
|
|
func (b *balanceCache) sortRanges(account common.Address) {
|
|
keys := make([]int, 0, len(b.nonceRanges[account]))
|
|
for k := range b.nonceRanges[account] {
|
|
keys = append(keys, int(k))
|
|
}
|
|
|
|
sort.Ints(keys)
|
|
|
|
ranges := []nonceRange{}
|
|
for _, k := range keys {
|
|
r := b.nonceRanges[account][int64(k)]
|
|
ranges = append(ranges, r)
|
|
}
|
|
|
|
b.sortedRanges[account] = ranges
|
|
}
|
|
|
|
func (b *balanceCache) findNonceInRange(account common.Address, block *big.Int) *int64 {
|
|
for k := range b.sortedRanges[account] {
|
|
nr := b.sortedRanges[account][k]
|
|
cmpMin := nr.min.Cmp(block)
|
|
if cmpMin == 1 {
|
|
return nil
|
|
} else if cmpMin == 0 {
|
|
return &nr.nonce
|
|
} else {
|
|
cmpMax := nr.max.Cmp(block)
|
|
if cmpMax >= 0 {
|
|
return &nr.nonce
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *balanceCache) updateNonceRange(account common.Address, blockNumber *big.Int, nonce *int64) {
|
|
_, exists := b.nonceRanges[account]
|
|
if !exists {
|
|
b.nonceRanges[account] = make(map[int64]nonceRange)
|
|
}
|
|
nr, exists := b.nonceRanges[account][*nonce]
|
|
if !exists {
|
|
r := nonceRange{
|
|
max: big.NewInt(0).Set(blockNumber),
|
|
min: big.NewInt(0).Set(blockNumber),
|
|
nonce: *nonce,
|
|
}
|
|
b.nonceRanges[account][*nonce] = r
|
|
} else {
|
|
if nr.max.Cmp(blockNumber) == -1 {
|
|
nr.max.Set(blockNumber)
|
|
}
|
|
|
|
if nr.min.Cmp(blockNumber) == 1 {
|
|
nr.min.Set(blockNumber)
|
|
}
|
|
|
|
b.nonceRanges[account][*nonce] = nr
|
|
b.sortRanges(account)
|
|
}
|
|
}
|
|
|
|
func (b *balanceCache) addNonceToCache(account common.Address, blockNumber *big.Int, nonce *int64) {
|
|
b.rw.Lock()
|
|
defer b.rw.Unlock()
|
|
|
|
_, exists := b.nonces[account]
|
|
if !exists {
|
|
b.nonces[account] = make(map[*big.Int]*int64)
|
|
}
|
|
b.nonces[account][blockNumber] = nonce
|
|
b.updateNonceRange(account, blockNumber, nonce)
|
|
}
|
|
|
|
func (b *balanceCache) NonceAt(ctx context.Context, client BalanceReader, account common.Address, blockNumber *big.Int) (*int64, error) {
|
|
cachedNonce := b.ReadCachedNonce(account, blockNumber)
|
|
if cachedNonce != nil {
|
|
return cachedNonce, nil
|
|
}
|
|
|
|
rangeNonce := b.findNonceInRange(account, blockNumber)
|
|
if rangeNonce != nil {
|
|
return rangeNonce, nil
|
|
}
|
|
|
|
nonce, err := client.NonceAt(ctx, account, blockNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
int64Nonce := int64(nonce)
|
|
b.addNonceToCache(account, blockNumber, &int64Nonce)
|
|
|
|
return &int64Nonce, nil
|
|
}
|
|
|
|
func newBalanceCache() *balanceCache {
|
|
return &balanceCache{
|
|
balances: make(map[common.Address]map[*big.Int]*big.Int),
|
|
nonces: make(map[common.Address]map[*big.Int]*int64),
|
|
nonceRanges: make(map[common.Address]map[int64]nonceRange),
|
|
sortedRanges: make(map[common.Address][]nonceRange),
|
|
}
|
|
}
|