code.vegaprotocol.io/vega@v0.79.0/core/client/eth/client.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  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    29  
    30  	ethcommon "github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/ethclient"
    32  )
    33  
    34  var (
    35  	ErrUnexpectedContractHash   = errors.New("hash of contract bytecode not as expected")
    36  	ErrUnexpectedSolidityFormat = errors.New("unexpected format of solidity bytecode")
    37  )
    38  
    39  // ContractHashes the sha3-256(contract-bytecode stripped of metadata).
    40  var ContractHashes = map[string]string{
    41  	"staking":    "d66948e12817f8ae6ca94d56b43ca12e66416e7e9bc23bb09056957b25afc6bd",
    42  	"vesting":    "5278802577f4aca315b9524bfa78790f8f0fae08939ec58bc9e8f0ea40123b09",
    43  	"collateral": "1cd7f315188baf26f70c77a764df361c5d01bd365b109b96033b8755ee2b2750",
    44  	"multisig":   "5b7070e6159628455b38f5796e8d0dc08185aaaa1fb6073767c88552d396c6c2",
    45  }
    46  
    47  type PrimaryClient struct {
    48  	ETHClient
    49  	ethConfig *types.EthereumConfig
    50  
    51  	// this is all just to prevent spamming the infura just
    52  	// to get the last height of the blockchain
    53  	mu                      sync.Mutex
    54  	currentHeightLastUpdate time.Time
    55  	currentHeight           uint64
    56  
    57  	retryDelay time.Duration
    58  }
    59  
    60  func PrimaryDial(ctx context.Context, cfg Config) (*PrimaryClient, error) {
    61  	if len(cfg.RPCEndpoint) <= 0 {
    62  		return nil, errors.New("no ethereum rpc endpoint configured. the configuration have move from the NodeWallet section to the Ethereum section, please make sure your vega configuration is up to date")
    63  	}
    64  
    65  	ethClient, err := ethclient.DialContext(ctx, cfg.RPCEndpoint)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("couldn't instantiate Ethereum client: %w", err)
    68  	}
    69  
    70  	return &PrimaryClient{
    71  		ETHClient:  newEthClientWrapper(ethClient),
    72  		retryDelay: cfg.RetryDelay.Get(),
    73  	}, nil
    74  }
    75  
    76  func (c *PrimaryClient) UpdateEthereumConfig(ctx context.Context, ethConfig *types.EthereumConfig) error {
    77  	if c == nil {
    78  		return nil
    79  	}
    80  
    81  	netID, err := c.NetworkID(ctx)
    82  	if err != nil {
    83  		return fmt.Errorf("couldn't retrieve the network ID from the ethereum client: %w", err)
    84  	}
    85  
    86  	chainID, err := c.ChainID(ctx)
    87  	if err != nil {
    88  		return fmt.Errorf("couldn't retrieve the chain ID from the ethereum client: %w", err)
    89  	}
    90  
    91  	if netID.String() != ethConfig.NetworkID() {
    92  		return fmt.Errorf("updated network ID does not match the one set during start up, expected %s got %v", ethConfig.NetworkID(), netID)
    93  	}
    94  
    95  	if chainID.String() != ethConfig.ChainID() {
    96  		return fmt.Errorf("updated chain ID does not match the one set during start up, expected %v got %v", ethConfig.ChainID(), chainID)
    97  	}
    98  
    99  	c.ethConfig = ethConfig
   100  
   101  	return nil
   102  }
   103  
   104  func (c *PrimaryClient) CollateralBridgeAddress() ethcommon.Address {
   105  	return c.ethConfig.CollateralBridge().Address()
   106  }
   107  
   108  func (c *PrimaryClient) CollateralBridgeAddressHex() string {
   109  	return c.ethConfig.CollateralBridge().HexAddress()
   110  }
   111  
   112  // IsEthereum returns whether or not this client is the "primary" one and pointing to Ethereum.
   113  func (c *PrimaryClient) IsEthereum() bool {
   114  	return true
   115  }
   116  
   117  func (c *PrimaryClient) CurrentHeight(ctx context.Context) (uint64, error) {
   118  	c.mu.Lock()
   119  	defer c.mu.Unlock()
   120  
   121  	if now := time.Now(); c.currentHeightLastUpdate.Add(c.retryDelay).Before(now) {
   122  		lastBlockHeader, err := c.HeaderByNumber(ctx, nil)
   123  		if err != nil {
   124  			return c.currentHeight, err
   125  		}
   126  		c.currentHeightLastUpdate = now
   127  		c.currentHeight = lastBlockHeader.Number.Uint64()
   128  	}
   129  
   130  	return c.currentHeight, nil
   131  }
   132  
   133  func (c *PrimaryClient) ConfirmationsRequired() uint64 {
   134  	return c.ethConfig.Confirmations()
   135  }
   136  
   137  // VerifyContract takes the address of a contract in hex and checks the hash of the byte-code is as expected.
   138  func (c *PrimaryClient) VerifyContract(ctx context.Context, address ethcommon.Address, expectedHash string) error {
   139  	// nil block number means latest block
   140  	b, err := c.CodeAt(ctx, address, nil)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	// the bytecode of the contract is appended which is deployment specific. We only care about
   146  	// the contract code itself and so we need to strip this meta-data before hashing it. For the version
   147  	// of Solidity we use, the format is [contract-bytecode]a264[CBOR-encoded meta-data]
   148  	asHex := strings.Split(hex.EncodeToString(b), "a264")
   149  	if len(asHex) != 2 {
   150  		return fmt.Errorf("%w: address: %s", ErrUnexpectedSolidityFormat, address)
   151  	}
   152  
   153  	// Back to bytes for hashing
   154  	b, err = hex.DecodeString(asHex[0])
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	h := hex.EncodeToString(vgcrypto.Hash(b))
   160  	if h != expectedHash {
   161  		return fmt.Errorf("%w: address: %s, hash: %s, expected: %s", ErrUnexpectedContractHash, address, h, expectedHash)
   162  	}
   163  
   164  	return nil
   165  }