diff --git a/cmd/attacknet/main.go b/cmd/attacknet/main.go index 9c61a2e..191c9f3 100644 --- a/cmd/attacknet/main.go +++ b/cmd/attacknet/main.go @@ -57,7 +57,7 @@ func main() { log.Fatal(err) os.Exit(1) } - case "plan ": + case "plan ": config, err := plan.LoadPlannerConfigFromPath(CLI.Plan.Path) if err != nil { log.Fatal(err) @@ -68,5 +68,7 @@ func main() { log.Fatal(err) os.Exit(1) } + default: + log.Fatal("unrecognized arguments") } } diff --git a/network-configs/plan/testing.yaml b/network-configs/plan/testing.yaml new file mode 100644 index 0000000..53e6b9d --- /dev/null +++ b/network-configs/plan/testing.yaml @@ -0,0 +1,88 @@ +participants: + - el_client_type: geth + el_client_image: ethereum/client-go:latest + cl_client_type: lighthouse + cl_client_image: sigp/lighthouse:latest + el_min_cpu: 1000 + el_max_cpu: 1000 + el_min_mem: 2048 + el_max_mem: 2048 + bn_min_cpu: 1000 + bn_max_cpu: 1000 + bn_min_mem: 2048 + bn_max_mem: 2048 + v_min_cpu: 1000 + v_max_cpu: 1000 + v_min_mem: 1024 + v_max_mem: 1024 + count: 1 + - el_client_type: reth + el_client_image: ghcr.io/paradigmxyz/reth:v0.1.0-alpha.13 + cl_client_type: teku + cl_client_image: consensys/teku:23.12.0 + el_min_cpu: 1000 + el_max_cpu: 1000 + el_min_mem: 2048 + el_max_mem: 2048 + bn_min_cpu: 1000 + bn_max_cpu: 1000 + bn_min_mem: 2048 + bn_max_mem: 2048 + count: 1 + - el_client_type: reth + el_client_image: ghcr.io/paradigmxyz/reth:v0.1.0-alpha.13 + cl_client_type: lodestar + cl_client_image: chainsafe/lodestar:v1.12.1 + el_min_cpu: 1000 + el_max_cpu: 1000 + el_min_mem: 2048 + el_max_mem: 2048 + bn_min_cpu: 1000 + bn_max_cpu: 1000 + bn_min_mem: 2048 + bn_max_mem: 2048 + v_min_cpu: 1000 + v_max_cpu: 1000 + v_min_mem: 1024 + v_max_mem: 1024 + count: 1 + - el_client_type: reth + el_client_image: ghcr.io/paradigmxyz/reth:v0.1.0-alpha.13 + cl_client_type: lighthouse + cl_client_image: sigp/lighthouse:latest + el_min_cpu: 1000 + el_max_cpu: 1000 + el_min_mem: 2048 + el_max_mem: 2048 + bn_min_cpu: 1000 + bn_max_cpu: 1000 + bn_min_mem: 2048 + bn_max_mem: 2048 + v_min_cpu: 1000 + v_max_cpu: 1000 + v_min_mem: 1024 + v_max_mem: 1024 + count: 1 + - el_client_type: reth + el_client_image: ghcr.io/paradigmxyz/reth:v0.1.0-alpha.13 + cl_client_type: prysm + cl_client_image: prysmaticlabs/prysm-beacon-chain:latest,prysmaticlabs/prysm-validator:latest + el_min_cpu: 1000 + el_max_cpu: 1000 + el_min_mem: 2048 + el_max_mem: 2048 + bn_min_cpu: 1000 + bn_max_cpu: 1000 + bn_min_mem: 2048 + bn_max_mem: 2048 + v_min_cpu: 1000 + v_max_cpu: 1000 + v_min_mem: 1024 + v_max_mem: 1024 + count: 1 +network_params: + num_validator_keys_per_node: 32 +additional_services: + - prometheus_grafana + - dora +parallel_keystore_generation: false diff --git a/pkg/health/ethereum/network_checker.go b/pkg/health/ethereum/network_checker.go index 67a9509..7597963 100644 --- a/pkg/health/ethereum/network_checker.go +++ b/pkg/health/ethereum/network_checker.go @@ -75,7 +75,7 @@ func (e *EthNetworkChecker) RunAllChecks(ctx context.Context) ([]*types.CheckRes rpcClients[i] = client } - log.Info("Ready to query for health checks") + log.Debug("Ready to query for health checks") latestResult, err := e.getBlockConsensus(ctx, rpcClients, "latest", 3) if err != nil { return nil, err @@ -113,7 +113,7 @@ func (e *EthNetworkChecker) getBlockConsensus(ctx context.Context, clients []*Ex consensusBlockNum, wrongBlockNum, consensusBlockHash, wrongBlockHash := determineForkConsensus(forkChoice) if len(wrongBlockNum) > 0 { if maxAttempts > 0 { - log.Infof("Nodes not at consensus for %s block. Waiting and re-trying in case we're on block propagation boundary. Attempts left: %d", blockType, maxAttempts-1) + log.Debugf("Nodes not at consensus for %s block. Waiting and re-trying in case we're on block propagation boundary. Attempts left: %d", blockType, maxAttempts-1) time.Sleep(2 * time.Second) return e.getBlockConsensus(ctx, clients, blockType, maxAttempts-1) } else { diff --git a/pkg/plan/config.go b/pkg/plan/config.go index 0e577d8..5211fe7 100644 --- a/pkg/plan/config.go +++ b/pkg/plan/config.go @@ -1,18 +1,18 @@ package plan import ( - planTypes "attacknet/cmd/pkg/plan/types" + "attacknet/cmd/pkg/plan/suite" "github.com/kurtosis-tech/stacktrace" "gopkg.in/yaml.v3" "os" ) -func validatePlannerFaultConfiguration(c planTypes.PlannerConfig) error { +func validatePlannerFaultConfiguration(c PlannerConfig) error { // fault type - _, ok := planTypes.FaultTypes[c.FaultConfig.FaultType] + _, ok := suite.FaultTypes[c.FaultConfig.FaultType] if !ok { - faults := make([]planTypes.FaultTypeEnum, 0, len(planTypes.FaultTypes)) - for k := range planTypes.FaultTypes { + faults := make([]suite.FaultTypeEnum, 0, len(suite.FaultTypes)) + for k := range suite.FaultTypes { faults = append(faults, k) } return stacktrace.NewError("the fault type '%s' is not supported. Supported faults: %v", c.FaultConfig.FaultType, faults) @@ -26,10 +26,10 @@ func validatePlannerFaultConfiguration(c planTypes.PlannerConfig) error { // targeting dimensions for _, spec := range c.FaultConfig.TargetingDimensions { - _, ok := planTypes.TargetingSpecs[spec] + _, ok := suite.TargetingSpecs[spec] if !ok { - specs := make([]planTypes.TargetingSpec, 0, len(planTypes.TargetingSpecs)) - for k := range planTypes.TargetingSpecs { + specs := make([]suite.TargetingSpec, 0, len(suite.TargetingSpecs)) + for k := range suite.TargetingSpecs { specs = append(specs, k) } return stacktrace.NewError("the fault targeting dimension %s is not supported. Supported dimensions: %v", spec, specs) @@ -38,10 +38,10 @@ func validatePlannerFaultConfiguration(c planTypes.PlannerConfig) error { // attack size dimensions for _, attackSize := range c.FaultConfig.AttackSizeDimensions { - _, ok := planTypes.AttackSizes[attackSize] + _, ok := suite.AttackSizes[attackSize] if !ok { - sizes := make([]planTypes.AttackSize, 0, len(planTypes.AttackSizes)) - for k := range planTypes.AttackSizes { + sizes := make([]suite.AttackSize, 0, len(suite.AttackSizes)) + for k := range suite.AttackSizes { sizes = append(sizes, k) } return stacktrace.NewError("the attack size dimension %s is not supported. Supported dimensions: %v", attackSize, sizes) @@ -58,13 +58,13 @@ func validatePlannerFaultConfiguration(c planTypes.PlannerConfig) error { return nil } -func LoadPlannerConfigFromPath(path string) (*planTypes.PlannerConfig, error) { +func LoadPlannerConfigFromPath(path string) (*PlannerConfig, error) { bs, err := os.ReadFile(path) if err != nil { return nil, stacktrace.Propagate(err, "could not planner config on path %s", path) } - var config planTypes.PlannerConfig + var config PlannerConfig err = yaml.Unmarshal(bs, &config) if err != nil { return nil, stacktrace.Propagate(err, "unable to unmarshal planner config from %s", path) diff --git a/pkg/plan/file.go b/pkg/plan/file.go new file mode 100644 index 0000000..7004f39 --- /dev/null +++ b/pkg/plan/file.go @@ -0,0 +1,75 @@ +package plan + +import ( + "fmt" + "github.com/kurtosis-tech/stacktrace" + "os" + "path/filepath" +) + +func preparePaths(testName string) (netRefPath, netConfigPath, planConfigPath string, err error) { + dir, err := os.Getwd() + // initialize to empty string for error cases + netConfigPath = "" + planConfigPath = "" + if err != nil { + return + } + + netRefPath = fmt.Sprintf("plan/%s.yaml", testName) + networkConfigName := fmt.Sprintf("network-configs/%s", netRefPath) + netConfigPath = filepath.Join(dir, networkConfigName) + if _, err = os.Stat(netConfigPath); err == nil { + // delete file + err = os.Remove(netConfigPath) + if err != nil { + err = stacktrace.Propagate(err, "unable to remove file") + return + } + } + + suiteName := fmt.Sprintf("test-suites/plan/%s.yaml", testName) + planConfigPath = filepath.Join(dir, suiteName) + if _, err = os.Stat(planConfigPath); err == nil { + // delete file + err = os.Remove(planConfigPath) + if err != nil { + err = stacktrace.Propagate(err, "unable to remove file") + return + } + } + err = nil + return +} + +func writePlans(netConfigPath, suiteConfigPath string, netConfig, suiteConfig []byte) error { + f, err := os.Create(netConfigPath) + if err != nil { + return stacktrace.Propagate(err, "cannot open network types path %s", netConfigPath) + } + _, err = f.Write(netConfig) + if err != nil { + return stacktrace.Propagate(err, "could not write network types to file") + } + + err = f.Close() + if err != nil { + return stacktrace.Propagate(err, "could not close network types file") + } + + f, err = os.Create(suiteConfigPath) + if err != nil { + return stacktrace.Propagate(err, "cannot open suite types path %s", suiteConfigPath) + } + _, err = f.Write(suiteConfig) + if err != nil { + return stacktrace.Propagate(err, "could not write suite types to file") + } + + err = f.Close() + if err != nil { + return stacktrace.Propagate(err, "could not close suite types file") + } + + return nil +} diff --git a/pkg/plan/network/clients.go b/pkg/plan/network/clients.go index 3be8976..4d49dc8 100644 --- a/pkg/plan/network/clients.go +++ b/pkg/plan/network/clients.go @@ -1,68 +1,25 @@ package network import ( - "attacknet/cmd/pkg/plan/types" - "strings" + "github.com/kurtosis-tech/stacktrace" ) -const default_el_cpu = 1000 -const default_cl_cpu = 1000 -const default_val_cpu = 1000 - -const default_el_mem = 1024 -const default_cl_mem = 2048 -const default_val_mem = 1024 - -func buildConsensusClient(config types.ClientVersion) *types.ConsensusClient { - image := config.Image - validatorImage := "" - - if strings.Contains(config.Image, ",") { - images := strings.Split(config.Image, ",") - image = images[0] - validatorImage = images[1] - } - if config.HasSidecar { - return &types.ConsensusClient{ - Type: config.Name, - Image: image, - HasValidatorSidecar: true, - ValidatorImage: validatorImage, - ExtraLabels: make(map[string]string), - CpuRequired: default_cl_cpu, - MemoryRequired: default_cl_mem, - SidecarCpuRequired: default_val_cpu, - SidecarMemoryRequired: default_val_mem, - } - } else { - return &types.ConsensusClient{ - Type: config.Name, - Image: image, - HasValidatorSidecar: false, - ValidatorImage: validatorImage, - ExtraLabels: make(map[string]string), - CpuRequired: default_cl_cpu, - MemoryRequired: default_cl_mem, - SidecarCpuRequired: 0, - SidecarMemoryRequired: 0, - } +func buildNode(index int, execConf, consensusConf ClientVersion) *Node { + return &Node{ + Index: index, + Execution: composeExecutionClient(execConf), + Consensus: composeConsensusClient(consensusConf), } } -func buildExecutionClient(config types.ClientVersion) *types.ExecutionClient { - return &types.ExecutionClient{ - Type: config.Name, - Image: config.Image, - ExtraLabels: make(map[string]string), - CpuRequired: default_cl_cpu, - MemoryRequired: default_cl_mem, +func composeBootnode(execClients, consensusClients map[string]ClientVersion) (*Node, error) { + execConf, ok := execClients["geth"] + if !ok { + return nil, stacktrace.NewError("unable to load configuration for exec client geth") } -} - -func buildNode(index int, execConf, consensusConf types.ClientVersion) *types.Node { - return &types.Node{ - Index: index, - Execution: buildExecutionClient(execConf), - Consensus: buildConsensusClient(consensusConf), + consConf, ok := consensusClients["lighthouse"] + if !ok { + return nil, stacktrace.NewError("unable to load configuration for exec client lighthouse") } + return buildNode(0, execConf, consConf), nil } diff --git a/pkg/plan/network/consensus.go b/pkg/plan/network/consensus.go index 333a3d9..b53d8da 100644 --- a/pkg/plan/network/consensus.go +++ b/pkg/plan/network/consensus.go @@ -1,67 +1,90 @@ package network -import "attacknet/cmd/pkg/plan/types" +import ( + "github.com/kurtosis-tech/stacktrace" + "strings" +) -func createPrysmClient() *types.ConsensusClient { - return &types.ConsensusClient{ - Type: "prysm", - Image: "prysmaticlabs/prysm-beacon-chain:latest", - ValidatorImage: "prysmaticlabs/prysm-validator:latest", - HasValidatorSidecar: true, - ExtraLabels: make(map[string]string), - CpuRequired: 2000, - MemoryRequired: 2048, - SidecarCpuRequired: 1000, - SidecarMemoryRequired: 1024, +const defaultClCpu = 1000 +const defaultValCpu = 1000 + +const defaultClMem = 2048 +const defaultValMem = 1024 + +func composeConsensusTesterNetwork(consensusClient string, execClients, consClients []ClientVersion) ([]*Node, error) { + execClientMap, consClientMap, err := clientListsToMaps(execClients, consClients) + if err != nil { + return nil, err } -} -func createLighthouseClient() *types.ConsensusClient { - return &types.ConsensusClient{ - Type: "lighthouse", - Image: "sigp/lighthouse:latest", - HasValidatorSidecar: true, - ExtraLabels: make(map[string]string), - CpuRequired: 2000, - MemoryRequired: 2048, - SidecarCpuRequired: 1000, - SidecarMemoryRequired: 1024, + // make sure consensusClient actually exists + clientUnderTest, ok := consClientMap[consensusClient] + if !ok { + return nil, stacktrace.NewError("unknown consensus client %s", consensusClient) } -} -func createTekuClient() *types.ConsensusClient { - return &types.ConsensusClient{ - Type: "teku", - Image: "consensys/teku:23.12.0", - HasValidatorSidecar: false, - ExtraLabels: make(map[string]string), - CpuRequired: 2000, - MemoryRequired: 2048, + var nodes []*Node + index := 1 + bootnode, err := composeBootnode(execClientMap, consClientMap) + if err != nil { + return nil, err + } + nodes = append(nodes, bootnode) + index += 1 + + n, err := composeNodesForClTesting(index, clientUnderTest, execClientMap) + nodes = append(nodes, n...) + if err != nil { + return nil, err } + + return nodes, nil } -func createLodestarClient() *types.ConsensusClient { - return &types.ConsensusClient{ - Type: "lodestar", - Image: "chainsafe/lodestar:v1.12.1", - HasValidatorSidecar: true, - ExtraLabels: make(map[string]string), - CpuRequired: 2000, - MemoryRequired: 2048, - SidecarCpuRequired: 1000, - SidecarMemoryRequired: 1024, +func composeNodesForClTesting(index int, consensusClient ClientVersion, execClients map[string]ClientVersion) ([]*Node, error) { + var nodes []*Node + + for _, execClient := range execClients { + node := buildNode(index, execClient, consensusClient) + nodes = append(nodes, node) + + index += 1 } + return nodes, nil } -func createNimbusClient() *types.ConsensusClient { - return &types.ConsensusClient{ - Type: "nimbus", - Image: "statusim/nimbus-eth2:amd64-v23.11.0", - HasValidatorSidecar: true, - ExtraLabels: make(map[string]string), - CpuRequired: 2000, - MemoryRequired: 2048, - SidecarCpuRequired: 1000, - SidecarMemoryRequired: 1024, +func composeConsensusClient(config ClientVersion) *ConsensusClient { + image := config.Image + validatorImage := "" + + if strings.Contains(config.Image, ",") { + images := strings.Split(config.Image, ",") + image = images[0] + validatorImage = images[1] + } + if config.HasSidecar { + return &ConsensusClient{ + Type: config.Name, + Image: image, + HasValidatorSidecar: true, + ValidatorImage: validatorImage, + ExtraLabels: make(map[string]string), + CpuRequired: defaultClCpu, + MemoryRequired: defaultClMem, + SidecarCpuRequired: defaultValCpu, + SidecarMemoryRequired: defaultValMem, + } + } else { + return &ConsensusClient{ + Type: config.Name, + Image: image, + HasValidatorSidecar: false, + ValidatorImage: validatorImage, + ExtraLabels: make(map[string]string), + CpuRequired: defaultClCpu, + MemoryRequired: defaultClMem, + SidecarCpuRequired: 0, + SidecarMemoryRequired: 0, + } } } diff --git a/pkg/plan/network/execution.go b/pkg/plan/network/execution.go index 83f5860..b984239 100644 --- a/pkg/plan/network/execution.go +++ b/pkg/plan/network/execution.go @@ -1,53 +1,60 @@ package network -import "attacknet/cmd/pkg/plan/types" +import ( + "github.com/kurtosis-tech/stacktrace" +) -func createGethClient() *types.ExecutionClient { - return &types.ExecutionClient{ - Type: "geth", - Image: "ethereum/client-go:latest", - ExtraLabels: make(map[string]string), - CpuRequired: 1000, - MemoryRequired: 1024, +const defaultElCpu = 1000 +const defaultElMem = 1024 + +func composeExecTesterNetwork(execClient string, execClients, consClients []ClientVersion) ([]*Node, error) { + execClientMap, consClientMap, err := clientListsToMaps(execClients, consClients) + if err != nil { + return nil, err } -} -func createRethClient() *types.ExecutionClient { - return &types.ExecutionClient{ - Type: "reth", - Image: "ghcr.io/paradigmxyz/reth:v0.1.0-alpha.13", - ExtraLabels: make(map[string]string), - CpuRequired: 1000, - MemoryRequired: 1024, + // make sure execClient actually exists + clientUnderTest, ok := execClientMap[execClient] + if !ok { + return nil, stacktrace.NewError("unknown execution client %s", execClient) } -} -func createNethermindClient() *types.ExecutionClient { - return &types.ExecutionClient{ - Type: "nethermind", - Image: "nethermind/nethermind:1.23.0", - ExtraLabels: make(map[string]string), - CpuRequired: 1000, - MemoryRequired: 1024, + var nodes []*Node + index := 1 + bootnode, err := composeBootnode(execClientMap, consClientMap) + if err != nil { + return nil, err } + nodes = append(nodes, bootnode) + index += 1 + + n, err := composeNodesForElTesting(index, clientUnderTest, consClientMap) + nodes = append(nodes, n...) + if err != nil { + return nil, err + } + + return nodes, nil } -func createErigonClient() *types.ExecutionClient { - return &types.ExecutionClient{ - Type: "erigon", - Image: "thorax/erigon:v2.53.4", - ExtraLabels: make(map[string]string), - CpuRequired: 1000, - MemoryRequired: 1024, +func composeNodesForElTesting(index int, execClient ClientVersion, consensusClients map[string]ClientVersion) ([]*Node, error) { + var nodes []*Node + + for _, consensusClient := range consensusClients { + node := buildNode(index, execClient, consensusClient) + nodes = append(nodes, node) + + index += 1 } + return nodes, nil } -func createBesuClient() *types.ExecutionClient { - return &types.ExecutionClient{ - Type: "besu", - Image: "hyperledger/besu:latest", +func composeExecutionClient(config ClientVersion) *ExecutionClient { + return &ExecutionClient{ + Type: config.Name, + Image: config.Image, ExtraLabels: make(map[string]string), - CpuRequired: 1000, - MemoryRequired: 1024, + CpuRequired: defaultElCpu, + MemoryRequired: defaultElMem, } } diff --git a/pkg/plan/network/network_builder.go b/pkg/plan/network/network_builder.go index dabcaeb..5823a74 100644 --- a/pkg/plan/network/network_builder.go +++ b/pkg/plan/network/network_builder.go @@ -1,79 +1,12 @@ package network import ( - "attacknet/cmd/pkg/plan/types" - "fmt" "github.com/kurtosis-tech/stacktrace" - "gopkg.in/yaml.v3" - "strings" ) -func serializeNodes(nodes []*types.Node) []*types.Participant { - participants := make([]*types.Participant, len(nodes)) - for i, node := range nodes { - consensusImage := node.Consensus.Image - - // prysm contingency - if node.Consensus.HasValidatorSidecar && node.Consensus.ValidatorImage != "" { - consensusImage = consensusImage + fmt.Sprintf(",%s", node.Consensus.ValidatorImage) - } - - p := &types.Participant{ - ElClientType: node.Execution.Type, - ElClientImage: node.Execution.Image, - - ClClientType: node.Consensus.Type, - ClClientImage: consensusImage, - - ElMinCpu: node.Execution.CpuRequired, - ElMaxCpu: node.Execution.CpuRequired, - ElMinMemory: node.Execution.MemoryRequired, - ElMaxMemory: node.Execution.MemoryRequired, - - ClMinCpu: node.Consensus.CpuRequired, - ClMaxCpu: node.Consensus.CpuRequired, - ClMinMemory: node.Consensus.MemoryRequired, - ClMaxMemory: node.Consensus.MemoryRequired, - - ValMinCpu: node.Consensus.SidecarCpuRequired, - ValMaxCpu: node.Consensus.SidecarCpuRequired, - ValMinMemory: node.Consensus.SidecarMemoryRequired, - ValMaxMemory: node.Consensus.SidecarMemoryRequired, - Count: 1, - } - participants[i] = p - } - - return participants -} - -func createNodesForElTesting(index int, execClient types.ClientVersion, consensusClients map[string]types.ClientVersion) ([]*types.Node, error) { - var nodes []*types.Node - - for _, consensusClient := range consensusClients { - node := buildNode(index, execClient, consensusClient) - nodes = append(nodes, node) - - index += 1 - } - return nodes, nil -} - -func createBootnode(execClients, consensusClients map[string]types.ClientVersion) (*types.Node, error) { - execConf, ok := execClients["geth"] - if !ok { - return nil, stacktrace.NewError("unable to load configuration for exec client geth") - } - consConf, ok := consensusClients["lighthouse"] - if !ok { - return nil, stacktrace.NewError("unable to load configuration for exec client lighthouse") - } - return buildNode(0, execConf, consConf), nil -} - -func clientListsToMaps(execClients, consClients []types.ClientVersion) (execClientMap, consClientMap map[string]types.ClientVersion, err error) { - populateClientMap := func(li []types.ClientVersion) (map[string]types.ClientVersion, error) { - clients := make(map[string]types.ClientVersion) +func clientListsToMaps(execClients, consClients []ClientVersion) (execClientMap, consClientMap map[string]ClientVersion, err error) { + populateClientMap := func(li []ClientVersion) (map[string]ClientVersion, error) { + clients := make(map[string]ClientVersion) for _, client := range li { _, exists := clients[client.Name] if exists { @@ -97,7 +30,7 @@ func clientListsToMaps(execClients, consClients []types.ClientVersion) (execClie return execClientMap, consClientMap, nil } -func ComposeNetworkTopology(client string, execClients, consClients []types.ClientVersion) ([]*types.Node, error) { +func ComposeNetworkTopology(client string, execClients, consClients []ClientVersion) ([]*Node, error) { if client == "all" { return nil, stacktrace.NewError("target client 'all' not supported yet") } @@ -110,115 +43,9 @@ func ComposeNetworkTopology(client string, execClients, consClients []types.Clie } } // assume already checked client is a member of consClients or execClients - if isExecutionClient { return composeExecTesterNetwork(client, execClients, consClients) } else { - // todo - return nil, nil - } -} - -func composeExecTesterNetwork(execClient string, execClients, consClients []types.ClientVersion) ([]*types.Node, error) { - execClientMap, consClientMap, err := clientListsToMaps(execClients, consClients) - if err != nil { - return nil, err - } - - // make sure execClient actually exists - clientUnderTest, ok := execClientMap[execClient] - if !ok { - return nil, stacktrace.NewError("unknown execution client %s", execClient) - } - - var nodes []*types.Node - index := 0 - bootnode, err := createBootnode(execClientMap, consClientMap) - if err != nil { - return nil, err - } - nodes = append(nodes, bootnode) - index += 1 - - n, err := createNodesForElTesting(index, clientUnderTest, consClientMap) - nodes = append(nodes, n...) - if err != nil { - return nil, err - } - - return nodes, nil -} - -func SerializeNetworkTopology(nodes []*types.Node, config *types.GenesisConfig) ([]byte, error) { - serializableNodes := serializeNodes(nodes) - - netConfig := &types.EthNetConfig{ - Participants: serializableNodes, - NetParams: *config, - AdditionalServices: []string{ - "prometheus_grafana", - "dora", - }, - ParallelKeystoreGen: false, - } - - bs, err := yaml.Marshal(netConfig) - if err != nil { - return nil, stacktrace.Propagate(err, "intermediate yaml marshalling failed") - } - - return bs, nil -} - -func ParseKurtosisNetworkConfig(conf []byte) ([]*types.Node, error) { - parsedConf := types.EthNetConfig{} - err := yaml.Unmarshal(conf, &parsedConf) - if err != nil { - return nil, stacktrace.Propagate(err, "unable to parse eth network types") - } - - var nodes []*types.Node - - for i, participant := range parsedConf.Participants { - hasSidecar := false - consensusImage := participant.ClClientImage - validatorImage := "" - if participant.ValMaxCpu != 0 { - hasSidecar = true - if strings.Contains(consensusImage, ",") { - images := strings.Split(consensusImage, ",") - consensusImage = images[0] - validatorImage = images[1] - } - } - - votesPerNode := parsedConf.NetParams.NumValKeysPerNode - - node := &types.Node{ - Index: i + 1, - ConsensusVotes: votesPerNode, - Consensus: &types.ConsensusClient{ - Type: participant.ClClientType, - Image: consensusImage, - ValidatorImage: validatorImage, - HasValidatorSidecar: hasSidecar, - ExtraLabels: map[string]string{}, - CpuRequired: participant.ClMinCpu, - MemoryRequired: participant.ClMinMemory, - SidecarCpuRequired: participant.ValMinCpu, - SidecarMemoryRequired: participant.ValMinMemory, - }, - Execution: &types.ExecutionClient{ - Type: participant.ElClientType, - Image: participant.ElClientImage, - ExtraLabels: map[string]string{}, - CpuRequired: participant.ElMinCpu, - MemoryRequired: participant.ElMinMemory, - }, - } - - nodes = append(nodes, node) - + return composeConsensusTesterNetwork(client, execClients, consClients) } - return nodes, nil } diff --git a/pkg/plan/types/network.go b/pkg/plan/network/types.go similarity index 50% rename from pkg/plan/types/network.go rename to pkg/plan/network/types.go index 5922a23..2db9ef0 100644 --- a/pkg/plan/types/network.go +++ b/pkg/plan/network/types.go @@ -1,6 +1,7 @@ -package types +package network + +import "fmt" -// todo: how much of these should we move to the types module? type GenesisConfig struct { PreregisteredValidatorKeysMnemonic *string `yaml:"preregistered_validator_keys_mnemonic,omitempty"` PreregisteredValidatorCount *int `yaml:"preregistered_validator_count,omitempty"` @@ -14,44 +15,42 @@ type GenesisConfig struct { CapellaForkEpoch *int `yaml:"capella_fork_epoch,omitempty"` DenebForkEpoch *int `yaml:"deneb_fork_epoch,omitempty"` ElectraForkEpoch *int `yaml:"electra_fork_epoch,omitempty"` - - NumValKeysPerNode int `yaml:"num_validator_keys_per_node"` + NumValKeysPerNode int `yaml:"num_validator_keys_per_node"` } -type EthNetConfig struct { - Participants []*Participant `yaml:"participants"` - NetParams GenesisConfig `yaml:"network_params"` - AdditionalServices []string `yaml:"additional_services"` - ParallelKeystoreGen bool `yaml:"parallel_keystore_generation"` +type ClientVersion struct { + Name string `yaml:"name"` + Image string `yaml:"image"` + HasSidecar bool `yaml:"has_sidecar,omitempty"` } -type Participant struct { - ElClientType string `yaml:"el_client_type"` - ElClientImage string `yaml:"el_client_image"` - - ClClientType string `yaml:"cl_client_type"` - ClClientImage string `yaml:"cl_client_image"` - - ElMinCpu int `yaml:"el_min_cpu"` - ElMaxCpu int `yaml:"el_max_cpu"` - ElMinMemory int `yaml:"el_min_mem"` - ElMaxMemory int `yaml:"el_max_mem"` - - ClMinCpu int `yaml:"bn_min_cpu"` - ClMaxCpu int `yaml:"bn_max_cpu"` - ClMinMemory int `yaml:"bn_min_mem"` - ClMaxMemory int `yaml:"bn_max_mem"` +type ExecutionClient struct { + Type string + Image string + ExtraLabels map[string]string + CpuRequired int + MemoryRequired int +} - ValMinCpu int `yaml:"v_min_cpu,omitempty"` - ValMaxCpu int `yaml:"v_max_cpu,omitempty"` - ValMinMemory int `yaml:"v_min_mem,omitempty"` - ValMaxMemory int `yaml:"v_max_mem,omitempty"` +type ConsensusClient struct { + Type string + Image string + HasValidatorSidecar bool + ValidatorImage string + ExtraLabels map[string]string + CpuRequired int + MemoryRequired int + SidecarCpuRequired int + SidecarMemoryRequired int +} - Count int `yaml:"count"` +type Node struct { + Index int + Execution *ExecutionClient + Consensus *ConsensusClient + ConsensusVotes int } -type ClientVersion struct { - Name string `yaml:"name"` - Image string `yaml:"image"` - HasSidecar bool `yaml:"has_sidecar,omitempty"` +func (n *Node) ToString() string { + return fmt.Sprintf("#%d %s/%s", n.Index, n.Execution.Type, n.Consensus.Type) } diff --git a/pkg/plan/plan.go b/pkg/plan/plan.go index 6fddf7a..3cbb979 100644 --- a/pkg/plan/plan.go +++ b/pkg/plan/plan.go @@ -2,113 +2,70 @@ package plan import ( "attacknet/cmd/pkg/plan/network" - "attacknet/cmd/pkg/plan/types" - "fmt" - "github.com/kurtosis-tech/stacktrace" - "os" - "path/filepath" + "attacknet/cmd/pkg/plan/suite" + types "attacknet/cmd/pkg/types" + "gopkg.in/yaml.v3" ) -func preparePaths(testName string) (netRefPath, netConfigPath, planConfigPath string, err error) { - dir, err := os.Getwd() - // initialize to empty string for error cases - netConfigPath = "" - planConfigPath = "" - if err != nil { - return - } - - netRefPath = fmt.Sprintf("plan/%s.yaml", testName) - networkConfigName := fmt.Sprintf("network-configs/%s", netRefPath) - netConfigPath = filepath.Join(dir, networkConfigName) - if _, err = os.Stat(netConfigPath); err == nil { - // delete file - err = os.Remove(netConfigPath) - if err != nil { - err = stacktrace.Propagate(err, "unable to remove file") - return - } - } - - suiteName := fmt.Sprintf("test-suites/plan/%s.yaml", testName) - planConfigPath = filepath.Join(dir, suiteName) - if _, err = os.Stat(planConfigPath); err == nil { - // delete file - err = os.Remove(planConfigPath) - if err != nil { - err = stacktrace.Propagate(err, "unable to remove file") - return - } - } - err = nil - return -} +func BuildPlan(planName string, config *PlannerConfig) error { -func writePlans(netConfigPath, suiteConfigPath string, netConfig, suiteConfig []byte) error { - f, err := os.Create(netConfigPath) - if err != nil { - return stacktrace.Propagate(err, "cannot open network types path %s", netConfigPath) - } - _, err = f.Write(netConfig) + netRefPath, netConfigPath, suiteConfigPath, err := preparePaths(planName) if err != nil { - return stacktrace.Propagate(err, "could not write network types to file") + return err } - err = f.Close() + nodes, err := network.ComposeNetworkTopology(config.FaultConfig.TargetClient, config.ExecutionClients, config.ConsensusClients) if err != nil { - return stacktrace.Propagate(err, "could not close network types file") + return err } - f, err = os.Create(suiteConfigPath) + isExecTarget := config.IsTargetExecutionClient() + tests, err := suite.ComposeTestSuite(config.FaultConfig, isExecTarget, nodes) if err != nil { - return stacktrace.Propagate(err, "cannot open suite types path %s", suiteConfigPath) - } - _, err = f.Write(suiteConfig) - if err != nil { - return stacktrace.Propagate(err, "could not write suite types to file") + return err } - err = f.Close() - if err != nil { - return stacktrace.Propagate(err, "could not close suite types file") + var attacknetConfig types.AttacknetConfig + if config.KubernetesNamespace == "" { + attacknetConfig = types.AttacknetConfig{ + GrafanaPodName: "grafana", + GrafanaPodPort: "3000", + WaitBeforeInjectionSeconds: 0, + ReuseDevnetBetweenRuns: true, + AllowPostFaultInspection: false, + } + } else { + attacknetConfig = types.AttacknetConfig{ + GrafanaPodName: "grafana", + GrafanaPodPort: "3000", + WaitBeforeInjectionSeconds: 0, + ReuseDevnetBetweenRuns: true, + ExistingDevnetNamespace: config.KubernetesNamespace, + AllowPostFaultInspection: false, + } } - return nil -} - -func BuildPlan(planName string, config *types.PlannerConfig) error { + c := types.Config{ + AttacknetConfig: attacknetConfig, + HarnessConfig: types.HarnessConfig{ + NetworkPackage: config.KurtosisPackage, + NetworkConfigPath: netRefPath, + NetworkType: "ethereum", + }, + TestConfig: types.SuiteTestConfigs{Tests: tests}, + } - netRefPath, netConfigPath, suiteConfigPath, err := preparePaths(planName) + suiteConfig, err := yaml.Marshal(c) if err != nil { return err } - nodes, err := network.ComposeNetworkTopology(config.FaultConfig.TargetClient, config.ExecutionClients, config.ConsensusClients) + networkConfig, err := SerializeNetworkTopology(nodes, &config.GenesisParams) if err != nil { return err } - //suiteConfig, err := suite.ComposeAndSerializeTestSuite(netRefPath, nodes) - //_ = suiteConfig - _ = nodes - _ = netConfigPath - _ = suiteConfigPath - _ = netRefPath - /* - suiteConfig, err := suite.WritePlab(netRefPath, nodes) - if err != nil { - return err - } - - networkConfig, err := network.SerializeNetworkTopology(nodes, genesisConfig) - if err != nil { - return err - } - - return writePlans(netConfigPath, suiteConfigPath, networkConfig, suiteConfig) - */ - - return nil + return writePlans(netConfigPath, suiteConfigPath, networkConfig, suiteConfig) } /* diff --git a/pkg/plan/serialization.go b/pkg/plan/serialization.go new file mode 100644 index 0000000..af718a6 --- /dev/null +++ b/pkg/plan/serialization.go @@ -0,0 +1,122 @@ +package plan + +import ( + "attacknet/cmd/pkg/plan/network" + "fmt" + "github.com/kurtosis-tech/stacktrace" + "gopkg.in/yaml.v3" + "strings" +) + +func SerializeNetworkTopology(nodes []*network.Node, config *network.GenesisConfig) ([]byte, error) { + serializableNodes := serializeNodes(nodes) + + netConfig := &EthKurtosisConfig{ + Participants: serializableNodes, + NetParams: *config, + AdditionalServices: []string{ + "prometheus_grafana", + "dora", + }, + ParallelKeystoreGen: false, + } + + bs, err := yaml.Marshal(netConfig) + if err != nil { + return nil, stacktrace.Propagate(err, "intermediate yaml marshalling failed") + } + + return bs, nil +} + +func DeserializeNetworkTopology(conf []byte) ([]*network.Node, error) { + parsedConf := EthKurtosisConfig{} + err := yaml.Unmarshal(conf, &parsedConf) + if err != nil { + return nil, stacktrace.Propagate(err, "unable to parse eth network types") + } + + var nodes []*network.Node + + for i, participant := range parsedConf.Participants { + hasSidecar := false + consensusImage := participant.ClClientImage + validatorImage := "" + if participant.ValMaxCpu != 0 { + hasSidecar = true + if strings.Contains(consensusImage, ",") { + images := strings.Split(consensusImage, ",") + consensusImage = images[0] + validatorImage = images[1] + } + } + + votesPerNode := parsedConf.NetParams.NumValKeysPerNode + + node := &network.Node{ + Index: i + 1, + ConsensusVotes: votesPerNode, + Consensus: &network.ConsensusClient{ + Type: participant.ClClientType, + Image: consensusImage, + ValidatorImage: validatorImage, + HasValidatorSidecar: hasSidecar, + ExtraLabels: map[string]string{}, + CpuRequired: participant.ClMinCpu, + MemoryRequired: participant.ClMinMemory, + SidecarCpuRequired: participant.ValMinCpu, + SidecarMemoryRequired: participant.ValMinMemory, + }, + Execution: &network.ExecutionClient{ + Type: participant.ElClientType, + Image: participant.ElClientImage, + ExtraLabels: map[string]string{}, + CpuRequired: participant.ElMinCpu, + MemoryRequired: participant.ElMinMemory, + }, + } + + nodes = append(nodes, node) + + } + return nodes, nil +} + +func serializeNodes(nodes []*network.Node) []*Participant { + participants := make([]*Participant, len(nodes)) + for i, node := range nodes { + consensusImage := node.Consensus.Image + + // prysm contingency + if node.Consensus.HasValidatorSidecar && node.Consensus.ValidatorImage != "" { + consensusImage = consensusImage + fmt.Sprintf(",%s", node.Consensus.ValidatorImage) + } + + p := &Participant{ + ElClientType: node.Execution.Type, + ElClientImage: node.Execution.Image, + + ClClientType: node.Consensus.Type, + ClClientImage: consensusImage, + + ElMinCpu: node.Execution.CpuRequired, + ElMaxCpu: node.Execution.CpuRequired, + ElMinMemory: node.Execution.MemoryRequired, + ElMaxMemory: node.Execution.MemoryRequired, + + ClMinCpu: node.Consensus.CpuRequired, + ClMaxCpu: node.Consensus.CpuRequired, + ClMinMemory: node.Consensus.MemoryRequired, + ClMaxMemory: node.Consensus.MemoryRequired, + + ValMinCpu: node.Consensus.SidecarCpuRequired, + ValMaxCpu: node.Consensus.SidecarCpuRequired, + ValMinMemory: node.Consensus.SidecarMemoryRequired, + ValMaxMemory: node.Consensus.SidecarMemoryRequired, + Count: 1, + } + participants[i] = p + } + + return participants +} diff --git a/pkg/plan/suite/faults.go b/pkg/plan/suite/faults.go index b0e64d6..e7bac06 100644 --- a/pkg/plan/suite/faults.go +++ b/pkg/plan/suite/faults.go @@ -10,15 +10,15 @@ import ( // and deserialize to the same struct. Instead, we create pared down copies of the structs with no inlining. // Completely scuffed. -type ExpressionSelector struct { +type ChaosExpressionSelector struct { Key string `yaml:"key"` Operator string `yaml:"operator"` Values []string `yaml:"values"` } type Selector struct { - LabelSelectors map[string]string `yaml:"labelSelectors,omitempty"` - ExpressionSelectors []ExpressionSelector `yaml:"expressionSelectors,omitempty"` + LabelSelectors map[string]string `yaml:"labelSelectors,omitempty"` + ExpressionSelectors []ChaosExpressionSelector `yaml:"expressionSelectors,omitempty"` } type TimeChaosSpec struct { @@ -70,7 +70,7 @@ func convertFaultSpecToMap(s interface{}) (map[string]interface{}, error) { return faultSpec, nil } -func buildClockSkewFault(description, timeOffset, duration string, expressionSelectors []ExpressionSelector) (*types.PlanStep, error) { +func buildClockSkewFault(description, timeOffset, duration string, expressionSelectors []ChaosExpressionSelector) (*types.PlanStep, error) { t := TimeChaosWrapper{ TimeChaosFault: TimeChaosFault{ @@ -101,7 +101,7 @@ func buildClockSkewFault(description, timeOffset, duration string, expressionSel return step, nil } -func buildPodRestartFault(description string, expressionSelectors []ExpressionSelector) (*types.PlanStep, error) { +func buildPodRestartFault(description string, expressionSelectors []ChaosExpressionSelector) (*types.PlanStep, error) { t := PodChaosWrapper{ PodChaosFault: PodChaosFault{ Kind: "PodChaos", diff --git a/pkg/plan/suite/step_builder.go b/pkg/plan/suite/step_builder.go index 8835c4b..15d7344 100644 --- a/pkg/plan/suite/step_builder.go +++ b/pkg/plan/suite/step_builder.go @@ -1,7 +1,7 @@ package suite import ( - planTypes "attacknet/cmd/pkg/plan/types" + "attacknet/cmd/pkg/plan/network" "attacknet/cmd/pkg/types" "fmt" log "github.com/sirupsen/logrus" @@ -15,7 +15,7 @@ const ( Validator clientType = "validator" ) -func convertToNodeIdTag(node *planTypes.Node, client clientType) string { +func convertToNodeIdTag(node *network.Node, client clientType) string { switch client { case Execution: return fmt.Sprintf("el-%d-%s-%s", node.Index, node.Execution.Type, node.Consensus.Type) @@ -29,11 +29,11 @@ func convertToNodeIdTag(node *planTypes.Node, client clientType) string { } } -func buildWaitForFaultCompletionStep() *types.PlanStep { +func composeWaitForFaultCompletionStep() *types.PlanStep { return &types.PlanStep{StepType: types.WaitForFaultCompletion, StepDescription: "wait for faults to terminate"} } -func buildNodeClockSkewPlanSteps(nodesSelected []*TargetSelector, skew, duration string) ([]types.PlanStep, error) { +func composeNodeClockSkewPlanSteps(nodesSelected []*ChaosTargetSelector, skew, duration string) ([]types.PlanStep, error) { var steps []types.PlanStep for _, target := range nodesSelected { description := fmt.Sprintf("Inject clock skew on target %s", target.Description) @@ -48,7 +48,7 @@ func buildNodeClockSkewPlanSteps(nodesSelected []*TargetSelector, skew, duration return steps, nil } -func buildNodeRestartSteps(nodesSelected []*TargetSelector) ([]types.PlanStep, error) { +func composeNodeRestartSteps(nodesSelected []*ChaosTargetSelector) ([]types.PlanStep, error) { var steps []types.PlanStep for _, target := range nodesSelected { diff --git a/pkg/plan/suite/suite_builder.go b/pkg/plan/suite/suite_builder.go index 5b5a9b3..db33cb2 100644 --- a/pkg/plan/suite/suite_builder.go +++ b/pkg/plan/suite/suite_builder.go @@ -1,71 +1,93 @@ package suite import ( - planTypes "attacknet/cmd/pkg/plan/types" + "attacknet/cmd/pkg/plan/network" "attacknet/cmd/pkg/types" - "gopkg.in/yaml.v3" + "fmt" + "github.com/kurtosis-tech/stacktrace" + log "github.com/sirupsen/logrus" + "time" ) -func ComposeAndSerializeTestSuite( - faultConfig planTypes.PlannerFaultConfiguration, - networkConfigPath string, - nodes []*planTypes.Node) ([]byte, error) { +func ComposeTestSuite( + config PlannerFaultConfiguration, + isExecClient bool, + nodes []*network.Node) ([]types.SuiteTest, error) { var tests []types.SuiteTest + runtimeEstimate := 0 - //slice by targeting (client, node) - // -> creates targeting lambdas - //slice by attack size - // -> takes nodes[], returns target selectors. count reduced as aliasing overlaps will cause dupes + nodeFilter := buildNodeFilteringLambda(config.TargetClient, isExecClient) - // slice by intensity - // -> creates []intensities + for _, targetDimension := range config.TargetingDimensions { + targetFilter, err := targetSpecEnumToLambda(targetDimension, isExecClient) + if err != nil { + return nil, err + } + nodeCountsTested := make(map[int]bool) + for _, attackSize := range config.AttackSizeDimensions { + targetSelectors, err := buildChaosMeshTargetSelectors(nodes, attackSize, nodeFilter, targetFilter) + if err != nil { + cannotMeet, ok := err.(CannotMeetConstraintError) + if !ok { + return nil, err + } + log.Infof("Attack size %s for %d nodes cannot be satisfied. Use more clients if this attack size needs to be tested.", cannotMeet.AttackSize, cannotMeet.TargetableCount) + continue + } + // deduplicate attack sizes that produce the same scope + _, alreadyTested := nodeCountsTested[len(targetSelectors)] + if alreadyTested { + continue + } else { + nodeCountsTested[len(targetSelectors)] = true + } - _ = tests - return nil, nil -} + for _, faultConfig := range config.FaultConfigDimensions { + // update runtime estimate. find better way + duration, ok := faultConfig["duration"] + if ok { + d, err := time.ParseDuration(duration) + if err == nil { + runtimeEstimate += int(d.Seconds()) + } + } -func WritePlab(networkConfigPath string, nodes []*planTypes.Node) ([]byte, error) { - skew := "-5m" - duration := "1m" - criteriaLambda := createDualClientTargetCriteria("reth", "teku") - targetSelectors, err := BuildTargetSelectors(nodes, planTypes.AttackAll, criteriaLambda, impactNode) - if err != nil { - return nil, err + test, err := composeTestsForFaultType(config.FaultType, faultConfig, targetSelectors) + if err != nil { + return nil, err + } + tests = append(tests, *test) + } + } } - test, err := buildNodeClockSkewTest("clock skew", targetSelectors, skew, duration) - if err != nil { - return nil, err - } - var a []types.SuiteTest - tests := append(a, *test) - _ = tests - c := types.Config{ - AttacknetConfig: types.AttacknetConfig{ - GrafanaPodName: "grafana", - GrafanaPodPort: "3000", - WaitBeforeInjectionSeconds: 0, - ReuseDevnetBetweenRuns: true, - ExistingDevnetNamespace: "kt-ethereum", - AllowPostFaultInspection: false, - }, - HarnessConfig: types.HarnessConfig{ - NetworkPackage: "github.com/kurtosis-tech/ethereum-package", - NetworkConfigPath: networkConfigPath, - NetworkType: "ethereum", - }, - TestConfig: types.SuiteTestConfigs{Tests: tests}, - } + log.Infof("ESTIMATE: Running this test suite will take, at minimum, %d minutes", runtimeEstimate/60) - if err != nil { - return nil, err - } + return tests, nil +} - b, err := yaml.Marshal(c) - if err != nil { - return nil, err +func composeTestsForFaultType( + faultType FaultTypeEnum, + config map[string]string, + targetSelectors []*ChaosTargetSelector) (*types.SuiteTest, error) { + + switch faultType { + case FaultClockSkew: + skew, ok := config["skew"] + if !ok { + return nil, stacktrace.NewError("missing skew field for clock skew fault") + } + duration, ok := config["duration"] + if !ok { + return nil, stacktrace.NewError("missing duration field for clock skew fault") + } + description := fmt.Sprintf("Apply %s clock skew for %s against %d targets", skew, duration, len(targetSelectors)) + return composeNodeClockSkewTest(description, targetSelectors, skew, duration) + case FaultContainerRestart: + description := fmt.Sprintf("Restarting %d targets", len(targetSelectors)) + return composeNodeRestartTest(description, targetSelectors) } - return b, nil + return nil, nil } diff --git a/pkg/plan/suite/targeting.go b/pkg/plan/suite/targeting.go index 6f1f3ce..bff5a8b 100644 --- a/pkg/plan/suite/targeting.go +++ b/pkg/plan/suite/targeting.go @@ -1,17 +1,18 @@ package suite import ( - "attacknet/cmd/pkg/plan/types" + "attacknet/cmd/pkg/plan/network" "fmt" + "github.com/kurtosis-tech/stacktrace" ) -type TargetSelector struct { - Selector []ExpressionSelector +type ChaosTargetSelector struct { + Selector []ChaosExpressionSelector Description string } type CannotMeetConstraintError struct { - types.AttackSize + AttackSize TargetableCount int } @@ -19,14 +20,20 @@ func (e CannotMeetConstraintError) Error() string { return fmt.Sprintf("Cannot target '%s' for %d nodes", e.AttackSize, e.TargetableCount) } -type NodeFilterCriteria func(n *types.Node) bool +type NodeFilterCriteria func(n *network.Node) bool +type TargetCriteriaFilter func(AttackSize, []*network.Node) ([]*network.Node, error) +type NodeImpactSelector func(node *network.Node) *ChaosTargetSelector -type TargetCriteriaFilter func(types.AttackSize, []*types.Node) ([]*types.Node, error) - -type NodeImpactSelector func(node *types.Node) *TargetSelector +func buildNodeFilteringLambda(clientType string, isExecClient bool) TargetCriteriaFilter { + if isExecClient { + return filterNodesByExecClient(clientType) + } else { + return filterNodesByConsensusClient(clientType) + } +} -func filterNodes(nodes []*types.Node, criteria NodeFilterCriteria) []*types.Node { - var result []*types.Node +func filterNodes(nodes []*network.Node, criteria NodeFilterCriteria) []*network.Node { + var result []*network.Node for _, n := range nodes { if criteria(n) { result = append(result, n) @@ -35,17 +42,17 @@ func filterNodes(nodes []*types.Node, criteria NodeFilterCriteria) []*types.Node return result } -func chooseTargetsUsingAttackSize(size types.AttackSize, targetable []*types.Node) ([]*types.Node, error) { +func chooseTargetsUsingAttackSize(size AttackSize, targetable []*network.Node) ([]*network.Node, error) { totalTargetable := float32(len(targetable)) var nodesToTarget int switch size { - case types.AttackOne: + case AttackOne: nodesToTarget = 1 - case types.AttackAll: + case AttackAll: nodesToTarget = len(targetable) - case types.AttackMinority: + case AttackMinority: nodesToTarget = int(totalTargetable * 0.32) - case types.AttackSuperminority: + case AttackSuperminority: nodesToTarget = int(totalTargetable * 0.34) if float32(nodesToTarget)/totalTargetable < 0.333333333 { nodesToTarget += 1 @@ -57,7 +64,7 @@ func chooseTargetsUsingAttackSize(size types.AttackSize, targetable []*types.Nod TargetableCount: len(targetable), } } - case types.AttackMajority: + case AttackMajority: nodesToTarget = int(totalTargetable * 0.51) if float32(nodesToTarget)/totalTargetable <= 0.50 { nodesToTarget += 1 @@ -69,7 +76,7 @@ func chooseTargetsUsingAttackSize(size types.AttackSize, targetable []*types.Nod TargetableCount: len(targetable), } } - case types.AttackSupermajority: + case AttackSupermajority: nodesToTarget = int(totalTargetable * 0.67) if float32(nodesToTarget)/totalTargetable <= 0.666666666 { nodesToTarget += 1 @@ -90,14 +97,14 @@ func chooseTargetsUsingAttackSize(size types.AttackSize, targetable []*types.Nod } } - var targets []*types.Node + var targets []*network.Node for i := 0; i < nodesToTarget; i++ { targets = append(targets, targetable[i]) } return targets, nil } -func impactNode(node *types.Node) *TargetSelector { +func createTargetSelectorForNode(node *network.Node) *ChaosTargetSelector { var targets []string elId := convertToNodeIdTag(node, Execution) @@ -111,35 +118,35 @@ func impactNode(node *types.Node) *TargetSelector { targets = append(targets, valId) } - selector := ExpressionSelector{ + selector := ChaosExpressionSelector{ Key: "kurtosistech.com/id", Operator: "In", Values: targets, } description := fmt.Sprintf("%s/%s Node (Node #%d)", node.Execution.Type, node.Consensus.Type, node.Index) - return &TargetSelector{ - Selector: []ExpressionSelector{selector}, + return &ChaosTargetSelector{ + Selector: []ChaosExpressionSelector{selector}, Description: description, } } -func impactExecClient(node *types.Node) *TargetSelector { +func createTargetSelectorForExecClient(node *network.Node) *ChaosTargetSelector { elId := convertToNodeIdTag(node, Execution) - selector := ExpressionSelector{ + selector := ChaosExpressionSelector{ Key: "kurtosistech.com/id", Operator: "In", Values: []string{elId}, } description := fmt.Sprintf("%s client of %s/%s Node (Node #%d)", node.Execution.Type, node.Execution.Type, node.Consensus.Type, node.Index) - return &TargetSelector{ - Selector: []ExpressionSelector{selector}, + return &ChaosTargetSelector{ + Selector: []ChaosExpressionSelector{selector}, Description: description, } } -func impactConsensusClient(node *types.Node) *TargetSelector { +func createTargetSelectorForConsensusClient(node *network.Node) *ChaosTargetSelector { var targets []string clId := convertToNodeIdTag(node, Consensus) targets = append(targets, clId) @@ -149,22 +156,36 @@ func impactConsensusClient(node *types.Node) *TargetSelector { targets = append(targets, valId) } - selector := ExpressionSelector{ + selector := ChaosExpressionSelector{ Key: "kurtosistech.com/id", Operator: "In", Values: targets, } description := fmt.Sprintf("%s client of %s/%s Node (Node #%d)", node.Consensus.Type, node.Execution.Type, node.Consensus.Type, node.Index) - return &TargetSelector{ - Selector: []ExpressionSelector{selector}, + return &ChaosTargetSelector{ + Selector: []ChaosExpressionSelector{selector}, Description: description, } } -func createExecClientTargetCriteria(elClientType string) TargetCriteriaFilter { - return func(size types.AttackSize, nodes []*types.Node) ([]*types.Node, error) { - criteria := func(n *types.Node) bool { +func targetSpecEnumToLambda(targetSelector TargetingSpec, isExecClient bool) (func(node *network.Node) *ChaosTargetSelector, error) { + if targetSelector == TargetMatchingNode { + return createTargetSelectorForNode, nil + } + if targetSelector == TargetMatchingClient { + if isExecClient { + return createTargetSelectorForExecClient, nil + } else { + return createTargetSelectorForConsensusClient, nil + } + } + return nil, stacktrace.NewError("target selector %s not supported", targetSelector) +} + +func filterNodesByExecClient(elClientType string) TargetCriteriaFilter { + return func(size AttackSize, nodes []*network.Node) ([]*network.Node, error) { + criteria := func(n *network.Node) bool { return n.Execution.Type == elClientType } targetableNodes := filterNodes(nodes, criteria) @@ -173,9 +194,9 @@ func createExecClientTargetCriteria(elClientType string) TargetCriteriaFilter { } } -func createConsensusClientTargetCriteria(clClientType string) TargetCriteriaFilter { - return func(size types.AttackSize, nodes []*types.Node) ([]*types.Node, error) { - criteria := func(n *types.Node) bool { +func filterNodesByConsensusClient(clClientType string) TargetCriteriaFilter { + return func(size AttackSize, nodes []*network.Node) ([]*network.Node, error) { + criteria := func(n *network.Node) bool { return n.Consensus.Type == clClientType } targetableNodes := filterNodes(nodes, criteria) @@ -184,9 +205,9 @@ func createConsensusClientTargetCriteria(clClientType string) TargetCriteriaFilt } } -func createDualClientTargetCriteria(elClientType, clClientType string) TargetCriteriaFilter { - return func(size types.AttackSize, nodes []*types.Node) ([]*types.Node, error) { - criteria := func(n *types.Node) bool { +func filterNodesByClientCombo(elClientType, clClientType string) TargetCriteriaFilter { + return func(size AttackSize, nodes []*network.Node) ([]*network.Node, error) { + criteria := func(n *network.Node) bool { return n.Consensus.Type == clClientType && n.Execution.Type == elClientType } targetableNodes := filterNodes(nodes, criteria) @@ -195,13 +216,13 @@ func createDualClientTargetCriteria(elClientType, clClientType string) TargetCri } } -func BuildTargetSelectors(nodes []*types.Node, size types.AttackSize, targetCriteria TargetCriteriaFilter, impactSelector NodeImpactSelector) ([]*TargetSelector, error) { +func buildChaosMeshTargetSelectors(nodes []*network.Node, size AttackSize, targetCriteria TargetCriteriaFilter, impactSelector NodeImpactSelector) ([]*ChaosTargetSelector, error) { targets, err := targetCriteria(size, nodes) if err != nil { return nil, err } - var targetSelectors []*TargetSelector + var targetSelectors []*ChaosTargetSelector for _, node := range targets { targetSelectors = append(targetSelectors, impactSelector(node)) } diff --git a/pkg/plan/suite/test_builder.go b/pkg/plan/suite/test_builder.go index 86ac56e..a1fab8d 100644 --- a/pkg/plan/suite/test_builder.go +++ b/pkg/plan/suite/test_builder.go @@ -5,15 +5,18 @@ import ( "time" ) -func buildNodeClockSkewTest(description string, targets []*TargetSelector, skew, duration string) (*types.SuiteTest, error) { +const clockSkewGracePeriod = time.Second * 1800 +const containerRestartGracePeriod = time.Second * 3600 + +func composeNodeClockSkewTest(description string, targets []*ChaosTargetSelector, skew, duration string) (*types.SuiteTest, error) { var steps []types.PlanStep - s, err := buildNodeClockSkewPlanSteps(targets, skew, duration) + s, err := composeNodeClockSkewPlanSteps(targets, skew, duration) if err != nil { return nil, err } steps = append(steps, s...) - waitStep := buildWaitForFaultCompletionStep() + waitStep := composeWaitForFaultCompletionStep() steps = append(steps, *waitStep) test := &types.SuiteTest{ @@ -21,23 +24,23 @@ func buildNodeClockSkewTest(description string, targets []*TargetSelector, skew, PlanSteps: steps, HealthConfig: types.HealthCheckConfig{ EnableChecks: true, - GracePeriod: time.Second * 120, + GracePeriod: clockSkewGracePeriod, }, } return test, nil } -func buildNodeRestartTest(description string, targets []*TargetSelector) (*types.SuiteTest, error) { +func composeNodeRestartTest(description string, targets []*ChaosTargetSelector) (*types.SuiteTest, error) { var steps []types.PlanStep - s, err := buildNodeRestartSteps(targets) + s, err := composeNodeRestartSteps(targets) if err != nil { return nil, err } steps = append(steps, s...) - waitStep := buildWaitForFaultCompletionStep() + waitStep := composeWaitForFaultCompletionStep() steps = append(steps, *waitStep) test := &types.SuiteTest{ @@ -45,11 +48,11 @@ func buildNodeRestartTest(description string, targets []*TargetSelector) (*types PlanSteps: steps, HealthConfig: types.HealthCheckConfig{ EnableChecks: true, - GracePeriod: time.Second * 240, + GracePeriod: containerRestartGracePeriod, }, } return test, nil } -//func buildCpuPressureTest(description string, targets []*TargetSelector, pressure int) (*types.SuiteTest, error) { +//func buildCpuPressureTest(description string, targets []*ChaosTargetSelector, pressure int) (*types.SuiteTest, error) { diff --git a/pkg/plan/types/targeting.go b/pkg/plan/suite/types.go similarity index 63% rename from pkg/plan/types/targeting.go rename to pkg/plan/suite/types.go index c73fde5..837b143 100644 --- a/pkg/plan/types/targeting.go +++ b/pkg/plan/suite/types.go @@ -1,4 +1,4 @@ -package types +package suite type TargetingSpec string @@ -33,3 +33,23 @@ var AttackSizes = map[AttackSize]bool{ AttackMajority: true, AttackSupermajority: true, } + +type FaultTypeEnum string + +const ( + FaultClockSkew FaultTypeEnum = "ClockSkew" + FaultContainerRestart FaultTypeEnum = "RestartContainers" +) + +var FaultTypes = map[FaultTypeEnum]bool{ + FaultClockSkew: true, + FaultContainerRestart: true, +} + +type PlannerFaultConfiguration struct { + FaultType FaultTypeEnum `yaml:"fault_type"` + TargetClient string `yaml:"target_client"` + FaultConfigDimensions []map[string]string `yaml:"fault_config_dimensions"` + TargetingDimensions []TargetingSpec `yaml:"fault_targeting_dimensions"` + AttackSizeDimensions []AttackSize `yaml:"fault_attack_size_dimensions"` +} diff --git a/pkg/plan/types.go b/pkg/plan/types.go new file mode 100644 index 0000000..39ba4b8 --- /dev/null +++ b/pkg/plan/types.go @@ -0,0 +1,65 @@ +package plan + +import ( + "attacknet/cmd/pkg/plan/network" + "attacknet/cmd/pkg/plan/suite" +) + +type PlannerConfig struct { + ExecutionClients []network.ClientVersion `yaml:"execution"` + ConsensusClients []network.ClientVersion `yaml:"consensus"` + GenesisParams network.GenesisConfig `yaml:"network_params"` + KurtosisPackage string `yaml:"kurtosis_package"` + KubernetesNamespace string `yaml:"kubernetes_namespace"` + FaultConfig suite.PlannerFaultConfiguration `yaml:"fault_config"` +} + +func (c *PlannerConfig) IsTargetExecutionClient() bool { + for _, execClient := range c.ExecutionClients { + if execClient.Name == c.FaultConfig.TargetClient { + return true + } + } + return false +} + +func (c *PlannerConfig) IsTargetConsensusClient() bool { + for _, consClient := range c.ConsensusClients { + if consClient.Name == c.FaultConfig.TargetClient { + return true + } + } + return false +} + +type EthKurtosisConfig struct { + Participants []*Participant `yaml:"participants"` + NetParams network.GenesisConfig `yaml:"network_params"` + AdditionalServices []string `yaml:"additional_services"` + ParallelKeystoreGen bool `yaml:"parallel_keystore_generation"` +} + +type Participant struct { + ElClientType string `yaml:"el_client_type"` + ElClientImage string `yaml:"el_client_image"` + + ClClientType string `yaml:"cl_client_type"` + ClClientImage string `yaml:"cl_client_image"` + + ElMinCpu int `yaml:"el_min_cpu"` + ElMaxCpu int `yaml:"el_max_cpu"` + ElMinMemory int `yaml:"el_min_mem"` + ElMaxMemory int `yaml:"el_max_mem"` + + ClMinCpu int `yaml:"bn_min_cpu"` + ClMaxCpu int `yaml:"bn_max_cpu"` + ClMinMemory int `yaml:"bn_min_mem"` + ClMaxMemory int `yaml:"bn_max_mem"` + + ValMinCpu int `yaml:"v_min_cpu,omitempty"` + ValMaxCpu int `yaml:"v_max_cpu,omitempty"` + ValMinMemory int `yaml:"v_min_mem,omitempty"` + ValMaxMemory int `yaml:"v_max_mem,omitempty"` + + Count int `yaml:"count"` +} diff --git a/pkg/plan/types/config.go b/pkg/plan/types/config.go deleted file mode 100644 index 48aea78..0000000 --- a/pkg/plan/types/config.go +++ /dev/null @@ -1,83 +0,0 @@ -package types - -import ( - "fmt" -) - -type PlannerConfig struct { - ExecutionClients []ClientVersion `yaml:"execution"` - ConsensusClients []ClientVersion `yaml:"consensus"` - NetworkParams GenesisConfig `yaml:"network_params"` - FaultConfig PlannerFaultConfiguration `yaml:"fault_config"` -} - -type ExecutionClient struct { - Type string - Image string - ExtraLabels map[string]string - CpuRequired int - MemoryRequired int -} - -type ConsensusClient struct { - Type string - Image string - HasValidatorSidecar bool - ValidatorImage string - ExtraLabels map[string]string - CpuRequired int - MemoryRequired int - SidecarCpuRequired int - SidecarMemoryRequired int -} - -type Node struct { - Index int - Execution *ExecutionClient - Consensus *ConsensusClient - ConsensusVotes int -} - -func (n *Node) ToString() string { - return fmt.Sprintf("#%d %s/%s", n.Index, n.Execution.Type, n.Consensus.Type) -} - -type FaultTypeEnum string - -const ( - FaultClockSkew FaultTypeEnum = "ClockSkew" - FaultContainerRestart FaultTypeEnum = "RestartContainers" -) - -var FaultTypes = map[FaultTypeEnum]bool{ - FaultClockSkew: true, - FaultContainerRestart: true, -} - -var _ = FaultTypes - -type PlannerFaultConfiguration struct { - FaultType FaultTypeEnum `yaml:"fault_type"` - TargetClient string `yaml:"target_client"` - IntensityDimensions []map[string]interface{} `yaml:"fault_intensity_dimensions"` - TargetingDimensions []TargetingSpec `yaml:"fault_targeting_dimensions"` - AttackSizeDimensions []AttackSize `yaml:"fault_attack_size_dimensions"` -} - -func (c *PlannerConfig) IsTargetExecutionClient() bool { - for _, execClient := range c.ExecutionClients { - if execClient.Name == c.FaultConfig.TargetClient { - return true - } - } - return false -} - -func (c *PlannerConfig) IsTargetConsensusClient() bool { - for _, consClient := range c.ConsensusClients { - if consClient.Name == c.FaultConfig.TargetClient { - return true - } - } - return false -} diff --git a/planner-configs/latest-clients.yaml b/planner-configs/clock-skew-reth.yaml similarity index 62% rename from planner-configs/latest-clients.yaml rename to planner-configs/clock-skew-reth.yaml index 4c34562..27eeb3e 100644 --- a/planner-configs/latest-clients.yaml +++ b/planner-configs/clock-skew-reth.yaml @@ -27,3 +27,21 @@ consensus: # image: statusim/nimbus-eth2:amd64-v23.11.0 network_params: num_validator_keys_per_node: 32 +kurtosis_package: "github.com/kurtosis-tech/ethereum-package" +kubernetes_namespace: kt-ethereum +fault_config: + fault_type: ClockSkew + target_client: reth + fault_config_dimensions: + - skew: -2m + duration: 10m + fault_targeting_dimensions: + - MatchingNode + - MatchingClient + fault_attack_size_dimensions: + - AttackOneMatching + - AttackMinorityMatching + - AttackSuperminorityMatching + - AttackMajorityMatching + - AttackSupermajorityMatching + - AttackAllMatching \ No newline at end of file diff --git a/test-suites/plan/testing.yaml b/test-suites/plan/testing.yaml new file mode 100644 index 0000000..f007377 --- /dev/null +++ b/test-suites/plan/testing.yaml @@ -0,0 +1,324 @@ +attacknetConfig: + grafanaPodName: grafana + grafanaPodPort: "3000" + allowPostFaultInspection: false + waitBeforeInjectionSeconds: 0 + reuseDevnetBetweenRuns: true + existingDevnetNamespace: kt-ethereum +harnessConfig: + networkType: ethereum + networkPackage: github.com/kurtosis-tech/ethereum-package + networkConfig: plan/testing.yaml +testConfig: + tests: + - testName: Apply -2m clock skew for 10m against 1 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + - cl-2-teku-reth + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s + - testName: Apply -2m clock skew for 10m against 3 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + - cl-2-teku-reth + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth/lodestar Node (Node #3)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-3-reth-lodestar + - cl-3-lodestar-reth + - cl-3-lodestar-reth-validator + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth/lighthouse Node (Node #4)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-4-reth-lighthouse + - cl-4-lighthouse-reth + - cl-4-lighthouse-reth-validator + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s + - testName: Apply -2m clock skew for 10m against 4 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + - cl-2-teku-reth + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth/lodestar Node (Node #3)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-3-reth-lodestar + - cl-3-lodestar-reth + - cl-3-lodestar-reth-validator + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth/lighthouse Node (Node #4)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-4-reth-lighthouse + - cl-4-lighthouse-reth + - cl-4-lighthouse-reth-validator + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth/prysm Node (Node #5)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-5-reth-prysm + - cl-5-prysm-reth + - cl-5-prysm-reth-validator + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s + - testName: Apply -2m clock skew for 10m against 1 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s + - testName: Apply -2m clock skew for 10m against 3 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/lodestar Node (Node #3)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-3-reth-lodestar + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/lighthouse Node (Node #4)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-4-reth-lighthouse + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s + - testName: Apply -2m clock skew for 10m against 4 targets + planSteps: + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/teku Node (Node #2)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-2-reth-teku + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/lodestar Node (Node #3)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-3-reth-lodestar + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/lighthouse Node (Node #4)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-4-reth-lighthouse + timeOffset: -2m + - stepType: injectFault + description: 'Inject clock skew on target reth client of reth/prysm Node (Node #5)' + chaosFaultSpec: + apiVersion: chaos-mesh.org/v1alpha1 + kind: TimeChaos + spec: + action: delay + duration: 10m + mode: all + selector: + expressionSelectors: + - key: kurtosistech.com/id + operator: In + values: + - el-5-reth-prysm + timeOffset: -2m + - stepType: waitForFaultCompletion + description: wait for faults to terminate + health: + enableChecks: true + gracePeriod: 2m0s