Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: exit with validator index, allow BN URLs #3106

Merged
merged 3 commits into from
Jun 4, 2024
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
64 changes: 35 additions & 29 deletions cmd/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"context"
"time"

eth2http "github.com/attestantio/go-eth2-client/http"
eth2api "github.com/attestantio/go-eth2-client/api"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/cobra"

Expand All @@ -18,19 +18,22 @@
)

type exitConfig struct {
BeaconNodeURL string
ValidatorPubkey string
PrivateKeyPath string
ValidatorKeysDir string
LockFilePath string
PublishAddress string
PublishTimeout time.Duration
ExitEpoch uint64
FetchedExitPath string
PlaintextOutput bool
BeaconNodeTimeout time.Duration
ExitFromFilePath string
Log log.Config
BeaconNodeEndpoints []string
ValidatorPubkey string
ValidatorIndex uint64
ValidatorIndexPresent bool
ExpertMode bool
PrivateKeyPath string
ValidatorKeysDir string
LockFilePath string
PublishAddress string
PublishTimeout time.Duration
ExitEpoch uint64
FetchedExitPath string
PlaintextOutput bool
BeaconNodeTimeout time.Duration
ExitFromFilePath string
Log log.Config
}

func newExitCmd(cmds ...*cobra.Command) *cobra.Command {
Expand All @@ -49,7 +52,7 @@

const (
publishAddress exitFlag = iota
beaconNodeURL
beaconNodeEndpoints
privateKeyPath
lockFilePath
validatorKeysDir
Expand All @@ -59,14 +62,15 @@
beaconNodeTimeout
fetchedExitPath
publishTimeout
validatorIndex
)

func (ef exitFlag) String() string {
switch ef {
case publishAddress:
return "publish-address"
case beaconNodeURL:
return "beacon-node-url"
case beaconNodeEndpoints:
return "beacon-node-endpoints"

Check warning on line 73 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L72-L73

Added lines #L72 - L73 were not covered by tests
case privateKeyPath:
return "private-key-file"
case lockFilePath:
Expand All @@ -85,6 +89,8 @@
return "fetched-exit-path"
case publishTimeout:
return "publish-timeout"
case validatorIndex:
return "validator-index"

Check warning on line 93 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L92-L93

Added lines #L92 - L93 were not covered by tests
default:
return "unknown"
}
Expand All @@ -110,16 +116,16 @@
switch flag {
case publishAddress:
cmd.Flags().StringVar(&config.PublishAddress, publishAddress.String(), "https://api.obol.tech", maybeRequired("The URL of the remote API."))
case beaconNodeURL:
cmd.Flags().StringVar(&config.BeaconNodeURL, beaconNodeURL.String(), "", maybeRequired("Beacon node URL."))
case beaconNodeEndpoints:
cmd.Flags().StringSliceVar(&config.BeaconNodeEndpoints, beaconNodeEndpoints.String(), nil, maybeRequired("Comma separated list of one or more beacon node endpoint URLs."))

Check warning on line 120 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L119-L120

Added lines #L119 - L120 were not covered by tests
case privateKeyPath:
cmd.Flags().StringVar(&config.PrivateKeyPath, privateKeyPath.String(), ".charon/charon-enr-private-key", maybeRequired("The path to the charon enr private key file. "))
case lockFilePath:
cmd.Flags().StringVar(&config.LockFilePath, lockFilePath.String(), ".charon/cluster-lock.json", maybeRequired("The path to the cluster lock file defining the distributed validator cluster."))
case validatorKeysDir:
cmd.Flags().StringVar(&config.ValidatorKeysDir, validatorKeysDir.String(), ".charon/validator_keys", maybeRequired("Path to the directory containing the validator private key share files and passwords."))
case validatorPubkey:
cmd.Flags().StringVar(&config.ValidatorPubkey, validatorPubkey.String(), "", maybeRequired("Public key of the validator to exit, must be present in the cluster lock manifest."))
cmd.Flags().StringVar(&config.ValidatorPubkey, validatorPubkey.String(), "", maybeRequired("Public key of the validator to exit, must be present in the cluster lock manifest. If --validator-index is also provided, validator liveliness won't be checked on the beacon chain."))

Check warning on line 128 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L128

Added line #L128 was not covered by tests
case exitEpoch:
cmd.Flags().Uint64Var(&config.ExitEpoch, exitEpoch.String(), 162304, maybeRequired("Exit epoch at which the validator will exit, must be the same across all the partial exits."))
case exitFromFile:
Expand All @@ -130,6 +136,8 @@
cmd.Flags().StringVar(&config.FetchedExitPath, fetchedExitPath.String(), "./", maybeRequired("Path to store fetched signed exit messages."))
case publishTimeout:
cmd.Flags().DurationVar(&config.PublishTimeout, publishTimeout.String(), 30*time.Second, "Timeout for publishing a signed exit to the publish-address API.")
case validatorIndex:
cmd.Flags().Uint64Var(&config.ValidatorIndex, validatorIndex.String(), 0, "Validator index of the validator to exit, the associated public key must be present in the cluster lock manifest. If --validator-pubkey is also provided, validator liveliness won't be checked on the beacon chain.")

Check warning on line 140 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L139-L140

Added lines #L139 - L140 were not covered by tests
}

if f.required {
Expand All @@ -138,19 +146,17 @@
}
}

func eth2Client(ctx context.Context, u string, timeout time.Duration) (eth2wrap.Client, error) {
bnHTTPClient, err := eth2http.New(ctx,
eth2http.WithAddress(u),
eth2http.WithTimeout(timeout),
eth2http.WithLogLevel(1), // zerolog.InfoLevel
)
func eth2Client(ctx context.Context, u []string, timeout time.Duration) (eth2wrap.Client, error) {
cl, err := eth2wrap.NewMultiHTTP(timeout, u...)
if err != nil {
return nil, errors.Wrap(err, "can't connect to beacon node")
return nil, err

Check warning on line 152 in cmd/exit.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit.go#L152

Added line #L152 was not covered by tests
}

bnClient := bnHTTPClient.(*eth2http.Service)
if _, err = cl.NodeVersion(ctx, &eth2api.NodeVersionOpts{}); err != nil {
return nil, errors.Wrap(err, "can't connect to beacon node")
}

return eth2wrap.AdaptEth2HTTP(bnClient, timeout), nil
return cl, nil
}

// signExit signs a voluntary exit message for valIdx with the given keyShare.
Expand Down
4 changes: 2 additions & 2 deletions cmd/exit_broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
{validatorKeysDir, false},
{exitEpoch, false},
{validatorPubkey, true},
{beaconNodeURL, true},
{beaconNodeEndpoints, true},

Check warning on line 56 in cmd/exit_broadcast.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit_broadcast.go#L56

Added line #L56 was not covered by tests
{exitFromFile, false},
{beaconNodeTimeout, false},
})
Expand Down Expand Up @@ -81,7 +81,7 @@

ctx = log.WithCtx(ctx, z.Str("validator", validator.String()))

eth2Cl, err := eth2Client(ctx, config.BeaconNodeURL, config.BeaconNodeTimeout)
eth2Cl, err := eth2Client(ctx, config.BeaconNodeEndpoints, config.BeaconNodeTimeout)
if err != nil {
return errors.Wrap(err, "cannot create eth2 client for specified beacon node")
}
Expand Down
80 changes: 40 additions & 40 deletions cmd/exit_broadcast_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func testRunBcastFullExitCmdFlow(t *testing.T, fromFile bool) {
require.NoError(t, beaconMock.Close())
}()

eth2Cl, err := eth2Client(ctx, beaconMock.Address(), 10*time.Second)
eth2Cl, err := eth2Client(ctx, []string{beaconMock.Address()}, 10*time.Second)
require.NoError(t, err)

eth2Cl.SetForkVersion([4]byte(lock.ForkVersion))
Expand All @@ -115,15 +115,15 @@ func testRunBcastFullExitCmdFlow(t *testing.T, fromFile bool) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", idx))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
}

require.NoError(t, runSignPartialExit(ctx, config), "operator index: %v", idx)
Expand All @@ -132,15 +132,15 @@ func testRunBcastFullExitCmdFlow(t *testing.T, fromFile bool) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", 0))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
}

if fromFile {
Expand All @@ -162,14 +162,14 @@ func testRunBcastFullExitCmdFlow(t *testing.T, fromFile bool) {
func Test_runBcastFullExitCmd_Config(t *testing.T) {
t.Parallel()
type test struct {
name string
noIdentity bool
noLock bool
badOAPIURL bool
badBeaconNodeURL bool
badValidatorAddr bool
badExistingExitPath bool
errData string
name string
noIdentity bool
noLock bool
badOAPIURL bool
badBeaconNodeEndpoints bool
badValidatorAddr bool
badExistingExitPath bool
errData string
}

tests := []test{
Expand All @@ -189,9 +189,9 @@ func Test_runBcastFullExitCmd_Config(t *testing.T) {
errData: "could not create obol api client",
},
{
name: "Bad beacon node URL",
badBeaconNodeURL: true,
errData: "cannot create eth2 client for specified beacon node",
name: "Bad beacon node URLs",
badBeaconNodeEndpoints: true,
errData: "cannot create eth2 client for specified beacon node",
},
{
name: "Bad validator address",
Expand Down Expand Up @@ -261,7 +261,7 @@ func Test_runBcastFullExitCmd_Config(t *testing.T) {

bnURL := badStr

if !test.badBeaconNodeURL {
if !test.badBeaconNodeEndpoints {
beaconMock, err := beaconmock.New()
require.NoError(t, err)
defer func() {
Expand All @@ -283,15 +283,15 @@ func Test_runBcastFullExitCmd_Config(t *testing.T) {
baseDir := filepath.Join(root, "op0") // one operator is enough

config := exitConfig{
BeaconNodeURL: bnURL,
ValidatorPubkey: valAddr,
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: oapiURL,
ExitEpoch: 0,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
BeaconNodeEndpoints: []string{bnURL},
ValidatorPubkey: valAddr,
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: oapiURL,
ExitEpoch: 0,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
}

if test.badExistingExitPath {
Expand Down
20 changes: 10 additions & 10 deletions cmd/exit_fetch_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func Test_runFetchExitFullFlow(t *testing.T) {
require.NoError(t, beaconMock.Close())
}()

eth2Cl, err := eth2Client(ctx, beaconMock.Address(), 10*time.Second)
eth2Cl, err := eth2Client(ctx, []string{beaconMock.Address()}, 10*time.Second)
require.NoError(t, err)

eth2Cl.SetForkVersion([4]byte(lock.ForkVersion))
Expand All @@ -99,15 +99,15 @@ func Test_runFetchExitFullFlow(t *testing.T) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", idx))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
ValidatorPubkey: lock.Validators[0].PublicKeyHex(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PublishAddress: srv.URL,
ExitEpoch: 194048,
BeaconNodeTimeout: 30 * time.Second,
PublishTimeout: 10 * time.Second,
}

require.NoError(t, runSignPartialExit(ctx, config), "operator index: %v", idx)
Expand Down
4 changes: 2 additions & 2 deletions cmd/exit_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

bindExitFlags(cmd, &config, []exitCLIFlag{
{lockFilePath, false},
{beaconNodeURL, true},
{beaconNodeEndpoints, true},

Check warning on line 44 in cmd/exit_list.go

View check run for this annotation

Codecov / codecov/patch

cmd/exit_list.go#L44

Added line #L44 was not covered by tests
{beaconNodeTimeout, false},
})

Expand Down Expand Up @@ -75,7 +75,7 @@
return nil, errors.Wrap(err, "could not load cluster-lock.json")
}

eth2Cl, err := eth2Client(ctx, config.BeaconNodeURL, config.BeaconNodeTimeout)
eth2Cl, err := eth2Client(ctx, config.BeaconNodeEndpoints, config.BeaconNodeTimeout)
if err != nil {
return nil, errors.Wrap(err, "cannot create eth2 client for specified beacon node")
}
Expand Down
36 changes: 18 additions & 18 deletions cmd/exit_list_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ func Test_runListActiveVals(t *testing.T) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", 0))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
}

require.NoError(t, runListActiveValidatorsCmd(ctx, config))
Expand Down Expand Up @@ -143,12 +143,12 @@ func Test_listActiveVals(t *testing.T) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", 0))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
}

vals, err := listActiveVals(ctx, config)
Expand Down Expand Up @@ -184,12 +184,12 @@ func Test_listActiveVals(t *testing.T) {
baseDir := filepath.Join(root, fmt.Sprintf("op%d", 0))

config := exitConfig{
BeaconNodeURL: beaconMock.Address(),
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
BeaconNodeEndpoints: []string{beaconMock.Address()},
PrivateKeyPath: filepath.Join(baseDir, "charon-enr-private-key"),
ValidatorKeysDir: filepath.Join(baseDir, "validator_keys"),
LockFilePath: filepath.Join(baseDir, "cluster-lock.json"),
PlaintextOutput: true,
BeaconNodeTimeout: 30 * time.Second,
}

vals, err := listActiveVals(ctx, config)
Expand Down
Loading
Loading