From d04b0b461ea2c5b21e13550e7a6d6392e6343095 Mon Sep 17 00:00:00 2001 From: Sukhendu <19183308+sugh01@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:29:13 +0100 Subject: [PATCH] DV Setup: Charon Integration with Sedge (#368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * charon poc * fix tppo * add distributed flag to vc if Distributed * Update configs/client_images.yaml Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * fix:remove "-" * fix: validator-blocker * add Distributed Client Test * Update types, fix bugs, reviews * fix types for distributed validator image defaults * Update templates/services/merge/distributedValidator/charon.tmpl Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Make DV service generic * fix tests * fix dv tests * allow import of distributed validator keystores * updates to Teku key import * updates to Teku validator init * fixed lighthouse imports * use DefaultAbsSedgeDataPath for charon key imports * Document DV Setup Process with Sedge * Update docs/docs/commands/importKey.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Update docs/docs/quickstart/charon.mdx Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> * Fixed defaultKeystorePath for Charon key imports * import key tests from distributed option * bug fixes * add dv extra flags feature * Allow custom dv images, and few review fixes * Add script to fetch and update Charon to the latest version * lighthouse import-key tests * teku import-key tests * run formatter * lodestar import keys test in distributed mode * debug tests * fix file permissions for distributed key imports * teku import key tests * skip key-import tests * Revert last commit * remove error logs * fix missing test data * add Nimbus client to DV * remove redundant methods * Update Changelog * Update CHANGELOG.md Co-authored-by: Miguel Tenorio <46824157+AntiD2ta@users.noreply.github.com> --------- Co-authored-by: Oisín Kyne <4981644+OisinKyne@users.noreply.github.com> Co-authored-by: xin <3235773541@qq.com> Co-authored-by: Miguel Tenorio <46824157+AntiD2ta@users.noreply.github.com> --- .gitignore | 5 + CHANGELOG.md | 4 + README.md | 5 + cli/actions/generation_test.go | 37 +++ cli/actions/importKeys.go | 227 ++++++++++++++++-- cli/actions/importKeys_test.go | 80 +++++- .../testdata/charon/charon-enr-private-key | 1 + cli/actions/testdata/charon/cluster-lock.json | 207 ++++++++++++++++ cli/actions/testdata/charon/deposit-data.json | 57 +++++ .../charon/validator_keys/keystore-0.json | 31 +++ .../charon/validator_keys/keystore-0.txt | 1 + .../charon/validator_keys/keystore-1.json | 31 +++ .../charon/validator_keys/keystore-1.txt | 1 + .../charon/validator_keys/keystore-2.json | 31 +++ .../charon/validator_keys/keystore-2.txt | 1 + .../charon/validator_keys/keystore-3.json | 31 +++ .../charon/validator_keys/keystore-3.txt | 1 + .../charon/validator_keys/keystore-4.json | 31 +++ .../charon/validator_keys/keystore-4.txt | 1 + cli/generate.go | 171 +++++++------ cli/generate_test.go | 42 ++++ cli/importKeys.go | 3 + cli/sub_gen.go | 6 + configs/client_images.yaml | 4 + configs/constants.go | 7 +- configs/images.go | 4 + configs/messages.go | 43 ++-- configs/paths.go | 1 + configs/ports.go | 3 + docs/docs/commands/generate.mdx | 3 + docs/docs/commands/importKey.mdx | 1 + docs/docs/quickstart/charon.mdx | 123 ++++++++++ .../lighthouse/context/Dockerfile | 2 +- .../lighthouse/context/validator-init.sh | 19 +- .../validator-import/prysm/context/Dockerfile | 15 ++ .../prysm/context/validator-init.sh | 29 +++ .../images/validator-import/prysm/prysm.go | 35 +++ .../teku/context/validator-init.sh | 15 +- internal/images/validator-import/teku/teku.go | 6 +- internal/pkg/clients/clients_test.go | 12 + internal/pkg/clients/init.go | 3 + internal/pkg/clients/types.go | 28 ++- internal/pkg/clients/types_test.go | 29 +++ internal/pkg/generate/errors.go | 3 + internal/pkg/generate/generate_scripts.go | 129 ++++++---- .../pkg/generate/generate_scripts_test.go | 38 +++ internal/pkg/generate/types.go | 185 ++++++++------ scripts/charon/import_lodestar_keys.sh | 11 + scripts/check-image-updates.sh | 4 + .../holesky/distributedValidator/charon.tmpl | 10 + templates/envs/holesky/env_base.tmpl | 3 +- templates/services/docker-compose_base.tmpl | 6 +- .../merge/distributedValidator/charon.tmpl | 30 +++ .../merge/distributedValidator/empty.tmpl | 3 + .../services/merge/validator/lighthouse.tmpl | 5 +- .../services/merge/validator/lodestar.tmpl | 2 +- .../services/merge/validator/nimbus.tmpl | 6 +- templates/services/merge/validator/prysm.tmpl | 2 +- templates/services/merge/validator/teku.tmpl | 2 +- 59 files changed, 1557 insertions(+), 269 deletions(-) create mode 100644 cli/actions/testdata/charon/charon-enr-private-key create mode 100644 cli/actions/testdata/charon/cluster-lock.json create mode 100644 cli/actions/testdata/charon/deposit-data.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-0.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-0.txt create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-1.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-1.txt create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-2.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-2.txt create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-3.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-3.txt create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-4.json create mode 100644 cli/actions/testdata/charon/validator_keys/keystore-4.txt create mode 100644 docs/docs/quickstart/charon.mdx create mode 100644 internal/images/validator-import/prysm/context/Dockerfile create mode 100644 internal/images/validator-import/prysm/context/validator-init.sh create mode 100644 internal/images/validator-import/prysm/prysm.go create mode 100644 scripts/charon/import_lodestar_keys.sh create mode 100644 templates/envs/holesky/distributedValidator/charon.tmpl create mode 100644 templates/services/merge/distributedValidator/charon.tmpl create mode 100644 templates/services/merge/distributedValidator/empty.tmpl diff --git a/.gitignore b/.gitignore index 3dc08d8b..5aaf65ac 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ courtney/ mocks/ build/lido-exporter +.charon/ +node*/ + +keystore* +!cli/actions/testdata/charon/validator_keys/keystore* diff --git a/CHANGELOG.md b/CHANGELOG.md index eb522925..79cd81ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- New cli flag --distributed for running cluster with Charon distributed validator + ## [v1.7.1] - 2024-11-1 ### Added @@ -23,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v1.6.0] - 2024-10-18 + ### Added - New command `lido-status` to display data of Lido Node Operator. - New command `monitoring` to run monitoring stack setup with Grafana, Prometheus, Node Exporter and Lido Exporter. diff --git a/README.md b/README.md index 4181d767..1338edd1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,11 @@ friends, to amateur operators — to operate validators by providing an ETH-base Sedge supports the Lido CSM, allowing users to generate validator keys and set up their full nodes with ease. You can read more about it in [our documentation](https://docs.sedge.nethermind.io/docs/quickstart/staking-with-lido)! +## Charon DV integration +Charon is used by stakers to distribute the responsibility of running Ethereum Validators across a number of different instances and client implementations. Setting up and running a full ethereum node with charon, needs some learning curve and compatibility knowledge, in order for the setup to be fully compliant with the charon configuration requirements for different BN-VC combinations. We want to provide a better and guided user experience for setting up a DV with Charon. + +Integrating Charon with Sedge would make it easy for stakers to setup and run a DV with Charon without having to go through each individual client setup docs and their compatibility with DVT. + ## Supported networks and clients ### Mainnet diff --git a/cli/actions/generation_test.go b/cli/actions/generation_test.go index 895c3a9a..ea80bfaf 100644 --- a/cli/actions/generation_test.go +++ b/cli/actions/generation_test.go @@ -95,6 +95,13 @@ func TestGenerateDockerCompose(t *testing.T) { if err != nil { t.Errorf("SupportedClients(\"validator\") failed: %v", err) } + var distributedValidatorClients []string + if network == "holesky" { + distributedValidatorClients, err = c.SupportedClients("distributedValidator") + if err != nil { + t.Errorf("SupportedClients(\"distributedValidator\") failed: %v", err) + } + } rNum, err := rand.Int(rand.Reader, big.NewInt(int64(100))) if err != nil { @@ -265,6 +272,25 @@ func TestGenerateDockerCompose(t *testing.T) { }, ) } + + // For distributedValidator + if utils.Contains(distributedValidatorClients, "charon") { + tests = append(tests, + genTestData{ + name: fmt.Sprintf("execution: %s, consensus: %s, validator: %s,distributedValidator: %s, network: %s, all, with distributedValidator", executionCl, consensusCl, consensusCl, distributedValidatorClients, network), + genData: generate.GenData{ + Distributed: true, + DistributedValidatorClient: &clients.Client{Name: "charon", Type: "distributedValidator"}, + ExecutionClient: &clients.Client{Name: executionCl, Type: "execution"}, + ConsensusClient: &clients.Client{Name: consensusCl, Type: "consensus"}, + ValidatorClient: &clients.Client{Name: consensusCl, Type: "validator"}, + Services: []string{"execution", "consensus", "validator", "distributedValidator"}, + Network: network, + }, + }, + ) + } + } } } @@ -287,6 +313,9 @@ func TestGenerateDockerCompose(t *testing.T) { if tc.genData.ValidatorClient != nil { tc.genData.ValidatorClient.SetImageOrDefault("") } + if tc.genData.DistributedValidatorClient != nil { + tc.genData.DistributedValidatorClient.SetImageOrDefault("") + } _, err := sedgeAction.Generate(actions.GenerateOptions{ GenerationData: tc.genData, @@ -474,6 +503,14 @@ func TestGenerateDockerCompose(t *testing.T) { } } + // Validate that Distributed Validator Client info matches the sample data + if tc.genData.DistributedValidatorClient != nil { + // Check that the distributed-validator service is set. + assert.NotNil(t, cmpData.Services.DistributedValidator) + // Check that the distributed-validator container Volume is set. + assert.Equal(t, "${DV_DATA_DIR}:/opt/charon/.charon", cmpData.Services.DistributedValidator.Volumes[0]) + } + if tc.genData.ValidatorClient == nil { // Check validator blocker is not set if validator is not set assert.Nil(t, cmpData.Services.ValidatorBlocker) diff --git a/cli/actions/importKeys.go b/cli/actions/importKeys.go index b6895b51..e43e076b 100644 --- a/cli/actions/importKeys.go +++ b/cli/actions/importKeys.go @@ -27,6 +27,7 @@ import ( "github.com/NethermindEth/sedge/configs" "github.com/NethermindEth/sedge/internal/images/validator-import/lighthouse" + "github.com/NethermindEth/sedge/internal/images/validator-import/prysm" "github.com/NethermindEth/sedge/internal/images/validator-import/teku" "github.com/NethermindEth/sedge/internal/pkg/commands" "github.com/NethermindEth/sedge/internal/pkg/services" @@ -51,6 +52,7 @@ type ImportValidatorKeysOptions struct { GenerationPath string ContainerTag string CustomConfig ImportValidatorKeysCustomOptions + Distributed bool } type ImportValidatorKeysCustomOptions struct { NetworkConfigPath string @@ -100,19 +102,95 @@ func (s *sedgeActions) ImportValidatorKeys(options ImportValidatorKeysOptions) e options.GenerationPath = absGenerationPath if !isDefaultKeysPath(options.GenerationPath, options.From) { - defaultKeystorePath := filepath.Join(options.GenerationPath, "keystore") - log.Warnf("The keys path is not the default one, copying the keys to the default path %s", defaultKeystorePath) - copy.Copy(options.From, defaultKeystorePath) + if !options.Distributed { + defaultKeystorePath := filepath.Join(options.GenerationPath, "keystore") + log.Warnf("The keys path is not the default one, copying the keys to the default path %s", defaultKeystorePath) + copy.Copy(options.From, defaultKeystorePath) + } + } + + if options.Distributed { + cwd, _ := os.Getwd() + charonPath := filepath.Join(cwd, ".charon") + + if !isDefaultKeysPath(options.GenerationPath, options.From) { + charonPath = options.From + log.Infof("Copying the keys from %s", charonPath) + options.From = filepath.Join(options.GenerationPath, "keystore") + } + defaultCharonPath := filepath.Join(configs.DefaultAbsSedgeDataPath, ".charon") + // Copy the folder from charonPath to defaultCharonPath + log.Infof("Copying Charon contents to the default path %s", defaultCharonPath) + if err := os.MkdirAll(defaultCharonPath, 0o755); err != nil { + return err + } + if err := copy.Copy(charonPath, defaultCharonPath); err != nil { + return err + } + charonValidatorKeysPath := filepath.Join(charonPath, "validator_keys") + defaultKeystorePath := filepath.Join(configs.DefaultAbsSedgeDataPath, "keystore") + log.Infof("Copying the keys to the default path %s", defaultKeystorePath) + if err := os.MkdirAll(defaultKeystorePath, 0o755); err != nil { + return err + } + + validatorKeysPath := filepath.Join(defaultKeystorePath, "validator_keys") + if err := os.MkdirAll(validatorKeysPath, 0o755); err != nil { + return err + } + + depositDataPath := filepath.Join(charonPath, "deposit-data.json") + depositDataPathDest := filepath.Join(defaultKeystorePath, "deposit-data.json") + if err := copy.Copy(depositDataPath, depositDataPathDest); err != nil { + return err + } + + files, err := os.ReadDir(charonValidatorKeysPath) + if err != nil { + log.Fatal(err) + } + len := len(files) + for i := 0; i < len/2; i++ { + keystorePath := filepath.Join(charonValidatorKeysPath, fmt.Sprintf("keystore-%d.json", i)) + validatorPath := filepath.Join(validatorKeysPath, fmt.Sprintf("keystore-%d.json", i)) + if err := copy.Copy(keystorePath, validatorPath); err != nil { + return err + } + + keystoreTxtPath := filepath.Join(charonValidatorKeysPath, fmt.Sprintf("keystore-%d.txt", i)) + keystorePasswordPath := filepath.Join(defaultKeystorePath, fmt.Sprintf("keystore-%d.txt", i)) + if err := copy.Copy(keystoreTxtPath, keystorePasswordPath); err != nil { + return err + } + } + if options.ValidatorClient == "prysm" { + keystorePasswordPath := filepath.Join(defaultKeystorePath, "keystore_password.txt") + f, err := os.Create(keystorePasswordPath) + if err != nil { + return err + } + f.WriteString("prysm-validator-secret") + defer f.Close() + } } var ctID string switch options.ValidatorClient { case "prysm": - prysmCtID, err := setupPrysmValidatorImportContainer(s.dockerClient, s.dockerServiceManager, options) - if err != nil { - return err + prysmCtID := "" + if options.Distributed { + prysmCtID, err = setupPrysmValidatorImportContainerDV(s.dockerClient, s.commandRunner, s.dockerServiceManager, options) + if err != nil { + return err + } + ctID = prysmCtID + } else { + prysmCtID, err := setupPrysmValidatorImportContainer(s.dockerClient, s.dockerServiceManager, options) + if err != nil { + return err + } + ctID = prysmCtID } - ctID = prysmCtID case "nimbus": nimbusCtID, err := setupNimbusValidatorImport(s.dockerClient, s.dockerServiceManager, options) if err != nil { @@ -143,7 +221,11 @@ func (s *sedgeActions) ImportValidatorKeys(options ImportValidatorKeysOptions) e log.Info("Importing validator keys") var runErr error if options.ValidatorClient == "nimbus" { - runErr = runAndWaitImportKeysNimbus(s.dockerClient, s.dockerServiceManager, ctID) + if !options.Distributed { + runErr = runAndWaitImportKeysNimbus(s.dockerClient, s.dockerServiceManager, ctID) + } else { + runErr = runAndWaitImportKeys(s.dockerClient, s.dockerServiceManager, ctID) + } } else { runErr = runAndWaitImportKeys(s.dockerClient, s.dockerServiceManager, ctID) } @@ -260,17 +342,47 @@ func setupNimbusValidatorImport(dockerClient client.APIClient, dockerServiceMana } else { cmd = append(cmd, "--network="+options.Network) } + containerConfig := &container.Config{ + Image: validatorImage, + Cmd: cmd, + AttachStdin: true, + AttachStderr: true, + AttachStdout: true, + OpenStdin: true, + Tty: true, + } + if options.Distributed { + containerConfig = &container.Config{ + Image: validatorImage, + Entrypoint: []string{ + "sh", "-c", ` + #!/usr/bin/env bash + set -e + tmpkeys="/keystore/validator_keys/tmpkeys" + mkdir -p ${tmpkeys} + for f in /keystore/validator_keys/keystore-*.json; do + echo "Importing key ${f}" + pwdfile="/keystore/$(basename "$f" .json).txt" + password=$(cat ${pwdfile}) + echo "Using password file ${pwdfile}" + echo "Using password ${password}" + cp "${f}" "${tmpkeys}" + # Import keystore with password. + echo "$password" | \ + /home/user/nimbus_beacon_node deposits import \ + --data-dir=/data \ + ${tmpkeys} + filename="$(basename ${f})" + rm "${tmpkeys}/${filename}" + done + `, + }, + } + } + log.Debugf("Creating %s container", validatorImportCtName) ct, err := dockerClient.ContainerCreate(context.Background(), - &container.Config{ - Image: validatorImage, - Cmd: cmd, - AttachStdin: true, - AttachStderr: true, - AttachStdout: true, - OpenStdin: true, - Tty: true, - }, + containerConfig, &container.HostConfig{ Mounts: mounts, VolumesFrom: []string{consensusCtName, validatorCtName}, @@ -330,11 +442,33 @@ func setupLodestarValidatorImport(dockerClient client.APIClient, dockerServiceMa cmd = append(cmd, "--preset", preset) } log.Debugf("Creating %s container", validatorImportCtName) - ct, err := dockerClient.ContainerCreate(context.Background(), - &container.Config{ + containerConfig := &container.Config{ + Image: validatorImage, + Cmd: cmd, + } + if options.Distributed { + containerConfig = &container.Config{ Image: validatorImage, - Cmd: cmd, - }, + Entrypoint: []string{ + "sh", "-c", ` + #!/bin/sh + set -e + for f in /keystore/validator_keys/keystore-*.json; do + echo "Importing key ${f}" + pwdfile="/keystore/$(basename "$f" .json).txt" + echo "Using password file ${pwdfile}" + # Import keystore with password. + node /usr/app/packages/cli/bin/lodestar validator import \ + --dataDir="/data" \ + --importKeystores="$f" \ + --importKeystoresPassword="${pwdfile}" + done + `, + }, + } + } + ct, err := dockerClient.ContainerCreate(context.Background(), + containerConfig, &container.HostConfig{ Mounts: mounts, VolumesFrom: []string{validatorCtName}, @@ -515,6 +649,57 @@ func runAndWaitImportKeys(dockerClient client.APIClient, dockerServiceManager Do } } +func setupPrysmValidatorImportContainerDV(dockerClient client.APIClient, commandRunner commands.CommandRunner, serviceManager DockerServiceManager, options ImportValidatorKeysOptions) (string, error) { + var ( + validatorCtName = services.ContainerNameWithTag(services.DefaultSedgeValidatorClient, options.ContainerTag) + validatorImportCtName = services.ContainerNameWithTag(services.ServiceCtValidatorImport, options.ContainerTag) + ) + // Init build context + contextDir, err := prysm.InitContext() + if err != nil { + return "", err + } + // Build image + buildCmd := commandRunner.BuildDockerBuildCMD(commands.DockerBuildOptions{ + Path: contextDir, + Tag: "sedge/prysm-import-teku", + Args: map[string]string{ + "NETWORK": options.Network, + "PRYSM_VERSION": configs.ClientImages.Validator.Prysm.String(), + }, + }) + log.Infof(configs.RunningCommand, buildCmd.Cmd) + if _, _, err := commandRunner.RunCMD(buildCmd); err != nil { + return "", err + } + // Mounts + mounts := []mount.Mount{ + { + Type: mount.TypeBind, + Source: options.From, + Target: "/keystore", + }, + } + log.Debugf("Creating %s container", validatorImportCtName) + containerConfig := &container.Config{ + Image: "sedge/prysm-import-teku", + } + ct, err := dockerClient.ContainerCreate(context.Background(), + containerConfig, + &container.HostConfig{ + Mounts: mounts, + VolumesFrom: []string{validatorCtName}, + }, + &network.NetworkingConfig{}, + &v1.Platform{}, + validatorImportCtName, + ) + if err != nil { + return "", err + } + return ct.ID, nil +} + // runAndWaitImportKeysNimbus starts the container in interactive mode and waits for it to finish. func runAndWaitImportKeysNimbus(dockerClient client.APIClient, dockerServiceManager DockerServiceManager, ctID string) error { log.Debugf("Starting interactive container with id: %s", ctID) diff --git a/cli/actions/importKeys_test.go b/cli/actions/importKeys_test.go index 8b0eeb9b..cb3f8251 100644 --- a/cli/actions/importKeys_test.go +++ b/cli/actions/importKeys_test.go @@ -77,8 +77,7 @@ func TestImportKeys_ValidatorRunning(t *testing.T) { t.Run(validatorClient, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - - dockerClient := importKeysGoldenPath(t, ctrl, false) + dockerClient := importKeysGoldenPath(t, ctrl, false, false) dockerServiceManager := services.NewDockerServiceManager(dockerClient) cmdRunner := test.SimpleCMDRunner{} s := actions.NewSedgeActions(actions.SedgeActionsOptions{ @@ -112,8 +111,7 @@ func TestImportKeysCustom_ValidatorRunning(t *testing.T) { t.Run(validatorClient, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - - dockerClient := importKeysGoldenPath(t, ctrl, true) + dockerClient := importKeysGoldenPath(t, ctrl, true, false) dockerServiceManager := services.NewDockerServiceManager(dockerClient) cmdRunner := test.SimpleCMDRunner{} s := actions.NewSedgeActions(actions.SedgeActionsOptions{ @@ -196,8 +194,7 @@ func TestImportKeys_CustomOptions(t *testing.T) { t.Run(fmt.Sprintf("%s_%s", tt.client, tt.network), func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - - dockerClient := importKeysGoldenPath(t, ctrl, tt.customImage) + dockerClient := importKeysGoldenPath(t, ctrl, tt.customImage, false) dockerServiceManager := services.NewDockerServiceManager(dockerClient) cmdRunner := test.SimpleCMDRunner{} s := actions.NewSedgeActions(actions.SedgeActionsOptions{ @@ -280,6 +277,37 @@ func TestImportKeys_UnexpectedExitCode(t *testing.T) { assert.ErrorIs(t, err, actions.ErrValidatorImportCtBadExitCode) } +func TestImportKeys_DistributedMode(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + dockerClient := importKeysGoldenPath(t, ctrl, false, true) + serviceManager := services.NewDockerServiceManager(dockerClient) + cmdRunner := test.SimpleCMDRunner{} + s := actions.NewSedgeActions(actions.SedgeActionsOptions{ + DockerClient: dockerClient, + DockerServiceManager: serviceManager, + CommandRunner: &cmdRunner, + }) + + from, err := setupCharonKeystoreDir(t) + if err != nil { + t.Logf("Error setting up keystore dir: %v", err) + t.Fatal(err) + } + + generationPath := t.TempDir() + + err = s.ImportValidatorKeys(actions.ImportValidatorKeysOptions{ + ValidatorClient: "lodestar", + Network: "holesky", + GenerationPath: generationPath, + Distributed: true, + From: from, + }) + assert.NoError(t, err) +} + //go:embed testdata/keystore var keystoreTestData embed.FS @@ -316,9 +344,45 @@ func setupKeystoreDir(t *testing.T) (string, error) { return tempKeystore, nil } +//go:embed testdata/charon +var charonKeystoreTestData embed.FS + +func setupCharonKeystoreDir(t *testing.T) (string, error) { + t.Helper() + tempKeystore := t.TempDir() + + baseTestDir := "testdata/charon" + dirs := []string{""} + for len(dirs) > 0 { + currentDir := dirs[0] + dirEntries, err := charonKeystoreTestData.ReadDir(path.Join(baseTestDir, currentDir)) + if err != nil { + return "", err + } + for _, entry := range dirEntries { + if entry.IsDir() { + dirs = append(dirs, filepath.Join(currentDir, entry.Name())) + } else { + entryData, err := charonKeystoreTestData.ReadFile(path.Join(baseTestDir, currentDir, entry.Name())) + if err != nil { + return "", err + } + if err := os.MkdirAll(filepath.Join(tempKeystore, currentDir), 0o755); err != nil { + return "", err + } + if err := ioutil.WriteFile(filepath.Join(tempKeystore, currentDir, entry.Name()), entryData, 0o755); err != nil { + return "", err + } + } + } + dirs = dirs[1:] + } + return tempKeystore, nil +} + // importKeysGoldenPath returns a mocked docker client interface with all the // required responses for a correct validator import keys container execution. -func importKeysGoldenPath(t *testing.T, ctrl *gomock.Controller, withCustomImage bool) client.APIClient { +func importKeysGoldenPath(t *testing.T, ctrl *gomock.Controller, withCustomImage bool, withDistributedOption bool) client.APIClient { t.Helper() dockerClient := sedge_mocks.NewMockAPIClient(ctrl) @@ -350,6 +414,8 @@ func importKeysGoldenPath(t *testing.T, ctrl *gomock.Controller, withCustomImage }, nil) if withCustomImage { inspectCall.Times(2) + } else if withDistributedOption { + inspectCall.MinTimes(2) } else { inspectCall.Times(3) } diff --git a/cli/actions/testdata/charon/charon-enr-private-key b/cli/actions/testdata/charon/charon-enr-private-key new file mode 100644 index 00000000..5fdce315 --- /dev/null +++ b/cli/actions/testdata/charon/charon-enr-private-key @@ -0,0 +1 @@ +09504dd3fea5458aafe25e52b60d2c8db170e58ffe12d0976f5e50d405a7ec42 \ No newline at end of file diff --git a/cli/actions/testdata/charon/cluster-lock.json b/cli/actions/testdata/charon/cluster-lock.json new file mode 100644 index 00000000..7edb20a1 --- /dev/null +++ b/cli/actions/testdata/charon/cluster-lock.json @@ -0,0 +1,207 @@ +{ + "cluster_definition": { + "name": "charon", + "creator": { + "address": "", + "config_signature": "" + }, + "operators": [ + { + "address": "", + "enr": "enr:-HW4QBh0mb_rT1jq4x3gJ9dE8aZWN53Sdu7f8CTv1jKBs60LFETyxbykCpdRsGXPjIpGjXo4m-uKQsud2ydARI1pw5SAgmlkgnY0iXNlY3AyNTZrMaEDTAapPhQbAJDzA671KvvapM1S977OZmYvzm6Y3imG-LE", + "config_signature": "", + "enr_signature": "" + }, + { + "address": "", + "enr": "enr:-HW4QCusjxLUvMJ5avudca_7QOg0seqmLMxWUvNadCICc9YkWHdFIpggg2Yu5dg-WIjesuJlSUha9lzVj7dzqrSGeceAgmlkgnY0iXNlY3AyNTZrMaEC7TaC4CUd-01tawFPibioiT0iBeZnHunIFkF7sCPOWwU", + "config_signature": "", + "enr_signature": "" + }, + { + "address": "", + "enr": "enr:-HW4QPiL6GTDEXIvXq3Qk1rbLvy204J0ilnXlV15ieS1g8dydDi8yJuj56pnzvox4d2Etdg_SNI7lMxKpbKxUV8jd92AgmlkgnY0iXNlY3AyNTZrMaEDj62I2poaSERPLDD3ajrPqvaWTUYQGXoUEyJ2MqTsfY0", + "config_signature": "", + "enr_signature": "" + }, + { + "address": "", + "enr": "enr:-HW4QJLmPzoubFm2rScigHgKpbHpzJkfp0Cf1RXL6Qc47wvlQUXsson1qh_riFdmWeY472gKLLpXgIQgmAR3KGZzKMeAgmlkgnY0iXNlY3AyNTZrMaED-RCZFFpNlAAP6OdlEaBNekpSHnfoYlML_9iMGW7scXQ", + "config_signature": "", + "enr_signature": "" + } + ], + "uuid": "D3FB9672-FC7B-6769-FF67-920BB9A6CC1A", + "version": "v1.8.0", + "timestamp": "2024-07-29T19:43:43Z", + "num_validators": 5, + "threshold": 3, + "validators": [ + { + "fee_recipient_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B", + "withdrawal_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B" + }, + { + "fee_recipient_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B", + "withdrawal_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B" + }, + { + "fee_recipient_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B", + "withdrawal_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B" + }, + { + "fee_recipient_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B", + "withdrawal_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B" + }, + { + "fee_recipient_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B", + "withdrawal_address": "0x7cE7390C41Ce3416c4A0a297761C71763d89Ca3B" + } + ], + "dkg_algorithm": "default", + "fork_version": "0x01017000", + "deposit_amounts": null, + "config_hash": "0xe23258c6a9932e05e7354a57511a8e7317ceb52507001922b8ffd35c457ad75b", + "definition_hash": "0x96b7158837410fbf179cbfadf0a681124c5aadb20c77d0d88a8ed314d3ab34f9" + }, + "distributed_validators": [ + { + "distributed_public_key": "0xa54440e320af34dfb3963175be103ec951f9b3f0d4aa82fd5eef0a42fdc216af8249297d213e23e91449d539d5726e95", + "public_shares": [ + "0x937efb721750cc7a9f6285647282bb3eb6a015785be164f44b563951d65c54e7bb744c73dd2755327913f009f5948923", + "0xa4a08c0cf1238f3a5b2d0d0cf021475cd4fab8118459f6d429b4d47ac9066d76a5a1c800920a0e84842c6551a3d7f061", + "0x8893e76aee6142192e89d8d6ee03bbefc5e9cc92e3d24f4f27738bb5e9d11258ecd6323775890ee9afe04b29f888d0f9", + "0x91a6006f0af9b171c82026b7d5c7c7ae092320e6e93c12d4dd30e775a8226393cd3fa0a95310fa6d516457ac22b91336" + ], + "builder_registration": { + "message": { + "fee_recipient": "0x7ce7390c41ce3416c4a0a297761c71763d89ca3b", + "gas_limit": 30000000, + "timestamp": 1696000704, + "pubkey": "0xa54440e320af34dfb3963175be103ec951f9b3f0d4aa82fd5eef0a42fdc216af8249297d213e23e91449d539d5726e95" + }, + "signature": "0xa1204a3b6d5e9a38b47fbe89b0b22efa5b2295a5fe16ac4c01717fd7550db0c622f77798fb275791cc1774ea8bbc962a156bdda0453353b4c5dc24456aaf88aa788c87106e4292607efc1e812cd36d0a5b47584ff5abeff754d5bcad261a57d3" + }, + "partial_deposit_data": [ + { + "pubkey": "0xa54440e320af34dfb3963175be103ec951f9b3f0d4aa82fd5eef0a42fdc216af8249297d213e23e91449d539d5726e95", + "withdrawal_credentials": "0x0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": "32000000000", + "signature": "0xa8154d7b3daece140f22ca5a66371bac2e0329a9dbb1deda24108cdb9d4b04e47f4696adc5889dae22be6145a28256fc0d587bf3a6a066edd4de109e51a8941b8b9f6ee61f84891bc26ee3693323b294781de14760674f023af21184b335b699" + } + ] + }, + { + "distributed_public_key": "0xb817ffd0770913f31a0c2ca37bf8777b5838f3871fc804558c6cbebb29d610d5d2c64b5bcd361584a7e6ba30795ac6c4", + "public_shares": [ + "0xb34d921aad847e8a868a3259633ebf797659ffb7debb7f681aae9d5169b34a720cbaaea2cd87b17d440bb34f452cb8d0", + "0x8c0e1c978fec684ee836f457185076c89c5a3d982174d4ecb3b485c41b1b73fa50f538866678c0de3b797318df4f5662", + "0xad34d5578eea61b47a24a7dc49194bb19f07e5cb6db604b352fe9a700cc2cb21d390efb65c4306877076cd12075fcff0", + "0x8d0d8c1ee7726500146f35dddef28756736f0b206701813c5378aabbc145967fd6b271a45721e1a0f437d7be53b93682" + ], + "builder_registration": { + "message": { + "fee_recipient": "0x7ce7390c41ce3416c4a0a297761c71763d89ca3b", + "gas_limit": 30000000, + "timestamp": 1696000704, + "pubkey": "0xb817ffd0770913f31a0c2ca37bf8777b5838f3871fc804558c6cbebb29d610d5d2c64b5bcd361584a7e6ba30795ac6c4" + }, + "signature": "0xb3f89ed741d6ec9cae60a58f6b74ccd1239bfc66d98e90f049fa34e7eaa826208f33771bb7331211e74e605257a7612818feb0dd98f54a746eb0f9fdc057c93b1732f1a59a6d68899cefa767745cc5ebcf8f535b15270c197ceb8e7b7b58ad43" + }, + "partial_deposit_data": [ + { + "pubkey": "0xb817ffd0770913f31a0c2ca37bf8777b5838f3871fc804558c6cbebb29d610d5d2c64b5bcd361584a7e6ba30795ac6c4", + "withdrawal_credentials": "0x0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": "32000000000", + "signature": "0x996be98ac1ab42aa2594fb5345578c6ee01d9d11facb61c623119b6b2bf3ea88bd73fcc7a386c0c712d30493c7d2cc7a09471a360d8186a65ca072ad3419fe44bfa43b5a179c43e050839d1681556273001bec3ec6829e6dad87975cf36f31fe" + } + ] + }, + { + "distributed_public_key": "0xa92a6349443cb28db93056ae73362791cccb854da50c6acf7ab23ab4b6a6ff8fe479ad85f2334b40d37b3fc2594ab2f5", + "public_shares": [ + "0xb9d5ba740412ac60427986b1f906ebe27debfe9ca5d8631853160fbbf296b897c27f3142886ac69d5ce54d1e53abd45f", + "0x8c0c68f650806321f875504777dbc6a79bc3fb52023189a886c8178c7c44c7e98cafe8e5083b68eb375cb1267816a2f2", + "0x82d0f86cfe4bdabe3ea88f6340007f0e9d7e1be2a4380e42c5277b464b394849cd90f1b179f4c9f2d67701b9d0c2b790", + "0xae988804216084b85025415967bf4e85f250c0486a4374a99be0a543b4495fd05a3922f601e85aad164928f22fa0fb98" + ], + "builder_registration": { + "message": { + "fee_recipient": "0x7ce7390c41ce3416c4a0a297761c71763d89ca3b", + "gas_limit": 30000000, + "timestamp": 1696000704, + "pubkey": "0xa92a6349443cb28db93056ae73362791cccb854da50c6acf7ab23ab4b6a6ff8fe479ad85f2334b40d37b3fc2594ab2f5" + }, + "signature": "0xb4a0e4a1cf7905b8abd07341be400295aa58aa606cd4fd77c7d8e77848f65a478243e7fe9d0424a0503330e7d61fd08c0b7992fbf6541e8171da95f4e40ba6088595048846a304fcf555069aa0999d9febed1f2b9a0542e3255a7cb6a992efb3" + }, + "partial_deposit_data": [ + { + "pubkey": "0xa92a6349443cb28db93056ae73362791cccb854da50c6acf7ab23ab4b6a6ff8fe479ad85f2334b40d37b3fc2594ab2f5", + "withdrawal_credentials": "0x0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": "32000000000", + "signature": "0xb631d3e0d2cfedff007ac5d5a7eb294de60d0e75405f5d612fdee9a3da5a83ee2ee40faeae44d775e96a99a0f3dbbc9f1023f2b41ef32a2201e2a6b46253849e3c224dbdec8d80ed89ffc7f1bd82154ef665604231c86312ec8319f95156a837" + } + ] + }, + { + "distributed_public_key": "0x8305208fe7e85093f51c9b1703bde1bf86bacbabba2b13fb03578efa0e3f38bb9f921d1b6160ae1d90894ee897b829b7", + "public_shares": [ + "0x9234b10e8d612de8dfb90e63ac2249046069f8e3d664699593903daf15c2cb3eb97ea07a3daaabffbe69c4ada631eba7", + "0x8a99369dd589ef13d43178e72838a0dc0f77da811f93980b9ff50450a4c417bcaf9c456221847d19c0e57f2997387fdc", + "0x99be39451c462522f6e363a60ebc6d3f06ff3ffaec1d321a39e25e56f118492027cfdb0a874931fe8097ff49fbb6056f", + "0x919e278fafe4097ece4829e4a76d4c85a72b599362b62180d51bfa0e8b34755f60678e8e6139ed4b3c5bef38a93a14e5" + ], + "builder_registration": { + "message": { + "fee_recipient": "0x7ce7390c41ce3416c4a0a297761c71763d89ca3b", + "gas_limit": 30000000, + "timestamp": 1696000704, + "pubkey": "0x8305208fe7e85093f51c9b1703bde1bf86bacbabba2b13fb03578efa0e3f38bb9f921d1b6160ae1d90894ee897b829b7" + }, + "signature": "0x807006423c456a64eb91590f088f39e019c15519730db089daebf78952b9c31f362664b42b40da76a7a4b27106eaf38b09b0230468a935b5b3e6f3ea375a08e6c2f76e014e043c128d392df234cf06f81d4d87cc0f6de07792b0d96fbcd3a99e" + }, + "partial_deposit_data": [ + { + "pubkey": "0x8305208fe7e85093f51c9b1703bde1bf86bacbabba2b13fb03578efa0e3f38bb9f921d1b6160ae1d90894ee897b829b7", + "withdrawal_credentials": "0x0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": "32000000000", + "signature": "0x8e870a48e1e5c61b8650d17537ddd7dc01a017f76c97461fd1956de819ef21af318d9a5eee56cbbdee0b8f8e30ae74ab047d0220de1e7cbb25e159614bc64b76195d1193ed4aeacdd8dd70eb40d62c161e933c9fcef49544c406631fb2063eb2" + } + ] + }, + { + "distributed_public_key": "0x9520c02ebab3efaec45d0fe7f8b08e532ffe2e03f59b417f5d33fdf721888d116674814c3ddb1972c5ec6588c2506986", + "public_shares": [ + "0x8e173333b166ca47cad2dbbe6fcf95a37ab62b191b076e54d8a8f8374efad616761cae930893553eab7a1ff179b70e44", + "0x92887a45f4732242f2fd1d63dbcd3fcaf3b2b199de81570c38ddd5636ec877ddb2f286a0868b34183c0745068413282a", + "0x882ce8daf0e30218f4b521643be409abbd5b7fdb192e429df6b58d7c194c48f6187a29abf8801bb7a4b92b1485b6348b", + "0xb3a5be4f54eb7bde8204d963dfd3c8a0b97e00b227551f7fe4146d71718f11687e2515768d2d89e5ae816b0a287ae91f" + ], + "builder_registration": { + "message": { + "fee_recipient": "0x7ce7390c41ce3416c4a0a297761c71763d89ca3b", + "gas_limit": 30000000, + "timestamp": 1696000704, + "pubkey": "0x9520c02ebab3efaec45d0fe7f8b08e532ffe2e03f59b417f5d33fdf721888d116674814c3ddb1972c5ec6588c2506986" + }, + "signature": "0x943464845fc610cc441c15fff6b6ecdaad53411d255619e9dceca156ef8a7be94fa2985fcc7e5199de3f1b607acc74970ef3b00fff70c036f5634fcb80963a952d767a155cd38ab7d071bfc3190425a1ed21c6fb256bc3080ecc88733bae6b05" + }, + "partial_deposit_data": [ + { + "pubkey": "0x9520c02ebab3efaec45d0fe7f8b08e532ffe2e03f59b417f5d33fdf721888d116674814c3ddb1972c5ec6588c2506986", + "withdrawal_credentials": "0x0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": "32000000000", + "signature": "0xa13a9419fd3a6efd1fed6fe2a9b09475d8bda5df141c2e7ca44aa659c21a042aea076a6e571b96e7efb2fc504ac7743301dbd6ad28df04339eea2dd6b16de5a8534df13e1d573e0695d3071de731737ef7afd9520ad14d213347032b483f1d68" + } + ] + } + ], + "signature_aggregate": "0x90a148183712f41bd68d2d4f7cbe70874d81543bcb0ae6908bf6b19d931f35ccec8f60498482a9c5926bb8b88382c99112551a5e80c868bdf0fe99fbed9f3b039b35b83c412315ee0ad2b6d31d7898d51c61dd63fd00ffa2dd9f613937e1a208", + "lock_hash": "0x3ff18c9196e4cbbb9790497100e501e1ae46b20106db4dc608b809fee0f8d19a", + "node_signatures": [ + "0x283bdfea1c1f5154397e281a6641a61f0a5804d498c7ad40094d74b52f55538a55188b1056eea924d29b8c787e734552ea796d02f48cdfdfa23a0363b6f7e30000", + "0x871c6559739af1b8670b918bd5ab14b8cc18243e9bac85c75232f3c98e8dc379437aeaca501906d2904b4b85307c7ce41b2542f6b0bd37fff65528918735bd2f00", + "0xa4da37becc9ed38722dee85856084bfaf08bc4a7a58bf19d49edb316c4860a0e45d534445d1d513191c4324b97371585e0e58e9a388c7928cd9591671ca154ad00", + "0x24255721797165450160039f5575d0ec4b06f6e3ab7ebcff9c1e11bc4927429b2c1dc1d4ad05c4b31996681bd7197b50cb4f8ba4305749c39aa4137178ff9e9201" + ] +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/deposit-data.json b/cli/actions/testdata/charon/deposit-data.json new file mode 100644 index 00000000..b1abad39 --- /dev/null +++ b/cli/actions/testdata/charon/deposit-data.json @@ -0,0 +1,57 @@ +[ + { + "pubkey": "8305208fe7e85093f51c9b1703bde1bf86bacbabba2b13fb03578efa0e3f38bb9f921d1b6160ae1d90894ee897b829b7", + "withdrawal_credentials": "0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": 32000000000, + "signature": "8e870a48e1e5c61b8650d17537ddd7dc01a017f76c97461fd1956de819ef21af318d9a5eee56cbbdee0b8f8e30ae74ab047d0220de1e7cbb25e159614bc64b76195d1193ed4aeacdd8dd70eb40d62c161e933c9fcef49544c406631fb2063eb2", + "deposit_message_root": "7de8922c584ee15b6cbe971dba5fdb6a54910c012624a712bbdeb46e9453cc62", + "deposit_data_root": "5506b2520a86668358d7ab60cbdcefa55e1e74d2e88209b13d5d62dda9e33208", + "fork_version": "01017000", + "network_name": "holesky", + "deposit_cli_version": "2.7.0" + }, + { + "pubkey": "9520c02ebab3efaec45d0fe7f8b08e532ffe2e03f59b417f5d33fdf721888d116674814c3ddb1972c5ec6588c2506986", + "withdrawal_credentials": "0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": 32000000000, + "signature": "a13a9419fd3a6efd1fed6fe2a9b09475d8bda5df141c2e7ca44aa659c21a042aea076a6e571b96e7efb2fc504ac7743301dbd6ad28df04339eea2dd6b16de5a8534df13e1d573e0695d3071de731737ef7afd9520ad14d213347032b483f1d68", + "deposit_message_root": "b0d8277db9b4072039a60d641bfca99a5b4e163d163d90f7ed0fc0429c75b7db", + "deposit_data_root": "e73f77a8388bbb9d87eda265d6ae7868e1ea808f200b8cbca1a39fd17b9cb363", + "fork_version": "01017000", + "network_name": "holesky", + "deposit_cli_version": "2.7.0" + }, + { + "pubkey": "a54440e320af34dfb3963175be103ec951f9b3f0d4aa82fd5eef0a42fdc216af8249297d213e23e91449d539d5726e95", + "withdrawal_credentials": "0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": 32000000000, + "signature": "a8154d7b3daece140f22ca5a66371bac2e0329a9dbb1deda24108cdb9d4b04e47f4696adc5889dae22be6145a28256fc0d587bf3a6a066edd4de109e51a8941b8b9f6ee61f84891bc26ee3693323b294781de14760674f023af21184b335b699", + "deposit_message_root": "9339492e6f4ad30b191aa5c4c495cd5fb3835357036dfad002fba96cef5b2743", + "deposit_data_root": "68febc17db64a1d4c41ada21c257542a51bb9df63db5bfefb63557a39d5048fa", + "fork_version": "01017000", + "network_name": "holesky", + "deposit_cli_version": "2.7.0" + }, + { + "pubkey": "a92a6349443cb28db93056ae73362791cccb854da50c6acf7ab23ab4b6a6ff8fe479ad85f2334b40d37b3fc2594ab2f5", + "withdrawal_credentials": "0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": 32000000000, + "signature": "b631d3e0d2cfedff007ac5d5a7eb294de60d0e75405f5d612fdee9a3da5a83ee2ee40faeae44d775e96a99a0f3dbbc9f1023f2b41ef32a2201e2a6b46253849e3c224dbdec8d80ed89ffc7f1bd82154ef665604231c86312ec8319f95156a837", + "deposit_message_root": "ecfa84f6bf752a54438540e280a16b551b48c092108e73526a9289f89ece5aa6", + "deposit_data_root": "beec5ed130b83b0ab3d07d994e3b9f6155703ccd1ee825d877260cfdf117266b", + "fork_version": "01017000", + "network_name": "holesky", + "deposit_cli_version": "2.7.0" + }, + { + "pubkey": "b817ffd0770913f31a0c2ca37bf8777b5838f3871fc804558c6cbebb29d610d5d2c64b5bcd361584a7e6ba30795ac6c4", + "withdrawal_credentials": "0100000000000000000000007ce7390c41ce3416c4a0a297761c71763d89ca3b", + "amount": 32000000000, + "signature": "996be98ac1ab42aa2594fb5345578c6ee01d9d11facb61c623119b6b2bf3ea88bd73fcc7a386c0c712d30493c7d2cc7a09471a360d8186a65ca072ad3419fe44bfa43b5a179c43e050839d1681556273001bec3ec6829e6dad87975cf36f31fe", + "deposit_message_root": "eeae8e9cda1c60073cdb182894a934f644a9c9a3f1d14f6be579232214126af2", + "deposit_data_root": "0a5868d133b29a19e130422532badba536fe5820a48b5a35702bef84f7cce742", + "fork_version": "01017000", + "network_name": "holesky", + "deposit_cli_version": "2.7.0" + } +] \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-0.json b/cli/actions/testdata/charon/validator_keys/keystore-0.json new file mode 100644 index 00000000..1b49b12f --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-0.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "checksum": { + "function": "sha256", + "message": "e9f381a02e05aaace38ef3bf623d42fb0b7afa1652c23ec85fc7ac784e377ef8", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "870a45577062e499f7077474bd9cc4f1ab54948438aa6331b39743d80a09e59d", + "params": { + "iv": "8d254fc1f1358e53c60e438c6a1b2254" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "6236ed00a3647288a11b3b9a1030727b5d02cc3f1b23f3111134cfe22d464dc8" + } + } + }, + "description": "", + "pubkey": "937efb721750cc7a9f6285647282bb3eb6a015785be164f44b563951d65c54e7bb744c73dd2755327913f009f5948923", + "path": "m/12381/3600/0/0/0", + "uuid": "CAB6EB10-CAAD-2EF0-672F-5774CD629B59", + "version": 4 +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-0.txt b/cli/actions/testdata/charon/validator_keys/keystore-0.txt new file mode 100644 index 00000000..d88c8450 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-0.txt @@ -0,0 +1 @@ +70efc92352783e3b09cb508733c31514 \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-1.json b/cli/actions/testdata/charon/validator_keys/keystore-1.json new file mode 100644 index 00000000..24d7b399 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-1.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "checksum": { + "function": "sha256", + "message": "b9099d846620e6913b30adb1213b2b9da92db365d9924c64f7166dd0c3948d59", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "5675f43ed5c2725d0cb77c7161ccd0a82f28dd2b9727b436d72740324b01e4f7", + "params": { + "iv": "0bf4ddd1823e1270127a1a6eadc3478e" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "7be6b3e5b258a8557163305da4c612bdbf8b9d6f430f9ac552d7b50918fc0549" + } + } + }, + "description": "", + "pubkey": "b34d921aad847e8a868a3259633ebf797659ffb7debb7f681aae9d5169b34a720cbaaea2cd87b17d440bb34f452cb8d0", + "path": "m/12381/3600/0/0/0", + "uuid": "B06F1B25-D50B-9CCB-6326-253F2E64BC5C", + "version": 4 +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-1.txt b/cli/actions/testdata/charon/validator_keys/keystore-1.txt new file mode 100644 index 00000000..a0911a40 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-1.txt @@ -0,0 +1 @@ +935d77b09a4430b1ac4f352f09e77077 \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-2.json b/cli/actions/testdata/charon/validator_keys/keystore-2.json new file mode 100644 index 00000000..2665e09e --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-2.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "checksum": { + "function": "sha256", + "message": "5fd4151f9c43670ad78f3d0386cfa6b645b87eeb45d357c3d27376aab50b1222", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "05d5a05014befddcc63cc8845f2292e66b9200f6655ed6fd67deeb66c387e7b5", + "params": { + "iv": "06125a640ac42578ca72cd74cb2b78be" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "3a08b9634d34cdfcc1d7e5c12ada5439d2436d73a1569fb589bf404064b88325" + } + } + }, + "description": "", + "pubkey": "b9d5ba740412ac60427986b1f906ebe27debfe9ca5d8631853160fbbf296b897c27f3142886ac69d5ce54d1e53abd45f", + "path": "m/12381/3600/0/0/0", + "uuid": "3412F0C5-2435-E01A-C116-ED771D463367", + "version": 4 +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-2.txt b/cli/actions/testdata/charon/validator_keys/keystore-2.txt new file mode 100644 index 00000000..d96b896f --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-2.txt @@ -0,0 +1 @@ +63b4699c34dc88eb93f96bf19bf1d328 \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-3.json b/cli/actions/testdata/charon/validator_keys/keystore-3.json new file mode 100644 index 00000000..a71ba2a4 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-3.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "checksum": { + "function": "sha256", + "message": "baa547e3b7b1994ad4b0bea96cdabb554df612d6b4b5aaa98f1497dec5515689", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "6eb279a8a4e758c986868b59170b48c61263f7d5472fdcc0127a08df330cdf33", + "params": { + "iv": "2e5ca0e1aaab98178ea394b085674e2a" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "c17443410eae76a59691fd4e82b3194e3c77082fd1d146f093a5123e774db7b5" + } + } + }, + "description": "", + "pubkey": "9234b10e8d612de8dfb90e63ac2249046069f8e3d664699593903daf15c2cb3eb97ea07a3daaabffbe69c4ada631eba7", + "path": "m/12381/3600/0/0/0", + "uuid": "913FE9DE-0C6D-6B62-B5A7-6E8C311114C2", + "version": 4 +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-3.txt b/cli/actions/testdata/charon/validator_keys/keystore-3.txt new file mode 100644 index 00000000..b301bb66 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-3.txt @@ -0,0 +1 @@ +45a1d9787d39f8408df644defe118a87 \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-4.json b/cli/actions/testdata/charon/validator_keys/keystore-4.json new file mode 100644 index 00000000..b17c3075 --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-4.json @@ -0,0 +1,31 @@ +{ + "crypto": { + "checksum": { + "function": "sha256", + "message": "c2ca683a2e0d27dff7f49318c750ce12e29e641afd4207ad28eeb100eadd8012", + "params": {} + }, + "cipher": { + "function": "aes-128-ctr", + "message": "0db16b735d66eb790c910cef2bcfce9316ef2086dc6aa62169055adb11e509d0", + "params": { + "iv": "26fe3d3b2be13bfba6c45cd3cf81b2f8" + } + }, + "kdf": { + "function": "pbkdf2", + "message": "", + "params": { + "c": 262144, + "dklen": 32, + "prf": "hmac-sha256", + "salt": "a7868e308da0af190ebf6f974dd35297331b94e14fc3a5a14c99e2b4762cc3c3" + } + } + }, + "description": "", + "pubkey": "8e173333b166ca47cad2dbbe6fcf95a37ab62b191b076e54d8a8f8374efad616761cae930893553eab7a1ff179b70e44", + "path": "m/12381/3600/0/0/0", + "uuid": "E46DE4A5-A22F-0FF6-2076-46D7A9FD0718", + "version": 4 +} \ No newline at end of file diff --git a/cli/actions/testdata/charon/validator_keys/keystore-4.txt b/cli/actions/testdata/charon/validator_keys/keystore-4.txt new file mode 100644 index 00000000..8c5a295b --- /dev/null +++ b/cli/actions/testdata/charon/validator_keys/keystore-4.txt @@ -0,0 +1 @@ +c6e5685910d5324cf084c287a43253b3 \ No newline at end of file diff --git a/cli/generate.go b/cli/generate.go index d5aec1f3..e9c400c1 100644 --- a/cli/generate.go +++ b/cli/generate.go @@ -47,8 +47,8 @@ var ( ) const ( - execution, consensus, validator, mevBoost, optimism, opExecution = "execution", "consensus", "validator", "mev-boost", "optimism", "opexecution" - jwtPathName = "jwtsecret" + execution, consensus, validator, distributedValidator, mevBoost, optimism, opExecution = "execution", "consensus", "validator", "distributedValidator", "mev-boost", "optimism", "opexecution" + jwtPathName = "jwtsecret" ) type CustomFlags struct { @@ -70,31 +70,34 @@ type OptimismFlags struct { type GenCmdFlags struct { CustomFlags OptimismFlags - executionName string - consensusName string - validatorName string - checkpointSyncUrl string - feeRecipient string - noMev bool - mevImage string - mevBoostOnVal bool - noValidator bool - jwtPath string - graffiti string - mapAllPorts bool - fallbackEL []string - elExtraFlags []string - clExtraFlags []string - vlExtraFlags []string - relayURLs []string - mevBoostUrl string - executionApiUrl string - executionAuthUrl string - consensusApiUrl string - waitEpoch int - customEnodes []string - customEnrs []string - latestVersion bool + executionName string + consensusName string + validatorName string + distributed bool + distributedValidatorName string + checkpointSyncUrl string + feeRecipient string + noMev bool + mevImage string + mevBoostOnVal bool + noValidator bool + jwtPath string + graffiti string + mapAllPorts bool + fallbackEL []string + elExtraFlags []string + clExtraFlags []string + vlExtraFlags []string + dvExtraFlags []string + relayURLs []string + mevBoostUrl string + executionApiUrl string + executionAuthUrl string + consensusApiUrl string + waitEpoch int + customEnodes []string + customEnrs []string + latestVersion bool } func GenerateCmd(sedgeAction actions.SedgeActions) *cobra.Command { @@ -309,46 +312,49 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio // Generate docker-compose scripts gd := generate.GenData{ - ExecutionClient: combinedClients.Execution, - ConsensusClient: combinedClients.Consensus, - ValidatorClient: combinedClients.Validator, - ExecutionOPClient: combinedClients.ExecutionOP, - OptimismClient: combinedClients.Optimism, - Network: network, - CheckpointSyncUrl: flags.checkpointSyncUrl, - FeeRecipient: flags.feeRecipient, - JWTSecretPath: flags.jwtPath, - Graffiti: flags.graffiti, - FallbackELUrls: flags.fallbackEL, - ElExtraFlags: flags.elExtraFlags, - ClExtraFlags: flags.clExtraFlags, - VlExtraFlags: flags.vlExtraFlags, - ElOpExtraFlags: flags.elOpExtraFlags, - OpExtraFlags: flags.opExtraFlags, - IsBase: flags.isBase, - MapAllPorts: flags.mapAllPorts, - Mev: !flags.noMev && utils.Contains(services, validator) && utils.Contains(services, consensus) && !flags.noValidator, - MevImage: flags.mevImage, - LoggingDriver: configs.GetLoggingDriver(logging), - RelayURLs: flags.relayURLs, - MevBoostService: utils.Contains(services, mevBoost), - MevBoostEndpoint: flags.mevBoostUrl, - Services: services, - VLStartGracePeriod: uint(vlStartGracePeriod.Seconds()), - ExecutionApiUrl: executionApiUrl, - ExecutionAuthUrl: executionAuthUrl, - ConsensusApiUrl: consensusApiUrl, - ECBootnodes: flags.customEnodes, - CCBootnodes: flags.customEnrs, - CustomChainSpecPath: flags.CustomFlags.customChainSpec, - CustomNetworkConfigPath: flags.CustomFlags.customNetworkConfig, - CustomGenesisPath: flags.CustomFlags.customGenesis, - CustomDeployBlock: flags.customDeployBlock, - CustomDeployBlockPath: flags.CustomFlags.customDeployBlock, - MevBoostOnValidator: flags.mevBoostOnVal, - ContainerTag: containerTag, - LatestVersion: flags.latestVersion, - JWTSecretOP: jwtSecretOP, + ExecutionClient: combinedClients.Execution, + ConsensusClient: combinedClients.Consensus, + ValidatorClient: combinedClients.Validator, + Distributed: flags.distributed, + DistributedValidatorClient: combinedClients.DistributedValidator, + ExecutionOPClient: combinedClients.ExecutionOP, + OptimismClient: combinedClients.Optimism, + Network: network, + CheckpointSyncUrl: flags.checkpointSyncUrl, + FeeRecipient: flags.feeRecipient, + JWTSecretPath: flags.jwtPath, + Graffiti: flags.graffiti, + FallbackELUrls: flags.fallbackEL, + ElExtraFlags: flags.elExtraFlags, + ClExtraFlags: flags.clExtraFlags, + VlExtraFlags: flags.vlExtraFlags, + DvExtraFlags: flags.dvExtraFlags, + ElOpExtraFlags: flags.elOpExtraFlags, + OpExtraFlags: flags.opExtraFlags, + IsBase: flags.isBase, + MapAllPorts: flags.mapAllPorts, + Mev: !flags.noMev && utils.Contains(services, validator) && utils.Contains(services, consensus) && !flags.noValidator, + MevImage: flags.mevImage, + LoggingDriver: configs.GetLoggingDriver(logging), + RelayURLs: flags.relayURLs, + MevBoostService: utils.Contains(services, mevBoost), + MevBoostEndpoint: flags.mevBoostUrl, + Services: services, + VLStartGracePeriod: uint(vlStartGracePeriod.Seconds()), + ExecutionApiUrl: executionApiUrl, + ExecutionAuthUrl: executionAuthUrl, + ConsensusApiUrl: consensusApiUrl, + ECBootnodes: flags.customEnodes, + CCBootnodes: flags.customEnrs, + CustomChainSpecPath: flags.CustomFlags.customChainSpec, + CustomNetworkConfigPath: flags.CustomFlags.customNetworkConfig, + CustomGenesisPath: flags.CustomFlags.customGenesis, + CustomDeployBlock: flags.customDeployBlock, + CustomDeployBlockPath: flags.CustomFlags.customDeployBlock, + MevBoostOnValidator: flags.mevBoostOnVal, + ContainerTag: containerTag, + LatestVersion: flags.latestVersion, + JWTSecretOP: jwtSecretOP, } _, err = sedgeAction.Generate(actions.GenerateOptions{ GenerationData: gd, @@ -377,6 +383,7 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services []string) (*clients.Clients, error) { var executionClient, consensusClient, validatorClient, executionOpClient, opClient *clients.Client + var distributedValidatorClient *clients.Client var err error // execution client @@ -495,12 +502,32 @@ func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services executionOpClient = nil } + // distributed validator client + if utils.Contains(services, distributedValidator) { + distributedValidatorClient, _ = clients.RandomChoice(allClients[distributedValidator]) + if flags.distributedValidatorName != "" { + distributedValidatorParts := strings.Split(flags.distributedValidatorName, ":") + distributedValidatorClient.Name = distributedValidatorParts[0] + if len(distributedValidatorParts) > 1 { + distributedValidatorClient.Image = strings.Join(distributedValidatorParts[1:], ":") + } + distributedValidatorClient.SetImageOrDefault(strings.Join(distributedValidatorParts[1:], ":")) + } else { + distributedValidatorClient.Name = "charon" + distributedValidatorClient.SetImageOrDefault("") + } + if err = clients.ValidateClient(distributedValidatorClient, distributedValidator); err != nil { + return nil, err + } + } + return &clients.Clients{ - Execution: executionClient, - Consensus: consensusClient, - Validator: validatorClient, - ExecutionOP: executionOpClient, - Optimism: opClient, + Execution: executionClient, + Consensus: consensusClient, + Validator: validatorClient, + DistributedValidator: distributedValidatorClient, + ExecutionOP: executionOpClient, + Optimism: opClient, }, err } diff --git a/cli/generate_test.go b/cli/generate_test.go index 0677e3e5..5fb469f7 100644 --- a/cli/generate_test.go +++ b/cli/generate_test.go @@ -156,6 +156,12 @@ func (flags *GenCmdFlags) argsList() []string { if flags.latestVersion { s = append(s, "--latest") } + if flags.distributed { + s = append(s, "--distributed") + } + if flags.distributedValidatorName != "" { + s = append(s, "--distributedValidator", flags.distributedValidatorName) + } return s } @@ -1316,6 +1322,42 @@ func TestGenerateCmd(t *testing.T) { globalFlags{}, nil, }, + { + "full-node random client Distributed", + subCmd{ + name: "full-node", + args: []string{}, + }, + GenCmdFlags{ + distributed: true, + validatorName: "lighthouse", + }, + globalFlags{ + network: "holesky", + }, + nil, + }, + { + "full-node Fixed clients with DV", + subCmd{ + name: "full-node", + args: []string{}, + }, + GenCmdFlags{ + distributed: true, + executionName: "nethermind", + consensusName: "lighthouse", + validatorName: "lighthouse", + distributedValidatorName: "charon", + feeRecipient: "0x0000000000000000000000000000000000000000", + }, + globalFlags{ + install: false, + logging: "", + network: "holesky", + }, + nil, + }, { "Optimism full node", subCmd{ diff --git a/cli/importKeys.go b/cli/importKeys.go index 44102a30..b076590a 100644 --- a/cli/importKeys.go +++ b/cli/importKeys.go @@ -37,6 +37,7 @@ func ImportKeysCmd(sedgeActions actions.SedgeActions, depsMgr dependencies.Depen customConfigPath string customGenesisPath string customDeployBlock string + distributed bool ) cmd := &cobra.Command{ @@ -86,6 +87,7 @@ the importation.`, GenesisPath: customGenesisPath, DeployBlockPath: customDeployBlock, }, + Distributed: distributed, } var err error if validatorClient == "nimbus" { @@ -115,5 +117,6 @@ the importation.`, cmd.Flags().StringVar(&customConfigPath, "custom-config", "", "file path or url to use as custom network config.") cmd.Flags().StringVar(&customGenesisPath, "custom-genesis", "", "file path or url to use as custom network genesis.") cmd.Flags().StringVar(&customDeployBlock, "custom-deploy-block", "", "custom network deploy block.") + cmd.Flags().BoolVar(&distributed, "distributed", false, "Import keys generated by Distributed Key Generation (DKG) process") return cmd } diff --git a/cli/sub_gen.go b/cli/sub_gen.go index 2f36f1e0..bc56e466 100644 --- a/cli/sub_gen.go +++ b/cli/sub_gen.go @@ -79,6 +79,9 @@ Additionally, you can use this syntax ':' to override the } else if flags.validatorName == "" { flags.validatorName = flags.consensusName } + if flags.distributed { + services = append(services, distributedValidator) + } return runGenCmd(cmd.OutOrStdout(), &flags, sedgeAction, services) }, } @@ -86,6 +89,8 @@ Additionally, you can use this syntax ':' to override the cmd.Flags().StringVarP(&flags.consensusName, "consensus", "c", "", "Consensus engine client, e.g. teku, lodestar, prysm, lighthouse, Nimbus. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name") cmd.Flags().StringVarP(&flags.executionName, "execution", "e", "", "Execution engine client, e.g. geth, nethermind, besu, erigon. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name") cmd.Flags().StringVarP(&flags.validatorName, "validator", "v", "", "Validator engine client, e.g. teku, lodestar, prysm, lighthouse, Nimbus. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name") + cmd.Flags().BoolVar(&flags.distributed, "distributed", false, "Deploy a node configured to run as part of a Distributed Validator Cluster.") + cmd.Flags().StringVarP(&flags.distributedValidatorName, "distributedValidator", "d", "", "Distributed Validator client, e.g. charon. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name") cmd.Flags().BoolVar(&flags.latestVersion, "latest", false, "Use the latest version of clients. This sets the \"latest\" tag on the client's docker images. Latest version might not work.") cmd.Flags().StringVar(&flags.checkpointSyncUrl, "checkpoint-sync-url", "", "Initial state endpoint (trusted synced consensus endpoint) for the consensus client to sync from a finalized checkpoint. Provide faster sync process for the consensus client and protect it from long-range attacks affored by Weak Subjetivity. Each network has a default checkpoint sync url.") cmd.Flags().StringVar(&flags.feeRecipient, "fee-recipient", "", "Suggested fee recipient. Is a 20-byte Ethereum address which the execution layer might choose to set as the coinbase and the recipient of other fees or rewards. There is no guarantee that an execution node will use the suggested fee recipient to collect fees, it may use any address it chooses. It is assumed that an honest execution node will use the suggested fee recipient, but users should note this trust assumption.\n"+ @@ -102,6 +107,7 @@ Additionally, you can use this syntax ':' to override the cmd.Flags().StringArrayVar(&flags.elExtraFlags, "el-extra-flag", []string{}, "Additional flag to configure the execution client service in the generated docker-compose script. Example: 'sedge generate full-node --el-extra-flag \"=value1\" --el-extra-flag \"=\\\"value2\\\"\"'") cmd.Flags().StringArrayVar(&flags.clExtraFlags, "cl-extra-flag", []string{}, "Additional flag to configure the consensus client service in the generated docker-compose script. Example: 'sedge generate full-node --cl-extra-flag \"=value1\" --cl-extra-flag \"=\\\"value2\\\"\"'") cmd.Flags().StringArrayVar(&flags.vlExtraFlags, "vl-extra-flag", []string{}, "Additional flag to configure the validator client service in the generated docker-compose script. Example: 'sedge generate full-node --vl-extra-flag \"=value1\" --vl-extra-flag \"=\\\"value2\\\"\"'") + cmd.Flags().StringArrayVar(&flags.dvExtraFlags, "dv-extra-flag", []string{}, "Additional flag to configure the distributed validator client service in the generated docker-compose script. Example: 'sedge generate full-node --distributed --dv-extra-flag \"=value1\" --dv-extra-flag \"=\\\"value2\\\"\"'") cmd.Flags().StringVar(&flags.customChainSpec, "custom-chainSpec", "", "File path or url to use as custom network chainSpec for execution client.") cmd.Flags().StringVar(&flags.customNetworkConfig, "custom-config", "", "File path or url to use as custom network config file for consensus client.") cmd.Flags().StringVar(&flags.customGenesis, "custom-genesis", "", "File path or url to use as custom network genesis for consensus client.") diff --git a/configs/client_images.yaml b/configs/client_images.yaml index 26d493e3..084019bf 100644 --- a/configs/client_images.yaml +++ b/configs/client_images.yaml @@ -43,6 +43,10 @@ validator: nimbus: name: statusim/nimbus-validator-client version: multiarch-v24.10.0 +distributed: + charon: + name: ghcr.io/obolnetwork/charon + version: v1.1.2 optimism: opnode: name: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node diff --git a/configs/constants.go b/configs/constants.go index eadcb910..a552e970 100644 --- a/configs/constants.go +++ b/configs/constants.go @@ -18,7 +18,8 @@ package configs var Dependencies []string = []string{"docker"} const ( - OnPremiseExecutionURL = "http://execution" - OnPremiseConsensusURL = "http://consensus" - OnPremiseOpExecutionURL = "http://execution-l2" + OnPremiseExecutionURL = "http://execution" + OnPremiseConsensusURL = "http://consensus" + OnPremiseDistributedValidatorURL = "http://dv" + OnPremiseOpExecutionURL = "http://execution-l2" ) diff --git a/configs/images.go b/configs/images.go index 7f3af8ea..cf78cc0f 100644 --- a/configs/images.go +++ b/configs/images.go @@ -37,6 +37,10 @@ var ClientImages struct { Teku Image `yaml:"teku"` Prysm Image `yaml:"prysm"` Nimbus Image `yaml:"nimbus"` + Charon Image `yaml:"charon"` + } + Distributed struct { + Charon Image `yaml:"charon"` } Optimism struct { OpNode Image `yaml:"opnode"` diff --git a/configs/messages.go b/configs/messages.go index d4b9be4f..4e758ab7 100644 --- a/configs/messages.go +++ b/configs/messages.go @@ -84,27 +84,28 @@ Your full-node is up and running. If you set up new validator keys, you will hav Happy Staking! ` - ExecutionClientNotSpecifiedWarn = "Execution client not provided. A random client will be selected. Random client: %s" - ConsensusClientNotSpecifiedWarn = "Consensus client not provided. Selecting same pair of clients for consensus and validator clients" - ValidatorClientNotSpecifiedWarn = "Validator client not provided. Selecting same pair of clients for consensus and validator clients" - CLNotSpecifiedWarn = "Consensus and validator clients not provided. Selecting same pair of clients for consensus and validator clients using a random client. Random client: %s" - GeneratingJWTSecret = "Generating JWT secret for client authentication" - JWTSecretGenerated = "JWT secret generated" - CreatingKeystorePassword = "Creating keystore_password.txt on keystore folder" - KeystorePasswordCreated = "keystore_password.txt on keystore folder created with provided password" - MnemonicTips = "The following mnemonic is going to be used to create the validator keystore. Please save it carefully. It can be used to generate the keystore folder again. If you lose the password and mnemonic, access to your keystore will be lost forever!" - GeneratingMnemonic = "Existing mnemonic not provided. Generating mnemonic for validator keystore:" - StoreMnemonic = "Make sure to store your mnemonic somewhere safe. Losing it could end in the lost of your validators. Press enter to continue" // TODO: improve warning message - PreparingTekuDatadir = "Preparing teku datadirs (must have full read/write/execute permissions to work)" - GettingContainersIP = "Proceeding to get execution and consensus containers IP address for the monitoring tool" - WaitingForNodesToStart = "Waiting a minute for nodes to start" - CustomExecutionImagesWarning = "You are using custom images for the execution client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." - CustomConsensusImagesWarning = "You are using custom images for the consensus client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." - CustomValidatorImagesWarning = "You are using custom images for the validator client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." - MapAllPortsWarning = "You are mapping all ports for the clients!!! Make sure this is intended. This could make the clients vulnerable to attacks. Be sure to setup a firewall." - CheckpointUrlUsedWarning = "A Checkpoint Sync Url will be used for the consensus node. Using %s ." - NoBootnodesFound = "No bootnodes found in %s env file " - UnableToCheckVersion = "Unable to check for new Version. Please check manually at " + + ExecutionClientNotSpecifiedWarn = "Execution client not provided. A random client will be selected. Random client: %s" + ConsensusClientNotSpecifiedWarn = "Consensus client not provided. Selecting same pair of clients for consensus and validator clients" + ValidatorClientNotSpecifiedWarn = "Validator client not provided. Selecting same pair of clients for consensus and validator clients" + CLNotSpecifiedWarn = "Consensus and validator clients not provided. Selecting same pair of clients for consensus and validator clients using a random client. Random client: %s" + GeneratingJWTSecret = "Generating JWT secret for client authentication" + JWTSecretGenerated = "JWT secret generated" + CreatingKeystorePassword = "Creating keystore_password.txt on keystore folder" + KeystorePasswordCreated = "keystore_password.txt on keystore folder created with provided password" + MnemonicTips = "The following mnemonic is going to be used to create the validator keystore. Please save it carefully. It can be used to generate the keystore folder again. If you lose the password and mnemonic, access to your keystore will be lost forever!" + GeneratingMnemonic = "Existing mnemonic not provided. Generating mnemonic for validator keystore:" + StoreMnemonic = "Make sure to store your mnemonic somewhere safe. Losing it could end in the lost of your validators. Press enter to continue" // TODO: improve warning message + PreparingTekuDatadir = "Preparing teku datadirs (must have full read/write/execute permissions to work)" + GettingContainersIP = "Proceeding to get execution and consensus containers IP address for the monitoring tool" + WaitingForNodesToStart = "Waiting a minute for nodes to start" + CustomExecutionImagesWarning = "You are using custom images for the execution client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." + CustomConsensusImagesWarning = "You are using custom images for the consensus client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." + CustomValidatorImagesWarning = "You are using custom images for the validator client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." + CustomDistributedValidatorImagesWarning = "You are using custom images for the distributed validator client!!! Make sure this is intended. Also check these images are correct and available from this device otherwise the setup will fail or have an unexpected behavior." + MapAllPortsWarning = "You are mapping all ports for the clients!!! Make sure this is intended. This could make the clients vulnerable to attacks. Be sure to setup a firewall." + CheckpointUrlUsedWarning = "A Checkpoint Sync Url will be used for the consensus node. Using %s ." + NoBootnodesFound = "No bootnodes found in %s env file " + UnableToCheckVersion = "Unable to check for new Version. Please check manually at " + "https://github.com/NethermindEth/sedge/releases, with error:" NeedVersionUpdate = "A new Version of sedge is available. Please update to the latest Version. See " + "https://github.com/NethermindEth/sedge/releases for more information. Latest detected tag:" diff --git a/configs/paths.go b/configs/paths.go index 1912a04c..0886e1fe 100644 --- a/configs/paths.go +++ b/configs/paths.go @@ -27,4 +27,5 @@ const ( ConsensusDir = "consensus-data" ValidatorDir = "validator-data" KeystoreDir = "keystore" + DistributedValidatorDir = ".charon" ) diff --git a/configs/ports.go b/configs/ports.go index 5b07f82a..60281f45 100644 --- a/configs/ports.go +++ b/configs/ports.go @@ -27,6 +27,9 @@ const ( DefaultAdditionalApiPortCL uint16 = 4001 DefaultMetricsPortVL uint16 = 5056 DefaultMevPort uint16 = 18550 + DefaultDiscoveryPortDV uint16 = 3610 + DefaultMetricsPortDV uint16 = 3620 + DefaultApiPortDV uint16 = 3600 DefaultApiPortELOP uint16 = 8547 DefaultAuthPortELOP uint16 = 8552 DefaultDiscoveryPortELOP uint16 = 30313 diff --git a/docs/docs/commands/generate.mdx b/docs/docs/commands/generate.mdx index 04b14300..b2706036 100644 --- a/docs/docs/commands/generate.mdx +++ b/docs/docs/commands/generate.mdx @@ -88,6 +88,8 @@ Flags: -c, --consensus string Consensus engine client, e.g. teku, lodestar, prysm, lighthouse, Nimbus. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name -e, --execution string Execution engine client, e.g. geth, nethermind, besu, erigon. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name -v, --validator string Validator engine client, e.g. teku, lodestar, prysm, lighthouse, Nimbus. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name + --distributed boolean Deploy a node configured to run as part of a Distributed Validator Cluster. + -d --distributedValidator string Distributed Validator client, e.g. charon. Additionally, you can use this syntax ':' to override the docker image used for the client. If you want to use the default docker image, just use the client name --latest Use the latest version of clients. This sets the "latest" tag on the client's docker images. Latest version might not work. --checkpoint-sync-url string Initial state endpoint (trusted synced consensus endpoint) for the consensus client to sync from a finalized checkpoint. Provide faster sync process for the consensus client and protect it from long-range attacks affored by Weak Subjetivity. Each network has a default checkpoint sync url. --fee-recipient string Suggested fee recipient. Is a 20-byte Ethereum address which the execution layer might choose to set as the coinbase and the recipient of other fees or rewards. There is no guarantee that an execution node will use the suggested fee recipient to collect fees, it may use any address it chooses. It is assumed that an honest execution node will use the suggested fee recipient, but users should note this trust assumption. @@ -104,6 +106,7 @@ Flags: --el-extra-flag stringArray Additional flag to configure the execution client service in the generated docker-compose script. Example: 'sedge generate full-node --el-extra-flag "=value1" --el-extra-flag "=\"value2\""' --cl-extra-flag stringArray Additional flag to configure the consensus client service in the generated docker-compose script. Example: 'sedge generate full-node --cl-extra-flag "=value1" --cl-extra-flag "=\"value2\""' --vl-extra-flag stringArray Additional flag to configure the validator client service in the generated docker-compose script. Example: 'sedge generate full-node --vl-extra-flag "=value1" --vl-extra-flag "=\"value2\""' + --dv-extra-flag stringArray Additional flag to configure the distributed validator client service in the generated docker-compose script. Example: 'sedge generate full-node --distributed --dv-extra-flag "=value1" --dv-extra-flag "CHARON_FEATURE_SET=\"alpha\""' --custom-chainSpec string File path or url to use as custom network chainSpec for execution client. --custom-config string File path or url to use as custom network config file for consensus client. --custom-genesis string File path or url to use as custom network genesis for consensus client. diff --git a/docs/docs/commands/importKey.mdx b/docs/docs/commands/importKey.mdx index 55ab55a0..9f6ec66e 100644 --- a/docs/docs/commands/importKey.mdx +++ b/docs/docs/commands/importKey.mdx @@ -40,6 +40,7 @@ Flags: --custom-config string file path or url to use as custom network config. --custom-deploy-block string custom network deploy block. --custom-genesis string file path or url to use as custom network genesis. + --distributed boolean Import keys generated in a Distributed Key Generation (DKG) process --from string path to the validator keys, must follow the EIP-2335: BLS12-381 Keystore standard (default "[WORK_DIR]/sedge-data/keystore") -h, --help help for import-key -n, --network string network (default "mainnet") diff --git a/docs/docs/quickstart/charon.mdx b/docs/docs/quickstart/charon.mdx new file mode 100644 index 00000000..a4e69322 --- /dev/null +++ b/docs/docs/quickstart/charon.mdx @@ -0,0 +1,123 @@ +--- +sidebar_position: 10 +id: staking-with-obol-DV +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Set up an Obol DV Node with Sedge + +## What are Distributed Validators? + +**[Read more about Obol DVs here](https://docs.obol.org/docs/int/key-concepts)** + +Sedge supports setting up distributed validator nodes just like it supports setting up a traditional validator node. This guide will walk you through the process of setting up a DV node using Sedge. + +:::tip + +Read more about how to generate **[Distributed Validator keys here](https://docs.obol.org/docs/next/start/quickstart_group)**. + +::: + +## Workflow breakdown + +The Obol DV Setup process involves a few steps: + +1. **Perform Obol DKG process**: Generate distributed node contents and validator keys compatible with Obol DVT using [DKG process](https://docs.obol.org/docs/next/start/quickstart_group). +2. **Set Up Your Full Node**: Set up your full node with **sedge generate** command. +3. **Import DKG Validator keys**: Import the keys generated in Step 1 using the **sedge import_key** command +4. **Run the cluster**: Run the cluster using **sedge run** command. + +Let's dive into each step in detail. + +## Using Sedge for setting up Obol DV node + +### Perform DKG and Generate DKG components + +To get started with Obol DVT using Sedge, you first need to generate your validator keys and deposit data and DV node components with [Obol DV Launchpad](https://holesky.launchpad.obol.org/). +You need to follow the steps listed below +1. [Step 1: Get your ENR](https://docs.obol.org/docs/next/start/quickstart_group#step-1-get-your-enr) +2. [Step 2: Create a cluster or accept an invitation to a cluster](https://docs.obol.org/docs/next/start/quickstart_group#step-2-create-a-cluster-or-accept-an-invitation-to-a-cluster) +3. [Step 3: Run the Distributed Key Generation (DKG) ceremony](https://docs.obol.org/docs/next/start/quickstart_group#step-3-run-the-distributed-key-generation-dkg-ceremony) + +At this stage, if DKG process is successful, a folder with name **.charon** will be created with the following structure for a cluster of 2 validators. + +```bash +$sedge % tree .charon +. +├── charon-enr-private-key +├── cluster-lock.json +├── deposit-data.json +└── validator_keys + ├── keystore-0.json + ├── keystore-0.txt + ├── keystore-1.json + ├── keystore-1.txt +``` + +### Setting up your full node + +Once the DKG process is complete and `.charon` folder and its contents are generated, you can set up your full node using **Sedge**: + +```bash +sedge generate full-node --validator=teku --consensus=prysm --execution=nethermind --network=holesky --distributed +``` + +:::note + +This command will generate a DV enabled cluster for the Holesky testnet. +If supported, you can set other networks by changing the `--network` flag. +If supported, you can set other execution, consensus and validator clients using the respective flags. +::: + +Once the full node setup process is complete a folder with name `sedge-data` will be created. + +```bash +$sedge % tree sedge-data +tree sedge-data +sedge-data +├── docker-compose.yml +└── jwtsecret +``` +Next, run the import keys step: + +```bash +sedge import-key --distributed --network holesky teku +``` + +:::note + +This command will import the keys inside `.charon` folder into the validator client. +If supported, you can set other networks by changing the `--network` flag. +The validator client should be identical to the client used in the full-node generate command +::: + +Once the private keys import process is complete, the `sedge-data` folder contents will be updated. + +```bash +sedge % tree sedge-data +sedge-data +├── docker-compose.yml +├── jwtsecret +├── keystore +│ ├── deposit-data.json +│ ├── keystore-0.txt +│ ├── keystore-1.txt +│ └── validator_keys +│ ├── keystore-0.json +│ ├── keystore-1.json +└── validator-data + ├── keys + │ ├── keystore-0.json + │ ├── keystore-1.json + └── passwords + ├── keystore-0.txt + ├── keystore-1.txt +``` + +Finally run the cluster. + +```bash +sedge run +``` \ No newline at end of file diff --git a/internal/images/validator-import/lighthouse/context/Dockerfile b/internal/images/validator-import/lighthouse/context/Dockerfile index 078043c1..9cf38642 100644 --- a/internal/images/validator-import/lighthouse/context/Dockerfile +++ b/internal/images/validator-import/lighthouse/context/Dockerfile @@ -8,7 +8,7 @@ ARG NETWORK RUN apt-get update && apt-get install -y curl dos2unix bash RUN version=$(echo $LH_VERSION | cut -d':' -f 2) \ -&& curl -L https://github.com/sigp/lighthouse/releases/download/$version/lighthouse-$version-x86_64-unknown-linux-gnu.tar.gz --output lh.gz +&& curl -L https://github.com/sigp/lighthouse/releases/download/$version/lighthouse-$version-aarch64-unknown-linux-gnu.tar.gz --output lh.gz RUN tar -xvf lh.gz diff --git a/internal/images/validator-import/lighthouse/context/validator-init.sh b/internal/images/validator-import/lighthouse/context/validator-init.sh index c4bf4c6c..209f2925 100644 --- a/internal/images/validator-import/lighthouse/context/validator-init.sh +++ b/internal/images/validator-import/lighthouse/context/validator-init.sh @@ -5,11 +5,24 @@ for key in /keystore/validator_keys/*; do if [ -f "$key" ]; then echo "Found validator key in $key" echo "Importing validator..." - if [ $VAL_NETWORK = "custom" ]; then - ./lighthouse account validator import --testnet-dir /network_config --password-file /keystore/keystore_password.txt --keystore $key --reuse-password --datadir /data + + # Extract filename without extension + FILENAME=$(basename "$key" .json) + + # Check if a corresponding password file exists for each key (generated by DKG) + if [ -f "/keystore/${FILENAME}.txt" ]; then + PASSWORD_FILE="/keystore/${FILENAME}.txt" else - ./lighthouse account validator import --network $VAL_NETWORK --password-file /keystore/keystore_password.txt --keystore $key --reuse-password --datadir /data + # Use default password file + PASSWORD_FILE="/keystore/keystore_password.txt" fi + + if [ "$VAL_NETWORK" = "custom" ]; then + ./lighthouse account validator import --testnet-dir /network_config --password-file "$PASSWORD_FILE" --keystore "$key" --reuse-password --datadir /data + else + ./lighthouse account validator import --network "$VAL_NETWORK" --password-file "$PASSWORD_FILE" --keystore "$key" --reuse-password --datadir /data + fi + echo "Validator imported" fi done diff --git a/internal/images/validator-import/prysm/context/Dockerfile b/internal/images/validator-import/prysm/context/Dockerfile new file mode 100644 index 00000000..599730cc --- /dev/null +++ b/internal/images/validator-import/prysm/context/Dockerfile @@ -0,0 +1,15 @@ +ARG PRYSM_VERSION +ARG NETWORK + +FROM ${PRYSM_VERSION} as prysm-validator + +FROM debian:buster-slim +COPY --from=prysm-validator /app/cmd/validator/validator validator + +VOLUME [ "/keystore" ] + +COPY validator-init.sh . + +RUN chmod +x validator-init.sh + +CMD ["/bin/sh", "validator-init.sh"] \ No newline at end of file diff --git a/internal/images/validator-import/prysm/context/validator-init.sh b/internal/images/validator-import/prysm/context/validator-init.sh new file mode 100644 index 00000000..af13e09a --- /dev/null +++ b/internal/images/validator-import/prysm/context/validator-init.sh @@ -0,0 +1,29 @@ +#!/bin/sh +WALLET_DIR="/data/wallet" +rm -rf $WALLET_DIR +mkdir $WALLET_DIR + +/validator wallet create --accept-terms-of-use --wallet-password-file=/keystore/keystore_password.txt --keymanager-kind=direct --wallet-dir="$WALLET_DIR" + +tmpkeys="/tmpkeys" +mkdir -p ${tmpkeys} + +for f in /keystore/validator_keys/keystore-*.json; do + echo "Importing key ${f}" + FILENAME=`echo ${key} | sed 's/.json//g'` + cp "${f}" "${tmpkeys}" + + /validator accounts import \ + --accept-terms-of-use=true \ + --wallet-dir="$WALLET_DIR" \ + --keys-dir="${tmpkeys}" \ + --account-password-file="/keystore/${FILENAME}.txt" \ + --wallet-password-file=/keystore/keystore-password.txt + + filename="$(basename ${f})" + rm "${tmpkeys}/${filename}" +done + +rm -r ${tmpkeys} + +echo "Imported all keys" \ No newline at end of file diff --git a/internal/images/validator-import/prysm/prysm.go b/internal/images/validator-import/prysm/prysm.go new file mode 100644 index 00000000..80596109 --- /dev/null +++ b/internal/images/validator-import/prysm/prysm.go @@ -0,0 +1,35 @@ +package prysm + +import ( + "embed" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +//go:embed context +var prysmContext embed.FS + +func InitContext() (string, error) { + tempDir, err := os.MkdirTemp(os.TempDir(), "sedge-validator-import-context-*") + if err != nil { + return "", fmt.Errorf("error creating prysm validator import dir context: %s", err.Error()) + } + + contextDir, err := prysmContext.ReadDir("context") + if err != nil { + return "", err + } + for _, item := range contextDir { + itemData, err := prysmContext.ReadFile(strings.Join([]string{"context", item.Name()}, "/")) + if err != nil { + return "", err + } + if err := ioutil.WriteFile(filepath.Join(tempDir, item.Name()), itemData, 0o755); err != nil { + return "", err + } + } + return tempDir, nil +} diff --git a/internal/images/validator-import/teku/context/validator-init.sh b/internal/images/validator-import/teku/context/validator-init.sh index 9d90bc12..adf1473d 100644 --- a/internal/images/validator-import/teku/context/validator-init.sh +++ b/internal/images/validator-import/teku/context/validator-init.sh @@ -1,4 +1,3 @@ -#!/bin/bash set -e if [[ "$(ls /data/keys)" ]]; then @@ -8,11 +7,19 @@ else cd /keystore/validator_keys for key in *; do FILENAME=`echo ${key} | sed 's/.json//g'` - cp ${key} "/data/keys/${FILENAME}.json" - cp ../keystore_password.txt "/data/passwords/${FILENAME}.txt" + cp "$key" "/data/keys/${FILENAME}.json" + + # Check if a corresponding password file exists for each key (generated by DKG) + if [[ -f "../${FILENAME}.txt" ]]; then + cp "../${FILENAME}.txt" "/data/passwords/${FILENAME}.txt" + else + # Use default password file + cp "../keystore_password.txt" "/data/passwords/${FILENAME}.txt" + fi + echo "Copying ${key}" done fi -# ensure teku access for new keys +# Ensure teku access for new keys chmod -R 777 /data diff --git a/internal/images/validator-import/teku/teku.go b/internal/images/validator-import/teku/teku.go index 5f2fbebb..91f0751c 100644 --- a/internal/images/validator-import/teku/teku.go +++ b/internal/images/validator-import/teku/teku.go @@ -10,7 +10,7 @@ import ( ) //go:embed context -var lighthouseContext embed.FS +var tekuContext embed.FS func InitContext() (string, error) { tempDir, err := os.MkdirTemp(os.TempDir(), "sedge-validator-import-context-*") @@ -18,12 +18,12 @@ func InitContext() (string, error) { return "", fmt.Errorf("error creating teku validator import dir context: %s", err.Error()) } - contextDir, err := lighthouseContext.ReadDir("context") + contextDir, err := tekuContext.ReadDir("context") if err != nil { return "", err } for _, item := range contextDir { - itemData, err := lighthouseContext.ReadFile(strings.Join([]string{"context", item.Name()}, "/")) + itemData, err := tekuContext.ReadFile(strings.Join([]string{"context", item.Name()}, "/")) if err != nil { return "", err } diff --git a/internal/pkg/clients/clients_test.go b/internal/pkg/clients/clients_test.go index f7879091..35527fee 100644 --- a/internal/pkg/clients/clients_test.go +++ b/internal/pkg/clients/clients_test.go @@ -35,6 +35,7 @@ func TestSupportedClients(t *testing.T) { {"execution", "mainnet", AllClients["execution"], false}, {"consensus", "mainnet", AllClients["consensus"], false}, {"validator", "mainnet", AllClients["validator"], false}, + {"distributedValidator", "holesky", AllClients["distributedValidator"], false}, {"random", "mainnet", []string{}, true}, } @@ -138,6 +139,17 @@ func TestClients(t *testing.T) { "gnosis", false, }, + { + map[string][]string{ + "validator": {"lighthouse", "prysm", "teku", "lodestar", "nimbus"}, + "consensus": {"lighthouse", "prysm", "teku", "lodestar", "nimbus"}, + "execution": {"nethermind", "geth", "besu", "erigon"}, + "distributedValidator": {"charon"}, + }, + []string{"consensus", "execution", "validator", "distributedValidator"}, + "holesky", + false, + }, } for i, input := range inputs { diff --git a/internal/pkg/clients/init.go b/internal/pkg/clients/init.go index 28838aff..46f59c82 100644 --- a/internal/pkg/clients/init.go +++ b/internal/pkg/clients/init.go @@ -36,6 +36,9 @@ var AllClients map[string][]string = map[string][]string{ "lodestar", "nimbus", }, + "distributedValidator": { + "charon", + }, "optimism": { "opnode", }, diff --git a/internal/pkg/clients/types.go b/internal/pkg/clients/types.go index 3f4ae25a..aa6138d0 100644 --- a/internal/pkg/clients/types.go +++ b/internal/pkg/clients/types.go @@ -15,7 +15,9 @@ limitations under the License. */ package clients -import "github.com/NethermindEth/sedge/configs" +import ( + "github.com/NethermindEth/sedge/configs" +) // Client : Struct Represent a client like geth, prysm, etc type Client struct { @@ -34,6 +36,8 @@ func (c *Client) SetImageOrDefault(image string) { c.setConsensusImage(image) case "execution": c.setExecutionImage(image) + case "distributedValidator": + c.setDistributedValidatorImage(image) case "optimism": c.setOptimismImage(image) case "opexecution": @@ -84,6 +88,15 @@ func (c *Client) setValidatorImage(image string) { } } +func (c *Client) setDistributedValidatorImage(image string) { + switch c.Name { + case "charon": + c.Image = valueOrDefault(image, configs.ClientImages.Distributed.Charon.String()) + default: + c.Image = valueOrDefault(image, configs.ClientImages.Distributed.Charon.String()) + } +} + func (c *Client) setOptimismImage(image string) { switch c.Name { case "opnode": @@ -109,13 +122,14 @@ func valueOrDefault(value string, defaultValue string) string { return value } -// Clients : Struct Represent a combination of execution, consensus and validator clients +// Clients : Struct Represent a combination of execution, consensus, validator and distributed validator clients type Clients struct { - Execution *Client - Consensus *Client - Validator *Client - Optimism *Client - ExecutionOP *Client + Execution *Client + Consensus *Client + Validator *Client + Optimism *Client + ExecutionOP *Client + DistributedValidator *Client } type ClientMap map[string]*Client diff --git a/internal/pkg/clients/types_test.go b/internal/pkg/clients/types_test.go index 2a48505d..1b784d0e 100644 --- a/internal/pkg/clients/types_test.go +++ b/internal/pkg/clients/types_test.go @@ -148,6 +148,28 @@ func TestSetImageOrDefault_Validator(t *testing.T) { } } +func TestSetImageOrDefault_DistributedValidator(t *testing.T) { + tests := []struct { + client Client + expectedImage regexp.Regexp + }{ + { + client: Client{ + Name: "charon", + Type: "distributedValidator", + }, + expectedImage: *regexp.MustCompile(`^ghcr.io/obolnetwork/charon:v\d+\.\d+\.\d+$`), + }, + } + for _, test := range tests { + t.Run(test.client.Name, func(t *testing.T) { + test.client.SetImageOrDefault("") + t.Logf("print %s", test.client.Image) + assert.True(t, test.expectedImage.Match([]byte(test.client.Image))) + }) + } +} + func TestSetImageOrDefault_CustomImage(t *testing.T) { tests := []struct { client Client @@ -174,6 +196,13 @@ func TestSetImageOrDefault_CustomImage(t *testing.T) { }, customImage: "my/prysm-image:v1.0.0", }, + { + client: Client{ + Name: "charon", + Type: "distributedValidator", + }, + customImage: "my/charon-image:v1.0.0", + }, } for _, test := range tests { t.Run(test.client.Name, func(t *testing.T) { diff --git a/internal/pkg/generate/errors.go b/internal/pkg/generate/errors.go index 30b2f0cd..cf22755b 100644 --- a/internal/pkg/generate/errors.go +++ b/internal/pkg/generate/errors.go @@ -34,3 +34,6 @@ var ErrExecutionClientNotValid = errors.New("invalid execution client") // ErrValidatorClientNotValid is returned when the validator client is not valid var ErrValidatorClientNotValid = errors.New("invalid validator client") + +// ErrDistributedValidatorClientNotValid is returned when the distributed validator client is not valid +var ErrDistributedValidatorClientNotValid = errors.New("invalid distributed validator client") diff --git a/internal/pkg/generate/generate_scripts.go b/internal/pkg/generate/generate_scripts.go index b1469de7..982eed42 100644 --- a/internal/pkg/generate/generate_scripts.go +++ b/internal/pkg/generate/generate_scripts.go @@ -34,15 +34,17 @@ import ( ) const ( - execution = "execution" - consensus = "consensus" - validator = "validator" - optimism = "optimism" - opExecution = "opexecution" - validatorImport = "validator-import" - mevBoost = "mev-boost" - configConsensus = "config_consensus" - empty = "empty" + execution = "execution" + consensus = "consensus" + validator = "validator" + optimism = "optimism" + opExecution = "opexecution" + validatorImport = "validator-import" + mevBoost = "mev-boost" + configConsensus = "config_consensus" + empty = "empty" + distributedValidator = "distributedValidator" + charon = "charon" ) // validateClients validates each client in GenData @@ -57,6 +59,9 @@ func validateClients(gd *GenData) error { if err := validateValidator(gd, &c); err != nil { return err } + if err := validateDistributedValidator(gd, &c); err != nil { + return err + } return nil } @@ -75,6 +80,21 @@ func validateValidator(gd *GenData, c *clients.ClientInfo) error { return nil } +// validateDistributedValidator validates the validator client in GenData +func validateDistributedValidator(gd *GenData, c *clients.ClientInfo) error { + if gd.DistributedValidatorClient == nil { + return nil + } + distributedValidatorClients, err := c.SupportedClients(distributedValidator) + if err != nil { + return ErrUnableToGetClientsInfo + } + if !utils.Contains(distributedValidatorClients, gd.DistributedValidatorClient.Name) { + return ErrDistributedValidatorClientNotValid + } + return nil +} + // validateExecution validates the execution client in GenData func validateExecution(gd *GenData, c *clients.ClientInfo) error { if gd.ExecutionClient == nil { @@ -109,11 +129,12 @@ func validateConsensus(gd *GenData, c *clients.ClientInfo) error { // mapClients convert genData clients to clients.Clients func mapClients(gd *GenData) map[string]*clients.Client { cls := map[string]*clients.Client{ - execution: gd.ExecutionClient, - consensus: gd.ConsensusClient, - validator: gd.ValidatorClient, - optimism: gd.OptimismClient, - opExecution: gd.ExecutionOPClient, + execution: gd.ExecutionClient, + consensus: gd.ConsensusClient, + validator: gd.ValidatorClient, + optimism: gd.OptimismClient, + opExecution: gd.ExecutionOPClient, + distributedValidator: gd.DistributedValidatorClient, } return cls @@ -142,6 +163,9 @@ func ComposeFile(gd *GenData, at io.Writer) error { "CLAdditionalApi": configs.DefaultAdditionalApiPortCL, "VLMetrics": configs.DefaultMetricsPortVL, "MevPort": configs.DefaultMevPort, + "DVDiscovery": configs.DefaultDiscoveryPortDV, + "DVMetrics": configs.DefaultMetricsPortDV, + "DVApi": configs.DefaultApiPortDV, "ApiPortELOP": configs.DefaultApiPortELOP, "AuthPortELOP": configs.DefaultAuthPortELOP, "DiscoveryPortELOP": configs.DefaultDiscoveryPortELOP, @@ -253,6 +277,12 @@ func ComposeFile(gd *GenData, at io.Writer) error { } gd.MevBoostService = slices.Contains(gd.Services, "mev-boost") + if gd.Distributed { + // Check for distributed validator + if cls[distributedValidator] != nil { + gd.DistributedValidatorClient.Endpoint = configs.OnPremiseDistributedValidatorURL + } + } consensusApiUrl := gd.ConsensusApiUrl if cls[consensus] != nil && consensusApiUrl == "" { consensusApiUrl = fmt.Sprintf("%s:%v", endpointOrEmpty(cls[consensus]), gd.Ports["CLApi"]) @@ -267,6 +297,7 @@ func ComposeFile(gd *GenData, at io.Writer) error { data := DockerComposeData{ Services: gd.Services, Network: gd.Network, + Distributed: gd.Distributed, XeeVersion: xeeVersion, Mev: networkConfig.SupportsMEVBoost && (gd.MevBoostService || (mevSupported && gd.Mev)), MevBoostOnValidator: gd.MevBoostService || (mevSupported && gd.Mev) || gd.MevBoostOnValidator, @@ -296,6 +327,7 @@ func ComposeFile(gd *GenData, at io.Writer) error { NetworkPrefix: networkPrefix, ClExtraFlags: gd.ClExtraFlags, VlExtraFlags: gd.VlExtraFlags, + DvExtraFlags: gd.DvExtraFlags, ECBootnodes: strings.Join(gd.ECBootnodes, ","), CCBootnodes: strings.Join(gd.CCBootnodes, ","), CCBootnodesList: gd.CCBootnodes, @@ -316,6 +348,9 @@ func ComposeFile(gd *GenData, at io.Writer) error { GID: os.Getegid(), ContainerTag: gd.ContainerTag, ConsensusApiURL: consensusApiUrl, + DVDiscoveryPort: gd.Ports["DVDiscovery"], + DVMetricsPort: gd.Ports["DVMetrics"], + DVApiPort: gd.Ports["DVApi"], } // Save to writer @@ -465,34 +500,46 @@ func EnvFile(gd *GenData, at io.Writer) error { } + distributedValidatorApiUrl := "" + if gd.Distributed { + // Check for distributed validator + if cls[distributedValidator] != nil { + distributedValidatorApiUrl = fmt.Sprintf("%s:%v", cls[distributedValidator].Endpoint, gd.Ports["DVApi"]) + } + } + data := EnvData{ - Services: gd.Services, - Mev: networkConfig.SupportsMEVBoost && (gd.MevBoostService || (mevSupported && gd.Mev) || gd.MevBoostOnValidator), - ElImage: imageOrEmpty(cls[execution], gd.LatestVersion), - ElDataDir: "./" + configs.ExecutionDir, - CcImage: imageOrEmpty(cls[consensus], gd.LatestVersion), - CcDataDir: "./" + configs.ConsensusDir, - VlImage: imageOrEmpty(cls[validator], gd.LatestVersion), - VlDataDir: "./" + configs.ValidatorDir, - ExecutionApiURL: executionApiUrl, - ExecutionAuthURL: executionAuthUrl, - ConsensusApiURL: consensusApiUrl, - ConsensusAdditionalApiURL: consensusAdditionalApiUrl, - FeeRecipient: gd.FeeRecipient, - JWTSecretPath: gd.JWTSecretPath, - ExecutionEngineName: nameOrEmpty(cls[execution]), - ConsensusClientName: nameOrEmpty(cls[consensus]), - KeystoreDir: "./" + configs.KeystoreDir, - Graffiti: graffiti, - RelayURLs: strings.Join(gd.RelayURLs, ","), - CheckpointSyncUrl: gd.CheckpointSyncUrl, - ExecutionOPApiURL: executionOPApiUrl, - JWTOPSecretPath: gd.JWTSecretOP, - OPImageVersion: opImageVersion, - ElOpImage: elOpImage, - ElOPAuthPort: gd.Ports["AuthPortELOP"], - OpSequencerHttp: opSequencerHttp, - RethNetwork: rethNetwork, + Services: gd.Services, + Mev: networkConfig.SupportsMEVBoost && (gd.MevBoostService || (mevSupported && gd.Mev) || gd.MevBoostOnValidator), + ElImage: imageOrEmpty(cls[execution], gd.LatestVersion), + ElDataDir: "./" + configs.ExecutionDir, + CcImage: imageOrEmpty(cls[consensus], gd.LatestVersion), + CcDataDir: "./" + configs.ConsensusDir, + VlImage: imageOrEmpty(cls[validator], gd.LatestVersion), + VlDataDir: "./" + configs.ValidatorDir, + ExecutionApiURL: executionApiUrl, + ExecutionAuthURL: executionAuthUrl, + ConsensusApiURL: consensusApiUrl, + ConsensusAdditionalApiURL: consensusAdditionalApiUrl, + FeeRecipient: gd.FeeRecipient, + JWTSecretPath: gd.JWTSecretPath, + ExecutionEngineName: nameOrEmpty(cls[execution]), + ConsensusClientName: nameOrEmpty(cls[consensus]), + KeystoreDir: "./" + configs.KeystoreDir, + Graffiti: graffiti, + RelayURLs: strings.Join(gd.RelayURLs, ","), + CheckpointSyncUrl: gd.CheckpointSyncUrl, + Distributed: gd.Distributed, + DistributedValidatorApiUrl: distributedValidatorApiUrl, + DvDataDir: "./" + configs.DistributedValidatorDir, + DvImage: imageOrEmpty(cls[distributedValidator], gd.LatestVersion), + ExecutionOPApiURL: executionOPApiUrl, + JWTOPSecretPath: gd.JWTSecretOP, + OPImageVersion: opImageVersion, + ElOpImage: elOpImage, + ElOPAuthPort: gd.Ports["AuthPortELOP"], + OpSequencerHttp: opSequencerHttp, + RethNetwork: rethNetwork, } // Save to writer diff --git a/internal/pkg/generate/generate_scripts_test.go b/internal/pkg/generate/generate_scripts_test.go index 38ad4355..ed2c00d1 100644 --- a/internal/pkg/generate/generate_scripts_test.go +++ b/internal/pkg/generate/generate_scripts_test.go @@ -379,6 +379,20 @@ func TestGenerateComposeServices(t *testing.T) { }, CheckFunctions: []CheckFunc{checkExtraFlagsOnExecution, checkValidatorBlocker}, }, + { + Description: "Test Distributed Validator", + GenerationData: &GenData{ + ExecutionClient: &clients.Client{Name: "nethermind"}, + ConsensusClient: &clients.Client{Name: "teku"}, + ValidatorClient: &clients.Client{Name: "teku"}, + DistributedValidatorClient: &clients.Client{Name: "charon"}, + Distributed: true, + Network: "holesky", + Services: []string{execution, consensus, validator, distributedValidator}, + DvExtraFlags: []string{"extra", "flag"}, + }, + CheckFunctions: []CheckFunc{defaultFunc, checkValidatorBlocker}, + }, } tests = append(tests, generateTestCases(t)...) @@ -536,6 +550,16 @@ func TestValidateClients(t *testing.T) { }, Error: ErrUnableToGetClientsInfo, }, + { + Description: "Wrong network, good distributed validator", + Data: &GenData{ + Distributed: true, + DistributedValidatorClient: &clients.Client{Name: "charon"}, + ValidatorClient: &clients.Client{Name: "teku"}, + Network: wrongDep, + }, + Error: ErrUnableToGetClientsInfo, + }, } for _, tt := range tests { t.Run(tt.Description, func(t *testing.T) { @@ -609,6 +633,17 @@ func TestEnvFileAndFlags(t *testing.T) { }, Error: nil, }, + { + Description: "Distributed validator charon with ConsensusApiUrl", + Data: &GenData{ + ConsensusClient: &clients.Client{Name: "teku"}, + ValidatorClient: &clients.Client{Name: "teku"}, + DistributedValidatorClient: &clients.Client{Name: "charon"}, + Distributed: true, + Network: "holesky", + ConsensusApiUrl: "http://localhost:8080", + }, + }, } for _, tt := range tests { @@ -631,6 +666,9 @@ func TestEnvFileAndFlags(t *testing.T) { } } } + if tt.Data.Distributed { + assert.Contains(t, buffer.String(), "DV_API_URL="+endpointOrEmpty(tt.Data.ConsensusClient)) + } }) } } diff --git a/internal/pkg/generate/types.go b/internal/pkg/generate/types.go index 0332415a..69740e22 100644 --- a/internal/pkg/generate/types.go +++ b/internal/pkg/generate/types.go @@ -21,84 +21,92 @@ import ( // EnvData : Struct Data object to be applied to the docker-compose script environment (.env) template type EnvData struct { - Services []string - Mev bool - ElImage string - ElOpImage string - ElDataDir string - CcImage string - CcDataDir string - VlImage string - VlDataDir string - ExecutionApiURL string - ExecutionAuthURL string - ConsensusApiURL string - ConsensusAdditionalApiURL string - FeeRecipient string - JWTSecretPath string - ExecutionEngineName string - ConsensusClientName string - KeystoreDir string - Graffiti string - RelayURLs string - CheckpointSyncUrl string - ExecutionOPApiURL string - JWTOPSecretPath string - OPImageVersion string - ElOPAuthPort uint16 - OpSequencerHttp string - RethNetwork string + Services []string + Mev bool + ElImage string + ElOpImage string + ElDataDir string + CcImage string + CcDataDir string + VlImage string + VlDataDir string + ExecutionApiURL string + ExecutionAuthURL string + ConsensusApiURL string + ConsensusAdditionalApiURL string + Distributed bool + FeeRecipient string + JWTSecretPath string + ExecutionEngineName string + ConsensusClientName string + KeystoreDir string + Graffiti string + RelayURLs string + CheckpointSyncUrl string + DistributedValidatorApiUrl string + DvDataDir string + DvImage string + ExecutionOPApiURL string + JWTOPSecretPath string + OPImageVersion string + ElOPAuthPort uint16 + OpSequencerHttp string + RethNetwork string } // GenData : Struct Data object for script's generation type GenData struct { - Services []string - ExecutionClient *clients.Client - ConsensusClient *clients.Client - ValidatorClient *clients.Client - ExecutionOPClient *clients.Client - OptimismClient *clients.Client - Network string - CheckpointSyncUrl string - FeeRecipient string - JWTSecretPath string - FallbackELUrls []string - ElExtraFlags []string - ClExtraFlags []string - VlExtraFlags []string - ElOpExtraFlags []string - OpExtraFlags []string - IsBase bool - MapAllPorts bool - Mev bool - RelayURLs []string - MevImage string - MevBoostService bool - MevBoostEndpoint string - MevBoostOnValidator bool - Ports map[string]uint16 - Graffiti string - LoggingDriver string - ECBootnodes []string - CCBootnodes []string - CustomChainSpecPath string - CustomNetworkConfigPath string - CustomGenesisPath string - CustomDeployBlock string - CustomDeployBlockPath string - VLStartGracePeriod uint - ExecutionApiUrl string - ExecutionAuthUrl string - ConsensusApiUrl string - ContainerTag string - LatestVersion bool - JWTSecretOP string + Services []string + ExecutionClient *clients.Client + ConsensusClient *clients.Client + ValidatorClient *clients.Client + DistributedValidatorClient *clients.Client + Distributed bool + ExecutionOPClient *clients.Client + OptimismClient *clients.Client + Network string + CheckpointSyncUrl string + FeeRecipient string + JWTSecretPath string + FallbackELUrls []string + ElExtraFlags []string + ClExtraFlags []string + VlExtraFlags []string + DvExtraFlags []string + ElOpExtraFlags []string + OpExtraFlags []string + IsBase bool + MapAllPorts bool + Mev bool + RelayURLs []string + MevImage string + MevBoostService bool + MevBoostEndpoint string + MevBoostOnValidator bool + Ports map[string]uint16 + Graffiti string + LoggingDriver string + ECBootnodes []string + CCBootnodes []string + CustomChainSpecPath string + CustomNetworkConfigPath string + CustomGenesisPath string + CustomDeployBlock string + CustomDeployBlockPath string + VLStartGracePeriod uint + ExecutionApiUrl string + ExecutionAuthUrl string + ConsensusApiUrl string + ContainerTag string + LatestVersion bool + JWTSecretOP string } // DockerComposeData : Struct Data object to be applied to docker-compose script type DockerComposeData struct { Services []string Network string + Distributed bool XeeVersion bool Mev bool MevBoostOnValidator bool @@ -128,6 +136,7 @@ type DockerComposeData struct { NetworkPrefix string ClExtraFlags []string VlExtraFlags []string + DvExtraFlags []string ECBootnodes string CCBootnodes string CCBootnodesList []string @@ -146,6 +155,9 @@ type DockerComposeData struct { UID int // Needed for teku GID int // Needed for teku ContainerTag string + DVDiscoveryPort uint16 + DVMetricsPort uint16 + DVApiPort uint16 ConsensusApiURL string } @@ -189,6 +201,16 @@ func (d EnvData) WithMevBoostClient() bool { return false } +// WithDistributedValidatorClient returns true if the DistributedValidator client is set +func (d EnvData) WithDistributedValidatorClient() bool { + for _, service := range d.Services { + if service == distributedValidator { + return true + } + } + return false +} + type ComposeData struct { Version string `yaml:"version,omitempty"` Services *Services `yaml:"services"` @@ -273,14 +295,27 @@ type ConfigConsensus struct { Command []string `yaml:"command"` Logging *Logging `yaml:"logging,omitempty"` } + +type DistributedValidator struct { + ContainerName string `yaml:"container_name"` + Image string `yaml:"image"` + DependsOn []string `yaml:"depends_on"` + Networks []string `yaml:"networks"` + Ports []string `yaml:"ports"` + Volumes []string `yaml:"volumes"` + Command []string `yaml:"command"` + Logging *Logging `yaml:"logging,omitempty"` +} + type Services struct { - Execution *Execution `yaml:"execution,omitempty"` - Mevboost *Mevboost `yaml:"mev-boost,omitempty"` - Consensus *Consensus `yaml:"consensus,omitempty"` - ConsensusSync *ConsensusSync `yaml:"consensus-sync,omitempty"` - ValidatorBlocker *ValidatorBlocker `yaml:"validator-blocker,omitempty"` - Validator *Validator `yaml:"validator,omitempty"` - ConfigConsensus *ConfigConsensus `yaml:"config_consensus,omitempty"` + Execution *Execution `yaml:"execution,omitempty"` + Mevboost *Mevboost `yaml:"mev-boost,omitempty"` + Consensus *Consensus `yaml:"consensus,omitempty"` + ConsensusSync *ConsensusSync `yaml:"consensus-sync,omitempty"` + ValidatorBlocker *ValidatorBlocker `yaml:"validator-blocker,omitempty"` + Validator *Validator `yaml:"validator,omitempty"` + ConfigConsensus *ConfigConsensus `yaml:"config_consensus,omitempty"` + DistributedValidator *DistributedValidator `yaml:"dv,omitempty"` } type Sedge struct { Name string `yaml:"name"` diff --git a/scripts/charon/import_lodestar_keys.sh b/scripts/charon/import_lodestar_keys.sh new file mode 100644 index 00000000..c7b714eb --- /dev/null +++ b/scripts/charon/import_lodestar_keys.sh @@ -0,0 +1,11 @@ +#!/bin/sh +for f in /keystore/validator_keys/keystore-*.json; do + echo "Importing key ${f}" + pwdfile="/keystore/$(basename "$f" .json).txt" + echo "Using password file ${pwdfile}" + # Import keystore with password. + node /usr/app/packages/cli/bin/lodestar validator import \ + --dataDir="/data" \ + --importKeystores="$f" \ + --importKeystoresPassword="${pwdfile}" +done \ No newline at end of file diff --git a/scripts/check-image-updates.sh b/scripts/check-image-updates.sh index 19fcc7d4..6b09e3ab 100755 --- a/scripts/check-image-updates.sh +++ b/scripts/check-image-updates.sh @@ -59,3 +59,7 @@ update-client "Teku" "validator" ".validator.teku" "$TEKU_LATEST_VERSION" PRYSM_LATEST_VERSION=$(curl -H "Authorization: Bearer $PAT" -sL https://api.github.com/repos/prysmaticlabs/prysm/releases/latest | jq -r ".tag_name") update-client "Prysm" "consensus" ".consensus.prysm" "$PRYSM_LATEST_VERSION" update-client "Prysm" "validator" ".validator.prysm" "$PRYSM_LATEST_VERSION" + +## Charon +CHARON_LATEST_VERSION=$(curl -H "Authorization: Bearer $PAT" -sL https://api.github.com/repos/ObolNetwork/charon/releases/latest | jq -r ".tag_name") +update-client "Charon" "distributed" ".distributed.charon" "$PRYSM_LATEST_VERSION" \ No newline at end of file diff --git a/templates/envs/holesky/distributedValidator/charon.tmpl b/templates/envs/holesky/distributedValidator/charon.tmpl new file mode 100644 index 00000000..96f69b96 --- /dev/null +++ b/templates/envs/holesky/distributedValidator/charon.tmpl @@ -0,0 +1,10 @@ +{{/* charon.tmpl */}} +{{ define "distributedValidator" }} +# --- Charon - distributed validator - configuration --- +DV_IMAGE_VERSION={{.DvImage}} +DV_LOG_LEVEL=info +DV_LOG_FORMAT=console +DV_INSTANCE_NAME=charon +DV_DATA_DIR={{.DvDataDir}} +DV_API_URL={{.DistributedValidatorApiUrl}} +{{ end }} \ No newline at end of file diff --git a/templates/envs/holesky/env_base.tmpl b/templates/envs/holesky/env_base.tmpl index fbecca6d..78203585 100644 --- a/templates/envs/holesky/env_base.tmpl +++ b/templates/envs/holesky/env_base.tmpl @@ -6,5 +6,6 @@ RELAY_URLS={{.RelayURLs}}{{end}}{{if .FeeRecipient}} FEE_RECIPIENT={{.FeeRecipient}}{{end}} {{template "execution" .}} {{template "consensus" .}} -{{template "validator" .}} +{{template "validator" .}}{{if .Distributed}} +{{template "distributedValidator" .}}{{end}} {{ end }} \ No newline at end of file diff --git a/templates/services/docker-compose_base.tmpl b/templates/services/docker-compose_base.tmpl index 8f049847..37611014 100644 --- a/templates/services/docker-compose_base.tmpl +++ b/templates/services/docker-compose_base.tmpl @@ -19,15 +19,15 @@ services: - -relays - ${RELAY_URLS}{{end}} {{template "consensus" .}} +{{if .Distributed}} +{{template "distributedValidator" .}}{{ end }} {{ if .WithValidatorClient}} {{template "validator-blocker" .}}{{end}} {{template "validator" .}} - {{ if .WithOptimismClient}} {{template "opexecution" .}} {{template "optimism" .}}{{end}} - networks: sedge: name: sedge-network{{if .ContainerTag}}-{{.ContainerTag}}{{end}} -{{ end }} +{{ end }} \ No newline at end of file diff --git a/templates/services/merge/distributedValidator/charon.tmpl b/templates/services/merge/distributedValidator/charon.tmpl new file mode 100644 index 00000000..8660c24e --- /dev/null +++ b/templates/services/merge/distributedValidator/charon.tmpl @@ -0,0 +1,30 @@ +{{/* charon.tmpl */}} +{{ define "distributedValidator" }} + dv: + container_name: sedge-dv-client{{if .ContainerTag}}-{{.ContainerTag}}{{end}} + image: ${DV_IMAGE_VERSION} + environment: + - CHARON_BEACON_NODE_ENDPOINTS=${CC_API_URL} + - CHARON_LOG_LEVEL=${DV_LOG_LEVEL:-info} + - CHARON_LOG_FORMAT=${DV_LOG_FORMAT:-console} + - CHARON_P2P_RELAYS=${DV_P2P_RELAYS:-https://0.relay.obol.tech,https://1.relay.obol.tech} + - CHARON_P2P_EXTERNAL_HOSTNAME=${DV_INSTANCE_NAME:-charon} + - CHARON_P2P_TCP_ADDRESS=0.0.0.0:{{.DVDiscoveryPort}} + - CHARON_VALIDATOR_API_ADDRESS=127.0.0.1:{{.DVApiPort}} + - CHARON_MONITORING_ADDRESS=0.0.0.0:{{.DVMetricsPort}}{{if .MevBoostOnValidator}} + - CHARON_BUILDER_API={{.MevBoostOnValidator}}{{end}} + {{- range $i, $flag := .DvExtraFlags }} + - {{$flag}} + {{- end }} + ports: + - ${CHARON_PORT_P2P_TCP:-{{.DVDiscoveryPort}}}:${CHARON_PORT_P2P_TCP:-{{.DVDiscoveryPort}}}/tcp + expose: + - {{.DVMetricsPort}} + networks: + - sedge + volumes: + - ${DV_DATA_DIR}:/opt/charon/.charon + restart: unless-stopped + healthcheck: + test: wget -qO- http://localhost:{{.DVMetricsPort}}/readyz +{{ end }} \ No newline at end of file diff --git a/templates/services/merge/distributedValidator/empty.tmpl b/templates/services/merge/distributedValidator/empty.tmpl new file mode 100644 index 00000000..138fb486 --- /dev/null +++ b/templates/services/merge/distributedValidator/empty.tmpl @@ -0,0 +1,3 @@ +{{/* empty.tmpl */}} +{{ define "distributedValidator" }} +{{ end }} \ No newline at end of file diff --git a/templates/services/merge/validator/lighthouse.tmpl b/templates/services/merge/validator/lighthouse.tmpl index c815c171..bb365649 100644 --- a/templates/services/merge/validator/lighthouse.tmpl +++ b/templates/services/merge/validator/lighthouse.tmpl @@ -20,7 +20,7 @@ - --testnet-dir=/network_config{{end}} - vc{{if not .CustomConsensusConfigs}} - --network={{if .SplittedNetwork}}${CL_NETWORK}{{else}}${NETWORK}{{end}}{{end}} - - --beacon-nodes=${CC_API_URL} + - --beacon-nodes={{if .Distributed}}${DV_API_URL}{{else}}${CC_API_URL}{{end}} - --graffiti=${GRAFFITI} - --debug-level=${VL_LOG_LEVEL} - --validators-dir=/data/validators{{with .FeeRecipient}} @@ -29,7 +29,8 @@ - --metrics-port={{.VlMetricsPort}} - --metrics-address=0.0.0.0{{range $flag := .VlExtraFlags}} - --{{$flag}}{{end}}{{if .MevBoostOnValidator}} - - --builder-proposals{{end}}{{if .LoggingDriver}} + - --builder-proposals{{end}}{{if .Distributed}} + - --distributed{{end}}{{if .LoggingDriver}} logging: driver: "{{.LoggingDriver}}"{{if eq .LoggingDriver "json-file"}} options: diff --git a/templates/services/merge/validator/lodestar.tmpl b/templates/services/merge/validator/lodestar.tmpl index e5855ebf..0eb20c91 100644 --- a/templates/services/merge/validator/lodestar.tmpl +++ b/templates/services/merge/validator/lodestar.tmpl @@ -21,7 +21,7 @@ - --dataDir=/data - --logFile=/data/logs/validator.log - --logFileLevel=${VL_LOG_LEVEL} - - --server=${CC_API_URL} + - --server={{if .Distributed}}${DV_API_URL}{{else}}${CC_API_URL}{{end}} - --metrics=true - --metrics.address=0.0.0.0 - --metrics.port={{.VlMetricsPort}}{{with .FeeRecipient}} diff --git a/templates/services/merge/validator/nimbus.tmpl b/templates/services/merge/validator/nimbus.tmpl index aacd40b9..eea1ed57 100644 --- a/templates/services/merge/validator/nimbus.tmpl +++ b/templates/services/merge/validator/nimbus.tmpl @@ -17,13 +17,15 @@ - --data-dir=/data - --log-file=/data/logs/validator.log - --log-level=${VL_LOG_LEVEL} - - --beacon-node=${CC_API_URL} + - --beacon-node={{if .Distributed}}${DV_API_URL}{{else}}${CC_API_URL}{{end}} - --metrics=true - --metrics-address=0.0.0.0 - --metrics-port={{.VlMetricsPort}}{{with .FeeRecipient}} - --suggested-fee-recipient=${FEE_RECIPIENT}{{end}}{{range $flag := .VlExtraFlags}} - --{{$flag}}{{end}} - - --graffiti=${GRAFFITI}{{if .LoggingDriver}} + - --graffiti=${GRAFFITI}{{if .MevBoostOnValidator}} + - --payload-builder=true{{end}}{{if .Distributed}} + - --distributed{{end}}{{if .LoggingDriver}} logging: driver: "{{.LoggingDriver}}"{{if eq .LoggingDriver "json-file"}} options: diff --git a/templates/services/merge/validator/prysm.tmpl b/templates/services/merge/validator/prysm.tmpl index 9f2ca868..95421613 100644 --- a/templates/services/merge/validator/prysm.tmpl +++ b/templates/services/merge/validator/prysm.tmpl @@ -20,7 +20,7 @@ - --wallet-password-file=/keystore/keystore_password.txt{{if .CustomConsensusConfigs}}{{if .CustomNetworkConfigPath}} - --chain-config-file=/network_config/config.yml{{end}}{{else}} - --{{if .SplittedNetwork}}${CL_NETWORK}{{else}}${NETWORK}{{end}}{{end}} - - --beacon-rpc-provider=${CC_ADD_API_URL} + - --beacon-rpc-provider={{if .Distributed}}${DV_API_URL}{{else}}${CC_ADD_API_URL}{{end}} - --graffiti=${GRAFFITI} - --verbosity=${VL_LOG_LEVEL} - --accept-terms-of-use diff --git a/templates/services/merge/validator/teku.tmpl b/templates/services/merge/validator/teku.tmpl index 5455ba49..011235b8 100644 --- a/templates/services/merge/validator/teku.tmpl +++ b/templates/services/merge/validator/teku.tmpl @@ -17,7 +17,7 @@ command: - validator-client{{if .CustomConsensusConfigs}}{{if .CustomNetworkConfigPath}} - --network=/network_config/config.yml{{end}}{{end}} - - --beacon-node-api-endpoint=${CC_API_URL} + - --beacon-node-api-endpoint={{if .Distributed}}${DV_API_URL}{{else}}${CC_API_URL}{{end}} - --data-path=/data - --log-destination=CONSOLE - --validators-graffiti=${GRAFFITI}