github.com/ChainSafe/chainbridge-core@v1.4.2/chains/evm/calls/evmclient/evm-client.go (about) 1 package evmclient 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math/big" 9 "sync" 10 "time" 11 12 "github.com/ethereum/go-ethereum" 13 "github.com/ethereum/go-ethereum/common" 14 "github.com/ethereum/go-ethereum/common/hexutil" 15 "github.com/ethereum/go-ethereum/core/types" 16 "github.com/ethereum/go-ethereum/crypto" 17 "github.com/ethereum/go-ethereum/ethclient" 18 "github.com/ethereum/go-ethereum/ethclient/gethclient" 19 "github.com/ethereum/go-ethereum/rpc" 20 ) 21 22 type EVMClient struct { 23 *ethclient.Client 24 signer Signer 25 gethClient *gethclient.Client 26 rpClient *rpc.Client 27 nonce *big.Int 28 nonceLock sync.Mutex 29 } 30 31 type Signer interface { 32 CommonAddress() common.Address 33 34 // Sign calculates an ECDSA signature. 35 // The produced signature must be in the [R || S || V] format where V is 0 or 1. 36 Sign(digestHash []byte) ([]byte, error) 37 } 38 39 type CommonTransaction interface { 40 // Hash returns the transaction hash. 41 Hash() common.Hash 42 43 // RawWithSignature Returns signed transaction by provided signer 44 RawWithSignature(signer Signer, domainID *big.Int) ([]byte, error) 45 } 46 47 // NewEVMClient creates a client for EVMChain with provided signer 48 func NewEVMClient(url string, signer Signer) (*EVMClient, error) { 49 rpcClient, err := rpc.DialContext(context.TODO(), url) 50 if err != nil { 51 return nil, err 52 } 53 c := &EVMClient{} 54 c.Client = ethclient.NewClient(rpcClient) 55 c.gethClient = gethclient.New(rpcClient) 56 c.rpClient = rpcClient 57 c.signer = signer 58 return c, nil 59 } 60 61 func (c *EVMClient) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) { 62 return c.gethClient.SubscribePendingTransactions(ctx, ch) 63 } 64 65 // LatestBlock returns the latest block from the current chain 66 func (c *EVMClient) LatestBlock() (*big.Int, error) { 67 var head *headerNumber 68 err := c.rpClient.CallContext(context.Background(), &head, "eth_getBlockByNumber", toBlockNumArg(nil), false) 69 if err == nil && head == nil { 70 err = ethereum.NotFound 71 } 72 if err != nil { 73 return nil, err 74 } 75 return head.Number, nil 76 } 77 78 type headerNumber struct { 79 Number *big.Int `json:"number" gencodec:"required"` 80 } 81 82 func (h *headerNumber) UnmarshalJSON(input []byte) error { 83 type headerNumber struct { 84 Number *hexutil.Big `json:"number" gencodec:"required"` 85 } 86 var dec headerNumber 87 if err := json.Unmarshal(input, &dec); err != nil { 88 return err 89 } 90 if dec.Number == nil { 91 return errors.New("missing required field 'number' for Header") 92 } 93 h.Number = (*big.Int)(dec.Number) 94 return nil 95 } 96 97 func (c *EVMClient) WaitAndReturnTxReceipt(h common.Hash) (*types.Receipt, error) { 98 retry := 50 99 for retry > 0 { 100 receipt, err := c.Client.TransactionReceipt(context.Background(), h) 101 if err != nil { 102 retry-- 103 time.Sleep(5 * time.Second) 104 continue 105 } 106 if receipt.Status != 1 { 107 return receipt, fmt.Errorf("transaction failed on chain. Receipt status %v", receipt.Status) 108 } 109 return receipt, nil 110 } 111 return nil, errors.New("tx did not appear") 112 } 113 114 func (c *EVMClient) GetTransactionByHash(h common.Hash) (tx *types.Transaction, isPending bool, err error) { 115 return c.Client.TransactionByHash(context.Background(), h) 116 } 117 118 func (c *EVMClient) FetchEventLogs(ctx context.Context, contractAddress common.Address, event string, startBlock *big.Int, endBlock *big.Int) ([]types.Log, error) { 119 logs, err := c.FilterLogs(ctx, buildQuery(contractAddress, event, startBlock, endBlock)) 120 if err != nil { 121 return []types.Log{}, err 122 } 123 124 validLogs := make([]types.Log, 0) 125 for _, log := range logs { 126 if log.Removed { 127 continue 128 } 129 130 validLogs = append(validLogs, log) 131 } 132 return validLogs, nil 133 } 134 135 // SendRawTransaction accepts rlp-encode of signed transaction and sends it via RPC call 136 func (c *EVMClient) SendRawTransaction(ctx context.Context, tx []byte) error { 137 return c.rpClient.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(tx)) 138 } 139 140 func (c *EVMClient) CallContract(ctx context.Context, callArgs map[string]interface{}, blockNumber *big.Int) ([]byte, error) { 141 var hex hexutil.Bytes 142 err := c.rpClient.CallContext(ctx, &hex, "eth_call", callArgs, toBlockNumArg(blockNumber)) 143 if err != nil { 144 return nil, err 145 } 146 return hex, nil 147 } 148 149 func (c *EVMClient) CallContext(ctx context.Context, target interface{}, rpcMethod string, args ...interface{}) error { 150 err := c.rpClient.CallContext(ctx, target, rpcMethod, args...) 151 if err != nil { 152 return err 153 } 154 return nil 155 } 156 157 func (c *EVMClient) PendingCallContract(ctx context.Context, callArgs map[string]interface{}) ([]byte, error) { 158 var hex hexutil.Bytes 159 err := c.rpClient.CallContext(ctx, &hex, "eth_call", callArgs, "pending") 160 if err != nil { 161 return nil, err 162 } 163 return hex, nil 164 } 165 166 func (c *EVMClient) From() common.Address { 167 return c.signer.CommonAddress() 168 } 169 170 func (c *EVMClient) SignAndSendTransaction(ctx context.Context, tx CommonTransaction) (common.Hash, error) { 171 id, err := c.ChainID(ctx) 172 if err != nil { 173 // panic(err) 174 // Probably chain does not support chainID eg. CELO 175 id = nil 176 } 177 rawTx, err := tx.RawWithSignature(c.signer, id) 178 if err != nil { 179 return common.Hash{}, err 180 } 181 err = c.SendRawTransaction(ctx, rawTx) 182 if err != nil { 183 return common.Hash{}, err 184 } 185 return tx.Hash(), nil 186 } 187 188 func (c *EVMClient) RelayerAddress() common.Address { 189 return c.signer.CommonAddress() 190 } 191 192 func (c *EVMClient) LockNonce() { 193 c.nonceLock.Lock() 194 } 195 196 func (c *EVMClient) UnlockNonce() { 197 c.nonceLock.Unlock() 198 } 199 200 func (c *EVMClient) UnsafeNonce() (*big.Int, error) { 201 var err error 202 for i := 0; i <= 10; i++ { 203 if c.nonce == nil { 204 nonce, err := c.PendingNonceAt(context.Background(), c.signer.CommonAddress()) 205 if err != nil { 206 time.Sleep(1 * time.Second) 207 continue 208 } 209 c.nonce = big.NewInt(0).SetUint64(nonce) 210 return c.nonce, nil 211 } 212 return c.nonce, nil 213 } 214 return nil, err 215 } 216 217 func (c *EVMClient) UnsafeIncreaseNonce() error { 218 nonce, err := c.UnsafeNonce() 219 if err != nil { 220 return err 221 } 222 c.nonce = nonce.Add(nonce, big.NewInt(1)) 223 return nil 224 } 225 226 func (c *EVMClient) BaseFee() (*big.Int, error) { 227 head, err := c.HeaderByNumber(context.TODO(), nil) 228 if err != nil { 229 return nil, err 230 } 231 return head.BaseFee, nil 232 } 233 234 func toBlockNumArg(number *big.Int) string { 235 if number == nil { 236 return "latest" 237 } 238 return hexutil.EncodeBig(number) 239 } 240 241 // buildQuery constructs a query for the bridgeContract by hashing sig to get the event topic 242 func buildQuery(contract common.Address, sig string, startBlock *big.Int, endBlock *big.Int) ethereum.FilterQuery { 243 query := ethereum.FilterQuery{ 244 FromBlock: startBlock, 245 ToBlock: endBlock, 246 Addresses: []common.Address{contract}, 247 Topics: [][]common.Hash{ 248 {crypto.Keccak256Hash([]byte(sig))}, 249 }, 250 } 251 return query 252 }