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 }