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  }