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  }