Skip to content

Commit

Permalink
feat: Allow keystores creation with eth1 withdrawal credentials (#235)
Browse files Browse the repository at this point in the history
* feat: allow keystores creation with eth1 withdrawal credentials

* fix: error when passing empty withdrawal eth1 address

* chore: Add devcontainer config

* fix: Withdrawal credentials prefix zeros in deposit data.

* feat: Add json schema and logic to validate deposit_data.json

* test: Add e2e test for keys gen with eth1 withdrawal cred

* feat: Support installing deps on debian 12

* fix: fix e2e tests error in Windows

* fix: e2e sedge keys test for Windows

---------

Co-authored-by: Miguel Tenorio <[email protected]>
Co-authored-by: AntiD2ta <[email protected]>
Co-authored-by: Haneen Khalifa <[email protected]>
  • Loading branch information
4 people authored Jul 4, 2024
1 parent 870874f commit 011fad2
Show file tree
Hide file tree
Showing 12 changed files with 431 additions and 145 deletions.
45 changes: 45 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "Go",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/go:1-1.22-bookworm",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"GitHub.copilot",
"streetsidesoftware.code-spell-checker",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"donjayamanne.githistory",
"GitHub.copilot-chat",
"GitHub.vscode-pull-request-github",
"golang.go",
"yzhang.markdown-all-in-one",
"DavidAnson.vscode-markdownlint",
"unifiedjs.vscode-mdx",
"redhat.vscode-yaml",
"eamodio.gitlens"
]
}
}

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "go version",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
32 changes: 24 additions & 8 deletions cli/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/NethermindEth/sedge/internal/pkg/commands"
"github.com/NethermindEth/sedge/internal/pkg/keystores"
"github.com/NethermindEth/sedge/internal/ui"
"github.com/NethermindEth/sedge/internal/utils"
eth2 "github.com/protolambda/zrnt/eth2/configs"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -83,6 +84,10 @@ func KeysCmd(cmdRunner commands.CommandRunner, p ui.Prompter) *cobra.Command {
if err := configs.NetworkCheck(flags.network); err != nil {
log.Fatal(err.Error())
}
// Validate fee recipient
if flags.eth1WithdrawalAddress != "" && !utils.IsAddress(flags.eth1WithdrawalAddress) {
log.Fatal(configs.ErrInvalidWithdrawalAddr)
}
// Ensure that path is absolute
log.Debugf("Path to keystore folder: %s", flags.path)
absPath, err := filepath.Abs(flags.path)
Expand All @@ -93,7 +98,10 @@ func KeysCmd(cmdRunner commands.CommandRunner, p ui.Prompter) *cobra.Command {
return nil
},
Run: func(cmd *cobra.Command, args []string) {
// TODO: allow usage of withdrawal address
// Warn about withdrawal address
if flags.eth1WithdrawalAddress != "" {
log.Warn(configs.WithdrawalAddressDefinedWarning)
}
// Get keystore passphrase
if !flags.randomPassphrase && flags.passphrasePath != "" {
content, err := readFileContent(flags.passphrasePath)
Expand Down Expand Up @@ -153,14 +161,22 @@ func KeysCmd(cmdRunner commands.CommandRunner, p ui.Prompter) *cobra.Command {
flags.numberVal = numberVal
}

keystorePath := filepath.Join(flags.path, "keystore")

var withdrawalAddress string
if flags.eth1WithdrawalAddress != "" {
withdrawalAddress = flags.eth1WithdrawalAddress[2:]
}

data := keystores.ValidatorKeysGenData{
Mnemonic: mnemonic,
Passphrase: passphrase,
OutputPath: keystorePath,
MinIndex: uint64(flags.existingVal),
MaxIndex: uint64(flags.existingVal) + uint64(flags.numberVal),
NetworkName: flags.network,
ForkVersion: configs.NetworksConfigs()[flags.network].GenesisForkVersion,
Mnemonic: mnemonic,
Passphrase: passphrase,
OutputPath: keystorePath,
MinIndex: uint64(flags.existingVal),
MaxIndex: uint64(flags.existingVal) + uint64(flags.numberVal),
NetworkName: flags.network,
ForkVersion: configs.NetworksConfigs()[flags.network].GenesisForkVersion,
WithdrawalAddress: withdrawalAddress,
// Constants
UseUniquePassphrase: true,
Insecure: false,
Expand Down
137 changes: 70 additions & 67 deletions configs/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,71 +17,74 @@ package configs

// All the strings that are needed for error logging.
const (
ReadingInstructionError = "failed to read instructions from file %s"
IncorrectClientError = "incorrect %s client name \"%s\". Please provide correct client name. Use 'clients' command to see the list of supported clients"
ClosingFileError = "failed to close file %s"
ScriptIsNotRunningError = "services of docker-compose script provided are not running. Error: %v"
GettingLogsError = "failed to get logs for services %s. Error: %v"
DockerComposePsReturnedEmptyError = "'docker compose ps --services' returned empty string"
InvalidVolumePathError = "invalid path provided: %s. If you intended to pass a host directory, use absolute path"
ZipError = "all lists must have the same size"
CommandError = "command '%s' throws error: %v"
DistroInfoError = "failed to get linux distribution info. Error: %v"
EmptyClientMapError = "is not possible to select a random element from an empty collection"
NoSupportedClientsError = "collection of clients given for random choice doesn't have any supported client. Check the target network (flag --network). Use 'clients' command to see the list of supported clients for every supported network"
NetworkValidationFailedError = "'network' flag validation failed. Error: %v"
UnknownNetworkError = "unknown network \"%s\". Please provide correct network name. Use 'networks' command to see the list of supported networks"
GenerateJWTSecretError = "JWT secret generation failed. Error: %v"
GetPWDError = "something failed trying to get current working directory. Error: %v"
EmptyFeeRecipientError = "you should provide an Ethereum address for the Fee Recipient"
KeystorePasswordError = "keystore password must have more than 8 characters"
PortOccupationError = "port occupation check failed. Error: %v"
DefaultPortInvalidError = "default %s can not be zero"
PrintFileError = "error printing file content: %v"
CleaningEnvFileError = "error cleaning env file: %v"
CleaningDCFileError = "error cleaning docker compose file: %v"
PassphraseReadFileError = "error reading passphrase file: %v"
MnemonicReadFileError = "error reading passphrase file: %v"
MnemonicGenerationError = "error creating mnemonic: %v"
KeyEntryGenerationError = "error generating keystore: could not read sufficient secure random bytes"
AESParamsCreationError = "failed to create AES128CTR params: %w"
SecretEncryptionError = "failed to encrypt secret: %w"
KeystoreOutputExistingError = "output folder for keystores already exists"
KeystoreGenerationError = "error generating keystores: %v"
KeystoreDerivationError = "keystore %s cannot be derived, continuing to next keystore"
KeystoreExistingInWalletError = "keystore with name \"%s\" already exists"
KeystoreImportingError = "failed to import keystore with pubkey %s into output wallet: %v"
InvalidMnemonicError = "mnemonic is not valid"
BadMnemonicError = "bad mnemonic: %v"
ForkVersionDecodeError = "cannot decode fork version: %v"
DepositFileWriteError = "cannot write deposit file: %v"
KeystoreSecretKeyCreationError = "failed to create validator private key for path %q: %v"
WithdrawalSecretKeyCreationError = "failed to create withdrawal private key for path %q: %v"
KeystoreSecretKeyConvertionError = "cannot convert validator priv key: %v"
DepositDataEncodingError = "could not encode deposit data to json: %v"
InvalidLoggingFlag = "bad logging flag: %v"
CannotGenerateSecret = "cannot generate 32 bytes long secret"
ShowMnemonicError = "error displaying mnemonic: %v"
InvalidFilePathOrUrl = "invalid filepath or url: %s"
CannotGetUrlContent = "cannot get url %s content: %v"
CannotReadFileContent = "cannot read file %s content: %v"
ErrorCheckingFile = "error checking file %s: %v"
DestFileAlreadyExist = "destiny file %s already exist"
ErrorCreatingFile = "error creating file %s: %v"
ErrorDownloadingFile = "error downloading file from %s: %v"
ErrorCopyingFile = "error copying file from %s: %v"
ErrorWritingDeployBlockFile = "error writing custom deploy block file %s: %v"
InvalidUrlFlagError = "invalid %s url %s. URL must be in the format http(s)://<host>:<port>/<api>/<endpoint>/... or http://<host>/<api>/<endpoint>/"
InvalidEnodeError = "invalid enode %s. Bootnode must be in the format enode://<node id>@<host>:<port>"
InvalidEnrError = "invalid enr %s. ENR must be in the format enr:<base64 encoded string>"
InvalidService = "provided service %s is invalid"
SetupContainersErr = "error setting up service containers: %w"
StartingContainersErr = "error starting service containers: %w"
ReadingYmlErr = "error reading yml file: %w"
ParsingYmlErr = "error parsing yml file, it seems is not a valid docker-compose script: %w"
ServicesNotFoundErr = "services not found in the docker-compose script"
InvalidComposeErr = "provided docker-compose script is invalid: %w"
ErrDuplicatedBootNode = "duplicated boot node"
ErrGraffitiLength = "graffiti must have 16 characters at most. Provided graffiti %s has %d characters"
ErrCMDArgsNotSupported = "command %s does not support arguments. Please use flags instead"
ReadingInstructionError = "failed to read instructions from file %s"
IncorrectClientError = "incorrect %s client name \"%s\". Please provide correct client name. Use 'clients' command to see the list of supported clients"
ClosingFileError = "failed to close file %s"
ScriptIsNotRunningError = "services of docker-compose script provided are not running. Error: %v"
GettingLogsError = "failed to get logs for services %s. Error: %v"
DockerComposePsReturnedEmptyError = "'docker compose ps --services' returned empty string"
InvalidVolumePathError = "invalid path provided: %s. If you intended to pass a host directory, use absolute path"
ZipError = "all lists must have the same size"
CommandError = "command '%s' throws error: %v"
DistroInfoError = "failed to get linux distribution info. Error: %v"
EmptyClientMapError = "is not possible to select a random element from an empty collection"
NoSupportedClientsError = "collection of clients given for random choice doesn't have any supported client. Check the target network (flag --network). Use 'clients' command to see the list of supported clients for every supported network"
NetworkValidationFailedError = "'network' flag validation failed. Error: %v"
UnknownNetworkError = "unknown network \"%s\". Please provide correct network name. Use 'networks' command to see the list of supported networks"
GenerateJWTSecretError = "JWT secret generation failed. Error: %v"
GetPWDError = "something failed trying to get current working directory. Error: %v"
EmptyFeeRecipientError = "you should provide an Ethereum address for the Fee Recipient"
KeystorePasswordError = "keystore password must have more than 8 characters"
PortOccupationError = "port occupation check failed. Error: %v"
DefaultPortInvalidError = "default %s can not be zero"
PrintFileError = "error printing file content: %v"
CleaningEnvFileError = "error cleaning env file: %v"
CleaningDCFileError = "error cleaning docker compose file: %v"
PassphraseReadFileError = "error reading passphrase file: %v"
MnemonicReadFileError = "error reading passphrase file: %v"
MnemonicGenerationError = "error creating mnemonic: %v"
KeyEntryGenerationError = "error generating keystore: could not read sufficient secure random bytes"
AESParamsCreationError = "failed to create AES128CTR params: %w"
SecretEncryptionError = "failed to encrypt secret: %w"
KeystoreOutputExistingError = "output folder for keystores already exists"
KeystoreGenerationError = "error generating keystores: %v"
KeystoreDerivationError = "keystore %s cannot be derived, continuing to next keystore"
KeystoreExistingInWalletError = "keystore with name \"%s\" already exists"
KeystoreImportingError = "failed to import keystore with pubkey %s into output wallet: %v"
InvalidMnemonicError = "mnemonic is not valid"
BadMnemonicError = "bad mnemonic: %v"
ForkVersionDecodeError = "cannot decode fork version: %v"
DepositFileWriteError = "cannot write deposit file: %v"
KeystoreSecretKeyCreationError = "failed to create validator private key for path %q: %v"
WithdrawalSecretKeyCreationError = "failed to create withdrawal private key for path %q: %v"
KeystoreSecretKeyConvertionError = "cannot convert validator priv key: %v"
DepositDataEncodingError = "could not encode deposit data to json: %v"
InvalidLoggingFlag = "bad logging flag: %v"
CannotGenerateSecret = "cannot generate 32 bytes long secret"
ShowMnemonicError = "error displaying mnemonic: %v"
InvalidFilePathOrUrl = "invalid filepath or url: %s"
CannotGetUrlContent = "cannot get url %s content: %v"
CannotReadFileContent = "cannot read file %s content: %v"
ErrorCheckingFile = "error checking file %s: %v"
DestFileAlreadyExist = "destiny file %s already exist"
ErrorCreatingFile = "error creating file %s: %v"
ErrorDownloadingFile = "error downloading file from %s: %v"
ErrorCopyingFile = "error copying file from %s: %v"
ErrorWritingDeployBlockFile = "error writing custom deploy block file %s: %v"
InvalidUrlFlagError = "invalid %s url %s. URL must be in the format http(s)://<host>:<port>/<api>/<endpoint>/... or http://<host>/<api>/<endpoint>/"
InvalidEnodeError = "invalid enode %s. Bootnode must be in the format enode://<node id>@<host>:<port>"
InvalidEnrError = "invalid enr %s. ENR must be in the format enr:<base64 encoded string>"
InvalidService = "provided service %s is invalid"
SetupContainersErr = "error setting up service containers: %w"
StartingContainersErr = "error starting service containers: %w"
ReadingYmlErr = "error reading yml file: %w"
ParsingYmlErr = "error parsing yml file, it seems is not a valid docker-compose script: %w"
ServicesNotFoundErr = "services not found in the docker-compose script"
InvalidComposeErr = "provided docker-compose script is invalid: %w"
ErrDuplicatedBootNode = "duplicated boot node"
ErrGraffitiLength = "graffiti must have 16 characters at most. Provided graffiti %s has %d characters"
ErrCMDArgsNotSupported = "command %s does not support arguments. Please use flags instead"
ErrWithdrawalEth1SecretKeyCreation = "failed to create withdrawal private key for address 0x%s: %v"
ErrWithdrawalBLSSecretKeyCreation = "failed to create withdrawal private key for path %q: %v"
ErrInvalidWithdrawalAddr = "provided withdrawal address is not a valid Ethereum address"
)
Loading

0 comments on commit 011fad2

Please sign in to comment.