code.vegaprotocol.io/vega@v0.79.0/core/client/eth/client_wrapper.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package eth 17 18 import ( 19 "context" 20 "math/big" 21 "regexp" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/metrics" 25 26 "github.com/ethereum/go-ethereum" 27 ethcommon "github.com/ethereum/go-ethereum/common" 28 ethtypes "github.com/ethereum/go-ethereum/core/types" 29 "github.com/hashicorp/golang-lru/v2/expirable" 30 ) 31 32 // ETHClient ... 33 // 34 //go:generate go run github.com/golang/mock/mockgen -destination mocks/eth_client_mock.go -package mocks code.vegaprotocol.io/vega/core/client/eth ETHClient 35 type ETHClient interface { //revive:disable:exported 36 // bind.ContractBackend 37 // ethereum.ChainReader 38 39 // client 40 ChainID(context.Context) (*big.Int, error) 41 NetworkID(context.Context) (*big.Int, error) 42 43 // ethereum.ChainReader 44 BlockByHash(ctx context.Context, hash ethcommon.Hash) (*ethtypes.Block, error) 45 HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) 46 BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) 47 HeaderByHash(ctx context.Context, hash ethcommon.Hash) (*ethtypes.Header, error) 48 SubscribeNewHead(ctx context.Context, ch chan<- *ethtypes.Header) (ethereum.Subscription, error) 49 TransactionCount(ctx context.Context, blockHash ethcommon.Hash) (uint, error) 50 TransactionInBlock(ctx context.Context, blockHash ethcommon.Hash, index uint) (*ethtypes.Transaction, error) 51 52 // bind.ContractCaller 53 CodeAt(ctx context.Context, contract ethcommon.Address, blockNumber *big.Int) ([]byte, error) 54 CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) 55 56 // bind.ContractTransactor 57 EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) 58 PendingCodeAt(ctx context.Context, account ethcommon.Address) ([]byte, error) 59 PendingNonceAt(ctx context.Context, account ethcommon.Address) (uint64, error) 60 SendTransaction(ctx context.Context, tx *ethtypes.Transaction) error 61 SuggestGasPrice(ctx context.Context) (*big.Int, error) 62 SuggestGasTipCap(ctx context.Context) (*big.Int, error) 63 64 // bind.ContractFilterer 65 FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]ethtypes.Log, error) 66 SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- ethtypes.Log) (ethereum.Subscription, error) 67 } 68 69 type ethClientWrapper struct { 70 clt ETHClient 71 72 headerByNumberCache *expirable.LRU[string, *ethtypes.Header] 73 } 74 75 func newEthClientWrapper(clt ETHClient) *ethClientWrapper { 76 return ðClientWrapper{ 77 clt: clt, 78 // arbitrary size of 100 blocks, kept for at most 10 minutes, 79 // let see later how to make this less hardcoded 80 headerByNumberCache: expirable.NewLRU[string, *ethtypes.Header](100, nil, 10*time.Minute), 81 } 82 } 83 84 func (c *ethClientWrapper) ChainID(ctx context.Context) (*big.Int, error) { 85 metrics.EthereumRPCCallCounterInc("chain_id") 86 id, err := c.clt.ChainID(ctx) 87 if err != nil { 88 return nil, ErrorWithStrippedSecrets{err: err} 89 } 90 return id, nil 91 } 92 93 func (c *ethClientWrapper) NetworkID(ctx context.Context) (*big.Int, error) { 94 metrics.EthereumRPCCallCounterInc("network_id") 95 id, err := c.clt.NetworkID(ctx) 96 if err != nil { 97 return nil, ErrorWithStrippedSecrets{err: err} 98 } 99 return id, nil 100 } 101 102 func (c *ethClientWrapper) BlockByHash(ctx context.Context, hash ethcommon.Hash) (*ethtypes.Block, error) { 103 metrics.EthereumRPCCallCounterInc("block_by_hash") 104 byHash, err := c.clt.BlockByHash(ctx, hash) 105 if err != nil { 106 return nil, ErrorWithStrippedSecrets{err: err} 107 } 108 return byHash, nil 109 } 110 111 func (c *ethClientWrapper) HeaderByNumber(ctx context.Context, number *big.Int) (*ethtypes.Header, error) { 112 if number != nil { 113 // first check the cache 114 if header, ok := c.headerByNumberCache.Get(number.String()); ok { 115 return ethtypes.CopyHeader(header), nil 116 } 117 } 118 119 // cache miss, so let's inc the counter, and call the rpc. 120 metrics.EthereumRPCCallCounterInc("header_by_number") 121 header, err := c.clt.HeaderByNumber(ctx, number) 122 if err != nil { 123 return nil, ErrorWithStrippedSecrets{err: err} 124 } 125 126 c.headerByNumberCache.Add(header.Number.String(), ethtypes.CopyHeader(header)) 127 128 return header, nil 129 } 130 131 func (c *ethClientWrapper) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { 132 metrics.EthereumRPCCallCounterInc("block_by_number") 133 byNumber, err := c.clt.BlockByNumber(ctx, number) 134 if err != nil { 135 return nil, ErrorWithStrippedSecrets{err: err} 136 } 137 return byNumber, nil 138 } 139 140 func (c *ethClientWrapper) HeaderByHash(ctx context.Context, hash ethcommon.Hash) (*ethtypes.Header, error) { 141 metrics.EthereumRPCCallCounterInc("header_by_hash") 142 byHash, err := c.clt.HeaderByHash(ctx, hash) 143 if err != nil { 144 return nil, ErrorWithStrippedSecrets{err: err} 145 } 146 return byHash, nil 147 } 148 149 func (c *ethClientWrapper) SubscribeNewHead(ctx context.Context, ch chan<- *ethtypes.Header) (ethereum.Subscription, error) { 150 head, err := c.clt.SubscribeNewHead(ctx, ch) 151 if err != nil { 152 return nil, ErrorWithStrippedSecrets{err: err} 153 } 154 return head, nil 155 } 156 157 func (c *ethClientWrapper) TransactionCount(ctx context.Context, blockHash ethcommon.Hash) (uint, error) { 158 metrics.EthereumRPCCallCounterInc("transaction_count") 159 count, err := c.clt.TransactionCount(ctx, blockHash) 160 if err != nil { 161 return 0, ErrorWithStrippedSecrets{err: err} 162 } 163 return count, nil 164 } 165 166 func (c *ethClientWrapper) TransactionInBlock(ctx context.Context, blockHash ethcommon.Hash, index uint) (*ethtypes.Transaction, error) { 167 metrics.EthereumRPCCallCounterInc("transaction_in_block") 168 block, err := c.clt.TransactionInBlock(ctx, blockHash, index) 169 if err != nil { 170 return nil, ErrorWithStrippedSecrets{err: err} 171 } 172 return block, nil 173 } 174 175 func (c *ethClientWrapper) CodeAt(ctx context.Context, contract ethcommon.Address, blockNumber *big.Int) ([]byte, error) { 176 metrics.EthereumRPCCallCounterInc("code_at") 177 at, err := c.clt.CodeAt(ctx, contract, blockNumber) 178 if err != nil { 179 return nil, ErrorWithStrippedSecrets{err: err} 180 } 181 return at, nil 182 } 183 184 func (c *ethClientWrapper) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 185 metrics.EthereumRPCCallCounterInc("call_contract") 186 contract, err := c.clt.CallContract(ctx, call, blockNumber) 187 if err != nil { 188 return nil, ErrorWithStrippedSecrets{err: err} 189 } 190 return contract, nil 191 } 192 193 func (c *ethClientWrapper) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { 194 metrics.EthereumRPCCallCounterInc("estimate_gas") 195 estimateGas, err := c.clt.EstimateGas(ctx, call) 196 if err != nil { 197 return 0, ErrorWithStrippedSecrets{err: err} 198 } 199 return estimateGas, nil 200 } 201 202 func (c *ethClientWrapper) PendingCodeAt(ctx context.Context, account ethcommon.Address) ([]byte, error) { 203 metrics.EthereumRPCCallCounterInc("pending_code_at") 204 at, err := c.clt.PendingCodeAt(ctx, account) 205 if err != nil { 206 return nil, ErrorWithStrippedSecrets{err: err} 207 } 208 return at, nil 209 } 210 211 func (c *ethClientWrapper) PendingNonceAt(ctx context.Context, account ethcommon.Address) (uint64, error) { 212 metrics.EthereumRPCCallCounterInc("pending_nonce_at") 213 at, err := c.clt.PendingNonceAt(ctx, account) 214 if err != nil { 215 return 0, ErrorWithStrippedSecrets{err: err} 216 } 217 return at, nil 218 } 219 220 func (c *ethClientWrapper) SendTransaction(ctx context.Context, tx *ethtypes.Transaction) error { 221 metrics.EthereumRPCCallCounterInc("send_transaction") 222 err := c.clt.SendTransaction(ctx, tx) 223 return ErrorWithStrippedSecrets{err: err} 224 } 225 226 func (c *ethClientWrapper) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 227 metrics.EthereumRPCCallCounterInc("suggest_gas_price") 228 price, err := c.clt.SuggestGasPrice(ctx) 229 if err != nil { 230 return nil, ErrorWithStrippedSecrets{err: err} 231 } 232 return price, nil 233 } 234 235 func (c *ethClientWrapper) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { 236 metrics.EthereumRPCCallCounterInc("suggest_gas_tip_cap") 237 tipCap, err := c.clt.SuggestGasTipCap(ctx) 238 if err != nil { 239 return nil, ErrorWithStrippedSecrets{err: err} 240 } 241 return tipCap, nil 242 } 243 244 func (c *ethClientWrapper) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]ethtypes.Log, error) { 245 metrics.EthereumRPCCallCounterInc("filter_logs") 246 logs, err := c.clt.FilterLogs(ctx, query) 247 if err != nil { 248 return nil, ErrorWithStrippedSecrets{err: err} 249 } 250 return logs, nil 251 } 252 253 func (c *ethClientWrapper) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- ethtypes.Log) (ethereum.Subscription, error) { 254 metrics.EthereumRPCCallCounterInc("subscribe_filter_logs") 255 logs, err := c.clt.SubscribeFilterLogs(ctx, query, ch) 256 if err != nil { 257 return nil, ErrorWithStrippedSecrets{err: err} 258 } 259 return logs, nil 260 } 261 262 // ErrorWithStrippedSecrets is an extremely naïve implementation of an error that 263 // will strip the API token from a URL. 264 type ErrorWithStrippedSecrets struct { 265 err error 266 } 267 268 func (e ErrorWithStrippedSecrets) Error() string { 269 return regexp. 270 MustCompile(`(?i)(apitoken|token|apikey|key)=(.+)"`). 271 ReplaceAllString(e.err.Error(), "$1=xxx\"") 272 }