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