Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions cli/GENERATE_CONFIG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# SSV Config Generator

**SSV Config Generator** is a command-line tool designed to generate configuration file for SSV Node. It streamlines the setup process by allowing users to specify various parameters through flags, which are then compiled into a YAML configuration file. This tool ensures that the SSV network setup is consistent, customizable, and easy to manage.

## Table of Contents

- [Usage](#usage)
- [Flags](#flags)
- [Examples](#examples)
- [Configuration](#configuration)

## Usage

The `generate-config` command allows you to generate a YAML configuration file by specifying various parameters through command-line flags.

### Syntax

```bash
ssvnode generate-config [flags]
```

### Flags

| Flag | Type | Default | Description |
|--------------------------------------|--------|-----------------------------------|----------------------------------------------------------------------------|
| `--output-path` | string | `./config/config.local.yaml` | Output path for the generated configuration file. |
| `--log-level` | string | `info` | Sets the logging level (e.g., `debug`, `info`, `warn`, `error`). |
| `--db-path` | string | `./data/db` | Path to the database directory. |
| `--discovery` | string | `mdns` | Discovery method. |
| `--consensus-client` | string | _Mandatory_ | Address of the consensus client (e.g., `http://localhost:9000`). |
| `--execution-client` | string | _Mandatory_ | Address of the execution client (e.g., `http://localhost:8545`). |
| `--operator-private-key` | string | | Secret key for the operator. |
| `--metrics-api-port` | int | `0` | Port number for the Metrics API (set to `0` to disable). |
| `--ssv-domain` | string | Derived from local testnet config | Hex-encoded domain type (prefixed with `0x`). |
| `--ssv-registry-sync-offset` | uint64 | Derived from local testnet config | Registry sync offset for the network. |
| `--ssv-registry-contract-addr` | string | Derived from local testnet config | Ethereum address of the network registry contract (e.g., `0xYourAddress`). |
| `--ssv-bootnodes` | string | Derived from local testnet config | Comma-separated list of network bootnodes. |
| `--ssv-discovery-protocol-id` | string | Derived from local testnet config | Hex-encoded discovery protocol ID (prefixed with `0x`). |
| `--ssv-alan-fork-epoch` | uint64 | Derived from local testnet config | Epoch at which the Alan fork occurs in the network. |
| `--ssv-max-validators-per-committee` | int | Derived from local testnet config | Max validators per committee. |


**Note:** The `--consensus-client` and `--execution-client` flags are mandatory and must be provided when running the CLI.

## Examples

### Basic Configuration

Generate a configuration file with default settings, specifying only the mandatory flags:

```bash
ssvnode generate-config \
--consensus-client "http://localhost:9000" \
--execution-client "http://localhost:8545"
```

This command generates a `config.local.yaml` file in the `./config` directory with default settings for all other parameters.

### Custom Output Path and Log Level

Generate a configuration file with a custom output path and set the log level to `debug`:

```bash
ssvnode generate-config \
--consensus-client "http://consensus.example.com:9000" \
--execution-client "http://execution.example.com:8545" \
--output-path "/etc/ssv/config.yaml" \
--log-level "debug"
```

### Specify Operator Private Key and Enable Metrics API

Generate a configuration with an operator's private key and enable the Metrics API on port `8080`:

```bash
ssvnode generate-config \
--consensus-client "http://consensus.example.com:9000" \
--execution-client "http://execution.example.com:8545" \
--operator-private-key "your-operator-private-key" \
--metrics-api-port 8080
```

### Advanced Network Configuration

Customize network settings such as bootnodes, discovery protocol ID, fork epoch, etc:

```bash
ssvnode generate-config \
--consensus-client "http://consensus.example.com:9000" \
--execution-client "http://execution.example.com:8545" \
--ssv-domain "0x12345678" \
--ssv-registry-sync-offset 50 \
--ssv-registry-contract-addr "0xYourRegistryContractAddress" \
--ssv-bootnodes "enode://bootnode1@127.0.0.1:30303,enode://bootnode2@127.0.0.1:30304" \
--ssv-discovery-protocol-id "0x1234567890ab" \
--ssv-max-validators-per-committee 560
```

## Configuration

The generated YAML configuration file (`config.local.yaml` by default) follows the structure defined by the `Config` struct. Below is an overview of each section and its corresponding fields:

### YAML Structure

```yaml
global:
LogLevel: info
db:
Path: ./data/db
eth2:
BeaconNodeAddr: http://localhost:9000
eth1:
ETH1Addr: http://localhost:8545
p2p:
Discovery: mdns
ssv:
Network: LocalTestnetSSV
CustomNetwork:
DomainType: "0x12345678"
RegistrySyncOffset: 0
RegistryContractAddr: "0xYourRegistryContractAddress"
Bootnodes:
- "enode://bootnode1@127.0.0.1:30303"
- "enode://bootnode2@127.0.0.1:30304"
DiscoveryProtocolID: "0x1234567890ab"
OperatorPrivateKey: your-operator-private-key
MetricsAPIPort: 8080
```

### Sections

- **global**
- `LogLevel`: Specifies the logging level (e.g., `debug`, `info`, `warn`, `error`).

- **db**
- `Path`: Path to the database directory.

- **eth2**
- `BeaconNodeAddr`: Address of the consensus client (Beacon Node).

- **eth1**
- `ETH1Addr`: Address of the execution client (ETH1 Node).

- **p2p**
- `Discovery`: Peer-to-peer discovery method (e.g., `mdns`).

- **ssv**
- `Network`: Name of the network.
- `CustomNetwork`: Contains custom network parameters.
- `DomainType`: Hex-encoded domain type (prefixed with `0x`).
- `RegistrySyncOffset`: Registry sync offset for the network.
- `RegistryContractAddr`: Ethereum address of the network registry contract.
- `Bootnodes`: List of network bootnodes.
- `DiscoveryProtocolID`: Hex-encoded discovery protocol ID (prefixed with `0x`).

- **OperatorPrivateKey**
- `OperatorPrivateKey`: Secret key for the operator.

- **MetricsAPIPort**
- `MetricsAPIPort`: Port number for the Metrics API.
149 changes: 149 additions & 0 deletions cli/generate_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package cli

import (
"encoding/hex"
"fmt"
"log"
"math/big"
"os"
"strings"

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"
spectypes "github.com/ssvlabs/ssv-spec/types"
"gopkg.in/yaml.v3"

"github.com/ssvlabs/ssv/networkconfig"
)

const (
defaultOutputPath = "./config/config.local.yaml"
defaultLogLevel = "info"
defaultDBPath = "./data/db"
defaultDiscovery = "mdns"
sliceSeparator = ","
configFilePermissions = 0644
)

var (
defaultNetwork = networkconfig.LocalTestnet
)

var (
outputPath string
logLevel string
dbPath string
discovery string
consensusClient string
executionClient string
operatorPrivateKey string
metricsAPIPort int
ssvDomain string
ssvRegistrySyncOffset uint64
ssvRegistryContractAddr string
ssvBootnodes string
ssvDiscoveryProtocolID string
)

type SSVConfig struct {
Global struct {
LogLevel string `yaml:"LogLevel,omitempty"`
} `yaml:"global,omitempty"`
DB struct {
Path string `yaml:"Path,omitempty"`
} `yaml:"db,omitempty"`
ConsensusClient struct {
Address string `yaml:"BeaconNodeAddr,omitempty"`
} `yaml:"eth2,omitempty"`
ExecutionClient struct {
Address string `yaml:"ETH1Addr,omitempty"`
} `yaml:"eth1,omitempty"`
P2P struct {
Discovery string `yaml:"Discovery,omitempty"`
} `yaml:"p2p,omitempty"`
SSV struct {
NetworkName string `yaml:"Network,omitempty" env:"NETWORK" env-description:"Network is the network of this node,omitempty"`
CustomNetwork *networkconfig.SSVConfig `yaml:"CustomNetwork,omitempty" env:"CUSTOM_NETWORK" env-description:"Custom network parameters,omitempty"`
} `yaml:"ssv,omitempty"`
OperatorPrivateKey string `yaml:"OperatorPrivateKey,omitempty"`
MetricsAPIPort int `yaml:"MetricsAPIPort,omitempty"`
}

// generateConfigCmd is the command to generate ssv operator config.
var generateConfigCmd = &cobra.Command{
Use: "generate-config",
Short: "generates ssv operator config",
Run: func(cmd *cobra.Command, args []string) {
parsedDomain, err := hex.DecodeString(strings.TrimPrefix(ssvDomain, "0x"))
if err != nil {
log.Fatalf("Failed to decode network domain: %v", err)
}

Check warning on line 80 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L76-L80

Added lines #L76 - L80 were not covered by tests

parsedDiscoveryProtocolID, err := hex.DecodeString(strings.TrimPrefix(ssvDiscoveryProtocolID, "0x"))
if err != nil {
log.Fatalf("Failed to decode discovery protocol ID: %v", err)
}

Check warning on line 85 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L82-L85

Added lines #L82 - L85 were not covered by tests

var parsedDiscoveryProtocolIDArr [6]byte
if len(parsedDiscoveryProtocolID) != 0 {
parsedDiscoveryProtocolIDArr = [6]byte(parsedDiscoveryProtocolID)
Comment thread
nkryuchkov marked this conversation as resolved.
}

Check warning on line 90 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L87-L90

Added lines #L87 - L90 were not covered by tests

var bootnodes []string
if ssvBootnodes != "" {
bootnodes = strings.Split(ssvBootnodes, sliceSeparator)
}

Check warning on line 95 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L92-L95

Added lines #L92 - L95 were not covered by tests

var config SSVConfig
config.Global.LogLevel = logLevel
config.DB.Path = dbPath
config.ConsensusClient.Address = consensusClient
config.ExecutionClient.Address = executionClient
config.P2P.Discovery = discovery
config.OperatorPrivateKey = operatorPrivateKey
config.MetricsAPIPort = metricsAPIPort
config.SSV.CustomNetwork = &networkconfig.SSVConfig{
DomainType: spectypes.DomainType(parsedDomain),
RegistrySyncOffset: new(big.Int).SetUint64(ssvRegistrySyncOffset),
RegistryContractAddr: ethcommon.HexToAddress(ssvRegistryContractAddr),
Bootnodes: bootnodes,
DiscoveryProtocolID: parsedDiscoveryProtocolIDArr,
}

data, err := yaml.Marshal(&config)
if err != nil {
log.Fatalf("Failed to marshal YAML: %v", err)
}

Check warning on line 116 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L97-L116

Added lines #L97 - L116 were not covered by tests

err = os.WriteFile(outputPath, data, configFilePermissions)
if err != nil {
log.Fatalf("Failed to write file: %v", err)
}

Check warning on line 121 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L118-L121

Added lines #L118 - L121 were not covered by tests

log.Printf("Saved config into '%s':", outputPath)
fmt.Println(string(data))

Check warning on line 124 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L123-L124

Added lines #L123 - L124 were not covered by tests
},
}

func init() {
generateConfigCmd.Flags().StringVarP(&outputPath, "output-path", "o", defaultOutputPath, "Output path for generated config")
generateConfigCmd.Flags().StringVar(&logLevel, "log-level", defaultLogLevel, "Log level")
generateConfigCmd.Flags().StringVar(&dbPath, "db-path", defaultDBPath, "DB path")
generateConfigCmd.Flags().StringVar(&discovery, "discovery", defaultDiscovery, "Discovery")
generateConfigCmd.Flags().StringVar(&consensusClient, "consensus-client", "", "Consensus client (required)")
_ = generateConfigCmd.MarkFlagRequired("consensus-client")
generateConfigCmd.Flags().StringVar(&executionClient, "execution-client", "", "Execution client (required)")
_ = generateConfigCmd.MarkFlagRequired("execution-client")
generateConfigCmd.Flags().StringVar(&operatorPrivateKey, "operator-private-key", "", "Secret key")
generateConfigCmd.Flags().IntVar(&metricsAPIPort, "metrics-api-port", 0, "Metrics API port")

ssvDomainDefault := "0x" + hex.EncodeToString(defaultNetwork.DomainType[:])
generateConfigCmd.Flags().StringVar(&ssvDomain, "ssv-domain", ssvDomainDefault, "SSV domain type")
generateConfigCmd.Flags().Uint64Var(&ssvRegistrySyncOffset, "ssv-registry-sync-offset", defaultNetwork.RegistrySyncOffset.Uint64(), "SSV registry sync offset")
generateConfigCmd.Flags().StringVar(&ssvRegistryContractAddr, "ssv-registry-contract-addr", defaultNetwork.RegistryContractAddr.String(), "SSV registry contract addr")
generateConfigCmd.Flags().StringVar(&ssvBootnodes, "ssv-bootnodes", strings.Join(defaultNetwork.Bootnodes, sliceSeparator), "SSV bootnodes (comma-separated)")
ssvDiscoveryProtocolIDDefault := "0x" + hex.EncodeToString(defaultNetwork.DiscoveryProtocolID[:])
generateConfigCmd.Flags().StringVar(&ssvDiscoveryProtocolID, "ssv-discovery-protocol-id", ssvDiscoveryProtocolIDDefault, "SSV discovery protocol ID")

RootCmd.AddCommand(generateConfigCmd)

Check warning on line 148 in cli/generate_config.go

View check run for this annotation

Codecov / codecov/patch

cli/generate_config.go#L128-L148

Added lines #L128 - L148 were not covered by tests
}
27 changes: 18 additions & 9 deletions cli/operator/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"time"

"github.com/attestantio/go-eth2-client/spec/phase0"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ilyakaznacheev/cleanenv"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -287,7 +286,7 @@
ec, err := executionclient.New(
cmd.Context(),
executionAddrList[0],
ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr),
ssvNetworkConfig.RegistryContractAddr,

Check warning on line 289 in cli/operator/node.go

View check run for this annotation

Codecov / codecov/patch

cli/operator/node.go#L289

Added line #L289 was not covered by tests
executionclient.WithLogger(logger),
executionclient.WithFollowDistance(executionclient.DefaultFollowDistance),
executionclient.WithConnectionTimeout(cfg.ExecutionClient.ConnectionTimeout),
Expand All @@ -305,7 +304,7 @@
ec, err := executionclient.NewMulti(
cmd.Context(),
executionAddrList,
ethcommon.HexToAddress(ssvNetworkConfig.RegistryContractAddr),
ssvNetworkConfig.RegistryContractAddr,

Check warning on line 307 in cli/operator/node.go

View check run for this annotation

Codecov / codecov/patch

cli/operator/node.go#L307

Added line #L307 was not covered by tests
executionclient.WithLoggerMulti(logger),
executionclient.WithFollowDistanceMulti(executionclient.DefaultFollowDistance),
executionclient.WithConnectionTimeoutMulti(cfg.ExecutionClient.ConnectionTimeout),
Expand Down Expand Up @@ -714,7 +713,6 @@
return fmt.Errorf("incompatible config change: %w", err)
}
} else {

if err := nodeStorage.SaveConfig(nil, currentConfig); err != nil {
return fmt.Errorf("failed to store config: %w", err)
}
Expand Down Expand Up @@ -902,9 +900,20 @@
}

func setupSSVNetwork(logger *zap.Logger) (networkconfig.SSVConfig, error) {
ssvConfig, err := networkconfig.GetSSVConfigByName(cfg.SSVOptions.NetworkName)
if err != nil {
return networkconfig.SSVConfig{}, err
var ssvConfig networkconfig.SSVConfig

if cfg.SSVOptions.CustomNetwork != nil {
ssvConfig = *cfg.SSVOptions.CustomNetwork
logger.Info("using custom network config")
} else if cfg.SSVOptions.NetworkName != "" {
snc, err := networkconfig.GetSSVConfigByName(cfg.SSVOptions.NetworkName)
if err != nil {
return ssvConfig, err
}
ssvConfig = snc
logger.Info("found network config by name",
zap.String("name", cfg.SSVOptions.NetworkName),
)

Check warning on line 916 in cli/operator/node.go

View check run for this annotation

Codecov / codecov/patch

cli/operator/node.go#L903-L916

Added lines #L903 - L916 were not covered by tests
}

if cfg.SSVOptions.CustomDomainType != "" {
Expand All @@ -923,7 +932,7 @@
postForkDomain := binary.BigEndian.Uint32(domainBytes) + 1
binary.BigEndian.PutUint32(ssvConfig.DomainType[:], postForkDomain)

logger.Info("running with custom domain type",
logger.Warn("running with custom domain type; it's deprecated, consider using custom network instead",

Check warning on line 935 in cli/operator/node.go

View check run for this annotation

Codecov / codecov/patch

cli/operator/node.go#L935

Added line #L935 was not covered by tests
fields.Domain(ssvConfig.DomainType),
)
}
Expand All @@ -936,7 +945,7 @@
logger.Info("setting ssv network",
zap.Any("config", ssvConfig),
zap.String("nodeType", nodeType),
zap.String("registryContract", ssvConfig.RegistryContractAddr),
zap.String("registryContract", ssvConfig.RegistryContractAddr.String()),

Check warning on line 948 in cli/operator/node.go

View check run for this annotation

Codecov / codecov/patch

cli/operator/node.go#L948

Added line #L948 was not covered by tests
)

return ssvConfig, nil
Expand Down
Loading