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 }