github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/docker/test_env/geth2.go (about) 1 package test_env 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "html/template" 8 "os" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/google/uuid" 14 "github.com/rs/zerolog" 15 16 tc "github.com/testcontainers/testcontainers-go" 17 tcwait "github.com/testcontainers/testcontainers-go/wait" 18 19 "github.com/smartcontractkit/chainlink-testing-framework/libs/blockchain" 20 "github.com/smartcontractkit/chainlink-testing-framework/libs/docker" 21 "github.com/smartcontractkit/chainlink-testing-framework/libs/logging" 22 "github.com/smartcontractkit/chainlink-testing-framework/libs/mirror" 23 "github.com/smartcontractkit/chainlink-testing-framework/libs/utils/testcontext" 24 ) 25 26 type Geth2 struct { 27 EnvComponent 28 ExternalHttpUrl string 29 InternalHttpUrl string 30 ExternalWsUrl string 31 InternalWsUrl string 32 InternalExecutionURL string 33 ExternalExecutionURL string 34 generatedDataHostDir string 35 chainConfg *EthereumChainConfig 36 consensusLayer ConsensusLayer 37 l zerolog.Logger 38 t *testing.T 39 } 40 41 func NewGeth2(networks []string, chainConfg *EthereumChainConfig, generatedDataHostDir string, consensusLayer ConsensusLayer, opts ...EnvComponentOption) (*Geth2, error) { 42 // currently it uses v1.13.10 43 dockerImage, err := mirror.GetImage("ethereum/client-go:v1.13") 44 if err != nil { 45 return nil, err 46 } 47 48 parts := strings.Split(dockerImage, ":") 49 g := &Geth2{ 50 EnvComponent: EnvComponent{ 51 ContainerName: fmt.Sprintf("%s-%s", "geth2", uuid.NewString()[0:8]), 52 Networks: networks, 53 ContainerImage: parts[0], 54 ContainerVersion: parts[1], 55 }, 56 chainConfg: chainConfg, 57 generatedDataHostDir: generatedDataHostDir, 58 consensusLayer: consensusLayer, 59 l: logging.GetTestLogger(nil), 60 } 61 g.SetDefaultHooks() 62 for _, opt := range opts { 63 opt(&g.EnvComponent) 64 } 65 return g, nil 66 } 67 68 func (g *Geth2) WithTestInstance(t *testing.T) ExecutionClient { 69 g.l = logging.GetTestLogger(t) 70 g.t = t 71 return g 72 } 73 74 func (g *Geth2) StartContainer() (blockchain.EVMNetwork, error) { 75 r, err := g.getContainerRequest(g.Networks) 76 if err != nil { 77 return blockchain.EVMNetwork{}, err 78 } 79 80 l := logging.GetTestContainersGoTestLogger(g.t) 81 ct, err := docker.StartContainerWithRetry(g.l, tc.GenericContainerRequest{ 82 ContainerRequest: *r, 83 Reuse: true, 84 Started: true, 85 Logger: l, 86 }) 87 if err != nil { 88 return blockchain.EVMNetwork{}, fmt.Errorf("cannot start geth container: %w", err) 89 } 90 91 host, err := GetHost(testcontext.Get(g.t), ct) 92 if err != nil { 93 return blockchain.EVMNetwork{}, err 94 } 95 if err != nil { 96 return blockchain.EVMNetwork{}, err 97 } 98 httpPort, err := ct.MappedPort(testcontext.Get(g.t), NatPort(TX_GETH_HTTP_PORT)) 99 if err != nil { 100 return blockchain.EVMNetwork{}, err 101 } 102 wsPort, err := ct.MappedPort(testcontext.Get(g.t), NatPort(TX_GETH_WS_PORT)) 103 if err != nil { 104 return blockchain.EVMNetwork{}, err 105 } 106 executionPort, err := ct.MappedPort(testcontext.Get(g.t), NatPort(ETH2_EXECUTION_PORT)) 107 if err != nil { 108 return blockchain.EVMNetwork{}, err 109 } 110 111 g.Container = ct 112 g.ExternalHttpUrl = FormatHttpUrl(host, httpPort.Port()) 113 g.InternalHttpUrl = FormatHttpUrl(g.ContainerName, TX_GETH_HTTP_PORT) 114 g.ExternalWsUrl = FormatWsUrl(host, wsPort.Port()) 115 g.InternalWsUrl = FormatWsUrl(g.ContainerName, TX_GETH_WS_PORT) 116 g.InternalExecutionURL = FormatHttpUrl(g.ContainerName, ETH2_EXECUTION_PORT) 117 g.ExternalExecutionURL = FormatHttpUrl(host, executionPort.Port()) 118 119 networkConfig := blockchain.SimulatedEVMNetwork 120 networkConfig.Name = fmt.Sprintf("Simulated Eth2 (geth + %s)", g.consensusLayer) 121 networkConfig.URLs = []string{g.ExternalWsUrl} 122 networkConfig.HTTPURLs = []string{g.ExternalHttpUrl} 123 124 g.l.Info().Str("containerName", g.ContainerName). 125 Msg("Started Geth2 container") 126 127 return networkConfig, nil 128 } 129 130 func (g *Geth2) GetInternalExecutionURL() string { 131 return g.InternalExecutionURL 132 } 133 134 func (g *Geth2) GetExternalExecutionURL() string { 135 return g.ExternalExecutionURL 136 } 137 138 func (g *Geth2) GetInternalHttpUrl() string { 139 return g.InternalHttpUrl 140 } 141 142 func (g *Geth2) GetInternalWsUrl() string { 143 return g.InternalWsUrl 144 } 145 146 func (g *Geth2) GetExternalHttpUrl() string { 147 return g.ExternalHttpUrl 148 } 149 150 func (g *Geth2) GetExternalWsUrl() string { 151 return g.ExternalWsUrl 152 } 153 154 func (g *Geth2) GetContainerName() string { 155 return g.ContainerName 156 } 157 158 func (g *Geth2) GetContainer() *tc.Container { 159 return &g.Container 160 } 161 162 func (g *Geth2) getContainerRequest(networks []string) (*tc.ContainerRequest, error) { 163 initFile, err := os.CreateTemp("", "init.sh") 164 if err != nil { 165 return nil, err 166 } 167 168 initScriptContent, err := g.buildInitScript() 169 if err != nil { 170 return nil, err 171 } 172 173 _, err = initFile.WriteString(initScriptContent) 174 if err != nil { 175 return nil, err 176 } 177 178 return &tc.ContainerRequest{ 179 Name: g.ContainerName, 180 Image: g.GetImageWithVersion(), 181 Networks: networks, 182 ImagePlatform: "linux/x86_64", 183 ExposedPorts: []string{NatPortFormat(TX_GETH_HTTP_PORT), NatPortFormat(TX_GETH_WS_PORT), NatPortFormat(ETH2_EXECUTION_PORT)}, 184 WaitingFor: tcwait.ForAll( 185 tcwait.ForLog("WebSocket enabled"). 186 WithStartupTimeout(120 * time.Second). 187 WithPollInterval(1 * time.Second), 188 ), 189 Entrypoint: []string{ 190 "sh", 191 "/init.sh", 192 }, 193 Files: []tc.ContainerFile{ 194 { 195 HostFilePath: initFile.Name(), 196 ContainerFilePath: "/init.sh", 197 FileMode: 0744, 198 }, 199 }, 200 Mounts: tc.ContainerMounts{ 201 tc.ContainerMount{ 202 Source: tc.GenericBindMountSource{ 203 HostPath: g.generatedDataHostDir, 204 }, 205 Target: tc.ContainerMountTarget(GENERATED_DATA_DIR_INSIDE_CONTAINER), 206 }, 207 }, 208 LifecycleHooks: []tc.ContainerLifecycleHooks{ 209 { 210 PostStarts: g.PostStartsHooks, 211 PostStops: g.PostStopsHooks, 212 }, 213 }, 214 }, nil 215 } 216 217 func (g *Geth2) WaitUntilChainIsReady(ctx context.Context, waitTime time.Duration) error { 218 waitForFirstBlock := tcwait.NewLogStrategy("Chain head was updated").WithPollInterval(1 * time.Second).WithStartupTimeout(waitTime) 219 return waitForFirstBlock.WaitUntilReady(ctx, *g.GetContainer()) 220 } 221 222 func (g *Geth2) buildInitScript() (string, error) { 223 initTemplate := `#!/bin/bash 224 mkdir -p {{.ExecutionDir}} 225 226 # copy general keystore to execution directory, because Geth doesn't allow to specify keystore location 227 echo "Copying keystore to {{.ExecutionDir}}/keystore" 228 cp -R {{.KeystoreDirLocation}} {{.ExecutionDir}}/keystore 229 230 echo "Creating sk.json file" 231 echo "2e0834786285daccd064ca17f1654f67b4aef298acbb82cef9ec422fb4975622" > {{.ExecutionDir}}/sk.json 232 233 echo "Running geth init" 234 geth init --state.scheme=path --datadir={{.ExecutionDir}} {{.GeneratedDataDir}}/genesis.json 235 exit_code=$? 236 if [ $exit_code -ne 0 ]; then 237 echo "Geth init failed with exit code $exit_code" 238 exit 1 239 fi 240 241 echo "Starting Geth..." 242 geth --http --http.api=eth,net,web3,debug --http.addr=0.0.0.0 --http.corsdomain=* \ 243 --http.vhosts=* --http.port={{.HttpPort}} --ws --ws.api=admin,debug,web3,eth,txpool,net \ 244 --ws.addr=0.0.0.0 --ws.origins=* --ws.port={{.WsPort}} --authrpc.vhosts=* \ 245 --authrpc.addr=0.0.0.0 --authrpc.jwtsecret={{.JwtFileLocation}} --datadir={{.ExecutionDir}} \ 246 --rpc.allow-unprotected-txs --rpc.txfeecap=0 --allow-insecure-unlock \ 247 --password={{.PasswordFileLocation}} --nodiscover --syncmode=full --networkid={{.ChainID}} \ 248 --graphql --graphql.corsdomain=* --unlock=0x123463a4b065722e99115d6c222f267d9cabb524` 249 250 data := struct { 251 HttpPort string 252 WsPort string 253 ChainID int 254 GeneratedDataDir string 255 JwtFileLocation string 256 PasswordFileLocation string 257 KeystoreDirLocation string 258 ExecutionDir string 259 }{ 260 HttpPort: TX_GETH_HTTP_PORT, 261 WsPort: TX_GETH_WS_PORT, 262 ChainID: g.chainConfg.ChainID, 263 GeneratedDataDir: GENERATED_DATA_DIR_INSIDE_CONTAINER, 264 JwtFileLocation: JWT_SECRET_FILE_LOCATION_INSIDE_CONTAINER, 265 PasswordFileLocation: ACCOUNT_PASSWORD_FILE_INSIDE_CONTAINER, 266 KeystoreDirLocation: KEYSTORE_DIR_LOCATION_INSIDE_CONTAINER, 267 ExecutionDir: "/execution-data", 268 } 269 270 t, err := template.New("init").Parse(initTemplate) 271 if err != nil { 272 fmt.Println("Error parsing template:", err) 273 os.Exit(1) 274 } 275 276 var buf bytes.Buffer 277 err = t.Execute(&buf, data) 278 279 return buf.String(), err 280 281 } 282 283 func (g *Geth2) GetContainerType() ContainerType { 284 return ContainerType_Geth 285 }