Skip to content

Commit

Permalink
feat: add OP stack using nethermind to Sedge (#407)
Browse files Browse the repository at this point in the history
* feat: add op-neth support

* docs: update changelog

* feat: add --Discovery.Discv5Enabled flag to op-nethermind

* fix: change --consensus-api-url to --consensus-url

* fix: remove references to op-enabled on full command

* feat: add --syncmode=execution-layer flag to op-l2 container

* fix: add more context on documentation

* docs: updated changelog

* Update docs/docs/quickstart/optimism.mdx

Co-authored-by: Miguel Tenorio <[email protected]>

---------

Co-authored-by: Miguel Tenorio <[email protected]>
  • Loading branch information
stdevMac and AntiD2ta authored Sep 6, 2024
1 parent 7398c2b commit 421e40b
Show file tree
Hide file tree
Showing 24 changed files with 596 additions and 54 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [v1.5.0] - 2024-09-05

### Added
- Add support for Optimism and Base, using Nethermind Client on Mainnet and Sepolia.

### Changed
- Update client images to latest versions.

#### Fixed
- Remove Peer upper limit of peers on CL

Expand Down
2 changes: 1 addition & 1 deletion cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func setupJWT(p ui.Prompter, o *CliCmdOptions, skip bool) error {
}
switch o.jwtSourceType {
case SourceTypeCreate:
jwtPath, err := handleJWTSecret(o.generationPath)
jwtPath, err := handleJWTSecret(o.generationPath, "jwtsecret")
o.genData.JWTSecretPath = jwtPath
if err != nil {
return err
Expand Down
107 changes: 96 additions & 11 deletions cli/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ var (
)

const (
execution, consensus, validator, mevBoost = "execution", "consensus", "validator", "mev-boost"
execution, consensus, validator, mevBoost, optimism = "execution", "consensus", "validator", "mev-boost", "optimism"
jwtPathName = "jwtsecret"
)

type CustomFlags struct {
Expand All @@ -57,9 +58,18 @@ type CustomFlags struct {
customDeployBlock string
}

type OptimismFlags struct {
optimismName string
optimismExecutionName string
elOpExtraFlags []string
opExtraFlags []string
isBase bool
}

// GenCmdFlags is a struct that holds the flags of the generate command
type GenCmdFlags struct {
CustomFlags
OptimismFlags
executionName string
consensusName string
validatorName string
Expand Down Expand Up @@ -98,6 +108,7 @@ It will create a 'docker-compose.yml' and a '.env', which you will need later to
You can generate:
- Full Node (execution + consensus + validator)
- Full Node without Validator (execution + consensus)
- Optimism Full Node
- Execution Node
- Consensus Node
- Validator Node
Expand All @@ -112,6 +123,7 @@ You can generate:
cmd.AddCommand(ConsensusSubCmd(sedgeAction))
cmd.AddCommand(ValidatorSubCmd(sedgeAction))
cmd.AddCommand(MevBoostSubCmd(sedgeAction))
cmd.AddCommand(OpFullNodeSubCmd(sedgeAction))

cmd.PersistentFlags().BoolVar(&lidoNode, "lido", false, "generate Lido CSM node")
cmd.PersistentFlags().StringVarP(&generationPath, "path", "p", configs.DefaultAbsSedgeDataPath, "generation path for sedge data. Default is sedge-data")
Expand Down Expand Up @@ -250,7 +262,7 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio

// Generate jwt secrets if needed
if flags.jwtPath == "" {
flags.jwtPath, err = handleJWTSecret(generationPath)
flags.jwtPath, err = handleJWTSecret(generationPath, jwtPathName)
if err != nil {
return err
}
Expand All @@ -260,6 +272,14 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio
return err
}
}
var jwtSecretOP string
// If optimism is included in the services, generate the jwt secret for it
if utils.Contains(services, optimism) {
jwtSecretOP, err = handleJWTSecret(generationPath, jwtPathName+"-op")
if err != nil {
return err
}
}

// Overwrite feeRecipient and relayURLs for Lido Node
if lidoNode {
Expand All @@ -275,11 +295,25 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio

vlStartGracePeriod := configs.NetworkEpochTime(network) * time.Duration(flags.waitEpoch)

var consensusApiUrl string
var executionApiUrl string
var executionAuthUrl string
if combinedClients.Consensus == nil {
consensusApiUrl = flags.consensusApiUrl
}

if combinedClients.Execution == nil {
executionApiUrl = flags.executionApiUrl
executionAuthUrl = flags.executionAuthUrl
}

// 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,
Expand All @@ -289,6 +323,9 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio
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,
Expand All @@ -298,9 +335,9 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio
MevBoostEndpoint: flags.mevBoostUrl,
Services: services,
VLStartGracePeriod: uint(vlStartGracePeriod.Seconds()),
ExecutionApiUrl: flags.executionApiUrl,
ExecutionAuthUrl: flags.executionAuthUrl,
ConsensusApiUrl: flags.consensusApiUrl,
ExecutionApiUrl: executionApiUrl,
ExecutionAuthUrl: executionAuthUrl,
ConsensusApiUrl: consensusApiUrl,
ECBootnodes: flags.customEnodes,
CCBootnodes: flags.customEnrs,
CustomChainSpecPath: flags.CustomFlags.customChainSpec,
Expand All @@ -311,6 +348,7 @@ func runGenCmd(out io.Writer, flags *GenCmdFlags, sedgeAction actions.SedgeActio
MevBoostOnValidator: flags.mevBoostOnVal,
ContainerTag: containerTag,
LatestVersion: flags.latestVersion,
JWTSecretOP: jwtSecretOP,
}
_, err = sedgeAction.Generate(actions.GenerateOptions{
GenerationData: gd,
Expand Down Expand Up @@ -338,7 +376,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 *clients.Client
var executionClient, consensusClient, validatorClient, executionOpClient, opClient *clients.Client
var err error

// execution client
Expand All @@ -353,6 +391,7 @@ func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services
if len(executionParts) > 1 {
log.Warn(configs.CustomExecutionImagesWarning)
executionClient.Image = strings.Join(executionParts[1:], ":")
flags.latestVersion = false
}
}
executionClient.SetImageOrDefault(strings.Join(executionParts[1:], ":"))
Expand All @@ -374,6 +413,7 @@ func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services
if len(consensusParts) > 1 {
log.Warn(configs.CustomConsensusImagesWarning)
consensusClient.Image = strings.Join(consensusParts[1:], ":")
flags.latestVersion = false
}
}
consensusClient.SetImageOrDefault(strings.Join(consensusParts[1:], ":"))
Expand All @@ -395,6 +435,7 @@ func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services
if len(validatorParts) > 1 {
log.Warn(configs.CustomValidatorImagesWarning)
validatorClient.Image = strings.Join(validatorParts[1:], ":")
flags.latestVersion = false

}
}
Expand All @@ -405,11 +446,55 @@ func valClients(allClients clients.OrderedClients, flags *GenCmdFlags, services
} else {
validatorClient = nil
}
// optimism client
if utils.Contains(services, optimism) {
optimismParts := strings.Split(flags.optimismName, ":")
opClient, err = clients.RandomChoice(allClients[optimism])
if err != nil {
return nil, err
}
if flags.optimismName != "" {
opClient.Name = "optimism"
if len(optimismParts) > 1 {
opClient.Image = strings.Join(optimismParts[1:], ":")

}
}
opClient.SetImageOrDefault(strings.Join(optimismParts[1:], ":"))
if err = clients.ValidateClient(opClient, optimism); err != nil {
return nil, err
}

optimismExecutionParts := strings.Split(flags.optimismExecutionName, ":")
executionOpClient = allClients[execution]["nethermind"]
if flags.optimismExecutionName != "" {
executionOpClient.Name = optimismExecutionParts[0]
if len(optimismExecutionParts) > 1 {
executionOpClient.Image = strings.Join(optimismExecutionParts[1:], ":")

}
}
executionOpClient.SetImageOrDefault(strings.Join(optimismExecutionParts[1:], ":"))
if err = clients.ValidateClient(executionOpClient, optimism); err != nil {
return nil, err
}

// If set execution-api-url, set execution and beacon to nil
if flags.executionApiUrl != "" {
executionClient = nil
consensusClient = nil
}
} else {
opClient = nil
executionOpClient = nil
}

return &clients.Clients{
Execution: executionClient,
Consensus: consensusClient,
Validator: validatorClient,
Execution: executionClient,
Consensus: consensusClient,
Validator: validatorClient,
ExecutionOP: executionOpClient,
Optimism: opClient,
}, err
}

Expand All @@ -434,7 +519,7 @@ func initGenPath(path string) error {
return nil
}

func handleJWTSecret(generationPath string) (string, error) {
func handleJWTSecret(generationPath, name string) (string, error) {
log.Info(configs.GeneratingJWTSecret)
if !filepath.IsAbs(generationPath) {
return "", fmt.Errorf(configs.GenerateJWTSecretError, fmt.Errorf("generation path must be absolute"))
Expand All @@ -445,7 +530,7 @@ func handleJWTSecret(generationPath string) (string, error) {
return "", fmt.Errorf(configs.GenerateJWTSecretError, err)
}

jwtPath, err := filepath.Abs(filepath.Join(generationPath, "jwtsecret"))
jwtPath, err := filepath.Abs(filepath.Join(generationPath, name))
if err != nil {
return "", fmt.Errorf(configs.GenerateJWTSecretError, err)
}
Expand Down
34 changes: 33 additions & 1 deletion cli/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ func TestGenerateCmd(t *testing.T) {
errors.New("invalid consensus client"),
},
{
"Validator missing consensus-api",
"Validator missing consensus-url",
subCmd{
name: "validator",
args: []string{},
Expand Down Expand Up @@ -1316,6 +1316,38 @@ func TestGenerateCmd(t *testing.T) {
globalFlags{},
nil,
},
{
"Optimism full node",
subCmd{
name: "op-full-node",
},
GenCmdFlags{},
globalFlags{},
nil,
},
{
"Optimism full node with api url",
subCmd{
name: "op-full-node",
},
GenCmdFlags{
executionApiUrl: "https://localhost:8545",
consensusApiUrl: "https://localhost:8000/api/endpoint",
},
globalFlags{},
nil,
},
{
"Optimism full node with only consensus api url",
subCmd{
name: "op-full-node",
},
GenCmdFlags{
consensusApiUrl: "https://localhost:8000/api/endpoint",
},
globalFlags{},
nil,
},
{
"Lido Full-node - Holesky without MEV",
subCmd{
Expand Down
56 changes: 55 additions & 1 deletion cli/sub_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package cli

import (
"errors"

"github.com/NethermindEth/sedge/cli/actions"
sedgeOpts "github.com/NethermindEth/sedge/internal/pkg/options"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -113,6 +112,61 @@ Additionally, you can use this syntax '<CLIENT>:<DOCKER_IMAGE>' to override the
return cmd
}

func OpFullNodeSubCmd(sedgeAction actions.SedgeActions) *cobra.Command {
var flags GenCmdFlags
cmd := &cobra.Command{
Use: "op-full-node [flags]",
Short: "Generate a full node config for Optimism or Base",
Long: `Generate a docker-compose and an environment file with a full node configuration for Optimism or Base networks.
This command sets up an Optimism or Base full node, which includes an execution client, a consensus client, a Optimism consensus client, and an Optimism node.
If you don't provide images for your clients, they will be chosen randomly. You can specify custom images for the Optimism and other nodes.
Use the --base flag to generate a configuration for a Base node (which is built on Optimism).
The command allows you to use external execution and consensus APIs instead of running your own nodes, by providing the respective URLs.
Additionally, you can use the syntax '<CLIENT>:<DOCKER_IMAGE>' to override the docker image used for the client, for example 'sedge generate op-full-node --execution nethermind:custom.image'. If you want to use the default docker image, just use the client name.
This command does not generate a validator configuration, as Optimism and Base use different validation mechanisms compared to standard Ethereum networks.`,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := validateCustomNetwork(&flags.CustomFlags, network); err != nil {
return err
}
return preValidationGenerateCmd(network, logging, &flags)
},
RunE: func(cmd *cobra.Command, args []string) error {
services := []string{execution, consensus, optimism}
return runGenCmd(cmd.OutOrStdout(), &flags, sedgeAction, services)
},
}
// Bind flags
cmd.Flags().StringVarP(&flags.consensusName, "consensus", "c", "", "Consensus engine client, e.g. teku, lodestar, prysm, lighthouse, Nimbus. Additionally, you can use this syntax '<CLIENT>:<DOCKER_IMAGE>' 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().StringVar(&flags.optimismName, "op-image", "", "Optimism consensus client image.")
cmd.Flags().StringVar(&flags.optimismExecutionName, "op-execution-image", "", "Image name set for nethermind client to be used with optimism.")
cmd.Flags().StringVarP(&flags.executionName, "execution", "e", "", "Execution engine client, e.g. geth, nethermind, besu, erigon. Additionally, you can use this syntax '<CLIENT>:<DOCKER_IMAGE>' 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.executionApiUrl, "execution-api-url", "", "", "Set execution api url. If Set, will omit the creation of execution and beacon nodes, and only create optimism nodes.")
cmd.Flags().StringVarP(&flags.consensusApiUrl, "consensus-url", "", "", "Set consensus api url. If Set, will omit the creation of execution and beacon nodes, and only create optimism nodes.")
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")
cmd.Flags().StringVar(&flags.jwtPath, "jwt-secret-path", "", "Path to the JWT secret file")
cmd.Flags().BoolVar(&flags.mapAllPorts, "map-all", false, "Map all clients ports to host. Use with care. Useful to allow remote access to the clients")
cmd.Flags().BoolVar(&flags.isBase, "base", false, "If set, will generate the docker-compose file for Base L2 config")
cmd.Flags().StringSliceVar(&flags.fallbackEL, "fallback-execution-urls", []string{}, "Fallback/backup execution endpoints for the consensus client. Not supported by Teku. Example: 'sedge generate full-node -r --fallback-execution=https://mainnet.infura.io/v3/YOUR-PROJECT-ID,https://eth-mainnet.alchemyapi.io/v2/YOUR-PROJECT-ID'")
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 \"<flag1>=value1\" --el-extra-flag \"<flag2>=\\\"value2\\\"\"'")
cmd.Flags().StringArrayVar(&flags.elOpExtraFlags, "el-op-extra-flag", []string{}, "Additional flag to configure the execution client for optimism service in the generated docker-compose script. Example: 'sedge generate full-node --el-extra-flag \"<flag1>=value1\" --el-extra-flag \"<flag2>=\\\"value2\\\"\"'")
cmd.Flags().StringArrayVar(&flags.opExtraFlags, "op-extra-flag", []string{}, "Additional flag to configure the optimism client service in the generated docker-compose script. Example: 'sedge generate full-node --el-extra-flag \"<flag1>=value1\" --el-extra-flag \"<flag2>=\\\"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 \"<flag1>=value1\" --cl-extra-flag \"<flag2>=\\\"value2\\\"\"'")
cmd.Flags().StringSliceVar(&flags.customEnodes, "execution-bootnodes", []string{}, "List of comma separated enodes to use as custom network peers for execution client.")
cmd.Flags().StringSliceVar(&flags.customEnrs, "consensus-bootnodes", []string{}, "List of comma separated enrs to use as custom network peers for consensus client.")

cmd.Flags().SortFlags = false
return cmd
}

func ExecutionSubCmd(sedgeAction actions.SedgeActions) *cobra.Command {
var flags GenCmdFlags

Expand Down
Loading

0 comments on commit 421e40b

Please sign in to comment.