From 76f0f139e8f75f4eea6fca7a58fb49110a898133 Mon Sep 17 00:00:00 2001 From: Sergey Kudasov Date: Tue, 4 Feb 2025 23:05:48 +0100 Subject: [PATCH] multi-don examples (#1614) multi-nodeset setup + chaos testing examples --- book/src/SUMMARY.md | 4 +- book/src/framework/chaos/chaos.md | 59 ++++++++ .../framework/components/chainlink/nodeset.md | 2 + book/src/framework/components/state.md | 1 + book/src/framework/nodeset_capabilities.md | 1 + book/src/framework/nodeset_compatibility.md | 1 + book/src/framework/nodeset_docker_rebuild.md | 1 + book/src/framework/nodeset_environment.md | 1 + framework/.changeset/v0.5.0.md | 2 + framework/cmd/docker.go | 2 +- framework/components/clnode/clnode.go | 5 +- framework/components/clnode/clnode_test.go | 4 +- framework/components/postgres/postgres.go | 8 +- .../components/simple_node_set/node_set.go | 13 +- .../simple_node_set/nodeset_test.go | 6 +- .../components/simple_node_set/reload.go | 2 +- .../examples/myproject/{ => chaos}/chaos.toml | 3 +- .../chaos/chaos_blockchain_evm_gas_test.go | 123 +++++++++++++++++ .../chaos/chaos_blockchain_evm_reorg_test.go | 126 ++++++++++++++++++ .../chaos_docker_test.go} | 66 +++++---- .../examples/myproject/chaos/chaos_gas.toml | 20 +++ .../myproject/{ => chaos}/chaos_k8s_test.go | 67 +++++----- .../examples/myproject/chaos/chaos_reorg.toml | 26 ++++ .../myproject/chaos_blockchain_evm_test.go | 59 -------- framework/examples/myproject/fake.toml | 1 + .../myproject/fork_plus_offchain.toml | 1 + framework/examples/myproject/go.mod | 2 +- .../myproject/performance_baseline.toml | 1 + .../examples/myproject/quick-deploy.toml | 1 + framework/examples/myproject/scalability.toml | 1 + framework/examples/myproject/smoke.toml | 1 + framework/examples/myproject/upgrade.toml | 1 + .../examples/myproject/upgrade_multi.toml | 37 +++++ .../myproject/upgrade_multi_each.toml | 77 +++++++++++ .../myproject/upgrade_multi_nodeset_test.go | 63 +++++++++ framework/rpc/rpc.go | 39 +++++- framework/rpc/rpc_suite.go | 67 ---------- framework/rpc/rpc_suite_test.go | 58 -------- framework/rpc/rpc_test.go | 4 +- 39 files changed, 689 insertions(+), 267 deletions(-) create mode 100644 book/src/framework/chaos/chaos.md create mode 100644 framework/.changeset/v0.5.0.md rename framework/examples/myproject/{ => chaos}/chaos.toml (95%) create mode 100644 framework/examples/myproject/chaos/chaos_blockchain_evm_gas_test.go create mode 100644 framework/examples/myproject/chaos/chaos_blockchain_evm_reorg_test.go rename framework/examples/myproject/{chaos_test.go => chaos/chaos_docker_test.go} (51%) create mode 100644 framework/examples/myproject/chaos/chaos_gas.toml rename framework/examples/myproject/{ => chaos}/chaos_k8s_test.go (85%) create mode 100644 framework/examples/myproject/chaos/chaos_reorg.toml delete mode 100644 framework/examples/myproject/chaos_blockchain_evm_test.go create mode 100644 framework/examples/myproject/upgrade_multi.toml create mode 100644 framework/examples/myproject/upgrade_multi_each.toml create mode 100644 framework/examples/myproject/upgrade_multi_nodeset_test.go delete mode 100644 framework/rpc/rpc_suite.go delete mode 100644 framework/rpc/rpc_suite_test.go diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 5057f91ca..33fb9ee9e 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,7 +12,6 @@ - [NodeSet Compat Environment](./framework/nodeset_compatibility.md) - [Creating your own components](./developing/developing_components.md) - [Asserting Logs](./developing/asserting_logs.md) - - [Fork Testing](./framework/fork.md) - [Quick Contracts Deployment](./framework/quick_deployment.md) - [Verifying Contracts](./framework/verify.md) - [NodeSet with External Blockchain]() @@ -55,7 +54,8 @@ - [Testing Maturity Model](framework/testing.md) - [Smoke]() - [Performance]() - - [Chaos]() + - [Chaos](./framework/chaos/chaos.md) + - [Fork Testing](./framework/fork.md) - [Interactive](framework/interactive.md) - [Libraries](./libraries.md) - [Seth](./libs/seth.md) diff --git a/book/src/framework/chaos/chaos.md b/book/src/framework/chaos/chaos.md new file mode 100644 index 000000000..96c5faf99 --- /dev/null +++ b/book/src/framework/chaos/chaos.md @@ -0,0 +1,59 @@ +# Chaos Testing + +We offer Docker and Kubernetes boilerplates designed to test the resilience of `NodeSet` and `Blockchain`, which you can customize and integrate into your pipeline. + + +## Goals + +We recommend structuring your tests as a linear suite that applies various chaos experiments and verifies the outcomes using a [load](../../libs/wasp.md) testing suite. Focus on critical user metrics, such as: + +- The ratio of successful responses to failed responses +- The nth percentile of response latency + +Next, evaluate observability: + +- Ensure proper alerts are triggered during failures (manual or automated) +- Verify the service recovers within the expected timeframe (manual or automated) + +In summary, the **primary** focus is on meeting user expectations and maintaining SLAs, while the **secondary** focus is on observability and making operational part smoother. + + +## Docker + +For Docker, we utilize [Pumba](https://github.com/alexei-led/pumba) to conduct chaos experiments, including: + +- Container reboots +- Network simulations (such as delays, packet loss, corruption, etc., using the tc tool) +- Stress testing for CPU and memory usage + +Additionally, we offer a [resources](../../framework/components/resources.md) API that allows you to test whether your software can operate effectively in low-resource environments. + +You can also use [fake](../../framework/components/mocking.md) package to create HTTP chaos experiments. + +Given the complexity of `Kubernetes`, we recommend starting with `Docker` first. Identifying faulty behavior in your services early—such as cascading latency—can prevent more severe issues when scaling up. Addressing these problems at a smaller scale can save significant time and effort later. + +Check `NodeSet` + `Blockchain` template [here](). + +## Kubernetes + +We utilize a subset of [ChaosMesh](https://chaos-mesh.org/) experiments that can be safely executed on an isolated node group. These include: + +- [Pod faults](https://chaos-mesh.org/docs/simulate-pod-chaos-on-kubernetes/) + +- [Network faults](https://chaos-mesh.org/docs/simulate-network-chaos-on-kubernetes/) – We focus on delay and partition experiments, as others may impact pods outside the dedicated node group. + +- [HTTP faults](https://chaos-mesh.org/docs/simulate-http-chaos-on-kubernetes/) + +Check `NodeSet` + `Blockchain` template [here](). + +## Blockchain + +We also offer a set of blockchain-specific experiments, which typically involve API calls to blockchain simulators to execute certain actions. These include: + +- Adjusting gas prices + +- Introducing chain reorganizations (setting a new head) + +- Utilizing developer APIs (e.g., Anvil) + +Check [gas]() and [reorg]() examples. diff --git a/book/src/framework/components/chainlink/nodeset.md b/book/src/framework/components/chainlink/nodeset.md index a84282356..c62f00f94 100644 --- a/book/src/framework/components/chainlink/nodeset.md +++ b/book/src/framework/components/chainlink/nodeset.md @@ -42,6 +42,8 @@ This component requires some Blockchain to be deployed, add this to config Then configure NodeSet ```toml [nodeset] + # unique NodeSet name + name = "don" # amount of Chainlink nodes to spin up nodes = 5 # Override mode: can be "all" or "each" diff --git a/book/src/framework/components/state.md b/book/src/framework/components/state.md index 841594a15..1385d3e0d 100644 --- a/book/src/framework/components/state.md +++ b/book/src/framework/components/state.md @@ -44,6 +44,7 @@ One node set is enough for any kind of testing, if you need more nodes consider You can also define a custom set of ports for any node. ```toml [nodeset] + name = "don" nodes = 5 override_mode = "each" diff --git a/book/src/framework/nodeset_capabilities.md b/book/src/framework/nodeset_capabilities.md index f8378c118..418f2ee9b 100644 --- a/book/src/framework/nodeset_capabilities.md +++ b/book/src/framework/nodeset_capabilities.md @@ -21,6 +21,7 @@ Create a configuration file `smoke.toml` docker_cmd_params = ["-b", "1"] [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/book/src/framework/nodeset_compatibility.md b/book/src/framework/nodeset_compatibility.md index bd170bd80..533258690 100644 --- a/book/src/framework/nodeset_compatibility.md +++ b/book/src/framework/nodeset_compatibility.md @@ -9,6 +9,7 @@ Create a configuration file `smoke.toml` docker_cmd_params = ["-b", "1"] [nodeset] + name = "don" nodes = 5 override_mode = "each" diff --git a/book/src/framework/nodeset_docker_rebuild.md b/book/src/framework/nodeset_docker_rebuild.md index d56d48368..0bb68e5cb 100644 --- a/book/src/framework/nodeset_docker_rebuild.md +++ b/book/src/framework/nodeset_docker_rebuild.md @@ -9,6 +9,7 @@ Create a configuration file `smoke.toml` docker_cmd_params = ["-b", "1"] [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/book/src/framework/nodeset_environment.md b/book/src/framework/nodeset_environment.md index a57c8a3d8..e3fb9ea82 100644 --- a/book/src/framework/nodeset_environment.md +++ b/book/src/framework/nodeset_environment.md @@ -9,6 +9,7 @@ Create a configuration file `smoke.toml` docker_cmd_params = ["-b", "1"] [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/.changeset/v0.5.0.md b/framework/.changeset/v0.5.0.md new file mode 100644 index 000000000..a96228e6d --- /dev/null +++ b/framework/.changeset/v0.5.0.md @@ -0,0 +1,2 @@ +- Allow multiple node sets simultaneously +- Add chaos examples for gas, reorg, k8s and docker \ No newline at end of file diff --git a/framework/cmd/docker.go b/framework/cmd/docker.go index 07f53081e..e7c757e92 100644 --- a/framework/cmd/docker.go +++ b/framework/cmd/docker.go @@ -14,7 +14,7 @@ func removeTestContainers() error { cmd := exec.Command("bash", "-c", ` docker ps -aq --filter "label=framework=ctf" | xargs -r docker rm -f && \ docker network ls --filter "label=framework=ctf" -q | xargs -r docker network rm && \ - docker volume rm postgresql_data || true + docker volume ls -q | xargs -r docker volume rm || true `) framework.L.Debug().Msg("Running command") if framework.L.GetLevel() == zerolog.DebugLevel { diff --git a/framework/components/clnode/clnode.go b/framework/components/clnode/clnode.go index 35c417d63..4fbba8191 100644 --- a/framework/components/clnode/clnode.go +++ b/framework/components/clnode/clnode.go @@ -244,7 +244,10 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) { }, ExposedPorts: exposedPorts, Entrypoint: generateEntryPoint(), - WaitingFor: wait.ForHTTP("/").WithPort(DefaultHTTPPort).WithStartupTimeout(1 * time.Minute).WithPollInterval(200 * time.Millisecond), + WaitingFor: wait.ForHTTP("/"). + WithPort(DefaultHTTPPort). + WithStartupTimeout(1 * time.Minute). + WithPollInterval(200 * time.Millisecond), } if in.Node.HTTPPort != 0 && in.Node.P2PPort != 0 { req.HostConfigModifier = func(h *container.HostConfig) { diff --git a/framework/components/clnode/clnode_test.go b/framework/components/clnode/clnode_test.go index 832dc55c3..cd946a6a7 100644 --- a/framework/components/clnode/clnode_test.go +++ b/framework/components/clnode/clnode_test.go @@ -24,7 +24,7 @@ func checkBasicOutputs(t *testing.T, output *clnode.Output) { require.Contains(t, output.Node.DockerP2PUrl, "cl-node") require.NotNil(t, output.PostgreSQL) require.Contains(t, output.PostgreSQL.Url, "postgresql://chainlink:thispasswordislongenough@127.0.0.1") - require.Contains(t, output.PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@ns-postgresql") + require.Contains(t, output.PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@pg") } func TestComponentDockerNodeWithSharedDB(t *testing.T) { @@ -35,6 +35,7 @@ func TestComponentDockerNodeWithSharedDB(t *testing.T) { DbInput: &postgres.Input{ Image: "postgres:12.0", Port: 16000, + Name: "pg-1", VolumeName: "a", }, Node: &clnode.NodeInput{ @@ -70,6 +71,7 @@ func TestComponentDockerNodeWithDB(t *testing.T) { DbInput: &postgres.Input{ Image: "postgres:12.0", Port: 15000, + Name: "pg-2", VolumeName: "b", }, Node: &clnode.NodeInput{ diff --git a/framework/components/postgres/postgres.go b/framework/components/postgres/postgres.go index 1b3f71c15..5f5700a6b 100644 --- a/framework/components/postgres/postgres.go +++ b/framework/components/postgres/postgres.go @@ -48,10 +48,10 @@ func NewPostgreSQL(in *Input) (*Output, error) { bindPort := fmt.Sprintf("%s/tcp", Port) var containerName string - if in.Name != "" { - containerName = framework.DefaultTCName(in.Name) + if in.Name == "" { + containerName = "ns-postgresql" } else { - containerName = framework.DefaultTCName("ns-postgresql") + containerName = in.Name } var sqlCommands []string @@ -116,7 +116,7 @@ func NewPostgreSQL(in *Input) (*Output, error) { }, WaitingFor: tcwait.ForExec([]string{"psql", "-h", "127.0.0.1", "-U", User, "-p", Port, "-c", "select", "1", "-d", Database}). - WithStartupTimeout(15 * time.Second). + WithStartupTimeout(40 * time.Second). WithPollInterval(200 * time.Millisecond), } var portToExpose int diff --git a/framework/components/simple_node_set/node_set.go b/framework/components/simple_node_set/node_set.go index 1ab5867a3..6097c4508 100644 --- a/framework/components/simple_node_set/node_set.go +++ b/framework/components/simple_node_set/node_set.go @@ -20,6 +20,7 @@ const ( // Input is a node set configuration input type Input struct { + Name string `toml:"name" validate:"required"` Nodes int `toml:"nodes" validate:"required"` HTTPPortRangeStart int `toml:"http_port_range_start"` P2PPortRangeStart int `toml:"p2p_port_range_start"` @@ -75,6 +76,10 @@ func printURLs(out *Output) { } func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) { + in.DbInput.Name = fmt.Sprintf("%s-%s", in.Name, "ns-postgresql") + in.DbInput.VolumeName = in.Name + + // create database for each node in.DbInput.Databases = in.Nodes dbOut, err := postgres.NewPostgreSQL(in.DbInput) if err != nil { @@ -116,9 +121,7 @@ func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) { return nil, fmt.Errorf("custom_ports can be used only with override_mode = 'each'") } } - if in.NodeSpecs[overrideIdx].Node.Name == "" { - nodeName = fmt.Sprintf("node%d", i) - } + eg.Go(func() error { var net string net, err = clnode.NewNetworkCfgOneNetworkAllNodes(bcOut) @@ -128,6 +131,8 @@ func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) { if in.NodeSpecs[overrideIdx].Node.TestConfigOverrides != "" { net = in.NodeSpecs[overrideIdx].Node.TestConfigOverrides } + nodeName = fmt.Sprintf("node%d", i) + nodeWithNodeSetPrefixName := fmt.Sprintf("%s-%s", in.Name, nodeName) nodeSpec := &clnode.Input{ DbInput: in.DbInput, @@ -137,7 +142,7 @@ func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) { DebuggerPort: dlvPortStart + i, CustomPorts: in.NodeSpecs[overrideIdx].Node.CustomPorts, Image: in.NodeSpecs[overrideIdx].Node.Image, - Name: nodeName, + Name: nodeWithNodeSetPrefixName, PullImage: in.NodeSpecs[overrideIdx].Node.PullImage, DockerFilePath: in.NodeSpecs[overrideIdx].Node.DockerFilePath, DockerContext: in.NodeSpecs[overrideIdx].Node.DockerContext, diff --git a/framework/components/simple_node_set/nodeset_test.go b/framework/components/simple_node_set/nodeset_test.go index 5fc63e5c3..41883ba3d 100644 --- a/framework/components/simple_node_set/nodeset_test.go +++ b/framework/components/simple_node_set/nodeset_test.go @@ -24,13 +24,13 @@ func checkBasicOutputs(t *testing.T, output *ns.Output) { require.NotNil(t, output.CLNodes) require.Len(t, output.CLNodes, 2) require.Contains(t, output.CLNodes[0].PostgreSQL.Url, "postgresql://chainlink:thispasswordislongenough@127.0.0.1") - require.Contains(t, output.CLNodes[0].PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@ns-postgresql-") + require.Contains(t, output.CLNodes[0].PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@don") require.Contains(t, output.CLNodes[0].Node.HostURL, "127.0.0.1") require.Contains(t, output.CLNodes[0].Node.DockerURL, "node") require.Contains(t, output.CLNodes[0].Node.DockerP2PUrl, "node") require.Contains(t, output.CLNodes[1].PostgreSQL.Url, "postgresql://chainlink:thispasswordislongenough@127.0.0.1") - require.Contains(t, output.CLNodes[1].PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@ns-postgresql-") + require.Contains(t, output.CLNodes[1].PostgreSQL.DockerInternalURL, "postgresql://chainlink:thispasswordislongenough@don") require.Contains(t, output.CLNodes[1].Node.HostURL, "127.0.0.1") require.Contains(t, output.CLNodes[1].Node.DockerURL, "node") require.Contains(t, output.CLNodes[1].Node.DockerP2PUrl, "node") @@ -47,6 +47,7 @@ func TestComponentDockerNodeSetSharedDB(t *testing.T) { ChainID: "31337", }, nodeSetInput: &ns.Input{ + Name: "don-1", Nodes: 2, OverrideMode: "all", DbInput: &postgres.Input{ @@ -74,6 +75,7 @@ func TestComponentDockerNodeSetSharedDB(t *testing.T) { ChainID: "31337", }, nodeSetInput: &ns.Input{ + Name: "don-2", Nodes: 2, OverrideMode: "each", HTTPPortRangeStart: 20000, diff --git a/framework/components/simple_node_set/reload.go b/framework/components/simple_node_set/reload.go index 85dfcdd88..d81b5daa2 100644 --- a/framework/components/simple_node_set/reload.go +++ b/framework/components/simple_node_set/reload.go @@ -17,7 +17,7 @@ func UpgradeNodeSet(t *testing.T, in *Input, bc *blockchain.Output, wait time.Du if _, err := framework.SaveContainerLogs(uniq); err != nil { return nil, err } - _, err := chaos.ExecPumba("rm --volumes=false re2:^node.*|ns-postgresql.*", wait) + _, err := chaos.ExecPumba(fmt.Sprintf("rm --volumes=false re2:^%s-node.*|%s-ns-postgresql.*", in.Name, in.Name), wait) if err != nil { return nil, err } diff --git a/framework/examples/myproject/chaos.toml b/framework/examples/myproject/chaos/chaos.toml similarity index 95% rename from framework/examples/myproject/chaos.toml rename to framework/examples/myproject/chaos/chaos.toml index 8d32196e8..0bc5c8498 100644 --- a/framework/examples/myproject/chaos.toml +++ b/framework/examples/myproject/chaos/chaos.toml @@ -1,12 +1,13 @@ [blockchain_a] - type = "anvil" docker_cmd_params = ["-b", "1"] + type = "anvil" [data_provider] port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/chaos/chaos_blockchain_evm_gas_test.go b/framework/examples/myproject/chaos/chaos_blockchain_evm_gas_test.go new file mode 100644 index 000000000..e37dfd81f --- /dev/null +++ b/framework/examples/myproject/chaos/chaos_blockchain_evm_gas_test.go @@ -0,0 +1,123 @@ +package chaos + +import ( + "context" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/smartcontractkit/chainlink-testing-framework/framework" + "github.com/smartcontractkit/chainlink-testing-framework/framework/clclient" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake" + ns "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set" + "github.com/smartcontractkit/chainlink-testing-framework/framework/rpc" + "github.com/stretchr/testify/require" + "math/big" + "testing" + "time" +) + +func printBlockBaseFee(t *testing.T, url string) { + ec, err := ethclient.Dial(url) + require.NoError(t, err) + bn, err := ec.BlockNumber(context.Background()) + require.NoError(t, err) + b, err := ec.BlockByNumber(context.Background(), big.NewInt(int64(bn))) + require.NoError(t, err) + t.Logf("Current block base fee: %d", b.BaseFee()) +} + +type CfgGas struct { + BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` + MockerDataProvider *fake.Input `toml:"data_provider" validate:"required"` + NodeSet *ns.Input `toml:"nodeset" validate:"required"` +} + +func TestBlockchainGasChaos(t *testing.T) { + in, err := framework.Load[CfgGas](t) + require.NoError(t, err) + + // Can replace deployments with CRIB here + + bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) + require.NoError(t, err) + _, err = fake.NewFakeDataProvider(in.MockerDataProvider) + require.NoError(t, err) + out, err := ns.NewSharedDBNodeSet(in.NodeSet, bc) + require.NoError(t, err) + + c, err := clclient.New(out.CLNodes) + require.NoError(t, err) + + // !! This value must match anvil block speed to set gas values for every block !! + blockEvery := 1 * time.Second + waitBetweenTests := 1 * time.Minute + + gasControlFunc := func(t *testing.T, r *rpc.RPCClient, url string) { + startGasPrice := big.NewInt(2e9) + // ramp + for i := 0; i < 10; i++ { + printBlockBaseFee(t, url) + t.Logf("Setting block base fee: %d", startGasPrice) + err := r.AnvilSetNextBlockBaseFeePerGas(startGasPrice) + require.NoError(t, err) + startGasPrice = startGasPrice.Add(startGasPrice, big.NewInt(1e9)) + time.Sleep(blockEvery) + } + // hold + for i := 0; i < 10; i++ { + printBlockBaseFee(t, url) + t.Logf("Setting block base fee: %d", startGasPrice) + err := r.AnvilSetNextBlockBaseFeePerGas(startGasPrice) + require.NoError(t, err) + } + // release + for i := 0; i < 10; i++ { + printBlockBaseFee(t, url) + time.Sleep(blockEvery) + } + } + + testCases := []struct { + name string + chainURL string + increase *big.Int + waitBetweenTests time.Duration + gasFunc func(t *testing.T, r *rpc.RPCClient, url string) + validate func(t *testing.T, c []*clclient.ChainlinkClient) + }{ + { + name: "Slow and low", + chainURL: bc.Nodes[0].HostHTTPUrl, + waitBetweenTests: 30 * time.Second, + increase: big.NewInt(1e9), + gasFunc: gasControlFunc, + validate: func(t *testing.T, c []*clclient.ChainlinkClient) { + // add more clients and validate + }, + }, + { + name: "Fast and degen", + chainURL: bc.Nodes[0].HostHTTPUrl, + waitBetweenTests: 30 * time.Second, + increase: big.NewInt(5e9), + gasFunc: gasControlFunc, + validate: func(t *testing.T, c []*clclient.ChainlinkClient) { + // add more clients and validate + }, + }, + } + + // Start WASP load test here, apply average load profile that you expect in production! + // Configure timeouts and validate all the test cases until the test ends + + // Run test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.name) + printBlockBaseFee(t, tc.chainURL) + r := rpc.New(tc.chainURL, nil) + tc.gasFunc(t, r, tc.chainURL) + tc.validate(t, c) + time.Sleep(waitBetweenTests) + }) + } +} diff --git a/framework/examples/myproject/chaos/chaos_blockchain_evm_reorg_test.go b/framework/examples/myproject/chaos/chaos_blockchain_evm_reorg_test.go new file mode 100644 index 000000000..8eb2ea296 --- /dev/null +++ b/framework/examples/myproject/chaos/chaos_blockchain_evm_reorg_test.go @@ -0,0 +1,126 @@ +package chaos + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/framework" + "github.com/smartcontractkit/chainlink-testing-framework/framework/clclient" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/clnode" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake" + ns "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set" + "github.com/smartcontractkit/chainlink-testing-framework/framework/rpc" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +type CfgReorgTwoChains struct { + BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` + BlockchainB *blockchain.Input `toml:"blockchain_b" validate:"required"` + MockerDataProvider *fake.Input `toml:"data_provider" validate:"required"` + NodeSet *ns.Input `toml:"nodeset" validate:"required"` +} + +func TestBlockchainReorgChaos(t *testing.T) { + in, err := framework.Load[CfgReorgTwoChains](t) + require.NoError(t, err) + + // Can replace deployments with CRIB here + + bcA, err := blockchain.NewBlockchainNetwork(in.BlockchainA) + require.NoError(t, err) + bcB, err := blockchain.NewBlockchainNetwork(in.BlockchainB) + require.NoError(t, err) + // create network configs for 2 EVM networks + srcNetworkCfg, err := clnode.NewNetworkCfg(&clnode.EVMNetworkConfig{ + MinIncomingConfirmations: 1, + MinContractPayment: "0.00001 link", + ChainID: bcA.ChainID, + EVMNodes: []*clnode.EVMNode{ + { + SendOnly: false, + Order: 100, + }, + }, + }, bcA) + dstNetworkConfig, err := clnode.NewNetworkCfg(&clnode.EVMNetworkConfig{ + MinIncomingConfirmations: 1, + MinContractPayment: "0.00001 link", + ChainID: bcA.ChainID, + EVMNodes: []*clnode.EVMNode{ + { + SendOnly: false, + Order: 100, + }, + }, + }, bcB) + // override the configuration to connect with 2 networks + in.NodeSet.NodeSpecs[0].Node.TestConfigOverrides = srcNetworkCfg + dstNetworkConfig + // create a node set + nodesOut, err := ns.NewSharedDBNodeSet(in.NodeSet, bcA) + require.NoError(t, err) + + c, err := clclient.New(nodesOut.CLNodes) + require.NoError(t, err) + + testCases := []struct { + name string + wait time.Duration + chainURL string + reorgDepth int + validate func(c []*clclient.ChainlinkClient) error + }{ + { + name: "Reorg src with depth: 1", + wait: 30 * time.Second, + chainURL: bcA.Nodes[0].HostHTTPUrl, + reorgDepth: 1, + validate: func(c []*clclient.ChainlinkClient) error { + // add clients and validate + return nil + }, + }, + { + name: "Reorg dst with depth: 1", + wait: 30 * time.Second, + chainURL: bcB.Nodes[0].HostHTTPUrl, + reorgDepth: 1, + validate: func(c []*clclient.ChainlinkClient) error { + return nil + }, + }, + { + name: "Reorg src with depth: 5", + wait: 30 * time.Second, + chainURL: bcA.Nodes[0].HostHTTPUrl, + reorgDepth: 5, + validate: func(c []*clclient.ChainlinkClient) error { + return nil + }, + }, + { + name: "Reorg dst with depth: 5", + wait: 30 * time.Second, + chainURL: bcB.Nodes[0].HostHTTPUrl, + reorgDepth: 5, + validate: func(c []*clclient.ChainlinkClient) error { + return nil + }, + }, + } + + // Start WASP load test here, apply average load profile that you expect in production! + // Configure timeouts and validate all the test cases until the test ends + + // Run test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.name) + r := rpc.New(tc.chainURL, nil) + err := r.GethSetHead(tc.reorgDepth) + require.NoError(t, err) + time.Sleep(tc.wait) + err = tc.validate(c) + require.NoError(t, err) + }) + } +} diff --git a/framework/examples/myproject/chaos_test.go b/framework/examples/myproject/chaos/chaos_docker_test.go similarity index 51% rename from framework/examples/myproject/chaos_test.go rename to framework/examples/myproject/chaos/chaos_docker_test.go index 174b86952..1144c0028 100644 --- a/framework/examples/myproject/chaos_test.go +++ b/framework/examples/myproject/chaos/chaos_docker_test.go @@ -1,4 +1,4 @@ -package examples +package chaos import ( "github.com/smartcontractkit/chainlink-testing-framework/framework" @@ -18,11 +18,6 @@ type CfgChaos struct { NodeSet *ns.Input `toml:"nodeset" validate:"required"` } -func verifyServices(t *testing.T, c []*clclient.ChainlinkClient) { - _, _, err := c[0].ReadBridges() - require.NoError(t, err) -} - func TestChaos(t *testing.T) { in, err := framework.Load[CfgChaos](t) require.NoError(t, err) @@ -37,24 +32,43 @@ func TestChaos(t *testing.T) { c, err := clclient.New(out.CLNodes) require.NoError(t, err) - t.Run("run the cluster and test various chaos scenarios", func(t *testing.T) { - // Here are examples of using Pumba (https://github.com/alexei-led/pumba) - // for simplicity we allow users to run commands "as is", read their docs to learn more - // second parameter is experiment wait time - - // Restart the container - _, err = chaos.ExecPumba("stop --duration=20s --restart re2:node0", 30*time.Second) - require.NoError(t, err) - verifyServices(t, c) - - // Simulate poor network with 1s delay - _, err = chaos.ExecPumba("netem --tc-image=gaiadocker/iproute2 --duration=1m delay --time=1000 re2:node.*", 30*time.Second) - require.NoError(t, err) - verifyServices(t, c) - - // Stress container CPU (TODO: it is not portable, works only in CI or Linux VM, cgroups are required) - //_, err = chaos.ExecPumba(`stress --stress-image=alexeiled/stress-ng:latest-ubuntu --duration=30s --stressors="--cpu 1 --vm 2 --vm-bytes 1G" node0`, 30*time.Second) - //require.NoError(t, err) - //verifyServices(t, c) - }) + testCases := []struct { + name string + command string + wait time.Duration + validate func(c []*clclient.ChainlinkClient) error + }{ + { + name: "Reboot the pods", + wait: 1 * time.Minute, + command: "stop --duration=20s --restart re2:don-node0", + validate: func(c []*clclient.ChainlinkClient) error { + _, _, err := c[0].ReadBridges() + return err + }, + }, + { + name: "Introduce network delay", + wait: 1 * time.Minute, + command: "netem --tc-image=gaiadocker/iproute2 --duration=1m delay --time=1000 re2:don-node.*", + validate: func(c []*clclient.ChainlinkClient) error { + _, _, err := c[0].ReadBridges() + return err + }, + }, + } + + // Start WASP load test here, apply average load profile that you expect in production! + // Configure timeouts and validate all the test cases until the test ends + + // Run chaos test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.name) + _, err = chaos.ExecPumba(tc.command, tc.wait) + require.NoError(t, err) + err = tc.validate(c) + require.NoError(t, err) + }) + } } diff --git a/framework/examples/myproject/chaos/chaos_gas.toml b/framework/examples/myproject/chaos/chaos_gas.toml new file mode 100644 index 000000000..0bc5c8498 --- /dev/null +++ b/framework/examples/myproject/chaos/chaos_gas.toml @@ -0,0 +1,20 @@ + +[blockchain_a] + docker_cmd_params = ["-b", "1"] + type = "anvil" + +[data_provider] + port = 9111 + +[nodeset] + name = "don" + nodes = 5 + override_mode = "all" + + [nodeset.db] + image = "postgres:12.0" + + [[nodeset.node_specs]] + + [nodeset.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.17.0" diff --git a/framework/examples/myproject/chaos_k8s_test.go b/framework/examples/myproject/chaos/chaos_k8s_test.go similarity index 85% rename from framework/examples/myproject/chaos_k8s_test.go rename to framework/examples/myproject/chaos/chaos_k8s_test.go index de2c1440c..ffe5986fd 100644 --- a/framework/examples/myproject/chaos_k8s_test.go +++ b/framework/examples/myproject/chaos/chaos_k8s_test.go @@ -1,4 +1,4 @@ -package examples +package chaos import ( "context" @@ -252,32 +252,32 @@ func TestK8sChaos(t *testing.T) { ExperimentTotalDuration: experimentDuration, }) require.NoError(t, err) - //latenciesChaos, err := networkDelay(c, l, NodeLatenciesConfig{ - // Namespace: namespace, - // Description: "network issues", - // Latency: 5000 * time.Millisecond, - // LatencyDuration: 40 * time.Second, - // FromLabelKey: "app.kubernetes.io/instance", - // FromLabelValues: []string{"ccip-1"}, - // ToLabelKey: "app.kubernetes.io/instance", - // ToLabelValues: []string{"ccip-2", "ccip-3", "ccip-4"}, - // ExperimentTotalDuration: 3 * time.Minute, - // ExperimentCreateDelay: 3 * time.Minute, - //}) - //require.NoError(t, err) - //blockchainLatency, err := networkDelay(c, l, NodeLatenciesConfig{ - // Namespace: namespace, - // Description: "blockchain nodes network issues", - // Latency: 5000 * time.Millisecond, - // LatencyDuration: 40 * time.Second, - // FromLabelKey: "app.kubernetes.io/instance", - // FromLabelValues: []string{"ccip-1", "ccip-2", "ccip-3"}, - // ToLabelKey: "instance", - // ToLabelValues: []string{"geth-1337"}, - // ExperimentTotalDuration: 3 * time.Minute, - // //ExperimentCreateDelay: 3 * time.Minute, - //}) - //require.NoError(t, err) + latenciesChaos, err := networkDelay(c, l, NodeLatenciesConfig{ + Namespace: namespace, + Description: "network issues", + Latency: 5000 * time.Millisecond, + InjectionDuration: 40 * time.Second, + FromLabelKey: "app.kubernetes.io/instance", + FromLabelValues: []string{"ccip-1"}, + ToLabelKey: "app.kubernetes.io/instance", + ToLabelValues: []string{"ccip-2", "ccip-3", "ccip-4"}, + ExperimentTotalDuration: 3 * time.Minute, + ExperimentCreateDelay: 3 * time.Minute, + }) + require.NoError(t, err) + blockchainLatency, err := networkDelay(c, l, NodeLatenciesConfig{ + Namespace: namespace, + Description: "blockchain nodes network issues", + Latency: 5000 * time.Millisecond, + InjectionDuration: 40 * time.Second, + FromLabelKey: "app.kubernetes.io/instance", + FromLabelValues: []string{"ccip-1", "ccip-2", "ccip-3"}, + ToLabelKey: "instance", + ToLabelValues: []string{"geth-1337"}, + ExperimentTotalDuration: 3 * time.Minute, + //ExperimentCreateDelay: 3 * time.Minute, + }) + require.NoError(t, err) podCPUStress, err := podStressCPU(c, l, NodeCPUStressConfig{ Namespace: namespace, Description: "CPU hog", @@ -290,14 +290,14 @@ func TestK8sChaos(t *testing.T) { require.NoError(t, err) _ = rebootsChaos - //_ = latenciesChaos - //_ = blockchainLatency + _ = latenciesChaos + _ = blockchainLatency _ = podCPUStress chaosList := []havoc.ChaosEntity{ - //rebootsChaos, - //latenciesChaos, - //blockchainLatency, + rebootsChaos, + latenciesChaos, + blockchainLatency, podCPUStress, } @@ -314,6 +314,7 @@ func TestK8sChaos(t *testing.T) { } }) - // your load test comes here ! + // Start WASP load test here, apply average load profile that you expect in production! + // Configure timeouts and validate all the test cases until the test ends time.Sleep(3*time.Minute + 5*time.Second) } diff --git a/framework/examples/myproject/chaos/chaos_reorg.toml b/framework/examples/myproject/chaos/chaos_reorg.toml new file mode 100644 index 000000000..7e67871f6 --- /dev/null +++ b/framework/examples/myproject/chaos/chaos_reorg.toml @@ -0,0 +1,26 @@ + +[blockchain_a] + chain_id = "1337" + port = "8545" + type = "geth" + +[blockchain_b] + chain_id = "2337" + port = "8555" + type = "geth" + +[data_provider] + port = 9111 + +[nodeset] + name = "don" + nodes = 5 + override_mode = "all" + + [nodeset.db] + image = "postgres:12.0" + + [[nodeset.node_specs]] + + [nodeset.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.17.0" diff --git a/framework/examples/myproject/chaos_blockchain_evm_test.go b/framework/examples/myproject/chaos_blockchain_evm_test.go deleted file mode 100644 index b79512605..000000000 --- a/framework/examples/myproject/chaos_blockchain_evm_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package examples - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/framework/rpc" - "github.com/stretchr/testify/require" - "os" - "testing" - "time" -) - -func TestBlockchainChaos(t *testing.T) { - srcChain := os.Getenv("CTF_CHAOS_SRC_CHAIN_RPC_HTTP_URL") - require.NotEmpty(t, srcChain, "source chain RPC must be set") - dstChain := os.Getenv("CTF_CHAOS_DST_CHAIN_RPC_HTTP_URL") - require.NotEmpty(t, dstChain, "destination chain RPC must be set") - - recoveryIntervalDuration := 120 * time.Second - - testCases := []struct { - name string - chainURL string - reorgDepth int - }{ - { - name: "Reorg src with depth: 1", - chainURL: srcChain, - reorgDepth: 1, - }, - { - name: "Reorg dst with depth: 1", - chainURL: dstChain, - reorgDepth: 1, - }, - { - name: "Reorg src with depth: 5", - chainURL: srcChain, - reorgDepth: 5, - }, - { - name: "Reorg dst with depth: 5", - chainURL: dstChain, - reorgDepth: 5, - }, - } - - // Run test cases - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Log(tc.name) - r := rpc.New(tc.chainURL, nil) - err := r.GethSetHead(tc.reorgDepth) - require.NoError(t, err) - t.Logf("Awaiting chaos recovery: %s", tc.name) - time.Sleep(recoveryIntervalDuration) - - // Validate Chainlink product here, use load test assertions - }) - } -} diff --git a/framework/examples/myproject/fake.toml b/framework/examples/myproject/fake.toml index 36c0ad7d1..f978f66f6 100644 --- a/framework/examples/myproject/fake.toml +++ b/framework/examples/myproject/fake.toml @@ -6,6 +6,7 @@ port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/fork_plus_offchain.toml b/framework/examples/myproject/fork_plus_offchain.toml index 9dd7e0bbb..4c68f6e34 100644 --- a/framework/examples/myproject/fork_plus_offchain.toml +++ b/framework/examples/myproject/fork_plus_offchain.toml @@ -18,6 +18,7 @@ [contracts_src] [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/go.mod b/framework/examples/myproject/go.mod index 22f0bf083..a0230ad6a 100644 --- a/framework/examples/myproject/go.mod +++ b/framework/examples/myproject/go.mod @@ -16,7 +16,7 @@ require ( github.com/ethereum/go-ethereum v1.14.11 github.com/go-resty/resty/v2 v2.15.3 github.com/rs/zerolog v1.33.0 - github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.7 + github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.8 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.10 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 diff --git a/framework/examples/myproject/performance_baseline.toml b/framework/examples/myproject/performance_baseline.toml index 76da1d875..09b6996d4 100644 --- a/framework/examples/myproject/performance_baseline.toml +++ b/framework/examples/myproject/performance_baseline.toml @@ -6,6 +6,7 @@ port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/quick-deploy.toml b/framework/examples/myproject/quick-deploy.toml index c446b4f16..f795d3cd1 100644 --- a/framework/examples/myproject/quick-deploy.toml +++ b/framework/examples/myproject/quick-deploy.toml @@ -5,6 +5,7 @@ [contracts_src] [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/scalability.toml b/framework/examples/myproject/scalability.toml index 8d32196e8..5803e72c4 100644 --- a/framework/examples/myproject/scalability.toml +++ b/framework/examples/myproject/scalability.toml @@ -7,6 +7,7 @@ port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/smoke.toml b/framework/examples/myproject/smoke.toml index 7af8fc9ea..a88c057d7 100644 --- a/framework/examples/myproject/smoke.toml +++ b/framework/examples/myproject/smoke.toml @@ -7,6 +7,7 @@ port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "all" diff --git a/framework/examples/myproject/upgrade.toml b/framework/examples/myproject/upgrade.toml index 39cc8f581..fa38e2638 100644 --- a/framework/examples/myproject/upgrade.toml +++ b/framework/examples/myproject/upgrade.toml @@ -7,6 +7,7 @@ port = 9111 [nodeset] + name = "don" nodes = 5 override_mode = "each" diff --git a/framework/examples/myproject/upgrade_multi.toml b/framework/examples/myproject/upgrade_multi.toml new file mode 100644 index 000000000..af28ffb85 --- /dev/null +++ b/framework/examples/myproject/upgrade_multi.toml @@ -0,0 +1,37 @@ + +[blockchain_a] + docker_cmd_params = ["-b", "1"] + type = "anvil" + +[data_provider] + port = 9111 + +[nodeset_a] + http_port_range_start = 10000 + name = "exec" + nodes = 5 + override_mode = "all" + + [nodeset_a.db] + image = "postgres:12.0" + port = 13000 + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.17.0" + +[nodeset_b] + http_port_range_start = 10100 + name = "workflow" + nodes = 5 + override_mode = "all" + + [nodeset_b.db] + image = "postgres:12.0" + port = 13100 + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.17.0" diff --git a/framework/examples/myproject/upgrade_multi_each.toml b/framework/examples/myproject/upgrade_multi_each.toml new file mode 100644 index 000000000..1e41dc9da --- /dev/null +++ b/framework/examples/myproject/upgrade_multi_each.toml @@ -0,0 +1,77 @@ + +[blockchain_a] + docker_cmd_params = ["-b", "1"] + type = "anvil" + +[data_provider] + port = 9111 + +[nodeset_a] + http_port_range_start = 10100 + name = "exec" + nodes = 5 + override_mode = "each" + + [nodeset_a.db] + image = "postgres:12.0" + port = 13100 + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_a.node_specs]] + + [nodeset_a.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + +[nodeset_b] + http_port_range_start = 10000 + name = "workflow" + nodes = 5 + override_mode = "each" + + [nodeset_b.db] + image = "postgres:12.0" + port = 13000 + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" + + [[nodeset_b.node_specs]] + + [nodeset_b.node_specs.node] + image = "public.ecr.aws/chainlink/chainlink:v2.16.0" diff --git a/framework/examples/myproject/upgrade_multi_nodeset_test.go b/framework/examples/myproject/upgrade_multi_nodeset_test.go new file mode 100644 index 000000000..69f7498dc --- /dev/null +++ b/framework/examples/myproject/upgrade_multi_nodeset_test.go @@ -0,0 +1,63 @@ +package examples + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/framework" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/framework/components/fake" + ns "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +type CfgUpgradeMulti struct { + BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` + MockerDataProvider *fake.Input `toml:"data_provider" validate:"required"` + NodeSetA *ns.Input `toml:"nodeset_a" validate:"required"` + NodeSetB *ns.Input `toml:"nodeset_b" validate:"required"` +} + +func TestMultiUpgrade(t *testing.T) { + in, err := framework.Load[CfgUpgradeMulti](t) + require.NoError(t, err) + + bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) + require.NoError(t, err) + _, err = fake.NewFakeDataProvider(in.MockerDataProvider) + require.NoError(t, err) + + // deploy first time + ns1, err := ns.NewSharedDBNodeSet(in.NodeSetA, bc) + require.NoError(t, err) + ns2, err := ns.NewSharedDBNodeSet(in.NodeSetB, bc) + require.NoError(t, err) + + // reboot both node sets and upgrade with new configs + + in.NodeSetA.NodeSpecs[0].Node.Image = "public.ecr.aws/chainlink/chainlink:v2.17.0" + in.NodeSetA.NodeSpecs[0].Node.UserConfigOverrides = ` + [Log] + level = 'info' + ` + + ns1, err = ns.UpgradeNodeSet(t, in.NodeSetA, bc, 3*time.Second) + require.NoError(t, err) + + in.NodeSetB.NodeSpecs[0].Node.Image = "public.ecr.aws/chainlink/chainlink:v2.17.0" + in.NodeSetB.NodeSpecs[0].Node.UserConfigOverrides = ` + [Log] + level = 'info' + ` + + ns2, err = ns.UpgradeNodeSet(t, in.NodeSetB, bc, 3*time.Second) + require.NoError(t, err) + + t.Run("test something", func(t *testing.T) { + for _, n := range ns1.CLNodes { + require.NotEmpty(t, n.Node.HostURL) + } + for _, n := range ns2.CLNodes { + require.NotEmpty(t, n.Node.HostURL) + } + }) +} diff --git a/framework/rpc/rpc.go b/framework/rpc/rpc.go index ef246540f..83c3f3e9b 100644 --- a/framework/rpc/rpc.go +++ b/framework/rpc/rpc.go @@ -3,8 +3,10 @@ package rpc import ( "context" "crypto/tls" + "encoding/hex" "encoding/json" "fmt" + "math/big" "math/rand" "net/http" "os" @@ -115,12 +117,13 @@ func (m *RPCClient) AnvilTxPoolStatus(params []interface{}) (*TxStatusResponse, // AnvilSetMinGasPrice sets min gas price (pre-EIP-1559 anvil is required) // API Reference https://book.getfoundry.sh/reference/anvil/ -func (m *RPCClient) AnvilSetMinGasPrice(params []interface{}) error { +func (m *RPCClient) AnvilSetMinGasPrice(gas uint64) error { + hexGasPrice := fmt.Sprintf("0x%x", gas) rInt := rand.Int() payload := map[string]interface{}{ "jsonrpc": "2.0", "method": "anvil_setMinGasPrice", - "params": params, + "params": []interface{}{hexGasPrice}, "id": rInt, } if _, err := m.client.R().SetBody(payload).Post(m.URL); err != nil { @@ -129,14 +132,42 @@ func (m *RPCClient) AnvilSetMinGasPrice(params []interface{}) error { return nil } +// int64ToUint256 converts an int64 to a 256-bit unsigned integer encoded as a hex string. +func int64ToUint256(value int64) string { + bigValue := big.NewInt(value) + if bigValue.Sign() < 0 { + panic("value must be non-negative for uint256") + } + bytes := make([]byte, 32) + bigValue.FillBytes(bytes) + return "0x" + hex.EncodeToString(bytes) +} + +// int64ToU128 converts an int64 to a 128-bit unsigned integer encoded as a hex string. +func int64ToU128(value int64) string { + bigValue := big.NewInt(value) + if bigValue.Sign() < 0 { + panic("value must be non-negative for u128") + } + if bigValue.BitLen() > 128 { + panic("value exceeds 128 bits") + } + bytes := make([]byte, 16) + bigValue.FillBytes(bytes) + return "0x" + hex.EncodeToString(bytes) +} + // AnvilSetNextBlockBaseFeePerGas sets next block base fee per gas value // API Reference https://book.getfoundry.sh/reference/anvil/ -func (m *RPCClient) AnvilSetNextBlockBaseFeePerGas(params []interface{}) error { +func (m *RPCClient) AnvilSetNextBlockBaseFeePerGas(gas *big.Int) error { + //hexBaseFee := "0x" + strconv.FormatInt(gas, 10) + //bi := big.NewInt(gas) + //hexBaseFee := fmt.Sprintf("0x%x", bi) rInt := rand.Int() payload := map[string]interface{}{ "jsonrpc": "2.0", "method": "anvil_setNextBlockBaseFeePerGas", - "params": params, + "params": []interface{}{gas.String()}, "id": rInt, } if _, err := m.client.R().SetBody(payload).Post(m.URL); err != nil { diff --git a/framework/rpc/rpc_suite.go b/framework/rpc/rpc_suite.go deleted file mode 100644 index 4d0ba3335..000000000 --- a/framework/rpc/rpc_suite.go +++ /dev/null @@ -1,67 +0,0 @@ -package rpc - -import ( - "fmt" - "strconv" - "time" - - "github.com/rs/zerolog" -) - -// ModulateBaseFeeOverDuration will cause the gas price to rise or drop to a certain percentage of the starting gas price -// over the duration specified. -// Minimum duration is 1 s -// if spike is true, the gas price will rise to the target price -// if spike is false, the gas price will drop to the target price -func (m *RPCClient) ModulateBaseFeeOverDuration(lggr zerolog.Logger, startingBaseFee int64, percentage float64, duration time.Duration, spike bool) error { - if duration < time.Second { - return fmt.Errorf("duration must be at least 1s") - } - // Calculate the target gas price - targetBaseFee := float64(startingBaseFee) * (1 + percentage) - if !spike { - targetBaseFee = float64(startingBaseFee) * (1 - percentage) - } - lggr.Info(). - Int64("Starting Base Fee", startingBaseFee). - Float64("Percentage", percentage). - Dur("Duration", duration). - Int64("Target Base Fee", int64(targetBaseFee)). - Msg("Modulating base fee per gas over duration") - - // Divide the duration into 10 parts and update the gas price every part - intTargetBaseFee := int64(targetBaseFee) - partUpdate := (intTargetBaseFee - startingBaseFee) / 10 - partDuration := duration / 10 - ticker := time.NewTicker(partDuration) - defer ticker.Stop() - baseFeeToUpdate := startingBaseFee - for range ticker.C { - lggr.Info(). - Int64("Base Fee", baseFeeToUpdate). - Int64("Updating By", partUpdate). - Msg("Updating base fee per gas") - baseFeeToUpdate = baseFeeToUpdate + partUpdate - if spike { - if baseFeeToUpdate > intTargetBaseFee { - baseFeeToUpdate = intTargetBaseFee - } - } else { - if baseFeeToUpdate < intTargetBaseFee { - baseFeeToUpdate = intTargetBaseFee - } - } - err := m.AnvilSetNextBlockBaseFeePerGas([]interface{}{strconv.FormatInt(baseFeeToUpdate, 10)}) - if err != nil { - return fmt.Errorf("failed to set base fee %d: %w", baseFeeToUpdate, err) - } - lggr.Info().Int64("NextBlockBaseFeePerGas", baseFeeToUpdate).Msg("Updated base fee per gas") - if baseFeeToUpdate == intTargetBaseFee { - lggr.Info(). - Int64("Base Fee", baseFeeToUpdate). - Msg("Reached target base fee") - return nil - } - } - return fmt.Errorf("failed to reach target base fee %d", intTargetBaseFee) -} diff --git a/framework/rpc/rpc_suite_test.go b/framework/rpc/rpc_suite_test.go deleted file mode 100644 index f4b2a84ab..000000000 --- a/framework/rpc/rpc_suite_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package rpc - -import ( - "context" - "github.com/smartcontractkit/chainlink-testing-framework/framework" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/stretchr/testify/require" -) - -func TestRPCSuite(t *testing.T) { - t.Run("(anvil) test we can modulate next block base fee per gas over a duration", func(t *testing.T) { - ac, err := StartAnvil([]string{"--balance", "1", "--block-time", "1"}) - require.NoError(t, err) - client, err := ethclient.Dial(ac.URL) - require.NoError(t, err) - printGasPrices(t, client) - // set a base fee - anvilClient := New(ac.URL, nil) - // set fee for the next block - err = anvilClient.AnvilSetNextBlockBaseFeePerGas([]interface{}{"2000000000"}) - require.NoError(t, err) - // mine a block - err = anvilClient.AnvilMine(nil) - require.NoError(t, err) - blockNumber, err := client.BlockNumber(context.Background()) - require.NoError(t, err) - block, err := client.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber))) - require.NoError(t, err) - // check the base fee of the block - require.Equal(t, "2000000000", block.BaseFee().String(), "expected base fee to be 20 gwei") - err = anvilClient.ModulateBaseFeeOverDuration(framework.L, 2000000000, 0.5, 20*time.Second, true) - require.NoError(t, err) - // mine a block - err = anvilClient.AnvilMine(nil) - require.NoError(t, err) - blockNumber, err = client.BlockNumber(context.Background()) - require.NoError(t, err) - block, err = client.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber))) - require.NoError(t, err) - // check the base fee of the block - require.Equal(t, "3000000000", block.BaseFee().String(), "expected base fee to be 30 gwei") - err = anvilClient.ModulateBaseFeeOverDuration(framework.L, 3000000000, 0.25, 15*time.Second, false) - require.NoError(t, err) - // mine a block - err = anvilClient.AnvilMine(nil) - require.NoError(t, err) - blockNumber, err = client.BlockNumber(context.Background()) - require.NoError(t, err) - block, err = client.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber))) - require.NoError(t, err) - // check the base fee of the block - require.Equal(t, "2250000000", block.BaseFee().String(), "expected base fee to be 30 gwei") - }) -} diff --git a/framework/rpc/rpc_test.go b/framework/rpc/rpc_test.go index 9c75182a2..e808da4c6 100644 --- a/framework/rpc/rpc_test.go +++ b/framework/rpc/rpc_test.go @@ -181,7 +181,7 @@ func TestRPCAPI(t *testing.T) { printGasPrices(t, client) anvilClient := New(ac.URL, nil) - err = anvilClient.AnvilSetNextBlockBaseFeePerGas([]interface{}{"10000000000"}) + err = anvilClient.AnvilSetNextBlockBaseFeePerGas(big.NewInt(10000000000)) require.NoError(t, err) printGasPrices(t, client) gasPrice, err := client.SuggestGasPrice(context.Background()) @@ -191,7 +191,7 @@ func TestRPCAPI(t *testing.T) { _, err = sendTestTransaction(t, client, big.NewInt(1e9), big.NewInt(1e9), true) require.Error(t, err) - err = anvilClient.AnvilSetNextBlockBaseFeePerGas([]interface{}{"1"}) + err = anvilClient.AnvilSetNextBlockBaseFeePerGas(big.NewInt(1)) require.NoError(t, err) _, err = sendTestTransaction(t, client, big.NewInt(1e9), big.NewInt(1e9), true)