diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index a16e8f16..5469ec83 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -43,6 +43,10 @@ jobs: echo "repository: $commit_repo" echo "commit: $commit_ref" + if [[ "$has_docker_image_label" == "true" ]]; then + run_builds="true" + fi + manual_tag="${{ inputs.docker_tag }}" if [[ ! -z "$manual_tag" ]]; then has_docker_image_label="true" diff --git a/go.mod b/go.mod index 4644948b..6edea3ec 100644 --- a/go.mod +++ b/go.mod @@ -101,3 +101,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) + +replace github.com/attestantio/go-eth2-client => github.com/pk910/go-eth2-client v0.0.0-20231217052657-39326b2b91a7 diff --git a/go.sum b/go.sum index 8dee0927..f3d4a42d 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,10 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pk910/go-eth2-client v0.0.0-20231217052657-39326b2b91a7 h1:JG2FFabNp3W1bmUbJeiAV4WPFuaNnVO4y4G9WXLsXPo= +github.com/pk910/go-eth2-client v0.0.0-20231217052657-39326b2b91a7/go.mod h1:YKTloAuspHPDCFg8+jV14H1+UFoaDLqabb/C2hHg8Q4= +github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/pkg/coordinator/clients/consensus/rpc/beaconapi.go b/pkg/coordinator/clients/consensus/rpc/beaconapi.go index 4316f404..2ab82126 100644 --- a/pkg/coordinator/clients/consensus/rpc/beaconapi.go +++ b/pkg/coordinator/clients/consensus/rpc/beaconapi.go @@ -52,6 +52,8 @@ func (bc *BeaconClient) Initialize(ctx context.Context) error { http.WithLogLevel(zerolog.Disabled), // TODO (when upstream PR is merged) // http.WithConnectionCheck(false), + // TODO: remove when go-eth2-client has proper verkle support + http.WithEnforceJSON(true), } // set extra endpoint headers diff --git a/pkg/coordinator/clients/execution/rpc/executionapi.go b/pkg/coordinator/clients/execution/rpc/executionapi.go index fcfef4a8..0afafd4d 100644 --- a/pkg/coordinator/clients/execution/rpc/executionapi.go +++ b/pkg/coordinator/clients/execution/rpc/executionapi.go @@ -126,3 +126,10 @@ func (ec *ExecutionClient) GetTransactionReceipt(ctx context.Context, txHash com func (ec *ExecutionClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { return ec.ethClient.SendTransaction(ctx, tx) } + +func (ec *ExecutionClient) GetVerkleConversionState(ctx context.Context) (*VerkleConversionState, error) { + var result VerkleConversionState + err := ec.rpcClient.CallContext(ctx, &result, "debug_conversionStatus", "latest") + + return &result, err +} diff --git a/pkg/coordinator/clients/execution/rpc/verkleconversionstate.go b/pkg/coordinator/clients/execution/rpc/verkleconversionstate.go new file mode 100644 index 00000000..ea362d38 --- /dev/null +++ b/pkg/coordinator/clients/execution/rpc/verkleconversionstate.go @@ -0,0 +1,6 @@ +package rpc + +type VerkleConversionState struct { + Started bool `json:"started"` + Ended bool `json:"ended"` +} diff --git a/pkg/coordinator/tasks/check_execution_conversion_state/README.md b/pkg/coordinator/tasks/check_execution_conversion_state/README.md new file mode 100644 index 00000000..b41c9d46 --- /dev/null +++ b/pkg/coordinator/tasks/check_execution_conversion_state/README.md @@ -0,0 +1,35 @@ +## `check_execution_conversion_state` Task + +### Description +The `check_execution_conversion_state` task is designed to monitor the status of execution clients regarding their conversion to the Verkle tree structure, a significant upgrade in the Ethereum network. This task assesses whether the execution clients have started, are in the process of, or have completed the conversion to Verkle trees, ensuring that the network's upgrade transitions are proceeding as expected. + +### Configuration Parameters + +- **`clientPattern`**: + A regex pattern for selecting specific execution client endpoints to check. This allows for targeted monitoring of clients based on identifiers or characteristics defined in their endpoint URLs. + +- **`pollInterval`**: + The time interval, in seconds, at which the task will poll the clients to check their Verkle conversion status. A shorter interval results in more frequent checks, allowing for timely detection of state changes. + +- **`expectStarted`**: + If set to `true`, this option indicates the expectation that the Verkle conversion process has started on the targeted execution clients. The task checks for evidence that the conversion process is underway. + +- **`expectFinished`**: + When `true`, the task expects that the Verkle conversion process has been completed on the targeted execution clients. It verifies that the clients are fully upgraded to the new tree structure. + +- **`failOnUnexpected`**: + If set to `true`, the task will fail if the actual conversion status of the clients does not match the expected states (`expectStarted`, `expectFinished`). This is useful for scenarios where strict compliance with the conversion timeline is critical. + +### Defaults + +Default settings for the `check_execution_conversion_state` task: + +```yaml +- name: check_execution_conversion_state + config: + clientPattern: "" + pollInterval: 10s + expectStarted: false + expectFinished: false + failOnUnexpected: false +``` diff --git a/pkg/coordinator/tasks/check_execution_conversion_state/config.go b/pkg/coordinator/tasks/check_execution_conversion_state/config.go new file mode 100644 index 00000000..e79cc50a --- /dev/null +++ b/pkg/coordinator/tasks/check_execution_conversion_state/config.go @@ -0,0 +1,25 @@ +package checkexecutionconversionstate + +import ( + "time" + + "github.com/ethpandaops/assertoor/pkg/coordinator/human-duration" +) + +type Config struct { + ClientPattern string `yaml:"clientPattern" json:"clientPattern"` + PollInterval human.Duration `yaml:"pollInterval" json:"pollInterval"` + ExpectStarted bool `yaml:"expectStarted" json:"expectStarted"` + ExpectFinished bool `yaml:"expectFinished" json:"expectFinished"` + FailOnUnexpected bool `yaml:"failOnUnexpected" json:"failOnUnexpected"` +} + +func DefaultConfig() Config { + return Config{ + PollInterval: human.Duration{Duration: 10 * time.Second}, + } +} + +func (c *Config) Validate() error { + return nil +} diff --git a/pkg/coordinator/tasks/check_execution_conversion_state/task.go b/pkg/coordinator/tasks/check_execution_conversion_state/task.go new file mode 100644 index 00000000..c10ddbfd --- /dev/null +++ b/pkg/coordinator/tasks/check_execution_conversion_state/task.go @@ -0,0 +1,156 @@ +package checkexecutionconversionstate + +import ( + "context" + "fmt" + "time" + + "github.com/ethpandaops/assertoor/pkg/coordinator/clients/execution/rpc" + "github.com/ethpandaops/assertoor/pkg/coordinator/types" + "github.com/sirupsen/logrus" +) + +var ( + TaskName = "check_execution_conversion_state" + TaskDescriptor = &types.TaskDescriptor{ + Name: TaskName, + Description: "Checks execution clients for their verkle conversion status.", + Config: DefaultConfig(), + NewTask: NewTask, + } +) + +type Task struct { + ctx *types.TaskContext + options *types.TaskOptions + config Config + logger logrus.FieldLogger + firstHeight map[uint16]uint64 +} + +func NewTask(ctx *types.TaskContext, options *types.TaskOptions) (types.Task, error) { + return &Task{ + ctx: ctx, + options: options, + logger: ctx.Logger.GetLogger(), + firstHeight: map[uint16]uint64{}, + }, nil +} + +func (t *Task) Name() string { + return TaskName +} + +func (t *Task) Description() string { + return TaskDescriptor.Description +} + +func (t *Task) Title() string { + return t.ctx.Vars.ResolvePlaceholders(t.options.Title) +} + +func (t *Task) Config() interface{} { + return t.config +} + +func (t *Task) Logger() logrus.FieldLogger { + return t.logger +} + +func (t *Task) Timeout() time.Duration { + return t.options.Timeout.Duration +} + +func (t *Task) LoadConfig() error { + config := DefaultConfig() + + // parse static config + if t.options.Config != nil { + if err := t.options.Config.Unmarshal(&config); err != nil { + return fmt.Errorf("error parsing task config for %v: %w", TaskName, err) + } + } + + // load dynamic vars + err := t.ctx.Vars.ConsumeVars(&config, t.options.ConfigVars) + if err != nil { + return err + } + + // validate config + if err := config.Validate(); err != nil { + return err + } + + t.config = config + + return nil +} + +func (t *Task) Execute(ctx context.Context) error { + t.processCheck(ctx) + + for { + select { + case <-time.After(t.config.PollInterval.Duration): + t.processCheck(ctx) + case <-ctx.Done(): + return nil + } + } +} + +func (t *Task) processCheck(ctx context.Context) { + allResultsPass := true + failedClients := []string{} + + for _, client := range t.ctx.Scheduler.GetServices().ClientPool().GetClientsByNamePatterns(t.config.ClientPattern, "") { + var checkResult bool + + checkLogger := t.logger.WithField("client", client.Config.Name) + conversionStatus, err := client.ExecutionClient.GetRPCClient().GetVerkleConversionState(ctx) + + if ctx.Err() != nil { + return + } + + if err != nil { + checkLogger.Warnf("error fetching verkle conversion status: %v", err) + + checkResult = false + } else { + checkResult = t.processClientCheck(conversionStatus, checkLogger) + } + + if !checkResult { + allResultsPass = false + + failedClients = append(failedClients, client.Config.Name) + } + } + + t.logger.Infof("Check result: %v, Failed Clients: %v", allResultsPass, failedClients) + + switch { + case allResultsPass: + t.ctx.SetResult(types.TaskResultSuccess) + case t.config.FailOnUnexpected: + t.ctx.SetResult(types.TaskResultFailure) + default: + t.ctx.SetResult(types.TaskResultNone) + } +} + +func (t *Task) processClientCheck(conversionStatus *rpc.VerkleConversionState, checkLogger logrus.FieldLogger) bool { + if conversionStatus.Started != t.config.ExpectStarted { + checkLogger.Debugf("check failed. check: ExpectStarted, expected: %v, got: %v", t.config.ExpectStarted, conversionStatus.Started) + return false + } + + if conversionStatus.Ended != t.config.ExpectFinished { + checkLogger.Debugf("check failed. check: ExpectFinished, expected: %v, got: %v", t.config.ExpectFinished, conversionStatus.Ended) + return false + } + + return true +} diff --git a/pkg/coordinator/tasks/tasks.go b/pkg/coordinator/tasks/tasks.go index ba0add04..d8d3e757 100644 --- a/pkg/coordinator/tasks/tasks.go +++ b/pkg/coordinator/tasks/tasks.go @@ -13,6 +13,7 @@ import ( checkconsensusslotrange "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_slot_range" checkconsensussyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_sync_status" checkconsensusvalidatorstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_consensus_validator_status" + checkexecutionconversionstate "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_execution_conversion_state" checkexecutionsyncstatus "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/check_execution_sync_status" generateblobtransactions "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_blob_transactions" generateblschanges "github.com/ethpandaops/assertoor/pkg/coordinator/tasks/generate_bls_changes" @@ -44,6 +45,7 @@ var AvailableTaskDescriptors = []*types.TaskDescriptor{ checkconsensusslotrange.TaskDescriptor, checkconsensussyncstatus.TaskDescriptor, checkconsensusvalidatorstatus.TaskDescriptor, + checkexecutionconversionstate.TaskDescriptor, checkexecutionsyncstatus.TaskDescriptor, generateblobtransactions.TaskDescriptor, generateblschanges.TaskDescriptor,