221
vendor/github.com/status-im/status-go/services/wallet/balance/balance_cache.go
generated
vendored
Normal file
221
vendor/github.com/status-im/status-go/services/wallet/balance/balance_cache.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package balance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/status-im/status-go/rpc/chain"
|
||||
)
|
||||
|
||||
// Reader interface for reading balance at a specified address.
|
||||
type Reader interface {
|
||||
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
|
||||
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
|
||||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, 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.
|
||||
type Cacher interface {
|
||||
BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error)
|
||||
NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error)
|
||||
Clear()
|
||||
Cache() CacheIface
|
||||
}
|
||||
|
||||
// Interface for cache of balances.
|
||||
type CacheIface interface {
|
||||
GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int
|
||||
GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64
|
||||
AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int)
|
||||
AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64)
|
||||
BalanceSize(account common.Address, chainID uint64) int
|
||||
NonceSize(account common.Address, chainID uint64) int
|
||||
Clear()
|
||||
}
|
||||
|
||||
type addressChainMap[T any] map[common.Address]map[uint64]T // address->chainID
|
||||
|
||||
type cacheIface[K comparable, V any] interface {
|
||||
get(K) V
|
||||
set(K, V)
|
||||
len() int
|
||||
keys() []K
|
||||
clear()
|
||||
init()
|
||||
}
|
||||
|
||||
// genericCache is a generic implementation of CacheIface
|
||||
type genericCache[B cacheIface[uint64, *big.Int], N cacheIface[uint64, *int64], NR cacheIface[int64, nonceRange]] struct {
|
||||
nonceRangeCache[NR]
|
||||
|
||||
// balances maps an address and chain to a cache of a block number and the balance of this particular address on the chain
|
||||
balances addressChainMap[B]
|
||||
nonces addressChainMap[N]
|
||||
rw sync.RWMutex
|
||||
}
|
||||
|
||||
func (b *genericCache[_, _, _]) GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
_, exists := b.balances[account]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, exists = b.balances[account][chainID]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return b.balances[account][chainID].get(blockNumber.Uint64())
|
||||
}
|
||||
|
||||
func (b *genericCache[B, _, _]) AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int) {
|
||||
b.rw.Lock()
|
||||
defer b.rw.Unlock()
|
||||
|
||||
_, exists := b.balances[account]
|
||||
if !exists {
|
||||
b.balances[account] = make(map[uint64]B)
|
||||
}
|
||||
|
||||
_, exists = b.balances[account][chainID]
|
||||
if !exists {
|
||||
b.balances[account][chainID] = reflect.New(reflect.TypeOf(b.balances[account][chainID]).Elem()).Interface().(B)
|
||||
b.balances[account][chainID].init()
|
||||
}
|
||||
|
||||
b.balances[account][chainID].set(blockNumber.Uint64(), balance)
|
||||
}
|
||||
|
||||
func (b *genericCache[_, _, _]) GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64 {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
_, exists := b.nonces[account]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, exists = b.nonces[account][chainID]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
nonce := b.nonces[account][chainID].get(blockNumber.Uint64())
|
||||
if nonce != nil {
|
||||
return nonce
|
||||
}
|
||||
|
||||
return b.findNonceInRange(account, chainID, blockNumber)
|
||||
}
|
||||
|
||||
func (b *genericCache[_, N, _]) AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
|
||||
b.rw.Lock()
|
||||
defer b.rw.Unlock()
|
||||
|
||||
_, exists := b.nonces[account]
|
||||
if !exists {
|
||||
b.nonces[account] = make(map[uint64]N)
|
||||
}
|
||||
|
||||
_, exists = b.nonces[account][chainID]
|
||||
if !exists {
|
||||
b.nonces[account][chainID] = reflect.New(reflect.TypeOf(b.nonces[account][chainID]).Elem()).Interface().(N)
|
||||
b.nonces[account][chainID].init()
|
||||
}
|
||||
|
||||
b.nonces[account][chainID].set(blockNumber.Uint64(), nonce)
|
||||
b.updateNonceRange(account, chainID, blockNumber, nonce)
|
||||
}
|
||||
|
||||
func (b *genericCache[_, _, _]) BalanceSize(account common.Address, chainID uint64) int {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
_, exists := b.balances[account]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, exists = b.balances[account][chainID]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
return b.balances[account][chainID].len()
|
||||
}
|
||||
|
||||
func (b *genericCache[_, N, _]) NonceSize(account common.Address, chainID uint64) int {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
_, exists := b.nonces[account]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, exists = b.nonces[account][chainID]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
return b.nonces[account][chainID].len()
|
||||
}
|
||||
|
||||
// implements Cacher interface that caches balance and nonce in memory.
|
||||
type cacherImpl struct {
|
||||
cache CacheIface
|
||||
}
|
||||
|
||||
func newCacherImpl(cache CacheIface) *cacherImpl {
|
||||
return &cacherImpl{
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *cacherImpl) BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
cachedBalance := b.cache.GetBalance(account, client.NetworkID(), blockNumber)
|
||||
if cachedBalance != nil {
|
||||
return cachedBalance, nil
|
||||
}
|
||||
|
||||
balance, err := client.BalanceAt(ctx, account, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.cache.AddBalance(account, client.NetworkID(), blockNumber, balance)
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func (b *cacherImpl) NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error) {
|
||||
cachedNonce := b.cache.GetNonce(account, client.NetworkID(), blockNumber)
|
||||
if cachedNonce != nil {
|
||||
return cachedNonce, nil
|
||||
}
|
||||
|
||||
nonce, err := client.NonceAt(ctx, account, blockNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
int64Nonce := int64(nonce)
|
||||
b.cache.AddNonce(account, client.NetworkID(), blockNumber, &int64Nonce)
|
||||
|
||||
return &int64Nonce, nil
|
||||
}
|
||||
|
||||
func (b *cacherImpl) Clear() {
|
||||
b.cache.Clear()
|
||||
}
|
||||
|
||||
func (b *cacherImpl) Cache() CacheIface {
|
||||
return b.cache
|
||||
}
|
||||
134
vendor/github.com/status-im/status-go/services/wallet/balance/nonce_range.go
generated
vendored
Normal file
134
vendor/github.com/status-im/status-go/services/wallet/balance/nonce_range.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package balance
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type nonceRange struct {
|
||||
nonce int64
|
||||
max *big.Int
|
||||
min *big.Int
|
||||
}
|
||||
|
||||
type sortedNonceRangesCacheType addressChainMap[[]nonceRange] // address->chainID->[]nonceRange
|
||||
|
||||
type nonceRangeCache[T cacheIface[int64, nonceRange]] struct {
|
||||
nonceRanges addressChainMap[T]
|
||||
sortedRanges sortedNonceRangesCacheType
|
||||
rw sync.RWMutex
|
||||
}
|
||||
|
||||
func newNonceRangeCache[T cacheIface[int64, nonceRange]]() *nonceRangeCache[T] {
|
||||
return &nonceRangeCache[T]{
|
||||
nonceRanges: make(addressChainMap[T]),
|
||||
sortedRanges: make(sortedNonceRangesCacheType),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *nonceRangeCache[T]) updateNonceRange(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
|
||||
b.rw.Lock()
|
||||
defer b.rw.Unlock()
|
||||
|
||||
_, exists := b.nonceRanges[account]
|
||||
if !exists {
|
||||
b.nonceRanges[account] = make(map[uint64]T)
|
||||
}
|
||||
_, exists = b.nonceRanges[account][chainID]
|
||||
if !exists {
|
||||
b.nonceRanges[account][chainID] = reflect.New(reflect.TypeOf(b.nonceRanges[account][chainID]).Elem()).Interface().(T)
|
||||
b.nonceRanges[account][chainID].init()
|
||||
}
|
||||
|
||||
nr := b.nonceRanges[account][chainID].get(*nonce)
|
||||
if nr == reflect.Zero(reflect.TypeOf(nr)).Interface() {
|
||||
nr = nonceRange{
|
||||
max: big.NewInt(0).Set(blockNumber),
|
||||
min: big.NewInt(0).Set(blockNumber),
|
||||
nonce: *nonce,
|
||||
}
|
||||
} else {
|
||||
if nr.max.Cmp(blockNumber) == -1 {
|
||||
nr.max.Set(blockNumber)
|
||||
}
|
||||
|
||||
if nr.min.Cmp(blockNumber) == 1 {
|
||||
nr.min.Set(blockNumber)
|
||||
}
|
||||
}
|
||||
|
||||
b.nonceRanges[account][chainID].set(*nonce, nr)
|
||||
b.sortRanges(account, chainID)
|
||||
}
|
||||
|
||||
func (b *nonceRangeCache[_]) findNonceInRange(account common.Address, chainID uint64, block *big.Int) *int64 {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
for k := range b.sortedRanges[account][chainID] {
|
||||
nr := b.sortedRanges[account][chainID][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 *nonceRangeCache[T]) sortRanges(account common.Address, chainID uint64) {
|
||||
// DO NOT LOCK HERE - this function is called from a locked function
|
||||
|
||||
keys := b.nonceRanges[account][chainID].keys()
|
||||
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||||
|
||||
ranges := []nonceRange{}
|
||||
for _, k := range keys {
|
||||
r := b.nonceRanges[account][chainID].get(k)
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
|
||||
_, exists := b.sortedRanges[account]
|
||||
if !exists {
|
||||
b.sortedRanges[account] = make(map[uint64][]nonceRange)
|
||||
}
|
||||
|
||||
b.sortedRanges[account][chainID] = ranges
|
||||
}
|
||||
|
||||
func (b *nonceRangeCache[T]) clear() {
|
||||
b.rw.Lock()
|
||||
defer b.rw.Unlock()
|
||||
|
||||
b.nonceRanges = make(addressChainMap[T])
|
||||
b.sortedRanges = make(sortedNonceRangesCacheType)
|
||||
}
|
||||
|
||||
func (b *nonceRangeCache[T]) size(account common.Address, chainID uint64) int {
|
||||
b.rw.RLock()
|
||||
defer b.rw.RUnlock()
|
||||
|
||||
_, exists := b.nonceRanges[account]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
_, exists = b.nonceRanges[account][chainID]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
|
||||
return b.nonceRanges[account][chainID].len()
|
||||
}
|
||||
127
vendor/github.com/status-im/status-go/services/wallet/balance/simple_cache.go
generated
vendored
Normal file
127
vendor/github.com/status-im/status-go/services/wallet/balance/simple_cache.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package balance
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func NewSimpleCacher() Cacher {
|
||||
return newCacherImpl(newSimpleCache())
|
||||
}
|
||||
|
||||
// implements cacheIface for plain map internal storage
|
||||
type mapCache[K comparable, V any] struct {
|
||||
cache map[K]V
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) get(key K) V {
|
||||
return c.cache[key]
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) set(key K, value V) {
|
||||
c.cache[key] = value
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) len() int {
|
||||
return len(c.cache)
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) keys() []K {
|
||||
keys := make([]K, 0, len(c.cache))
|
||||
for k := range c.cache {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) init() {
|
||||
c.cache = make(map[K]V)
|
||||
}
|
||||
|
||||
func (c *mapCache[K, V]) clear() {
|
||||
c.cache = make(map[K]V)
|
||||
}
|
||||
|
||||
// specializes generic cache
|
||||
type simpleCache struct {
|
||||
genericCache[*mapCache[uint64, *big.Int], *mapCache[uint64, *int64], *mapCache[int64, nonceRange]]
|
||||
}
|
||||
|
||||
func newSimpleCache() *simpleCache {
|
||||
return &simpleCache{
|
||||
genericCache: genericCache[*mapCache[uint64, *big.Int], *mapCache[uint64, *int64], *mapCache[int64, nonceRange]]{
|
||||
|
||||
balances: make(addressChainMap[*mapCache[uint64, *big.Int]]),
|
||||
nonces: make(addressChainMap[*mapCache[uint64, *int64]]),
|
||||
nonceRangeCache: *newNonceRangeCache[*mapCache[int64, nonceRange]](),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Doesn't remove all entries, but keeps max and min to use on next iterations of transfer blocks searching
|
||||
func (c *simpleCache) Clear() {
|
||||
c.rw.Lock()
|
||||
defer c.rw.Unlock()
|
||||
|
||||
for _, chainCache := range c.balances {
|
||||
for _, cache := range chainCache {
|
||||
if cache.len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var maxBlock uint64 = 0
|
||||
var minBlock uint64 = math.MaxUint64
|
||||
for _, key := range cache.keys() {
|
||||
if key > maxBlock {
|
||||
maxBlock = key
|
||||
}
|
||||
if key < minBlock {
|
||||
minBlock = key
|
||||
}
|
||||
}
|
||||
maxBlockValue := cache.get(maxBlock)
|
||||
minBlockValue := cache.get(maxBlock)
|
||||
cache.clear()
|
||||
|
||||
if maxBlockValue != nil {
|
||||
cache.set(maxBlock, maxBlockValue)
|
||||
}
|
||||
|
||||
if minBlockValue != nil {
|
||||
cache.set(minBlock, minBlockValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, chainCache := range c.nonces {
|
||||
for _, cache := range chainCache {
|
||||
if cache.len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var maxBlock uint64 = 0
|
||||
var minBlock uint64 = math.MaxUint64
|
||||
for _, key := range cache.keys() {
|
||||
if key > maxBlock {
|
||||
maxBlock = key
|
||||
}
|
||||
if key < minBlock {
|
||||
minBlock = key
|
||||
}
|
||||
}
|
||||
maxBlockValue := cache.get(maxBlock)
|
||||
minBlockValue := cache.get(maxBlock)
|
||||
cache.clear()
|
||||
|
||||
if maxBlockValue != nil {
|
||||
cache.set(maxBlock, maxBlockValue)
|
||||
}
|
||||
|
||||
if minBlockValue != nil {
|
||||
cache.set(minBlock, minBlockValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.nonceRangeCache.clear()
|
||||
}
|
||||
5
vendor/github.com/status-im/status-go/services/wallet/balance/testutils.go
generated
vendored
Normal file
5
vendor/github.com/status-im/status-go/services/wallet/balance/testutils.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package balance
|
||||
|
||||
type TestCacher struct {
|
||||
cacherImpl
|
||||
}
|
||||
105
vendor/github.com/status-im/status-go/services/wallet/balance/ttl_cache.go
generated
vendored
Normal file
105
vendor/github.com/status-im/status-go/services/wallet/balance/ttl_cache.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package balance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTTLValue = 5 * time.Minute
|
||||
)
|
||||
|
||||
func NewCacherWithTTL(ttl time.Duration) Cacher {
|
||||
return newCacherImpl(newCacheWithTTL(ttl))
|
||||
}
|
||||
|
||||
// TTL cache implementation of cacheIface
|
||||
type ttlCache[K comparable, V any] struct {
|
||||
cache *ttlcache.Cache[K, V]
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) get(key K) V {
|
||||
item := c.cache.Get(key)
|
||||
if item == nil {
|
||||
var v V
|
||||
return v
|
||||
}
|
||||
return item.Value()
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) set(key K, value V) {
|
||||
_ = c.cache.Set(key, value, ttlcache.DefaultTTL)
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) len() int {
|
||||
return c.cache.Len()
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) keys() []K {
|
||||
return c.cache.Keys()
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) init() {
|
||||
c.cache = ttlcache.New[K, V](
|
||||
ttlcache.WithTTL[K, V](defaultTTLValue),
|
||||
)
|
||||
c.cache.OnEviction(func(ctx context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[K, V]) {
|
||||
log.Debug("Evicting item from balance/nonce cache", "reason", reason, "key", item.Key, "value", item.Value)
|
||||
})
|
||||
go c.cache.Start() // starts automatic expired item deletion
|
||||
}
|
||||
|
||||
//nolint:golint,unused // linter does not detect using it via reflect
|
||||
func (c *ttlCache[K, V]) clear() {
|
||||
c.cache.DeleteAll()
|
||||
}
|
||||
|
||||
// specializes generic cache
|
||||
type cacheWithTTL struct {
|
||||
// TODO: use ttlCache instead of mapCache for nonceRangeCache. For that we need to update sortedRanges on item eviction
|
||||
// For now, nonceRanges cache is not updated on nonces items eviction, but it should not be as big as nonceCache is
|
||||
genericCache[*ttlCache[uint64, *big.Int], *ttlCache[uint64, *int64], *mapCache[int64, nonceRange]]
|
||||
}
|
||||
|
||||
func newCacheWithTTL(ttl time.Duration) *cacheWithTTL {
|
||||
defaultTTLValue = ttl
|
||||
|
||||
return &cacheWithTTL{
|
||||
genericCache: genericCache[*ttlCache[uint64, *big.Int], *ttlCache[uint64, *int64], *mapCache[int64, nonceRange]]{
|
||||
balances: make(addressChainMap[*ttlCache[uint64, *big.Int]]),
|
||||
nonces: make(addressChainMap[*ttlCache[uint64, *int64]]),
|
||||
nonceRangeCache: *newNonceRangeCache[*mapCache[int64, nonceRange]](),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cacheWithTTL) Clear() {
|
||||
c.rw.Lock()
|
||||
defer c.rw.Unlock()
|
||||
|
||||
// TTL cache removes expired items automatically
|
||||
// but in case we want to clear it manually we can do it here
|
||||
for _, chainCache := range c.balances {
|
||||
for _, cache := range chainCache {
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
for _, chainCache := range c.nonces {
|
||||
for _, cache := range chainCache {
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
c.nonceRangeCache.clear()
|
||||
}
|
||||
Reference in New Issue
Block a user