github.com/status-im/status-go@v1.1.0/services/wallet/balance/balance_cache.go (about)

     1  package balance
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"reflect"
     7  	"sync"
     8  
     9  	"github.com/ethereum/go-ethereum/common"
    10  	"github.com/ethereum/go-ethereum/core/types"
    11  )
    12  
    13  // Reader interface for reading balance at a specified address.
    14  type Reader interface {
    15  	BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
    16  	NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
    17  	HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
    18  	CallBlockHashByTransaction(ctx context.Context, blockNumber *big.Int, index uint) (common.Hash, error)
    19  	NetworkID() uint64
    20  }
    21  
    22  // Cacher interface for caching balance to BalanceCache. Requires BalanceReader to fetch balance.
    23  type Cacher interface {
    24  	BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error)
    25  	NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error)
    26  	Clear()
    27  	Cache() CacheIface
    28  }
    29  
    30  // Interface for cache of balances.
    31  type CacheIface interface {
    32  	GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int
    33  	GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64
    34  	AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int)
    35  	AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64)
    36  	BalanceSize(account common.Address, chainID uint64) int
    37  	NonceSize(account common.Address, chainID uint64) int
    38  	Clear()
    39  }
    40  
    41  type addressChainMap[T any] map[common.Address]map[uint64]T // address->chainID
    42  
    43  type cacheIface[K comparable, V any] interface {
    44  	get(K) V
    45  	set(K, V)
    46  	len() int
    47  	keys() []K
    48  	clear()
    49  	init()
    50  }
    51  
    52  // genericCache is a generic implementation of CacheIface
    53  type genericCache[B cacheIface[uint64, *big.Int], N cacheIface[uint64, *int64], NR cacheIface[int64, nonceRange]] struct {
    54  	nonceRangeCache[NR]
    55  
    56  	// balances maps an address and chain to a cache of a block number and the balance of this particular address on the chain
    57  	balances addressChainMap[B]
    58  	nonces   addressChainMap[N]
    59  	rw       sync.RWMutex
    60  }
    61  
    62  func (b *genericCache[_, _, _]) GetBalance(account common.Address, chainID uint64, blockNumber *big.Int) *big.Int {
    63  	b.rw.RLock()
    64  	defer b.rw.RUnlock()
    65  
    66  	_, exists := b.balances[account]
    67  	if !exists {
    68  		return nil
    69  	}
    70  
    71  	_, exists = b.balances[account][chainID]
    72  	if !exists {
    73  		return nil
    74  	}
    75  
    76  	return b.balances[account][chainID].get(blockNumber.Uint64())
    77  }
    78  
    79  func (b *genericCache[B, _, _]) AddBalance(account common.Address, chainID uint64, blockNumber *big.Int, balance *big.Int) {
    80  	b.rw.Lock()
    81  	defer b.rw.Unlock()
    82  
    83  	_, exists := b.balances[account]
    84  	if !exists {
    85  		b.balances[account] = make(map[uint64]B)
    86  	}
    87  
    88  	_, exists = b.balances[account][chainID]
    89  	if !exists {
    90  		b.balances[account][chainID] = reflect.New(reflect.TypeOf(b.balances[account][chainID]).Elem()).Interface().(B)
    91  		b.balances[account][chainID].init()
    92  	}
    93  
    94  	b.balances[account][chainID].set(blockNumber.Uint64(), balance)
    95  }
    96  
    97  func (b *genericCache[_, _, _]) GetNonce(account common.Address, chainID uint64, blockNumber *big.Int) *int64 {
    98  	b.rw.RLock()
    99  	defer b.rw.RUnlock()
   100  
   101  	_, exists := b.nonces[account]
   102  	if !exists {
   103  		return nil
   104  	}
   105  
   106  	_, exists = b.nonces[account][chainID]
   107  	if !exists {
   108  		return nil
   109  	}
   110  
   111  	nonce := b.nonces[account][chainID].get(blockNumber.Uint64())
   112  	if nonce != nil {
   113  		return nonce
   114  	}
   115  
   116  	return b.findNonceInRange(account, chainID, blockNumber)
   117  }
   118  
   119  func (b *genericCache[_, N, _]) AddNonce(account common.Address, chainID uint64, blockNumber *big.Int, nonce *int64) {
   120  	b.rw.Lock()
   121  	defer b.rw.Unlock()
   122  
   123  	_, exists := b.nonces[account]
   124  	if !exists {
   125  		b.nonces[account] = make(map[uint64]N)
   126  	}
   127  
   128  	_, exists = b.nonces[account][chainID]
   129  	if !exists {
   130  		b.nonces[account][chainID] = reflect.New(reflect.TypeOf(b.nonces[account][chainID]).Elem()).Interface().(N)
   131  		b.nonces[account][chainID].init()
   132  	}
   133  
   134  	b.nonces[account][chainID].set(blockNumber.Uint64(), nonce)
   135  	b.updateNonceRange(account, chainID, blockNumber, nonce)
   136  }
   137  
   138  func (b *genericCache[_, _, _]) BalanceSize(account common.Address, chainID uint64) int {
   139  	b.rw.RLock()
   140  	defer b.rw.RUnlock()
   141  
   142  	_, exists := b.balances[account]
   143  	if !exists {
   144  		return 0
   145  	}
   146  
   147  	_, exists = b.balances[account][chainID]
   148  	if !exists {
   149  		return 0
   150  	}
   151  
   152  	return b.balances[account][chainID].len()
   153  }
   154  
   155  func (b *genericCache[_, N, _]) NonceSize(account common.Address, chainID uint64) int {
   156  	b.rw.RLock()
   157  	defer b.rw.RUnlock()
   158  
   159  	_, exists := b.nonces[account]
   160  	if !exists {
   161  		return 0
   162  	}
   163  
   164  	_, exists = b.nonces[account][chainID]
   165  	if !exists {
   166  		return 0
   167  	}
   168  
   169  	return b.nonces[account][chainID].len()
   170  }
   171  
   172  // implements Cacher interface that caches balance and nonce in memory.
   173  type cacherImpl struct {
   174  	cache CacheIface
   175  }
   176  
   177  func newCacherImpl(cache CacheIface) *cacherImpl {
   178  	return &cacherImpl{
   179  		cache: cache,
   180  	}
   181  }
   182  
   183  func (b *cacherImpl) BalanceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*big.Int, error) {
   184  	cachedBalance := b.cache.GetBalance(account, client.NetworkID(), blockNumber)
   185  	if cachedBalance != nil {
   186  		return cachedBalance, nil
   187  	}
   188  
   189  	balance, err := client.BalanceAt(ctx, account, blockNumber)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	b.cache.AddBalance(account, client.NetworkID(), blockNumber, balance)
   194  	return balance, nil
   195  }
   196  
   197  func (b *cacherImpl) NonceAt(ctx context.Context, client Reader, account common.Address, blockNumber *big.Int) (*int64, error) {
   198  	cachedNonce := b.cache.GetNonce(account, client.NetworkID(), blockNumber)
   199  	if cachedNonce != nil {
   200  		return cachedNonce, nil
   201  	}
   202  
   203  	nonce, err := client.NonceAt(ctx, account, blockNumber)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	int64Nonce := int64(nonce)
   208  	b.cache.AddNonce(account, client.NetworkID(), blockNumber, &int64Nonce)
   209  
   210  	return &int64Nonce, nil
   211  }
   212  
   213  func (b *cacherImpl) Clear() {
   214  	b.cache.Clear()
   215  }
   216  
   217  func (b *cacherImpl) Cache() CacheIface {
   218  	return b.cache
   219  }