Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TT-950] Support for more eth1 clients #859

Merged
merged 42 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d647116
add support for pow besu
Tofel Mar 1, 2024
e3aa855
added pow erigon
Tofel Mar 1, 2024
7bd1f30
add support for nethermind pow (albeit as PoA) and tests for automati…
Tofel Mar 1, 2024
f1f896f
made older geth versions work
Tofel Mar 5, 2024
294e73c
move besu code to a single file
Tofel Mar 5, 2024
9958840
move erigon code to a single file
Tofel Mar 5, 2024
2089fa1
move geth to a single file
Tofel Mar 5, 2024
ff56b7a
move nethermind to a single file
Tofel Mar 5, 2024
19a704b
small cleanup
Tofel Mar 5, 2024
5923354
better file structure
Tofel Mar 5, 2024
653ec21
rename consensus type to ethereum version as that's more accurate
Tofel Mar 5, 2024
d9cc903
add helper methods to get consensus mechanism
Tofel Mar 5, 2024
b3a5e42
updated readme, fixed some bugs, added ability to get latest release …
Tofel Mar 6, 2024
dcb2f49
some docker image versions can now be marked as unsupported
Tofel Mar 6, 2024
4fe176f
fix backward compatibility
Tofel Mar 6, 2024
67d177d
Merge branch 'main' into tt_950_pow_clients
Tofel Mar 6, 2024
c00f6b6
more explicit container/network names + bug fixes
Tofel Mar 6, 2024
d038f5e
do not allow to run geth 1.9.0 due to random ws disconnection
Tofel Mar 7, 2024
32c4cbf
fix lints
Tofel Mar 7, 2024
05f05a8
Merge branch 'main' into tt_950_pow_clients
Tofel Mar 7, 2024
271b594
some cleanup
Tofel Mar 7, 2024
2596c53
Merge branch 'tt_950_pow_clients' of github.com:smartcontractkit/chai…
Tofel Mar 7, 2024
c250c91
simplify congnitive complexity
Tofel Mar 7, 2024
a2ce700
Merge branch 'main' into tt_950_pow_clients
Tofel Mar 8, 2024
ced94fa
final touches
Tofel Mar 8, 2024
2b181ba
simplify a bit, fix tests
Tofel Mar 8, 2024
c942874
use ubuntu-latest, we don't have a better runner
Tofel Mar 8, 2024
111229a
fix docker pipe
Tofel Mar 8, 2024
73d2662
do not run tests that start docker containers in parallel
Tofel Mar 8, 2024
ab8d3cf
fix more docker tests
Tofel Mar 8, 2024
8cf3b00
wrap ethereum container start errors
Tofel Mar 11, 2024
40228ea
use build tags to split docker tests
Tofel Mar 11, 2024
11a020b
use build tags with golanglint-ci
Tofel Mar 11, 2024
ef62a73
explicitly set gas limit for geth 1.10 as 0 is not supported
Tofel Mar 12, 2024
70d115f
use each retrier once, add dedicated method for restarting postgres D…
Tofel Mar 12, 2024
fcbca41
code review changes
Tofel Mar 14, 2024
99a5ee9
Merge branch 'main' into tt_950_pow_clients
Tofel Mar 14, 2024
c6082ae
add missing file
Tofel Mar 14, 2024
916b131
remove some stuff
Tofel Mar 14, 2024
624ab34
fix some tests
Tofel Mar 14, 2024
5a270cd
add missing struct
Tofel Mar 14, 2024
8f69256
give PG more time to start, 20 seconds
Tofel Mar 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions .github/workflows/docker-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
types: [labeled]

jobs:
docker:
eth_env:
if: ${{ github.event_name == 'pull_request_review' || github.event.label.name == 'docker_tests' }}
runs-on: ubuntu-latest
steps:
Expand All @@ -31,7 +31,83 @@ jobs:
set -euo pipefail
# disabled, because we want to use a multiline output of go list command
# shellcheck disable=SC2046
go test -timeout 20m -json -cover -covermode=atomic -coverprofile=unit-test-coverage.out $(go list ./... | grep /docker/test_env) 2>&1 | tee /tmp/gotest.log | gotestfmt
go test -timeout 20m -json -parallel 2 -cover -covermode=atomic -coverprofile=unit-test-coverage.out $(go list ./... | grep /docker/test_env) -run 'TestEthEnv' 2>&1 | tee /tmp/gotest.log | gotestfmt
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
files: ./unit-test-coverage.out
name: codecov-umbrella
- name: Publish Artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: test-logs
path: ./logs

eth_clients:
if: ${{ github.event_name == 'pull_request_review' || github.event.label.name == 'docker_tests' }}
runs-on: ubuntu-latest
steps:
- name: Checkout the Repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install Go
uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@00c6214deb10a3f374c6d3430c32c5202015d463 # v2.2.12
with:
test_download_vendor_packages_command: go mod download
go_mod_path: ./go.mod
cache_key_id: ctf-go
cache_restore_only: 'false'
# gotestfmt gives us pretty test output
- name: Set Up gotestfmt
uses: GoTestTools/gotestfmt-action@v2
with:
token: ${{ github.token }} # Avoids rate-limiting
- name: Run Tests
run: |
PATH=$PATH:$(go env GOPATH)/bin
export PATH
set -euo pipefail
# disabled, because we want to use a multiline output of go list command
# shellcheck disable=SC2046
go test -timeout 20m -json -parallel 2 -cover -covermode=atomic -coverprofile=unit-test-coverage.out $(go list ./... | grep /docker/test_env) -run 'TestBesu|TestGeth|TestNethermind|TestErigon' 2>&1 | tee /tmp/gotest.log | gotestfmt
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
files: ./unit-test-coverage.out
name: codecov-umbrella
- name: Publish Artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: test-logs
path: ./logs

other:
Tofel marked this conversation as resolved.
Show resolved Hide resolved
if: ${{ github.event_name == 'pull_request_review' || github.event.label.name == 'docker_tests' }}
runs-on: ubuntu-latest
steps:
- name: Checkout the Repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install Go
uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/setup-go@00c6214deb10a3f374c6d3430c32c5202015d463 # v2.2.12
with:
test_download_vendor_packages_command: go mod download
go_mod_path: ./go.mod
cache_key_id: ctf-go
cache_restore_only: 'false'
# gotestfmt gives us pretty test output
- name: Set Up gotestfmt
uses: GoTestTools/gotestfmt-action@v2
with:
token: ${{ github.token }} # Avoids rate-limiting
- name: Run Tests
run: |
PATH=$PATH:$(go env GOPATH)/bin
export PATH
set -euo pipefail
# disabled, because we want to use a multiline output of go list command
# shellcheck disable=SC2046
go test -timeout 20m -json -parallel 2 -cover -covermode=atomic -coverprofile=unit-test-coverage.out $(go list ./... | grep /docker/test_env) -run 'TestPostgres|TestMockServer|TestKillgrave' 2>&1 | tee /tmp/gotest.log | gotestfmt
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ test_unit: install_gotestfmt
go test -json -cover -covermode=count -coverprofile=unit-test-coverage.out ./client ./gauntlet ./testreporters ./k8s/config ./utils/osutil 2 2>&1 | tee /tmp/gotest.log | gotestfmt

test_docker: install_gotestfmt
go test -json -cover -covermode=count -coverprofile=unit-test-coverage.out ./docker/test_env ./logstream 2>&1 | tee /tmp/gotest.log | gotestfmt
go test -timeout 20m -json -failfast -parallel 3 -cover -covermode=atomic -coverprofile=unit-test-coverage.out ./docker/test_env 2>&1 | tee /tmp/gotest.log | gotestfmt


#######################
Expand Down
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,42 @@ We have extended support for execution layer clients in simulated networks. Foll

When it comes to consensus layer we currently support only `Prysm`.

The easiest way to start a simulated network is to use a builder. It allows to configure the network in a fluent way and then start it. For example:

```go
builder := NewEthereumNetworkBuilder()
cfg, err: = builder.
WithEthereumVersion(EthereumVersion_Eth2).
WithExecutionLayer(ExecutionLayer_Geth).
Build()
```

Since we support both `eth1` (aka pre-Merge) and `eth2` (aka post-Merge) client versions, you need to specify which one you want to use. You can do that by calling `WithEthereumVersion` method. There's no default provided. The only execption is when you use custom docker images (instead of default ones), because then we can determine which version it is based on the image version.
Tofel marked this conversation as resolved.
Show resolved Hide resolved

If you want your test to execute as fast as possible go for `eth1` since it's either using a fake PoW or PoA consensus and is much faster than `eth2` which uses PoS consensus (where there is a minimum viable length of slot/block, which is 4 seconds; for `eth1` it's 1 second). If you want to test the latest features, changes or forks in the Ethereum network and have your tests running on a network which is as close as possible to Ethereum Mainnet, go for `eth2`.

Every component has some default Docker image it uses, but builder has a method that allows to pass custom one:

```go
builder := NewEthereumNetworkBuilder()
cfg, err: = builder.
WithConsensusType(ConsensusType_PoS).
WithEthereumVersion(EthereumVersion_Eth2).
WithConsensusLayer(ConsensusLayer_Prysm).
WithExecutionLayer(ExecutionLayer_Geth).
WithCustomDockerImages(map[ContainerType]string{
ContainerType_Geth2: "my-custom-geth2-image:my-version"}).
ContainerType_Geth: "my-custom-geth-pos-image:my-version"}).
Build()
```

When using a custom image you can even further simplify the builder by calling only `WithCustomDockerImages` method. Based on the image name and version we will determine which execution layer client it is and whether it's `eth1` or `eth2` client:
```go
builder := NewEthereumNetworkBuilder()
cfg, err: = builder.
WithCustomDockerImages(map[ContainerType]string{
ContainerType_Geth: "ethereum/client-go:v1.13.10"}).
Build()
```
In the case above we would launch a `Geth` client with `eth2` network and `Prysm` consensus layer.

You can also configure epochs at which hardforks will happen. Currently only `Deneb` is supported. Epoch must be >= 1. Example:

Expand Down
3 changes: 3 additions & 0 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)

// EVMClient is the interface that wraps a given client implementation for a blockchain, to allow for switching
Expand Down Expand Up @@ -112,6 +113,8 @@ type EVMClient interface {
FilterLogs(ctx context.Context, filterQuery ethereum.FilterQuery) ([]types.Log, error)

RawJsonRPCCall(ctx context.Context, result interface{}, method string, params ...interface{}) error

GetEthClient() *ethclient.Client
}

// NodeHeader header with the ID of the node that received it
Expand Down
24 changes: 23 additions & 1 deletion blockchain/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,10 @@ func (e *EthereumClient) ProcessTransaction(tx *types.Transaction) error {
return nil
}

func (e *EthereumClient) GetEthClient() *ethclient.Client {
return e.Client
}

// ProcessEvent will queue or wait on an event depending on whether parallel transactions are enabled
func (e *EthereumClient) ProcessEvent(name string, event *types.Log, confirmedChan chan bool, errorChan chan error) error {
var eventConfirmer HeaderEventSubscription
Expand Down Expand Up @@ -1171,6 +1175,10 @@ type EthereumMultinodeClient struct {
Clients []EVMClient
}

func (e *EthereumMultinodeClient) GetEthClient() *ethclient.Client {
return e.DefaultClient.GetEthClient()
}

func (e *EthereumMultinodeClient) Backend() bind.ContractBackend {
return e.DefaultClient.Backend()
}
Expand Down Expand Up @@ -1296,10 +1304,24 @@ func ConnectEVMClient(networkSettings EVMNetwork, logger zerolog.Logger) (EVMCli
continue
}
// a call to BalanceAt to ensure the client is connected
_, err = ec.BalanceAt(context.Background(), ec.GetDefaultWallet().address)
b, err := ec.BalanceAt(context.Background(), ec.GetDefaultWallet().address)
if err == nil {
ec.SetID(idx)
ecl.Clients = append(ecl.Clients, ec)

logger.Info().
Uint64("Balance", b.Uint64()).
Str("Address", ec.GetDefaultWallet().address.Hex()).
Msg("Default address balance")

if networkSettings.Simulated && b.Cmp(big.NewInt(0)) == 0 {
noBalanceErr := fmt.Errorf("Default wallet %s has no balance", ec.GetDefaultWallet().address.Hex())
logger.Err(noBalanceErr).
Msg("Ending test before it fails anyway")

return nil, noBalanceErr
}

break
}
}
Expand Down
11 changes: 11 additions & 0 deletions client/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type GithubClient struct {
client *github.Client
}

const WITHOUT_TOKEN = ""

func NewGithubClient(token string) *GithubClient {
// Optional: Authenticate with a personal access token if necessary
// This is recommended to avoid rate limits for unauthenticated requests
Expand All @@ -34,12 +36,21 @@ func NewGithubClient(token string) *GithubClient {
}
}

// ListLatestReleases lists the latest releases for a given repository
func (g *GithubClient) ListLatestReleases(org, repository string, count int) ([]*github.RepositoryRelease, error) {
ctx := context.Background()
releases, _, err := g.client.Repositories.ListReleases(ctx, org, repository, &github.ListOptions{PerPage: count})
return releases, err
}

// ListLatestCLCoreReleases lists the latest releases for the Chainlink core repository
func (g *GithubClient) ListLatestCLCoreReleases(count int) ([]*github.RepositoryRelease, error) {
ctx := context.Background()
releases, _, err := g.client.Repositories.ListReleases(ctx, "smartcontractkit", "chainlink", &github.ListOptions{PerPage: count})
return releases, err
}

// ListLatestCLCoreTags lists the latest tags for the Chainlink core repository
func (g *GithubClient) ListLatestCLCoreTags(count int) ([]*github.RepositoryTag, error) {
ctx := context.Background()
tags, _, err := g.client.Repositories.ListTags(ctx, "smartcontractkit", "chainlink", &github.ListOptions{PerPage: count})
Expand Down
15 changes: 12 additions & 3 deletions config/examples/example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ polygon_mumbai = ["wss://polygon-endpoint.io"]
polygon_mumbai = ["change-me-to-your-PK"]

[PrivateEthereumNetwork]
# pos or pow
consensus_type="pos"
# eth1 or eth2
ethereum_version="eth2"
# only prysm supported currently
consensus_layer="prysm"
# geth, besu, nethermind or erigon
Expand All @@ -63,4 +63,13 @@ genesis_delay=15
validator_count=8
chain_id=1337
# list of addresses to be prefunded in genesis
addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]

[PrivateEthereumNetwork.EthereumChainConfig.HardForkEpochs]
Deneb=500

[PrivateEthereumNetwork.CustomDockerImages]
# this will override our default image
# also when set and no explicit ethereum_version or execution_layer is provided
# we will deduct it from this custom image
besu="hyperledger/besu:24.2.0-RC2"
39 changes: 39 additions & 0 deletions config/wasp_autobuild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package config

import (
"errors"
)

type WaspAutoBuildConfig struct {
Namespace *string `toml:"namespace"`
RepoImageVersionURI *string `toml:"repo_image_version_uri"`
TestBinaryName *string `toml:"test_binary_name"`
TestName *string `toml:"test_name"`
TestTimeout *string `toml:"test_timeout"`
KeepJobs bool `toml:"keep_jobs"`
WaspLogLevel *string `toml:"wasp_log_level"`
WaspJobs *string `toml:"wasp_jobs"`
UpdateImage bool `toml:"update_image"`
}

func (c *WaspAutoBuildConfig) Validate() error {
if c.Namespace == nil || *c.Namespace == "" {
return errors.New("WASP namespace name should not be empty, see WASP docs to setup it")
}
if c.RepoImageVersionURI == nil || *c.RepoImageVersionURI == "" {
return errors.New("WASP image URI is empty, must be ${registry}/${repo}:${tag}")
}
if c.TestBinaryName == nil || *c.TestBinaryName == "" {
return errors.New("WASP test binary is empty, should be 'ocr.test', run 'go test -c ./...' in load test dir to figure out the name")
}
if c.TestName == nil || *c.TestName == "" {
return errors.New("WASP test name is empty, should be a name of go test you want to run")
}
if c.TestTimeout == nil || *c.TestTimeout == "" {
return errors.New("WASP test timeout should be in Go time format: '1w2d3h4m5s'")
}
if c.WaspJobs == nil || *c.WaspJobs == "" {
return errors.New("WASP jobs are empty, amount of pods to spin up in k8s")
}
return nil
}
26 changes: 16 additions & 10 deletions docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ var NaiveRetrier = func(l zerolog.Logger, startErr error, req tc.GenericContaine
Str("Retrier", "NaiveRetrier").
Msgf("Attempting to start %s container", req.Name)

oldName := req.Name
req.Name = req.Name + "-naive-retry"

ct, err := tc.GenericContainer(testcontext.Get(nil), req)
if err == nil {
l.Debug().
Expand All @@ -72,6 +75,8 @@ var NaiveRetrier = func(l zerolog.Logger, startErr error, req tc.GenericContaine
}
}

req.Name = oldName

l.Debug().
Str("Original start error", startErr.Error()).
Str("Current start error", err.Error()).
Expand All @@ -81,12 +86,14 @@ var NaiveRetrier = func(l zerolog.Logger, startErr error, req tc.GenericContaine
return nil, startErr
}

var LinuxPlatoformImageRetrier = func(l zerolog.Logger, startErr error, req tc.GenericContainerRequest) (tc.Container, error) {
var LinuxPlatformImageRetrier = func(l zerolog.Logger, startErr error, req tc.GenericContainerRequest) (tc.Container, error) {
// if it's nil we don't know if we can handle it so we won't try
if startErr == nil {
return nil, startErr
}
req.Reuse = false // We need to force a new container to be created
oldName := req.Name
req.Name = req.Name + "-linux-retry"

// a bit lame, but that's the lame error we get in case there's no specific image for our platform :facepalm:
if !strings.Contains(startErr.Error(), "No such image") {
Expand Down Expand Up @@ -114,6 +121,7 @@ var LinuxPlatoformImageRetrier = func(l zerolog.Logger, startErr error, req tc.G
}

req.ImagePlatform = originalPlatform
req.Name = oldName

if ct != nil {
err := ct.Terminate(testcontext.Get(nil))
Expand Down Expand Up @@ -145,18 +153,16 @@ func StartContainerWithRetry(l zerolog.Logger, req tc.GenericContainerRequest, r
}

if len(retriers) == 0 {
retriers = append(retriers, LinuxPlatoformImageRetrier, NaiveRetrier)
retriers = append(retriers, LinuxPlatformImageRetrier, NaiveRetrier)
}

for i := 0; i < RetryAttempts; i++ {
l.Info().Err(err).Msgf("Cannot start %s container, retrying %d/%d", req.Name, i+1, RetryAttempts)
l.Info().Err(err).Msgf("Cannot start %s container, retrying", req.Name)

req.Reuse = true // Try and see if we can reuse the container for a retry
for _, retrier := range retriers {
ct, err = retrier(l, err, req)
if err == nil {
return ct, nil
}
req.Reuse = true // Try and see if we can reuse the container for a retry
for _, retrier := range retriers {
ct, err = retrier(l, err, req)
if err == nil {
return ct, nil
}
}

Expand Down
Loading
Loading