github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/docker/test_env/ethereum_env.go (about)

     1  package test_env
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/google/uuid"
    14  
    15  	"github.com/rs/zerolog"
    16  	tc "github.com/testcontainers/testcontainers-go"
    17  
    18  	"github.com/smartcontractkit/chainlink-testing-framework/libs/blockchain"
    19  	"github.com/smartcontractkit/chainlink-testing-framework/libs/docker"
    20  	"github.com/smartcontractkit/chainlink-testing-framework/libs/logging"
    21  	"github.com/smartcontractkit/chainlink-testing-framework/libs/utils/testcontext"
    22  	toml_utils "github.com/smartcontractkit/chainlink-testing-framework/libs/utils/toml"
    23  )
    24  
    25  const (
    26  	CONFIG_ENV_VAR_NAME      = "PRIVATE_ETHEREUM_NETWORK_CONFIG_PATH"
    27  	EXEC_CLIENT_ENV_VAR_NAME = "ETH2_EL_CLIENT"
    28  )
    29  
    30  var (
    31  	ErrMissingConsensusType     = errors.New("consensus type is required")
    32  	ErrMissingExecutionLayer    = errors.New("execution layer is required")
    33  	ErrMissingConsensusLayer    = errors.New("consensus layer is required for PoS")
    34  	ErrConsensusLayerNotAllowed = errors.New("consensus layer is not allowed for PoW")
    35  	ErrTestConfigNotSaved       = errors.New("could not save test env config")
    36  )
    37  
    38  type ConsensusType string
    39  
    40  const (
    41  	ConsensusType_PoS ConsensusType = "pos"
    42  	ConsensusType_PoW ConsensusType = "pow"
    43  )
    44  
    45  type ExecutionLayer string
    46  
    47  const (
    48  	ExecutionLayer_Geth       ExecutionLayer = "geth"
    49  	ExecutionLayer_Nethermind ExecutionLayer = "nethermind"
    50  	ExecutionLayer_Erigon     ExecutionLayer = "erigon"
    51  	ExecutionLayer_Besu       ExecutionLayer = "besu"
    52  )
    53  
    54  type ConsensusLayer string
    55  
    56  const (
    57  	ConsensusLayer_Prysm ConsensusLayer = "prysm"
    58  )
    59  
    60  type EthereumNetworkBuilder struct {
    61  	t                   *testing.T
    62  	dockerNetworks      []string
    63  	consensusType       ConsensusType
    64  	consensusLayer      *ConsensusLayer
    65  	executionLayer      ExecutionLayer
    66  	ethereumChainConfig *EthereumChainConfig
    67  	existingConfig      *EthereumNetwork
    68  	customDockerImages  map[ContainerType]string
    69  	addressesToFund     []string
    70  	waitForFinalization bool
    71  	existingFromEnvVar  bool
    72  }
    73  
    74  func NewEthereumNetworkBuilder() EthereumNetworkBuilder {
    75  	return EthereumNetworkBuilder{
    76  		dockerNetworks:      []string{},
    77  		waitForFinalization: false,
    78  	}
    79  }
    80  
    81  func (b *EthereumNetworkBuilder) WithConsensusType(consensusType ConsensusType) *EthereumNetworkBuilder {
    82  	b.consensusType = consensusType
    83  	return b
    84  }
    85  
    86  func (b *EthereumNetworkBuilder) WithConsensusLayer(consensusLayer ConsensusLayer) *EthereumNetworkBuilder {
    87  	b.consensusLayer = &consensusLayer
    88  	return b
    89  }
    90  
    91  func (b *EthereumNetworkBuilder) WithExecutionLayer(executionLayer ExecutionLayer) *EthereumNetworkBuilder {
    92  	b.executionLayer = executionLayer
    93  	return b
    94  }
    95  
    96  func (b *EthereumNetworkBuilder) WithEthereumChainConfig(config EthereumChainConfig) *EthereumNetworkBuilder {
    97  	b.ethereumChainConfig = &config
    98  	return b
    99  }
   100  
   101  func (b *EthereumNetworkBuilder) WithDockerNetworks(networks []string) *EthereumNetworkBuilder {
   102  	b.dockerNetworks = networks
   103  	return b
   104  }
   105  
   106  func (b *EthereumNetworkBuilder) WithExistingConfig(config EthereumNetwork) *EthereumNetworkBuilder {
   107  	b.existingConfig = &config
   108  	return b
   109  }
   110  
   111  func (b *EthereumNetworkBuilder) WihtExistingConfigFromEnvVar() *EthereumNetworkBuilder {
   112  	b.existingFromEnvVar = true
   113  	return b
   114  }
   115  
   116  func (b *EthereumNetworkBuilder) WithTest(t *testing.T) *EthereumNetworkBuilder {
   117  	b.t = t
   118  	return b
   119  }
   120  
   121  func (b *EthereumNetworkBuilder) WithCustomDockerImages(newImages map[ContainerType]string) *EthereumNetworkBuilder {
   122  	b.customDockerImages = newImages
   123  	return b
   124  }
   125  
   126  func (b *EthereumNetworkBuilder) WithWaitingForFinalization() *EthereumNetworkBuilder {
   127  	b.waitForFinalization = true
   128  	return b
   129  }
   130  
   131  func (b *EthereumNetworkBuilder) buildNetworkConfig() EthereumNetwork {
   132  	n := EthereumNetwork{
   133  		ConsensusType:  &b.consensusType,
   134  		ExecutionLayer: &b.executionLayer,
   135  		ConsensusLayer: b.consensusLayer,
   136  	}
   137  
   138  	if b.existingConfig != nil && len(b.existingConfig.Containers) > 0 {
   139  		n.isRecreated = true
   140  		n.Containers = b.existingConfig.Containers
   141  	}
   142  
   143  	n.DockerNetworkNames = b.dockerNetworks
   144  	n.WaitForFinalization = &b.waitForFinalization
   145  	n.EthereumChainConfig = b.ethereumChainConfig
   146  	n.CustomDockerImages = b.customDockerImages
   147  	n.t = b.t
   148  
   149  	return n
   150  }
   151  
   152  func (b *EthereumNetworkBuilder) Build() (EthereumNetwork, error) {
   153  	if b.existingFromEnvVar {
   154  		path := os.Getenv(CONFIG_ENV_VAR_NAME)
   155  		if path == "" {
   156  			return EthereumNetwork{}, fmt.Errorf("environment variable %s is not set, but build from env var was requested", CONFIG_ENV_VAR_NAME)
   157  		}
   158  
   159  		config, err := NewPrivateChainEnvConfigFromFile(path)
   160  		if err != nil {
   161  			return EthereumNetwork{}, err
   162  		}
   163  
   164  		config.isRecreated = true
   165  
   166  		return config, nil
   167  	}
   168  
   169  	if !b.importExistingConfig() {
   170  		if b.ethereumChainConfig == nil {
   171  			defaultConfig := GetDefaultChainConfig()
   172  			b.ethereumChainConfig = &defaultConfig
   173  		} else {
   174  			b.ethereumChainConfig.fillInMissingValuesWithDefault()
   175  		}
   176  
   177  		b.ethereumChainConfig.GenerateGenesisTimestamp()
   178  	}
   179  
   180  	err := b.validate()
   181  	if err != nil {
   182  		return EthereumNetwork{}, err
   183  	}
   184  
   185  	return b.buildNetworkConfig(), nil
   186  }
   187  
   188  func (b *EthereumNetworkBuilder) importExistingConfig() bool {
   189  	if b.existingConfig == nil {
   190  		return false
   191  	}
   192  
   193  	if b.existingConfig.ConsensusType != nil {
   194  		b.consensusType = *b.existingConfig.ConsensusType
   195  	}
   196  
   197  	if b.existingConfig.ConsensusLayer != nil {
   198  		b.consensusLayer = b.existingConfig.ConsensusLayer
   199  	}
   200  
   201  	if b.existingConfig.ExecutionLayer != nil {
   202  		b.executionLayer = *b.existingConfig.ExecutionLayer
   203  	}
   204  
   205  	if len(b.existingConfig.DockerNetworkNames) > 0 {
   206  		b.dockerNetworks = b.existingConfig.DockerNetworkNames
   207  	}
   208  	b.ethereumChainConfig = b.existingConfig.EthereumChainConfig
   209  	b.customDockerImages = b.existingConfig.CustomDockerImages
   210  
   211  	return true
   212  }
   213  
   214  func (b *EthereumNetworkBuilder) validate() error {
   215  	if b.consensusType == "" {
   216  		return ErrMissingConsensusType
   217  	}
   218  
   219  	if b.executionLayer == "" {
   220  		return ErrMissingExecutionLayer
   221  	}
   222  
   223  	if b.consensusType == ConsensusType_PoS && b.consensusLayer == nil {
   224  		return ErrMissingConsensusLayer
   225  	}
   226  
   227  	if b.consensusType == ConsensusType_PoW && b.consensusLayer != nil {
   228  		return ErrConsensusLayerNotAllowed
   229  	}
   230  
   231  	for _, addr := range b.addressesToFund {
   232  		if !common.IsHexAddress(addr) {
   233  			return fmt.Errorf("address %s is not a valid hex address", addr)
   234  		}
   235  	}
   236  
   237  	if b.ethereumChainConfig == nil {
   238  		return errors.New("ethereum chain config is required")
   239  	}
   240  
   241  	return b.ethereumChainConfig.Validate(logging.GetTestLogger(nil), b.consensusType)
   242  }
   243  
   244  type EthereumNetwork struct {
   245  	ConsensusType        *ConsensusType            `toml:"consensus_type"`
   246  	ConsensusLayer       *ConsensusLayer           `toml:"consensus_layer"`
   247  	ExecutionLayer       *ExecutionLayer           `toml:"execution_layer"`
   248  	DockerNetworkNames   []string                  `toml:"docker_network_names"`
   249  	Containers           EthereumNetworkContainers `toml:"containers"`
   250  	WaitForFinalization  *bool                     `toml:"wait_for_finalization"`
   251  	GeneratedDataHostDir *string                   `toml:"generated_data_host_dir"`
   252  	ValKeysDir           *string                   `toml:"val_keys_dir"`
   253  	EthereumChainConfig  *EthereumChainConfig      `toml:"EthereumChainConfig"`
   254  	CustomDockerImages   map[ContainerType]string  `toml:"CustomDockerImages"`
   255  	isRecreated          bool
   256  	t                    *testing.T
   257  }
   258  
   259  func (en *EthereumNetwork) Start() (blockchain.EVMNetwork, RpcProvider, error) {
   260  	switch *en.ConsensusType {
   261  	case ConsensusType_PoS:
   262  		return en.startPos()
   263  	case ConsensusType_PoW:
   264  		return en.startPow()
   265  	default:
   266  		return blockchain.EVMNetwork{}, RpcProvider{}, fmt.Errorf("unknown consensus type: %s", *en.ConsensusType)
   267  	}
   268  }
   269  
   270  func (en *EthereumNetwork) startPos() (blockchain.EVMNetwork, RpcProvider, error) {
   271  	rpcProvider := RpcProvider{
   272  		privateHttpUrls: []string{},
   273  		privatelWsUrls:  []string{},
   274  		publiclHttpUrls: []string{},
   275  		publicsUrls:     []string{},
   276  	}
   277  
   278  	var net blockchain.EVMNetwork
   279  
   280  	if *en.ConsensusLayer != ConsensusLayer_Prysm {
   281  		return blockchain.EVMNetwork{}, RpcProvider{}, fmt.Errorf("unsupported consensus layer: %s. Use 'prysm'", *en.ConsensusLayer)
   282  	}
   283  
   284  	dockerNetworks, err := en.getOrCreateDockerNetworks()
   285  	if err != nil {
   286  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   287  	}
   288  	var generatedDataHostDir, valKeysDir string
   289  
   290  	// create host directories and run genesis containers only if we are NOT recreating existing containers
   291  	if !en.isRecreated {
   292  		generatedDataHostDir, valKeysDir, err = createHostDirectories()
   293  
   294  		en.GeneratedDataHostDir = &generatedDataHostDir
   295  		en.ValKeysDir = &valKeysDir
   296  
   297  		if err != nil {
   298  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   299  		}
   300  
   301  		valKeysGeneretor, err := NewValKeysGeneretor(en.EthereumChainConfig, valKeysDir, en.getImageOverride(ContainerType_ValKeysGenerator)...)
   302  		if err != nil {
   303  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   304  		}
   305  		valKeysGeneretor.WithTestInstance(en.t)
   306  
   307  		err = valKeysGeneretor.StartContainer()
   308  		if err != nil {
   309  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   310  		}
   311  
   312  		genesis, err := NewEthGenesisGenerator(*en.EthereumChainConfig, generatedDataHostDir, en.getImageOverride(ContainerType_GenesisGenerator)...)
   313  		if err != nil {
   314  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   315  		}
   316  
   317  		genesis.WithTestInstance(en.t)
   318  
   319  		err = genesis.StartContainer()
   320  		if err != nil {
   321  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   322  		}
   323  
   324  		initHelper := NewInitHelper(*en.EthereumChainConfig, generatedDataHostDir).WithTestInstance(en.t)
   325  		err = initHelper.StartContainer()
   326  		if err != nil {
   327  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   328  		}
   329  	} else {
   330  		// we don't set actual values to not increase complexity, as they do not matter for containers that are already running
   331  		generatedDataHostDir = ""
   332  		valKeysDir = ""
   333  	}
   334  
   335  	var client ExecutionClient
   336  	var clientErr error
   337  	switch *en.ExecutionLayer {
   338  	case ExecutionLayer_Geth:
   339  		client, clientErr = NewGeth2(dockerNetworks, en.EthereumChainConfig, generatedDataHostDir, ConsensusLayer_Prysm, append(en.getImageOverride(ContainerType_Geth), en.setExistingContainerName(ContainerType_Geth))...)
   340  	case ExecutionLayer_Nethermind:
   341  		client, clientErr = NewNethermind(dockerNetworks, generatedDataHostDir, ConsensusLayer_Prysm, append(en.getImageOverride(ContainerType_Nethermind), en.setExistingContainerName(ContainerType_Nethermind))...)
   342  	case ExecutionLayer_Erigon:
   343  		client, clientErr = NewErigon(dockerNetworks, en.EthereumChainConfig, generatedDataHostDir, ConsensusLayer_Prysm, append(en.getImageOverride(ContainerType_Erigon), en.setExistingContainerName(ContainerType_Erigon))...)
   344  	case ExecutionLayer_Besu:
   345  		client, clientErr = NewBesu(dockerNetworks, en.EthereumChainConfig, generatedDataHostDir, ConsensusLayer_Prysm, append(en.getImageOverride(ContainerType_Besu), en.setExistingContainerName(ContainerType_Besu))...)
   346  	default:
   347  		return blockchain.EVMNetwork{}, RpcProvider{}, fmt.Errorf("unsupported execution layer: %s", *en.ExecutionLayer)
   348  	}
   349  
   350  	if clientErr != nil {
   351  		return blockchain.EVMNetwork{}, RpcProvider{}, clientErr
   352  	}
   353  
   354  	client.WithTestInstance(en.t)
   355  
   356  	net, err = client.StartContainer()
   357  	if err != nil {
   358  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   359  	}
   360  
   361  	beacon, err := NewPrysmBeaconChain(dockerNetworks, en.EthereumChainConfig, generatedDataHostDir, client.GetInternalExecutionURL(), append(en.getImageOverride(ContainerType_ValKeysGenerator), en.setExistingContainerName(ContainerType_PrysmBeacon))...)
   362  	if err != nil {
   363  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   364  	}
   365  
   366  	beacon.WithTestInstance(en.t)
   367  	err = beacon.StartContainer()
   368  	if err != nil {
   369  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   370  	}
   371  
   372  	validator, err := NewPrysmValidator(dockerNetworks, en.EthereumChainConfig, generatedDataHostDir, valKeysDir, beacon.
   373  		InternalBeaconRpcProvider, append(en.getImageOverride(ContainerType_ValKeysGenerator), en.setExistingContainerName(ContainerType_PrysmVal))...)
   374  	if err != nil {
   375  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   376  	}
   377  
   378  	validator.WithTestInstance(en.t)
   379  	err = validator.StartContainer()
   380  	if err != nil {
   381  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   382  	}
   383  
   384  	err = client.WaitUntilChainIsReady(testcontext.Get(en.t), en.EthereumChainConfig.GetDefaultWaitDuration())
   385  	if err != nil {
   386  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   387  	}
   388  
   389  	en.DockerNetworkNames = dockerNetworks
   390  	net.ChainID = int64(en.EthereumChainConfig.ChainID)
   391  	// use a higher value than the default, because eth2 is slower than dev-mode eth1
   392  	net.Timeout = blockchain.StrDuration{Duration: time.Duration(4 * time.Minute)}
   393  	net.FinalityTag = true
   394  	net.FinalityDepth = 0
   395  
   396  	if *en.ExecutionLayer == ExecutionLayer_Besu {
   397  		// Besu doesn't support "eth_maxPriorityFeePerGas" https://github.com/hyperledger/besu/issues/5658
   398  		// And if gas is too low, then transaction doesn't get to prioritized pool and is not a candidate for inclusion in the next block
   399  		net.GasEstimationBuffer = 10_000_000_000
   400  	} else {
   401  		net.SupportsEIP1559 = true
   402  	}
   403  
   404  	logger := logging.GetTestLogger(en.t)
   405  	if en.WaitForFinalization != nil && *en.WaitForFinalization {
   406  		evmClient, err := blockchain.NewEVMClientFromNetwork(net, logger)
   407  		if err != nil {
   408  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   409  		}
   410  
   411  		err = waitForChainToFinaliseAnEpoch(logger, evmClient, en.EthereumChainConfig.GetDefaultFinalizationWaitDuration())
   412  		if err != nil {
   413  			return blockchain.EVMNetwork{}, RpcProvider{}, err
   414  		}
   415  	} else {
   416  		logger.Info().Msg("Not waiting for chain to finalize first epoch")
   417  	}
   418  
   419  	containers := EthereumNetworkContainers{
   420  		{
   421  			ContainerName: client.GetContainerName(),
   422  			ContainerType: client.GetContainerType(),
   423  			Container:     client.GetContainer(),
   424  		},
   425  		{
   426  			ContainerName: beacon.ContainerName,
   427  			ContainerType: ContainerType_PrysmBeacon,
   428  			Container:     &beacon.Container,
   429  		},
   430  		{
   431  			ContainerName: validator.ContainerName,
   432  			ContainerType: ContainerType_PrysmVal,
   433  			Container:     &validator.Container,
   434  		},
   435  	}
   436  
   437  	en.Containers = append(en.Containers, containers...)
   438  
   439  	rpcProvider.privateHttpUrls = append(rpcProvider.privateHttpUrls, client.GetInternalHttpUrl())
   440  	rpcProvider.privatelWsUrls = append(rpcProvider.privatelWsUrls, client.GetInternalWsUrl())
   441  	rpcProvider.publiclHttpUrls = append(rpcProvider.publiclHttpUrls, client.GetExternalHttpUrl())
   442  	rpcProvider.publicsUrls = append(rpcProvider.publicsUrls, client.GetExternalWsUrl())
   443  
   444  	return net, rpcProvider, nil
   445  }
   446  
   447  func (en *EthereumNetwork) startPow() (blockchain.EVMNetwork, RpcProvider, error) {
   448  	var net blockchain.EVMNetwork
   449  	rpcProvider := RpcProvider{
   450  		privateHttpUrls: []string{},
   451  		privatelWsUrls:  []string{},
   452  		publiclHttpUrls: []string{},
   453  		publicsUrls:     []string{},
   454  	}
   455  
   456  	if *en.ExecutionLayer != ExecutionLayer_Geth {
   457  		return blockchain.EVMNetwork{}, RpcProvider{}, fmt.Errorf("unsupported execution layer: %s", *en.ExecutionLayer)
   458  	}
   459  	dockerNetworks, err := en.getOrCreateDockerNetworks()
   460  	if err != nil {
   461  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   462  	}
   463  
   464  	geth := NewGeth(dockerNetworks, en.EthereumChainConfig, append(en.getImageOverride(ContainerType_Geth), en.setExistingContainerName(ContainerType_Geth))...)
   465  	geth.WithTestInstance(en.t)
   466  
   467  	network, docker, err := geth.StartContainer()
   468  	if err != nil {
   469  		return blockchain.EVMNetwork{}, RpcProvider{}, err
   470  	}
   471  
   472  	net = network
   473  	containers := EthereumNetworkContainers{
   474  		{
   475  			ContainerName: geth.ContainerName,
   476  			ContainerType: ContainerType_Geth,
   477  			Container:     &geth.Container,
   478  		},
   479  	}
   480  
   481  	en.Containers = append(en.Containers, containers...)
   482  	rpcProvider.privateHttpUrls = append(rpcProvider.privateHttpUrls, docker.HttpUrl)
   483  	rpcProvider.privatelWsUrls = append(rpcProvider.privatelWsUrls, docker.WsUrl)
   484  	rpcProvider.publiclHttpUrls = append(rpcProvider.publiclHttpUrls, geth.ExternalHttpUrl)
   485  	rpcProvider.publicsUrls = append(rpcProvider.publicsUrls, geth.ExternalWsUrl)
   486  
   487  	en.DockerNetworkNames = dockerNetworks
   488  
   489  	return net, rpcProvider, nil
   490  }
   491  
   492  func (en *EthereumNetwork) getOrCreateDockerNetworks() ([]string, error) {
   493  	if len(en.DockerNetworkNames) != 0 {
   494  		return en.DockerNetworkNames, nil
   495  	}
   496  
   497  	network, err := docker.CreateNetwork(logging.GetTestLogger(en.t))
   498  	if err != nil {
   499  		return []string{}, err
   500  	}
   501  
   502  	return []string{network.Name}, nil
   503  }
   504  
   505  func (en *EthereumNetwork) Describe() string {
   506  	cL := "prysm"
   507  	if en.ConsensusLayer == nil {
   508  		cL = "(none)"
   509  	}
   510  	return fmt.Sprintf("consensus type: %s, execution layer: %s, consensus layer: %s", *en.ConsensusType, *en.ExecutionLayer, cL)
   511  }
   512  
   513  func (en *EthereumNetwork) setExistingContainerName(ct ContainerType) EnvComponentOption {
   514  	if !en.isRecreated {
   515  		return func(c *EnvComponent) {}
   516  	}
   517  
   518  	for _, container := range en.Containers {
   519  		if container.ContainerType == ct {
   520  			return func(c *EnvComponent) {
   521  				if container.ContainerName != "" {
   522  					c.ContainerName = container.ContainerName
   523  				}
   524  			}
   525  		}
   526  	}
   527  
   528  	return func(c *EnvComponent) {}
   529  }
   530  
   531  func (en *EthereumNetwork) getImageOverride(ct ContainerType) []EnvComponentOption {
   532  	options := []EnvComponentOption{}
   533  	if image, ok := en.CustomDockerImages[ct]; ok {
   534  		options = append(options, WithContainerImageWithVersion(image))
   535  	}
   536  	return options
   537  }
   538  
   539  func (en *EthereumNetwork) Save() error {
   540  	name := fmt.Sprintf("ethereum_network_%s", uuid.NewString()[0:8])
   541  	confPath, err := toml_utils.SaveStructAsToml(en, ".private_chains", name)
   542  	if err != nil {
   543  		return ErrTestConfigNotSaved
   544  	}
   545  
   546  	log := logging.GetTestLogger(en.t)
   547  	log.Info().Msgf("Saved private Ethereum Network config. To reuse in e2e tests, set: %s=%s", CONFIG_ENV_VAR_NAME, confPath)
   548  
   549  	return nil
   550  }
   551  
   552  func (en *EthereumNetwork) Validate() error {
   553  	if en.ConsensusType == nil || *en.ConsensusType == "" {
   554  		return ErrMissingConsensusType
   555  	}
   556  
   557  	if en.ExecutionLayer == nil || *en.ExecutionLayer == "" {
   558  		return ErrMissingExecutionLayer
   559  	}
   560  
   561  	if *en.ConsensusType == ConsensusType_PoS && (en.ConsensusLayer == nil || *en.ConsensusLayer == "") {
   562  		return ErrMissingConsensusLayer
   563  	}
   564  
   565  	if *en.ConsensusType == ConsensusType_PoW && (en.ConsensusLayer != nil && *en.ConsensusLayer != "") {
   566  		return ErrConsensusLayerNotAllowed
   567  	}
   568  
   569  	if en.EthereumChainConfig == nil {
   570  		return errors.New("ethereum chain config is required")
   571  	}
   572  
   573  	return en.EthereumChainConfig.Validate(logging.GetTestLogger(nil), *en.ConsensusType)
   574  }
   575  
   576  func (en *EthereumNetwork) ApplyOverrides(from *EthereumNetwork) error {
   577  	if from == nil {
   578  		return nil
   579  	}
   580  	if from.ConsensusLayer != nil {
   581  		en.ConsensusLayer = from.ConsensusLayer
   582  	}
   583  	if from.ExecutionLayer != nil {
   584  		en.ExecutionLayer = from.ExecutionLayer
   585  	}
   586  	if from.ConsensusType != nil {
   587  		en.ConsensusType = from.ConsensusType
   588  	}
   589  	if from.WaitForFinalization != nil {
   590  		en.WaitForFinalization = from.WaitForFinalization
   591  	}
   592  
   593  	if from.EthereumChainConfig != nil {
   594  		if en.EthereumChainConfig == nil {
   595  			en.EthereumChainConfig = from.EthereumChainConfig
   596  		} else {
   597  			err := en.EthereumChainConfig.ApplyOverrides(from.EthereumChainConfig)
   598  			if err != nil {
   599  				return fmt.Errorf("error applying overrides from network config file to config: %w", err)
   600  			}
   601  		}
   602  	}
   603  
   604  	return nil
   605  }
   606  
   607  // maybe only store ports here and depending on where the test is executed return different URLs?
   608  // maybe 3 different constructors for each "perspective"? (docker, k8s with local runner, k8s with remote runner)
   609  type RpcProvider struct {
   610  	privateHttpUrls []string
   611  	privatelWsUrls  []string
   612  	publiclHttpUrls []string
   613  	publicsUrls     []string
   614  }
   615  
   616  func (s *RpcProvider) PrivateHttpUrls() []string {
   617  	return s.privateHttpUrls
   618  }
   619  
   620  func (s *RpcProvider) PrivateWsUrsl() []string {
   621  	return s.privatelWsUrls
   622  }
   623  
   624  func (s *RpcProvider) PublicHttpUrls() []string {
   625  	return s.publiclHttpUrls
   626  }
   627  
   628  func (s *RpcProvider) PublicWsUrls() []string {
   629  	return s.publicsUrls
   630  }
   631  
   632  type ContainerType string
   633  
   634  const (
   635  	ContainerType_Geth             ContainerType = "geth"
   636  	ContainerType_Erigon           ContainerType = "erigon"
   637  	ContainerType_Besu             ContainerType = "besu"
   638  	ContainerType_Nethermind       ContainerType = "nethermind"
   639  	ContainerType_PrysmBeacon      ContainerType = "prysm-beacon"
   640  	ContainerType_PrysmVal         ContainerType = "prysm-validator"
   641  	ContainerType_GenesisGenerator ContainerType = "genesis-generator"
   642  	ContainerType_ValKeysGenerator ContainerType = "val-keys-generator"
   643  )
   644  
   645  type EthereumNetworkContainer struct {
   646  	ContainerName string        `toml:"container_name"`
   647  	ContainerType ContainerType `toml:"container_type"`
   648  	Container     *tc.Container `toml:"-"`
   649  }
   650  
   651  type EthereumNetworkContainers []EthereumNetworkContainer
   652  
   653  func createHostDirectories() (string, string, error) {
   654  	customConfigDataDir, err := os.MkdirTemp("", "custom_config_data")
   655  	if err != nil {
   656  		return "", "", err
   657  	}
   658  
   659  	valKeysDir, err := os.MkdirTemp("", "val_keys")
   660  	if err != nil {
   661  		return "", "", err
   662  	}
   663  
   664  	return customConfigDataDir, valKeysDir, nil
   665  }
   666  
   667  func waitForChainToFinaliseAnEpoch(lggr zerolog.Logger, evmClient blockchain.EVMClient, timeout time.Duration) error {
   668  	lggr.Info().Msg("Waiting for chain to finalize an epoch")
   669  
   670  	pollInterval := 15 * time.Second
   671  	endTime := time.Now().Add(timeout)
   672  
   673  	chainStarted := false
   674  	for {
   675  		finalized, err := evmClient.GetLatestFinalizedBlockHeader(context.Background())
   676  		if err != nil {
   677  			if strings.Contains(err.Error(), "finalized block not found") {
   678  				lggr.Err(err).Msgf("error getting finalized block number for %s", evmClient.GetNetworkName())
   679  			} else {
   680  				timeLeft := time.Until(endTime).Seconds()
   681  				lggr.Warn().Msgf("no epoch finalized yet for chain %s. Time left: %d sec", evmClient.GetNetworkName(), int(timeLeft))
   682  			}
   683  		}
   684  
   685  		if finalized != nil && finalized.Number.Int64() > 0 || time.Now().After(endTime) {
   686  			lggr.Info().Msgf("Chain '%s' finalized an epoch", evmClient.GetNetworkName())
   687  			chainStarted = true
   688  			break
   689  		}
   690  
   691  		time.Sleep(pollInterval)
   692  	}
   693  
   694  	if !chainStarted {
   695  		return fmt.Errorf("chain %s failed to finalize an epoch", evmClient.GetNetworkName())
   696  	}
   697  
   698  	return nil
   699  }
   700  
   701  func NewPrivateChainEnvConfigFromFile(path string) (EthereumNetwork, error) {
   702  	c := EthereumNetwork{}
   703  	err := toml_utils.OpenTomlFileAsStruct(path, &c)
   704  	return c, err
   705  }