github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/docker/test_env/erigon.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 Erigon 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 NewErigon(networks []string, chainConfg *EthereumChainConfig, generatedDataHostDir string, consensusLayer ConsensusLayer, opts ...EnvComponentOption) (*Erigon, error) {
    42  	// currently it uses v2.56.0
    43  	dockerImage, err := mirror.GetImage("thorax/erigon:v2.56")
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	parts := strings.Split(dockerImage, ":")
    49  	g := &Erigon{
    50  		EnvComponent: EnvComponent{
    51  			ContainerName:    fmt.Sprintf("%s-%s", "erigon", 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 *Erigon) WithTestInstance(t *testing.T) ExecutionClient {
    69  	g.l = logging.GetTestLogger(t)
    70  	g.t = t
    71  	return g
    72  }
    73  
    74  func (g *Erigon) 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 erigon 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  	executionPort, err := ct.MappedPort(testcontext.Get(g.t), NatPort(ETH2_EXECUTION_PORT))
   103  	if err != nil {
   104  		return blockchain.EVMNetwork{}, err
   105  	}
   106  
   107  	g.Container = ct
   108  	g.ExternalHttpUrl = FormatHttpUrl(host, httpPort.Port())
   109  	g.InternalHttpUrl = FormatHttpUrl(g.ContainerName, TX_GETH_HTTP_PORT)
   110  	g.ExternalWsUrl = FormatWsUrl(host, httpPort.Port())
   111  	g.InternalWsUrl = FormatWsUrl(g.ContainerName, TX_GETH_HTTP_PORT)
   112  	g.InternalExecutionURL = FormatHttpUrl(g.ContainerName, ETH2_EXECUTION_PORT)
   113  	g.ExternalExecutionURL = FormatHttpUrl(host, executionPort.Port())
   114  
   115  	networkConfig := blockchain.SimulatedEVMNetwork
   116  	networkConfig.Name = fmt.Sprintf("Simulated Eth2 (erigon + %s)", g.consensusLayer)
   117  	networkConfig.URLs = []string{g.ExternalWsUrl}
   118  	networkConfig.HTTPURLs = []string{g.ExternalHttpUrl}
   119  
   120  	g.l.Info().Str("containerName", g.ContainerName).
   121  		Msg("Started Erigon container")
   122  
   123  	return networkConfig, nil
   124  }
   125  
   126  func (g *Erigon) GetInternalExecutionURL() string {
   127  	return g.InternalExecutionURL
   128  }
   129  
   130  func (g *Erigon) GetExternalExecutionURL() string {
   131  	return g.ExternalExecutionURL
   132  }
   133  
   134  func (g *Erigon) GetInternalHttpUrl() string {
   135  	return g.InternalHttpUrl
   136  }
   137  
   138  func (g *Erigon) GetInternalWsUrl() string {
   139  	return g.InternalWsUrl
   140  }
   141  
   142  func (g *Erigon) GetExternalHttpUrl() string {
   143  	return g.ExternalHttpUrl
   144  }
   145  
   146  func (g *Erigon) GetExternalWsUrl() string {
   147  	return g.ExternalWsUrl
   148  }
   149  
   150  func (g *Erigon) GetContainerName() string {
   151  	return g.ContainerName
   152  }
   153  
   154  func (g *Erigon) GetContainer() *tc.Container {
   155  	return &g.Container
   156  }
   157  
   158  func (g *Erigon) getContainerRequest(networks []string) (*tc.ContainerRequest, error) {
   159  	initFile, err := os.CreateTemp("", "init.sh")
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	initScriptContent, err := g.buildInitScript()
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	_, err = initFile.WriteString(initScriptContent)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	return &tc.ContainerRequest{
   175  		Name:          g.ContainerName,
   176  		Image:         g.GetImageWithVersion(),
   177  		Networks:      networks,
   178  		ImagePlatform: "linux/x86_64",
   179  		ExposedPorts:  []string{NatPortFormat(TX_GETH_HTTP_PORT), NatPortFormat(ETH2_EXECUTION_PORT)},
   180  		WaitingFor: tcwait.ForAll(
   181  			tcwait.ForLog("Started P2P networking").
   182  				WithStartupTimeout(120 * time.Second).
   183  				WithPollInterval(1 * time.Second),
   184  		),
   185  		User: "0:0",
   186  		Entrypoint: []string{
   187  			"sh",
   188  			"/home/erigon/init.sh",
   189  		},
   190  		Files: []tc.ContainerFile{
   191  			{
   192  				HostFilePath:      initFile.Name(),
   193  				ContainerFilePath: "/home/erigon/init.sh",
   194  				FileMode:          0744,
   195  			},
   196  		},
   197  		Mounts: tc.ContainerMounts{
   198  			tc.ContainerMount{
   199  				Source: tc.GenericBindMountSource{
   200  					HostPath: g.generatedDataHostDir,
   201  				},
   202  				Target: tc.ContainerMountTarget(GENERATED_DATA_DIR_INSIDE_CONTAINER),
   203  			},
   204  		},
   205  		LifecycleHooks: []tc.ContainerLifecycleHooks{
   206  			{
   207  				PostStarts: g.PostStartsHooks,
   208  				PostStops:  g.PostStopsHooks,
   209  			},
   210  		},
   211  	}, nil
   212  }
   213  
   214  func (g *Erigon) WaitUntilChainIsReady(ctx context.Context, waitTime time.Duration) error {
   215  	waitForFirstBlock := tcwait.NewLogStrategy("Built block").WithPollInterval(1 * time.Second).WithStartupTimeout(waitTime)
   216  	return waitForFirstBlock.WaitUntilReady(ctx, *g.GetContainer())
   217  }
   218  
   219  func (g *Erigon) buildInitScript() (string, error) {
   220  	initTemplate := `#!/bin/bash
   221  	echo "Copied genesis file to {{.ExecutionDir}}"
   222  	mkdir -p {{.ExecutionDir}}
   223  	cp {{.GeneratedDataDir}}/genesis.json {{.ExecutionDir}}/genesis.json
   224  	echo "Running erigon init"
   225  	erigon init --datadir={{.ExecutionDir}} {{.ExecutionDir}}/genesis.json
   226  	exit_code=$?
   227  	if [ $exit_code -ne 0 ]; then
   228  		echo "Erigon init failed with exit code $exit_code"
   229  		exit 1
   230  	fi
   231  
   232  	echo "Starting Erigon..."
   233  	erigon --http --http.api=eth,erigon,engine,web3,net,debug,trace,txpool,admin --http.addr=0.0.0.0 --http.corsdomain=* \
   234  		--http.vhosts=* --http.port={{.HttpPort}} --ws --authrpc.vhosts=* --authrpc.addr=0.0.0.0 --authrpc.jwtsecret={{.JwtFileLocation}} \
   235  		--datadir={{.ExecutionDir}} --rpc.allow-unprotected-txs --rpc.txfeecap=0 --allow-insecure-unlock \
   236  		--nodiscover --networkid={{.ChainID}} --db.size.limit=8TB`
   237  
   238  	data := struct {
   239  		HttpPort         string
   240  		ChainID          int
   241  		GeneratedDataDir string
   242  		JwtFileLocation  string
   243  		ExecutionDir     string
   244  	}{
   245  		HttpPort:         TX_GETH_HTTP_PORT,
   246  		ChainID:          g.chainConfg.ChainID,
   247  		GeneratedDataDir: GENERATED_DATA_DIR_INSIDE_CONTAINER,
   248  		JwtFileLocation:  JWT_SECRET_FILE_LOCATION_INSIDE_CONTAINER,
   249  		ExecutionDir:     "/home/erigon/execution-data",
   250  	}
   251  
   252  	t, err := template.New("init").Parse(initTemplate)
   253  	if err != nil {
   254  		fmt.Println("Error parsing template:", err)
   255  		os.Exit(1)
   256  	}
   257  
   258  	var buf bytes.Buffer
   259  	err = t.Execute(&buf, data)
   260  
   261  	return buf.String(), err
   262  
   263  }
   264  
   265  func (g *Erigon) GetContainerType() ContainerType {
   266  	return ContainerType_Erigon
   267  }