Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
736b5e4
feat: initial nimbus implementation
stdevMac Jun 25, 2024
439cc96
feat: add nimbus validator env vars
stdevMac Jul 3, 2024
56ab370
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Sep 3, 2024
085bf5a
feat: nimbus validator working
stdevMac Sep 4, 2024
ac68c4a
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Sep 17, 2024
c80e0c3
feat: import-keys to nimbus
stdevMac Sep 19, 2024
6b9e405
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Sep 19, 2024
5ffd161
feat: add slashing import and export for nimbus
stdevMac Sep 19, 2024
196f7f9
fix: format
stdevMac Sep 19, 2024
96caeb7
fix: go.mod
stdevMac Sep 19, 2024
b91e6b7
chore: fix error in tests
stdevMac Sep 19, 2024
9b5c6d7
docs: udpate documentation
stdevMac Sep 19, 2024
43de0af
fix: failing test
stdevMac Sep 19, 2024
2f854ed
Merge branch 'develop' into feat/nimbus-full
AntiD2ta Oct 6, 2024
25d9221
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Oct 7, 2024
474226e
tests: add e2e tests for nimbus
stdevMac Oct 7, 2024
daa9bce
docs: add comments and docs for why nimbus use consensus client on im…
stdevMac Oct 7, 2024
fa551aa
Merge remote-tracking branch 'origin/feat/nimbus-full' into feat/nimb…
stdevMac Oct 7, 2024
de917b5
fix: format
stdevMac Oct 7, 2024
efbcad4
Merge branch 'develop' into feat/nimbus-full
stdevMac Oct 10, 2024
1773714
Merge branch 'develop' into feat/nimbus-full
stdevMac Oct 15, 2024
bd0661a
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Oct 15, 2024
a82b89d
Merge remote-tracking branch 'origin/feat/nimbus-full' into feat/nimb…
stdevMac Oct 15, 2024
0558bfe
fix: patch gnosis and chiado consensus clients for nimbus
stdevMac Oct 15, 2024
67aa676
Merge remote-tracking branch 'origin/develop' into feat/nimbus-full
stdevMac Oct 17, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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.
- Security policy.
- Support for Nimbus as Consensus and Validator client.

### Changed
- Update Go version from 1.21 to 1.22.
Expand Down
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,12 @@ read more about it in [our documentation](https://docs.sedge.nethermind.io/docs/
### Mainnet

| Execution | Consensus | Validator |
| ---------- | ---------- | ---------- |
| ---------- |------------|------------|
| Geth | Lighthouse | Lighthouse |
| Nethermind | Lodestar | Lodestar |
| Erigon | Prysm | Prysm |
| Besu | Teku | Teku |
| | Nimbus | Nimbus |

### Sepolia

Expand All @@ -155,6 +156,7 @@ read more about it in [our documentation](https://docs.sedge.nethermind.io/docs/
| Nethermind | Lodestar | Lodestar |
| Erigon | Prysm | Prysm |
| Besu | Teku | Teku |
| | Nimbus | Nimbus |

### Holesky

Expand All @@ -164,6 +166,7 @@ read more about it in [our documentation](https://docs.sedge.nethermind.io/docs/
| Nethermind | Lodestar | Lodestar |
| Erigon | Teku | Teku |
| Besu | Prysm | Prysm |
| | Nimbus | Nimbus |

### Gnosis

Expand All @@ -172,23 +175,26 @@ read more about it in [our documentation](https://docs.sedge.nethermind.io/docs/
| Nethermind | Lighthouse | Lighthouse |
| Erigon | Lodestar | Lodestar |
| | Teku | Teku |
| | Nimbus | Nimbus |

### Chiado (Gnosis testnet)

| Execution | Consensus | Validator |
| ------------- | ---------- | ---------- |
|---------------| ---------- | ---------- |
| Nethermind | Lighthouse | Lighthouse |
| Erigon (soon) | Lodestar | Lodestar |
| | Teku | Teku |
| | Nimbus | Nimbus |

### CL clients with Mev-Boost

| Client | Mev-Boost | Networks |
| ---------- | --------- |---------------------------|
| Lighthouse | yes | Mainnet, Sepolia, Holesky |
| Lodestar | yes | Mainnet, Sepolia, Holesky |
| Prysm | yes | Mainnet, Sepolia, Holesky |
| Teku | yes | Mainnet, Sepolia, Holesky |
| Client | Mev-Boost | Networks |
|------------|------------|---------------------------|
| Lighthouse | yes | Mainnet, Sepolia, Holesky |
| Lodestar | yes | Mainnet, Sepolia, Holesky |
| Prysm | yes | Mainnet, Sepolia, Holesky |
| Teku | yes | Mainnet, Sepolia, Holesky |
| Nimbus | yes | Mainnet, Sepolia, Holesky |

## Supported Linux flavours for dependency installation

Expand Down Expand Up @@ -256,9 +262,9 @@ The following roadmap covers the main features and ideas we want to implement bu

- [x] Support Erigon on Gnosis
- [x] Support for Lido CSM
- [x] Support for Nimbus client as Consensus and Validator
- [ ] Include monitoring tool for alerting, tracking validator balance, and tracking sync progress and status of nodes
- [ ] More tests!!!
- [ ] Support for Nimbus client


## 💪 Want to contribute?
Expand Down
6 changes: 5 additions & 1 deletion cli/actions/generation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,11 @@ func TestGenerateDockerCompose(t *testing.T) {
}
// Check that Checkpoint Sync URL is set
if tc.genData.CheckpointSyncUrl != "" {
assert.True(t, contains(t, cmpData.Services.Consensus.Command, tc.genData.CheckpointSyncUrl), "Checkpoint Sync URL not found in consensus service command: %s", cmpData.Services.Consensus.Command)
if tc.genData.ConsensusClient != nil && tc.genData.ConsensusClient.Name == "nimbus" {
assert.True(t, contains(t, cmpData.Services.ConsensusSync.Command, tc.genData.CheckpointSyncUrl), "Checkpoint Sync URL not found in consensus service command: %s", cmpData.Services.ConsensusSync.Command)
} else {
assert.True(t, contains(t, cmpData.Services.Consensus.Command, tc.genData.CheckpointSyncUrl), "Checkpoint Sync URL not found in consensus service command: %s", cmpData.Services.Consensus.Command)
}
}

// Check ccImage has the right format
Expand Down
165 changes: 164 additions & 1 deletion cli/actions/importKeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import (
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/NethermindEth/sedge/configs"
"github.com/NethermindEth/sedge/internal/images/validator-import/lighthouse"
Expand All @@ -35,6 +37,7 @@ import (
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/otiai10/copy"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
)

var ErrInterrupted = errors.New("interrupt")
Expand Down Expand Up @@ -110,6 +113,12 @@ func (s *sedgeActions) ImportValidatorKeys(options ImportValidatorKeysOptions) e
return err
}
ctID = prysmCtID
case "nimbus":
nimbusCtID, err := setupNimbusValidatorImport(s.dockerClient, s.dockerServiceManager, options)
if err != nil {
return err
}
ctID = nimbusCtID
case "lodestar":
lodestarCtID, err := setupLodestarValidatorImport(s.dockerClient, s.dockerServiceManager, options)
if err != nil {
Expand All @@ -132,7 +141,12 @@ func (s *sedgeActions) ImportValidatorKeys(options ImportValidatorKeysOptions) e
return fmt.Errorf("%w: %s", ErrUnsupportedValidatorClient, options.ValidatorClient)
}
log.Info("Importing validator keys")
runErr := runAndWaitImportKeys(s.dockerClient, s.dockerServiceManager, ctID)
var runErr error
if options.ValidatorClient == "nimbus" {
runErr = runAndWaitImportKeysNimbus(s.dockerClient, s.dockerServiceManager, ctID)
} else {
runErr = runAndWaitImportKeys(s.dockerClient, s.dockerServiceManager, ctID)
}
// Run validator again
if (previouslyRunning && !options.StopValidator) || options.StartValidator {
log.Info("The validator container is being restarted")
Expand Down Expand Up @@ -208,6 +222,69 @@ func setupPrysmValidatorImportContainer(dockerClient client.APIClient, dockerSer
return ct.ID, nil
}

func setupNimbusValidatorImport(dockerClient client.APIClient, dockerServiceManager DockerServiceManager, options ImportValidatorKeysOptions) (string, error) {
var (
// In the case of Nimbus, it's the consensus client the one that import the keys.
consensusCtName = services.ContainerNameWithTag(services.DefaultSedgeConsensusClient, options.ContainerTag)
validatorCtName = services.ContainerNameWithTag(services.DefaultSedgeValidatorClient, options.ContainerTag)
validatorImportCtName = services.ContainerNameWithTag(services.ServiceCtValidatorImport, options.ContainerTag)
)
validatorImage, err := dockerServiceManager.Image(consensusCtName)
if err != nil {
return "", err
}
// Mounts
mounts := []mount.Mount{
{
Type: mount.TypeBind,
Source: options.From,
Target: "/keystore",
},
}
// CMD
cmd := []string{
"deposits",
"import",
"--data-dir=/data",
"--method=single-salt",
"/keystore",
}
// Custom options
if options.CustomConfig.NetworkConfigPath != "" {
mounts = append(mounts, mount.Mount{
Type: mount.TypeBind,
Source: options.CustomConfig.NetworkConfigPath,
Target: "/network_config/config.yml",
})
cmd = append(cmd, "--config-file=/network_config/config.yml")
} else {
cmd = append(cmd, "--network="+options.Network)
}
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,
},
&container.HostConfig{
Mounts: mounts,
VolumesFrom: []string{consensusCtName, validatorCtName},
},
&network.NetworkingConfig{},
&v1.Platform{},
validatorImportCtName,
)
if err != nil {
return "", err
}
return ct.ID, nil
}

func setupLodestarValidatorImport(dockerClient client.APIClient, dockerServiceManager DockerServiceManager, options ImportValidatorKeysOptions) (string, error) {
var (
validatorCtName = services.ContainerNameWithTag(services.DefaultSedgeValidatorClient, options.ContainerTag)
Expand Down Expand Up @@ -437,3 +514,89 @@ func runAndWaitImportKeys(dockerClient client.APIClient, dockerServiceManager Do
}
}
}

// 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)

// Attach to the container's input/output for direct interaction
resp, err := dockerClient.ContainerAttach(context.Background(), ctID, container.AttachOptions{
Stream: true,
Stdin: true,
Stdout: true,
Stderr: true,
Logs: false, // Don't attach previous logs
})
if err != nil {
return err
}
defer resp.Close()

// Put the terminal in raw mode for proper TTY handling
oldState, err := terminal.MakeRaw(int(syscall.Stdin))
if err != nil {
return err
}
defer func() {
// Restore the terminal state immediately when the container finishes
terminal.Restore(int(syscall.Stdin), oldState)
// Clear the line again before printing the final success logs
fmt.Print("\033[2K\r") // Clear the current line in the terminal
}()

// Start the container
if err := dockerClient.ContainerStart(context.Background(), ctID, container.StartOptions{}); err != nil {
return err
}

// Use goroutines to pipe stdin, stdout, and stderr directly to the user's terminal
go func() {
// Pipe container stdout and stderr to the terminal
_, err := io.Copy(os.Stdout, resp.Reader)
if err != nil {
log.Errorf("Error piping container output: %v", err)
}
}()
go func() {
// Pipe terminal input to the container stdin
_, err := io.Copy(resp.Conn, os.Stdin)
if err != nil {
log.Errorf("Error piping user input: %v", err)
}
}()

// Wait for the container to finish execution
ctExit, errChan := dockerServiceManager.Wait(ctID, container.WaitConditionNextExit)

// Handle OS interrupts (e.g., Ctrl+C) to gracefully stop the container
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM)

select {
case exitResult := <-ctExit:
if err = deleteContainer(dockerClient, ctID); err != nil {
return err
}
// Wait for the container to exit normally
if exitResult.StatusCode != 0 {
log.Errorf("Container exited with non-zero status: %d", exitResult.StatusCode)
return newValidatorImportCtBadExitCodeError(ctID, exitResult.StatusCode, "Container logs...")
}

case <-osSignals:
// If the user interrupts (e.g., Ctrl+C), stop the container
log.Infof("Received interrupt signal, stopping container %s", ctID)
if err := stopContainer(dockerClient, ctID); err != nil {
log.Errorf("Error stopping container: %v", err)
}
if err = deleteContainer(dockerClient, ctID); err != nil {
return err
}
return ErrInterrupted

case err := <-errChan:
return err
}

return nil
}
39 changes: 32 additions & 7 deletions cli/actions/slashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type SlashingImportOptions struct {

func (s *sedgeActions) ImportSlashingInterchangeData(options SlashingImportOptions) error {
validatorContainerName := services.ContainerNameWithTag(services.DefaultSedgeValidatorClient, options.ContainerTag)
consensusContainerName := services.ContainerNameWithTag(services.DefaultSedgeConsensusClient, options.ContainerTag)
slashingContainerName := services.ContainerNameWithTag(services.ServiceCtSlashingData, options.ContainerTag)
// Check validator container exists
_, err := s.dockerServiceManager.ContainerID(validatorContainerName)
Expand Down Expand Up @@ -105,11 +106,18 @@ func (s *sedgeActions) ImportSlashingInterchangeData(options SlashingImportOptio
"--data-path=/data",
"--from=/data/slashing_protection.json",
}
case "nimbus":
cmd = []string{
"slashingdb",
"import",
"/data/slashing_protection.json",
"--validators-dir=/data",
}
default:
return fmt.Errorf("%w: %s", ErrUnsupportedValidatorClient, options.ValidatorClient)
}
log.Infof("Importing slashing data to client %s from %s", options.ValidatorClient, options.From)
if err := runSlashingContainer(s.dockerClient, s.dockerServiceManager, cmd, validatorContainerName, slashingContainerName); err != nil {
if err := runSlashingContainer(s.dockerClient, s.dockerServiceManager, cmd, validatorContainerName, consensusContainerName, slashingContainerName, options.ValidatorClient == "nimbus"); err != nil {
return err
}

Expand All @@ -136,6 +144,7 @@ type SlashingExportOptions struct {

func (s *sedgeActions) ExportSlashingInterchangeData(options SlashingExportOptions) error {
validatorContainerName := services.ContainerNameWithTag(services.DefaultSedgeValidatorClient, options.ContainerTag)
consensusContainerName := services.ContainerNameWithTag(services.DefaultSedgeConsensusClient, options.ContainerTag)
slashingContainerName := services.ContainerNameWithTag(services.ServiceCtSlashingData, options.ContainerTag)
// Check validator container exists
_, err := s.dockerServiceManager.ContainerID(validatorContainerName)
Expand Down Expand Up @@ -187,11 +196,18 @@ func (s *sedgeActions) ExportSlashingInterchangeData(options SlashingExportOptio
"--data-path=/data",
"--to=/data/slashing_protection.json",
}
case "nimbus":
cmd = []string{
"slashingdb",
"export",
"/data/slashing_protection.json",
"--validators-dir=/data",
}
default:
return fmt.Errorf("%w: %s", ErrUnsupportedValidatorClient, options.ValidatorClient)
}
log.Infof("Exporting slashing data from client %s", options.ValidatorClient)
if err := runSlashingContainer(s.dockerClient, s.dockerServiceManager, cmd, validatorContainerName, slashingContainerName); err != nil {
if err := runSlashingContainer(s.dockerClient, s.dockerServiceManager, cmd, validatorContainerName, consensusContainerName, slashingContainerName, options.ValidatorClient == "nimbus"); err != nil {
return err
}
copyFrom := filepath.Join(options.GenerationPath, configs.ValidatorDir, "slashing_protection.json")
Expand All @@ -212,16 +228,25 @@ func (s *sedgeActions) ExportSlashingInterchangeData(options SlashingExportOptio
}

func runSlashingContainer(dockerClient client.APIClient, dockerServiceManager DockerServiceManager, cmd []string,
validatorContainerName string, slashingContainerName string,
validatorContainerName string, consensusContainerName string, slashingContainerName string, isNimbus bool,
) error {
validatorImage, err := dockerServiceManager.Image(validatorContainerName)
if err != nil {
return err
slashingImage := ""
var err error
if isNimbus {
slashingImage, err = dockerServiceManager.Image(consensusContainerName)
if err != nil {
return err
}
} else {
slashingImage, err = dockerServiceManager.Image(validatorContainerName)
if err != nil {
return err
}
}
log.Debugf("Creating %s container", services.ServiceCtSlashingData)
ct, err := dockerClient.ContainerCreate(context.Background(),
&container.Config{
Image: validatorImage,
Image: slashingImage,
Cmd: cmd,
},
&container.HostConfig{
Expand Down
Loading