Add validator withdrawal.

This commit is contained in:
Jim McDonald
2023-04-15 10:17:11 +01:00
parent 4aadee3fad
commit e8513e60b2
53 changed files with 770 additions and 265 deletions

View File

@@ -1,3 +1,7 @@
dev:
- add "chain spec" command
- add "validator withdrawal" command
1.29.2:
- fix regression where validator index could not be used as an account specifier

View File

@@ -51,11 +51,11 @@ func init() {
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
}
func accountCreateBindings() {
if err := viper.BindPFlag("participants", accountCreateCmd.Flags().Lookup("participants")); err != nil {
func accountCreateBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("participants", cmd.Flags().Lookup("participants")); err != nil {
panic(err)
}
if err := viper.BindPFlag("signing-threshold", accountCreateCmd.Flags().Lookup("signing-threshold")); err != nil {
if err := viper.BindPFlag("signing-threshold", cmd.Flags().Lookup("signing-threshold")); err != nil {
panic(err)
}
}

View File

@@ -51,11 +51,11 @@ func init() {
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
}
func accountDeriveBindings() {
if err := viper.BindPFlag("show-private-key", accountDeriveCmd.Flags().Lookup("show-private-key")); err != nil {
func accountDeriveBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("show-private-key", cmd.Flags().Lookup("show-private-key")); err != nil {
panic(err)
}
if err := viper.BindPFlag("show-withdrawal-credentials", accountDeriveCmd.Flags().Lookup("show-withdrawal-credentials")); err != nil {
if err := viper.BindPFlag("show-withdrawal-credentials", cmd.Flags().Lookup("show-withdrawal-credentials")); err != nil {
panic(err)
}
}

View File

@@ -52,14 +52,14 @@ func init() {
accountImportCmd.Flags().String("keystore-passphrase", "", "Passphrase of keystore")
}
func accountImportBindings() {
if err := viper.BindPFlag("key", accountImportCmd.Flags().Lookup("key")); err != nil {
func accountImportBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("key", cmd.Flags().Lookup("key")); err != nil {
panic(err)
}
if err := viper.BindPFlag("keystore", accountImportCmd.Flags().Lookup("keystore")); err != nil {
if err := viper.BindPFlag("keystore", cmd.Flags().Lookup("keystore")); err != nil {
panic(err)
}
if err := viper.BindPFlag("keystore-passphrase", accountImportCmd.Flags().Lookup("keystore-passphrase")); err != nil {
if err := viper.BindPFlag("keystore-passphrase", cmd.Flags().Lookup("keystore-passphrase")); err != nil {
panic(err)
}
}

View File

@@ -44,11 +44,11 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
// Disallow wildcards (for now)
assert(fmt.Sprintf("%s/%s", wallet.Name(), account.Name()) == viper.GetString("account"), "Mismatched account name")
if quiet {
if viper.GetBool("quiet") {
os.Exit(_exitSuccess)
}
outputIf(verbose, fmt.Sprintf("UUID: %v", account.ID()))
outputIf(viper.GetBool("verbose"), fmt.Sprintf("UUID: %v", account.ID()))
var withdrawalPubKey e2types.PublicKey
if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
fmt.Printf("Public key: %#x\n", pubKeyProvider.PublicKey().Marshal())
@@ -58,7 +58,7 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
if distributedAccount, ok := account.(e2wtypes.DistributedAccount); ok {
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
if verbose {
if viper.GetBool("verbose") {
fmt.Printf("Participants:\n")
for k, v := range distributedAccount.Participants() {
fmt.Printf(" %d: %s\n", k, v)
@@ -67,7 +67,7 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
withdrawalPubKey = distributedAccount.CompositePublicKey()
}
if verbose {
if viper.GetBool("verbose") {
withdrawalCredentials := util.SHA256(withdrawalPubKey.Marshal())
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
fmt.Printf("Withdrawal credentials: %#x\n", withdrawalCredentials)

View File

@@ -51,11 +51,11 @@ func init() {
attesterDutiesCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
}
func attesterDutiesBindings() {
if err := viper.BindPFlag("epoch", attesterDutiesCmd.Flags().Lookup("epoch")); err != nil {
func attesterDutiesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", attesterDutiesCmd.Flags().Lookup("validator")); err != nil {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -52,14 +52,14 @@ func init() {
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
}
func attesterInclusionBindings() {
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
func attesterInclusionBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", attesterInclusionCmd.Flags().Lookup("validator")); err != nil {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
if err := viper.BindPFlag("index", attesterInclusionCmd.Flags().Lookup("index")); err != nil {
if err := viper.BindPFlag("index", cmd.Flags().Lookup("index")); err != nil {
panic(err)
}
}

View File

@@ -51,11 +51,11 @@ func init() {
blockAnalyzeCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
}
func blockAnalyzeBindings() {
if err := viper.BindPFlag("blockid", blockAnalyzeCmd.Flags().Lookup("blockid")); err != nil {
func blockAnalyzeBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
panic(err)
}
if err := viper.BindPFlag("stream", blockAnalyzeCmd.Flags().Lookup("stream")); err != nil {
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
panic(err)
}
}

View File

@@ -52,14 +52,14 @@ func init() {
blockInfoCmd.Flags().Bool("ssz", false, "output data in SSZ format")
}
func blockInfoBindings() {
if err := viper.BindPFlag("blockid", blockInfoCmd.Flags().Lookup("blockid")); err != nil {
func blockInfoBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
panic(err)
}
if err := viper.BindPFlag("stream", blockInfoCmd.Flags().Lookup("stream")); err != nil {
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
panic(err)
}
if err := viper.BindPFlag("ssz", blockInfoCmd.Flags().Lookup("ssz")); err != nil {
if err := viper.BindPFlag("ssz", cmd.Flags().Lookup("ssz")); err != nil {
panic(err)
}
}

View File

@@ -53,11 +53,11 @@ func init() {
chainEth1VotesCmd.Flags().String("period", "", "period for which to fetch the votes")
}
func chainEth1VotesBindings() {
if err := viper.BindPFlag("epoch", chainEth1VotesCmd.Flags().Lookup("epoch")); err != nil {
func chainEth1VotesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("period", chainEth1VotesCmd.Flags().Lookup("period")); err != nil {
if err := viper.BindPFlag("period", cmd.Flags().Lookup("period")); err != nil {
panic(err)
}
}

View File

@@ -54,7 +54,7 @@ In quiet mode this will return 0 if the chain information can be obtained, other
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
errCheck(err, "Failed to obtain current fork")
if quiet {
if viper.GetBool("quiet") {
os.Exit(_exitSuccess)
}
@@ -62,12 +62,12 @@ In quiet mode this will return 0 if the chain information can be obtained, other
fmt.Println("Genesis time: undefined")
} else {
fmt.Printf("Genesis time: %s\n", genesis.GenesisTime.Format(time.UnixDate))
outputIf(verbose, fmt.Sprintf("Genesis timestamp: %v", genesis.GenesisTime.Unix()))
outputIf(viper.GetBool("verbose"), fmt.Sprintf("Genesis timestamp: %v", genesis.GenesisTime.Unix()))
}
fmt.Printf("Genesis validators root: %#x\n", genesis.GenesisValidatorsRoot)
fmt.Printf("Genesis fork version: %#x\n", config["GENESIS_FORK_VERSION"].(spec.Version))
fmt.Printf("Current fork version: %#x\n", fork.CurrentVersion)
if verbose {
if viper.GetBool("verbose") {
forkData := &spec.ForkData{
CurrentVersion: fork.CurrentVersion,
GenesisValidatorsRoot: genesis.GenesisValidatorsRoot,
@@ -91,5 +91,5 @@ func init() {
chainFlags(chainInfoCmd)
}
func chainInfoBindings() {
func chainInfoBindings(_ *cobra.Command) {
}

View File

@@ -50,8 +50,8 @@ func init() {
chainQueuesCmd.Flags().String("epoch", "", "epoch for which to fetch the queues")
}
func chainQueuesBindings() {
if err := viper.BindPFlag("epoch", chainQueuesCmd.Flags().Lookup("epoch")); err != nil {
func chainQueuesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
}

97
cmd/chainspec.go Normal file
View File

@@ -0,0 +1,97 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"encoding/json"
"fmt"
"sort"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
)
var chainSpecCmd = &cobra.Command{
Use: "spec",
Short: "Obtain specification for a chain",
Long: `Obtain specification for a chain. For example:
ethdo chain spec
In quiet mode this will return 0 if the chain specification can be obtained, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !viper.GetBool("quiet"),
})
errCheck(err, "Failed to connect to Ethereum consensus node")
spec, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
errCheck(err, "Failed to obtain chain specification")
if viper.GetBool("quiet") {
return
}
// Tweak the spec for output.
for k, v := range spec {
switch t := v.(type) {
case phase0.Version:
spec[k] = fmt.Sprintf("%#x", t)
case phase0.DomainType:
spec[k] = fmt.Sprintf("%#x", t)
case time.Time:
spec[k] = fmt.Sprintf("%d", t.Unix())
case time.Duration:
spec[k] = fmt.Sprintf("%d", uint64(t.Seconds()))
case []byte:
spec[k] = fmt.Sprintf("%#x", t)
case uint64:
spec[k] = fmt.Sprintf("%d", t)
}
}
if viper.GetBool("json") {
data, err := json.Marshal(spec)
errCheck(err, "Failed to marshal JSON")
fmt.Printf("%s\n", string(data))
} else {
keys := make([]string, 0, len(spec))
for k := range spec {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
fmt.Printf("%s: %v\n", key, spec[key])
}
}
},
}
func init() {
chainCmd.AddCommand(chainSpecCmd)
chainFlags(chainSpecCmd)
}
func chainSpecBindings(_ *cobra.Command) {
}

View File

@@ -83,7 +83,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
res.WriteString(fmt.Sprintf("%d", epoch))
res.WriteString("\n")
if verbose {
if viper.GetBool("verbose") {
res.WriteString("Epoch slots: ")
res.WriteString(fmt.Sprintf("%d", epochStartSlot))
res.WriteString("-")
@@ -106,7 +106,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
res.WriteString("Justified epoch: ")
res.WriteString(fmt.Sprintf("%d", finality.Justified.Epoch))
res.WriteString("\n")
if verbose {
if viper.GetBool("verbose") {
distance := epoch - finality.Justified.Epoch
res.WriteString("Justified epoch distance: ")
res.WriteString(fmt.Sprintf("%d", distance))
@@ -116,14 +116,14 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
res.WriteString("Finalized epoch: ")
res.WriteString(fmt.Sprintf("%d", finality.Finalized.Epoch))
res.WriteString("\n")
if verbose {
if viper.GetBool("verbose") {
distance := epoch - finality.Finalized.Epoch
res.WriteString("Finalized epoch distance: ")
res.WriteString(fmt.Sprintf("%d", distance))
res.WriteString("\n")
}
if verbose {
if viper.GetBool("verbose") {
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
if isProvider {
validators, err := validatorsProvider.Validators(ctx, "head", nil)
@@ -165,7 +165,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
res.WriteString(fmt.Sprintf("%d", period))
res.WriteString("\n")
if verbose {
if viper.GetBool("verbose") {
res.WriteString("Sync committee epochs: ")
res.WriteString(fmt.Sprintf("%d", periodStartEpoch))
res.WriteString("-")

View File

@@ -47,14 +47,14 @@ func init() {
chainTimeCmd.Flags().String("timestamp", "", "The timestamp for which to obtain information (format YYYY-MM-DDTHH:MM:SS+ZZZZ)")
}
func chainTimeBindings() {
if err := viper.BindPFlag("slot", chainTimeCmd.Flags().Lookup("slot")); err != nil {
func chainTimeBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
panic(err)
}
if err := viper.BindPFlag("epoch", chainTimeCmd.Flags().Lookup("epoch")); err != nil {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("timestamp", chainTimeCmd.Flags().Lookup("timestamp")); err != nil {
if err := viper.BindPFlag("timestamp", cmd.Flags().Lookup("timestamp")); err != nil {
panic(err)
}
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
eth2util "github.com/wealdtech/go-eth2-util"
@@ -92,7 +93,7 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
withdrawalCredentials[0] = 0x01 // ETH1_ADDRESS_WITHDRAWAL_PREFIX
copy(withdrawalCredentials[12:], withdrawalAddressBytes)
}
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
outputIf(viper.GetBool("debug"), fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
depositAmount := uint64(0)
if depositVerifyDepositAmount != "" {
@@ -120,9 +121,9 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
}
if !verified {
failures = true
outputIf(!quiet, fmt.Sprintf("%s failed verification", depositName))
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%s failed verification", depositName))
} else {
outputIf(!quiet, fmt.Sprintf("%s verified", depositName))
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%s verified", depositName))
}
}
@@ -190,34 +191,34 @@ func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) {
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
if withdrawalCredentials == nil {
outputIf(!quiet, "Withdrawal public key or address not supplied; withdrawal credentials NOT checked")
outputIf(!viper.GetBool("quiet"), "Withdrawal public key or address not supplied; withdrawal credentials NOT checked")
} else {
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
outputIf(!quiet, "Withdrawal credentials incorrect")
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials incorrect")
return false, nil
}
outputIf(!quiet, "Withdrawal credentials verified")
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials verified")
}
if amount == 0 {
outputIf(!quiet, "Amount not supplied; NOT checked")
outputIf(!viper.GetBool("quiet"), "Amount not supplied; NOT checked")
} else {
if deposit.Amount != amount {
outputIf(!quiet, "Amount incorrect")
outputIf(!viper.GetBool("quiet"), "Amount incorrect")
return false, nil
}
outputIf(!quiet, "Amount verified")
outputIf(!viper.GetBool("quiet"), "Amount verified")
}
if len(validatorPubKeys) == 0 {
outputIf(!quiet, "Validator public key not suppled; NOT checked")
outputIf(!viper.GetBool("quiet"), "Validator public key not suppled; NOT checked")
} else {
var key [48]byte
copy(key[:], deposit.PublicKey)
if _, exists := validatorPubKeys[key]; !exists {
outputIf(!quiet, "Validator public key incorrect")
outputIf(!viper.GetBool("quiet"), "Validator public key incorrect")
return false, nil
}
outputIf(!quiet, "Validator public key verified")
outputIf(!viper.GetBool("quiet"), "Validator public key verified")
}
var pubKey phase0.BLSPubKey
@@ -237,33 +238,33 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
}
if bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
outputIf(!quiet, "Deposit data root verified")
outputIf(!viper.GetBool("quiet"), "Deposit data root verified")
} else {
outputIf(!quiet, "Deposit data root incorrect")
outputIf(!viper.GetBool("quiet"), "Deposit data root incorrect")
return false, nil
}
if len(deposit.ForkVersion) == 0 {
if depositVerifyForkVersion != "" {
outputIf(!quiet, "Data format does not contain fork version for verification; NOT verified")
outputIf(!viper.GetBool("quiet"), "Data format does not contain fork version for verification; NOT verified")
}
} else {
if depositVerifyForkVersion == "" {
outputIf(!quiet, "fork version not supplied; not checked")
outputIf(!viper.GetBool("quiet"), "fork version not supplied; not checked")
} else {
forkVersion, err := hex.DecodeString(strings.TrimPrefix(depositVerifyForkVersion, "0x"))
if err != nil {
return false, errors.Wrap(err, "failed to decode fork version")
}
if bytes.Equal(deposit.ForkVersion, forkVersion) {
outputIf(!quiet, "Fork version verified")
outputIf(!viper.GetBool("quiet"), "Fork version verified")
} else {
outputIf(!quiet, "Fork version incorrect")
outputIf(!viper.GetBool("quiet"), "Fork version incorrect")
return false, nil
}
if len(deposit.DepositMessageRoot) != 32 {
outputIf(!quiet, "Deposit message root not supplied; not checked")
outputIf(!viper.GetBool("quiet"), "Deposit message root not supplied; not checked")
} else {
// We can also verify the deposit message signature.
depositMessage := &phase0.DepositMessage{
@@ -277,9 +278,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
}
if bytes.Equal(deposit.DepositMessageRoot, depositMessageRoot[:]) {
outputIf(!quiet, "Deposit message root verified")
outputIf(!viper.GetBool("quiet"), "Deposit message root verified")
} else {
outputIf(!quiet, "Deposit message root incorrect")
outputIf(!viper.GetBool("quiet"), "Deposit message root incorrect")
return false, nil
}
@@ -305,9 +306,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
}
signatureVerified := blsSig.Verify(containerRoot[:], validatorPubKey)
if signatureVerified {
outputIf(!quiet, "Deposit message signature verified")
outputIf(!viper.GetBool("quiet"), "Deposit message signature verified")
} else {
outputIf(!quiet, "Deposit message signature NOT verified")
outputIf(!viper.GetBool("quiet"), "Deposit message signature NOT verified")
return false, nil
}
}

View File

@@ -33,8 +33,8 @@ func epochFlags(_ *cobra.Command) {
epochSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
}
func epochBindings() {
if err := viper.BindPFlag("epoch", epochSummaryCmd.Flags().Lookup("epoch")); err != nil {
func epochBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
}

View File

@@ -49,6 +49,6 @@ func init() {
epochFlags(epochSummaryCmd)
}
func epochSummaryBindings() {
epochBindings()
func epochSummaryBindings(cmd *cobra.Command) {
epochBindings(cmd)
}

View File

@@ -16,12 +16,14 @@ package cmd
import (
"fmt"
"os"
"github.com/spf13/viper"
)
// errCheck checks for an error and quits if it is present.
func errCheck(err error, msg string) {
if err != nil {
if !quiet {
if !viper.GetBool("quiet") {
if msg == "" {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
} else {
@@ -57,7 +59,7 @@ func assert(condition bool, msg string) {
// die prints an error and quits.
func die(msg string) {
if msg != "" && !quiet {
if msg != "" && !viper.GetBool("quiet") {
fmt.Fprintf(os.Stderr, "%s\n", msg)
}
os.Exit(_exitFailure)

View File

@@ -98,7 +98,7 @@ In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.
}
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
outputIf(verbose, "Verified")
outputIf(viper.GetBool("verbose"), "Verified")
os.Exit(_exitSuccess)
},
}
@@ -133,8 +133,8 @@ func init() {
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
}
func exitVerifyBindings() {
if err := viper.BindPFlag("signed-operation", exitVerifyCmd.Flags().Lookup("signed-operation")); err != nil {
func exitVerifyBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("signed-operation", cmd.Flags().Lookup("signed-operation")); err != nil {
panic(err)
}
}

View File

@@ -45,8 +45,8 @@ func init() {
nodeEventsCmd.Flags().StringSlice("topics", nil, "The topics of events for which to listen (attestation,block,chain_reorg,finalized_checkpoint,head,voluntary_exit)")
}
func nodeEventsBindings() {
if err := viper.BindPFlag("topics", nodeEventsCmd.Flags().Lookup("topics")); err != nil {
func nodeEventsBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("topics", cmd.Flags().Lookup("topics")); err != nil {
panic(err)
}
}

View File

@@ -43,11 +43,11 @@ In quiet mode this will return 0 if the node information can be obtained, otherw
})
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
if quiet {
if viper.GetBool("quiet") {
os.Exit(_exitSuccess)
}
if verbose {
if viper.GetBool("verbose") {
version, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx)
errCheck(err, "Failed to obtain node version")
fmt.Printf("Version: %s\n", version)

View File

@@ -50,8 +50,8 @@ func init() {
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
}
func proposerDutiesBindings() {
if err := viper.BindPFlag("epoch", proposerDutiesCmd.Flags().Lookup("epoch")); err != nil {
func proposerDutiesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2021 Weald Technology Trading.
// Copyright © 2019 - 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -33,21 +33,55 @@ import (
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
var (
cfgFile string
quiet bool
verbose bool
debug bool
)
var cfgFile string
// RootCmd represents the base command when called without any subcommands.
var RootCmd = &cobra.Command{
Use: "ethdo",
Short: "Ethereum 2 CLI",
Long: `Manage common Ethereum 2 tasks from the command line.`,
Short: "Ethereum consensus layer CLI",
Long: `Manage common Ethereum consensus layer tasks from the command line.`,
PersistentPreRunE: persistentPreRunE,
}
// bindings are the command-specific bindings.
var bindings = map[string]func(cmd *cobra.Command){
"account/create": accountCreateBindings,
"account/derive": accountDeriveBindings,
"account/import": accountImportBindings,
"attester/duties": attesterDutiesBindings,
"attester/inclusion": attesterInclusionBindings,
"block/analyze": blockAnalyzeBindings,
"block/info": blockInfoBindings,
"chain/eth1votes": chainEth1VotesBindings,
"chain/info": chainInfoBindings,
"chain/queues": chainQueuesBindings,
"chain/spec": chainSpecBindings,
"chain/time": chainTimeBindings,
"chain/verify/signedcontributionandproof": chainVerifySignedContributionAndProofBindings,
"epoch/summary": epochSummaryBindings,
"exit/verify": exitVerifyBindings,
"node/events": nodeEventsBindings,
"proposer/duties": proposerDutiesBindings,
"slot/time": slotTimeBindings,
"synccommittee/inclusion": synccommitteeInclusionBindings,
"synccommittee/members": synccommitteeMembersBindings,
"validator/credentials/get": validatorCredentialsGetBindings,
"validator/credentials/set": validatorCredentialsSetBindings,
"validator/depositdata": validatorDepositdataBindings,
"validator/duties": validatorDutiesBindings,
"validator/exit": validatorExitBindings,
"validator/info": validatorInfoBindings,
"validator/keycheck": validatorKeycheckBindings,
"validator/summary": validatorSummaryBindings,
"validator/yield": validatorYieldBindings,
"validator/expectation": validatorExpectationBindings,
"validator/withdrawal": validatorWithdrawalBindings,
"wallet/create": walletCreateBindings,
"wallet/import": walletImportBindings,
"wallet/sharedexport": walletSharedExportBindings,
"wallet/sharedimport": walletSharedImportBindings,
}
func persistentPreRunE(cmd *cobra.Command, _ []string) error {
if cmd.Name() == "help" {
// User just wants help
@@ -63,11 +97,14 @@ func persistentPreRunE(cmd *cobra.Command, _ []string) error {
zerolog.SetGlobalLevel(zerolog.Disabled)
// We bind viper here so that we bind to the correct command.
quiet = viper.GetBool("quiet")
verbose = viper.GetBool("verbose")
debug = viper.GetBool("debug")
quiet := viper.GetBool("quiet")
verbose := viper.GetBool("verbose")
debug := viper.GetBool("debug")
includeCommandBindings(cmd)
// Command-specific bindings.
if bindingsFunc, exists := bindings[commandPath(cmd)]; exists {
bindingsFunc(cmd)
}
if quiet && verbose {
fmt.Println("Cannot supply both quiet and verbose flags")
@@ -79,78 +116,6 @@ func persistentPreRunE(cmd *cobra.Command, _ []string) error {
return util.SetupStore()
}
// nolint:gocyclo
func includeCommandBindings(cmd *cobra.Command) {
switch commandPath(cmd) {
case "account/create":
accountCreateBindings()
case "account/derive":
accountDeriveBindings()
case "account/import":
accountImportBindings()
case "attester/duties":
attesterDutiesBindings()
case "attester/inclusion":
attesterInclusionBindings()
case "block/analyze":
blockAnalyzeBindings()
case "block/info":
blockInfoBindings()
case "chain/info":
chainInfoBindings()
case "chain/queues":
chainQueuesBindings()
case "chain/spec":
chainSpecBindings()
case "chain/time":
chainTimeBindings()
case "chain/verify/signedcontributionandproof":
chainVerifySignedContributionAndProofBindings(cmd)
case "epoch/summary":
epochSummaryBindings()
case "exit/verify":
exitVerifyBindings()
case "node/events":
nodeEventsBindings()
case "proposer/duties":
proposerDutiesBindings()
case "slot/time":
slotTimeBindings()
case "synccommittee/inclusion":
synccommitteeInclusionBindings()
case "synccommittee/members":
synccommitteeMembersBindings()
case "validator/credentials/get":
validatorCredentialsGetBindings()
case "validator/credentials/set":
validatorCredentialsSetBindings()
case "validator/depositdata":
validatorDepositdataBindings()
case "validator/duties":
validatorDutiesBindings()
case "validator/exit":
validatorExitBindings()
case "validator/info":
validatorInfoBindings()
case "validator/keycheck":
validatorKeycheckBindings()
case "validator/summary":
validatorSummaryBindings()
case "validator/yield":
validatorYieldBindings()
case "validator/expectation":
validatorExpectationBindings()
case "wallet/create":
walletCreateBindings()
case "wallet/import":
walletImportBindings()
case "wallet/sharedexport":
walletSharedExportBindings()
case "wallet/sharedimport":
walletSharedImportBindings()
}
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
@@ -167,8 +132,12 @@ func init() {
}
cobra.OnInitialize(initConfig)
addPersistentFlags()
}
func addPersistentFlags() {
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ethdo.yaml)")
RootCmd.PersistentFlags().String("log", "", "log activity to the named file (default $HOME/ethdo.log). Logs are written for every action that generates a transaction")
if err := viper.BindPFlag("log", RootCmd.PersistentFlags().Lookup("log")); err != nil {
panic(err)

View File

@@ -23,6 +23,7 @@ import (
"github.com/herumi/bls-eth-go-binary/bls"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
)
@@ -50,7 +51,7 @@ In quiet mode this will return 0 if the signatures can be aggregated, otherwise
}
errCheck(err, "Failed to aggregate signature")
outputIf(!quiet, fmt.Sprintf("%#x", signature.Serialize()))
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%#x", signature.Serialize()))
os.Exit(_exitSuccess)
},
}

View File

@@ -51,7 +51,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
errCheck(err, "Failed to parse domain")
assert(len(domain) == 32, "Domain data invalid")
}
outputIf(debug, fmt.Sprintf("Domain is %#x", domain))
outputIf(viper.GetBool("debug"), fmt.Sprintf("Domain is %#x", domain))
var account e2wtypes.Account
switch {
@@ -70,7 +70,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
errCheck(err, "Failed to sign")
outputIf(!quiet, fmt.Sprintf("%#x", signature.Marshal()))
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%#x", signature.Marshal()))
os.Exit(_exitSuccess)
},
}

View File

@@ -73,7 +73,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
account, err = util.ParseAccount(ctx, viper.GetString("public-key"), nil, false)
}
errCheck(err, "Failed to obtain account")
outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
outputIf(viper.GetBool("debug"), fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
var specDomain spec.Domain
copy(specDomain[:], domain)
@@ -83,7 +83,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
errCheck(err, "Failed to verify data")
assert(verified, "Failed to verify")
outputIf(verbose, "Verified")
outputIf(viper.GetBool("verbose"), "Verified")
os.Exit(_exitSuccess)
},
}

View File

@@ -50,8 +50,8 @@ func init() {
slotTimeCmd.Flags().String("slot", "", "the ID of the slot to fetch")
}
func slotTimeBindings() {
if err := viper.BindPFlag("slot", slotTimeCmd.Flags().Lookup("slot")); err != nil {
func slotTimeBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
panic(err)
}
}

View File

@@ -53,11 +53,11 @@ func init() {
synccommitteeInclusionCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
}
func synccommitteeInclusionBindings() {
if err := viper.BindPFlag("epoch", synccommitteeInclusionCmd.Flags().Lookup("epoch")); err != nil {
func synccommitteeInclusionBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", synccommitteeInclusionCmd.Flags().Lookup("validator")); err != nil {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -53,11 +53,11 @@ func init() {
synccommitteeMembersCmd.Flags().String("period", "", "the sync committee period for which to fetch sync committees ('current', 'next')")
}
func synccommitteeMembersBindings() {
if err := viper.BindPFlag("epoch", synccommitteeMembersCmd.Flags().Lookup("epoch")); err != nil {
func synccommitteeMembersBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("period", synccommitteeMembersCmd.Flags().Lookup("period")); err != nil {
if err := viper.BindPFlag("period", cmd.Flags().Lookup("period")); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,108 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorwithdrawl
import (
"context"
"encoding/json"
"time"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/services/chaintime"
)
type command struct {
quiet bool
verbose bool
debug bool
offline bool
json bool
// Input.
validator string
// Beacon node connection.
timeout time.Duration
connection string
allowInsecureConnections bool
// Processing.
consensusClient consensusclient.Service
chainTime chaintime.Service
maxWithdrawalsPerPayload uint64
maxEffectiveBalance phase0.Gwei
// Output.
res *res
}
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
offline: viper.GetBool("offline"),
json: viper.GetBool("json"),
timeout: viper.GetDuration("timeout"),
connection: viper.GetString("connection"),
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
validator: viper.GetString("validator"),
res: &res{},
}
// Timeout is required.
if c.timeout == 0 {
return nil, errors.New("timeout is required")
}
if c.validator == "" {
return nil, errors.New("validator is required")
}
return c, nil
}
type res struct {
WithdrawalsToGo uint64
BlocksToGo uint64
Block uint64
Wait time.Duration
Expected time.Time
}
type resJSON struct {
WithdrawalsToGo uint64 `json:"withdrawals_to_go"`
BlocksToGo uint64 `json:"blocks_to_go"`
Block uint64 `json:"block"`
Wait string `json:"wait"`
WaitSecs uint64 `json:"wait_secs"`
Expected string `json:"expected"`
ExpectedTimestamp int64 `json:"expected_timestamp"`
}
func (r *res) MarshalJSON() ([]byte, error) {
data := resJSON{
WithdrawalsToGo: r.WithdrawalsToGo,
BlocksToGo: r.BlocksToGo,
Block: r.Block,
Wait: r.Wait.Round(time.Second).String(),
WaitSecs: uint64(r.Wait.Round(time.Second).Seconds()),
Expected: r.Expected.Format("2006-01-02T15:04:05"),
ExpectedTimestamp: r.Expected.Unix(),
}
return json.Marshal(data)
}

View File

@@ -0,0 +1,39 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorwithdrawl
import (
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
)
//nolint:unparam
func (c *command) output(_ context.Context) (string, error) {
if c.quiet {
return "", nil
}
if c.json {
data, err := json.Marshal(c.res)
if err != nil {
return "", errors.Wrap(err, "failed to marshal results")
}
return string(data), nil
}
return fmt.Sprintf("Withdrawal expected at %s in block %d", c.res.Expected.Format("2006-01-02T15:04:05"), c.res.Block), nil
}

View File

@@ -0,0 +1,155 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorwithdrawl
import (
"context"
"fmt"
"os"
"time"
consensusclient "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
)
const (
ethWithdrawalPrefix = 0x01
)
func (c *command) process(ctx context.Context) error {
if err := c.setup(ctx); err != nil {
return err
}
validator, err := util.ParseValidator(ctx, c.consensusClient.(consensusclient.ValidatorsProvider), c.validator, "head")
if err != nil {
return errors.Wrap(err, "failed to parse validator")
}
if validator.Validator.WithdrawalCredentials[0] != ethWithdrawalPrefix {
return errors.New("validator does not have suitable withdrawal credentials")
}
if validator.Balance == 0 {
return errors.New("validator has nothing to withdraw")
}
block, err := c.consensusClient.(consensusclient.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, "head")
if err != nil {
return errors.Wrap(err, "failed to obtain block")
}
slot, err := block.Slot()
if err != nil {
return errors.Wrap(err, "failed to obtain block slot")
}
if c.debug {
fmt.Fprintf(os.Stderr, "Slot is %d\n", slot)
}
validatorsMap, err := c.consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, fmt.Sprintf("%d", slot), nil)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
validators := make([]*apiv1.Validator, len(validatorsMap))
for _, validator := range validatorsMap {
validators[validator.Index] = validator
}
var nextWithdrawalValidatorIndex phase0.ValidatorIndex
switch block.Version {
case spec.DataVersionCapella:
withdrawals := block.Capella.Message.Body.ExecutionPayload.Withdrawals
if len(withdrawals) == 0 {
return errors.New("block without withdrawals; cannot obtain next withdrawal validator index")
}
nextWithdrawalValidatorIndex = phase0.ValidatorIndex((int(withdrawals[len(withdrawals)-1].ValidatorIndex) + 1) % len(validators))
default:
return fmt.Errorf("unhandled block version %v", block.Version)
}
if c.debug {
fmt.Fprintf(os.Stderr, "Next withdrawal validator index is %d\n", nextWithdrawalValidatorIndex)
}
index := int(nextWithdrawalValidatorIndex)
for {
if index == len(validators) {
index = 0
}
if index == int(validator.Index) {
break
}
if validators[index].Validator.WithdrawalCredentials[0] == ethWithdrawalPrefix &&
validators[index].Validator.EffectiveBalance == c.maxEffectiveBalance {
c.res.WithdrawalsToGo++
}
index++
}
c.res.BlocksToGo = c.res.WithdrawalsToGo / c.maxWithdrawalsPerPayload
if c.res.WithdrawalsToGo%c.maxWithdrawalsPerPayload != 0 {
c.res.BlocksToGo++
}
c.res.Block = uint64(slot) + c.res.BlocksToGo
c.res.Expected = c.chainTime.StartOfSlot(phase0.Slot(c.res.Block))
c.res.Wait = time.Until(c.res.Expected)
return nil
}
func (c *command) setup(ctx context.Context) error {
// Connect to the consensus node.
var err error
c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
LogFallback: !c.quiet,
})
if err != nil {
return err
}
// Set up chaintime.
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(c.consensusClient.(consensusclient.GenesisTimeProvider)),
standardchaintime.WithSpecProvider(c.consensusClient.(consensusclient.SpecProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to create chaintime service")
}
spec, err := c.consensusClient.(consensusclient.SpecProvider).Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
if val, exists := spec["MAX_WITHDRAWALS_PER_PAYLOAD"]; !exists {
c.maxWithdrawalsPerPayload = 16
} else {
c.maxWithdrawalsPerPayload = val.(uint64)
}
if val, exists := spec["MAX_EFFECTIVE_BALANCE"]; !exists {
c.maxEffectiveBalance = 32000000000
} else {
c.maxEffectiveBalance = phase0.Gwei(val.(uint64))
}
return nil
}

View File

@@ -0,0 +1,50 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorwithdrawl
import (
"context"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// Run runs the command.
func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
c, err := newCommand(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to set up command")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
if err := c.process(ctx); err != nil {
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
return "", nil
}
results, err := c.output(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil
}

View File

@@ -50,8 +50,8 @@ func init() {
validatorCredentialsGetCmd.Flags().String("validator", "", "Validator for which to get validator credentials")
}
func validatorCredentialsGetBindings() {
if err := viper.BindPFlag("validator", validatorCredentialsGetCmd.Flags().Lookup("validator")); err != nil {
func validatorCredentialsGetBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -66,29 +66,29 @@ func init() {
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
}
func validatorCredentialsSetBindings() {
if err := viper.BindPFlag("prepare-offline", validatorCredentialsSetCmd.Flags().Lookup("prepare-offline")); err != nil {
func validatorCredentialsSetBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("prepare-offline", cmd.Flags().Lookup("prepare-offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", validatorCredentialsSetCmd.Flags().Lookup("validator")); err != nil {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
if err := viper.BindPFlag("signed-operations", validatorCredentialsSetCmd.Flags().Lookup("signed-operations")); err != nil {
if err := viper.BindPFlag("signed-operations", cmd.Flags().Lookup("signed-operations")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawal-account", validatorCredentialsSetCmd.Flags().Lookup("withdrawal-account")); err != nil {
if err := viper.BindPFlag("withdrawal-account", cmd.Flags().Lookup("withdrawal-account")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawal-address", validatorCredentialsSetCmd.Flags().Lookup("withdrawal-address")); err != nil {
if err := viper.BindPFlag("withdrawal-address", cmd.Flags().Lookup("withdrawal-address")); err != nil {
panic(err)
}
if err := viper.BindPFlag("offline", validatorCredentialsSetCmd.Flags().Lookup("offline")); err != nil {
if err := viper.BindPFlag("offline", cmd.Flags().Lookup("offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("fork-version", validatorCredentialsSetCmd.Flags().Lookup("fork-version")); err != nil {
if err := viper.BindPFlag("fork-version", cmd.Flags().Lookup("fork-version")); err != nil {
panic(err)
}
if err := viper.BindPFlag("genesis-validators-root", validatorCredentialsSetCmd.Flags().Lookup("genesis-validators-root")); err != nil {
if err := viper.BindPFlag("genesis-validators-root", cmd.Flags().Lookup("genesis-validators-root")); err != nil {
panic(err)
}
}

View File

@@ -59,29 +59,29 @@ func init() {
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
}
func validatorDepositdataBindings() {
if err := viper.BindPFlag("validatoraccount", validatorDepositDataCmd.Flags().Lookup("validatoraccount")); err != nil {
func validatorDepositdataBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validatoraccount", cmd.Flags().Lookup("validatoraccount")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawalaccount", validatorDepositDataCmd.Flags().Lookup("withdrawalaccount")); err != nil {
if err := viper.BindPFlag("withdrawalaccount", cmd.Flags().Lookup("withdrawalaccount")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawalpubkey", validatorDepositDataCmd.Flags().Lookup("withdrawalpubkey")); err != nil {
if err := viper.BindPFlag("withdrawalpubkey", cmd.Flags().Lookup("withdrawalpubkey")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawaladdress", validatorDepositDataCmd.Flags().Lookup("withdrawaladdress")); err != nil {
if err := viper.BindPFlag("withdrawaladdress", cmd.Flags().Lookup("withdrawaladdress")); err != nil {
panic(err)
}
if err := viper.BindPFlag("depositvalue", validatorDepositDataCmd.Flags().Lookup("depositvalue")); err != nil {
if err := viper.BindPFlag("depositvalue", cmd.Flags().Lookup("depositvalue")); err != nil {
panic(err)
}
if err := viper.BindPFlag("raw", validatorDepositDataCmd.Flags().Lookup("raw")); err != nil {
if err := viper.BindPFlag("raw", cmd.Flags().Lookup("raw")); err != nil {
panic(err)
}
if err := viper.BindPFlag("forkversion", validatorDepositDataCmd.Flags().Lookup("forkversion")); err != nil {
if err := viper.BindPFlag("forkversion", cmd.Flags().Lookup("forkversion")); err != nil {
panic(err)
}
if err := viper.BindPFlag("launchpad", validatorDepositDataCmd.Flags().Lookup("launchpad")); err != nil {
if err := viper.BindPFlag("launchpad", cmd.Flags().Lookup("launchpad")); err != nil {
panic(err)
}
}

View File

@@ -51,11 +51,11 @@ func init() {
validatorDutiesCmd.Flags().String("index", "", "validator index for duties")
}
func validatorDutiesBindings() {
if err := viper.BindPFlag("pubkey", validatorDutiesCmd.Flags().Lookup("pubkey")); err != nil {
func validatorDutiesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("pubkey", cmd.Flags().Lookup("pubkey")); err != nil {
panic(err)
}
if err := viper.BindPFlag("index", validatorDutiesCmd.Flags().Lookup("index")); err != nil {
if err := viper.BindPFlag("index", cmd.Flags().Lookup("index")); err != nil {
panic(err)
}
}

View File

@@ -63,26 +63,26 @@ func init() {
validatorExitCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
}
func validatorExitBindings() {
if err := viper.BindPFlag("epoch", validatorExitCmd.Flags().Lookup("epoch")); err != nil {
func validatorExitBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("prepare-offline", validatorExitCmd.Flags().Lookup("prepare-offline")); err != nil {
if err := viper.BindPFlag("prepare-offline", cmd.Flags().Lookup("prepare-offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", validatorExitCmd.Flags().Lookup("validator")); err != nil {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
if err := viper.BindPFlag("signed-operation", validatorExitCmd.Flags().Lookup("signed-operation")); err != nil {
if err := viper.BindPFlag("signed-operation", cmd.Flags().Lookup("signed-operation")); err != nil {
panic(err)
}
if err := viper.BindPFlag("offline", validatorExitCmd.Flags().Lookup("offline")); err != nil {
if err := viper.BindPFlag("offline", cmd.Flags().Lookup("offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("fork-version", validatorExitCmd.Flags().Lookup("fork-version")); err != nil {
if err := viper.BindPFlag("fork-version", cmd.Flags().Lookup("fork-version")); err != nil {
panic(err)
}
if err := viper.BindPFlag("genesis-validators-root", validatorExitCmd.Flags().Lookup("genesis-validators-root")); err != nil {
if err := viper.BindPFlag("genesis-validators-root", cmd.Flags().Lookup("genesis-validators-root")); err != nil {
panic(err)
}
}

View File

@@ -48,8 +48,8 @@ func init() {
validatorExpectationCmd.Flags().Int64("validators", 1, "Number of validators")
}
func validatorExpectationBindings() {
if err := viper.BindPFlag("validators", validatorExpectationCmd.Flags().Lookup("validators")); err != nil {
func validatorExpectationBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
panic(err)
}
}

View File

@@ -61,10 +61,10 @@ In quiet mode this will return 0 if the validator information can be obtained, o
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), "head")
errCheck(err, "Failed to obtain validator")
if verbose {
if viper.GetBool("verbose") {
network, err := util.Network(ctx, eth2Client)
errCheck(err, "Failed to obtain network")
outputIf(debug, fmt.Sprintf("Network is %s", network))
outputIf(viper.GetBool("debug"), fmt.Sprintf("Network is %s", network))
pubKey, err := validator.PubKey(ctx)
if err == nil {
deposits, totalDeposited, err := graphData(network, pubKey[:])
@@ -75,14 +75,14 @@ In quiet mode this will return 0 if the validator information can be obtained, o
}
}
if quiet {
if viper.GetBool("quiet") {
os.Exit(_exitSuccess)
}
if validator.Status.IsPending() || validator.Status.HasActivated() {
fmt.Printf("Index: %d\n", validator.Index)
}
if verbose {
if viper.GetBool("verbose") {
if validator.Status.IsPending() {
fmt.Printf("Activation eligibility epoch: %d\n", validator.Validator.ActivationEligibilityEpoch)
}
@@ -102,7 +102,7 @@ In quiet mode this will return 0 if the validator information can be obtained, o
if validator.Status.IsActive() {
fmt.Printf("Effective balance: %s\n", string2eth.GWeiToString(uint64(validator.Validator.EffectiveBalance), true))
}
if verbose {
if viper.GetBool("verbose") {
fmt.Printf("Withdrawal credentials: %#x\n", validator.Validator.WithdrawalCredentials)
}
@@ -174,8 +174,8 @@ func init() {
validatorFlags(validatorInfoCmd)
}
func validatorInfoBindings() {
if err := viper.BindPFlag("validator", validatorInfoCmd.Flags().Lookup("validator")); err != nil {
func validatorInfoBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -53,11 +53,11 @@ func init() {
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
}
func validatorKeycheckBindings() {
if err := viper.BindPFlag("withdrawal-credentials", validatorKeycheckCmd.Flags().Lookup("withdrawal-credentials")); err != nil {
func validatorKeycheckBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("withdrawal-credentials", cmd.Flags().Lookup("withdrawal-credentials")); err != nil {
panic(err)
}
if err := viper.BindPFlag("privkey", validatorKeycheckCmd.Flags().Lookup("privkey")); err != nil {
if err := viper.BindPFlag("privkey", cmd.Flags().Lookup("privkey")); err != nil {
panic(err)
}
}

View File

@@ -51,12 +51,12 @@ func init() {
validatorSummaryCmd.Flags().StringSlice("validators", nil, "the list of validators for which to obtain information")
}
func validatorSummaryBindings() {
func validatorSummaryBindings(cmd *cobra.Command) {
validatorBindings()
if err := viper.BindPFlag("epoch", validatorSummaryCmd.Flags().Lookup("epoch")); err != nil {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validators", validatorSummaryCmd.Flags().Lookup("validators")); err != nil {
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,57 @@
// Copyright © 2023 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
validatorwithdrawal "github.com/wealdtech/ethdo/cmd/validator/withdrawal"
)
var validatorWithdrawalCmd = &cobra.Command{
Use: "withdrawal",
Short: "Obtain next withdrawal for a validator",
Long: `Obtain next withdrawal for a validator. For example:
ethdo validator withdrawal --validator=primary/validator
In quiet mode this will return 0 if the validator exists, otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
res, err := validatorwithdrawal.Run(cmd)
if err != nil {
return err
}
if viper.GetBool("quiet") {
return nil
}
if res != "" {
fmt.Println(res)
}
return nil
},
}
func init() {
validatorCmd.AddCommand(validatorWithdrawalCmd)
validatorFlags(validatorWithdrawalCmd)
validatorWithdrawalCmd.Flags().String("validator", "", "Validator for which to get withdrawal")
}
func validatorWithdrawalBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -50,8 +50,8 @@ func init() {
validatorYieldCmd.Flags().String("validators", "", "Number of active validators (default fetches from chain)")
}
func validatorYieldBindings() {
if err := viper.BindPFlag("validators", validatorYieldCmd.Flags().Lookup("validators")); err != nil {
func validatorYieldBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
panic(err)
}
}

View File

@@ -86,8 +86,8 @@ In quiet mode this will return 0 if the wallet holds any addresses, otherwise 1.
}
for _, account := range accounts {
outputIf(!quiet, account.Name())
if verbose {
outputIf(!viper.GetBool("quiet"), account.Name())
if viper.GetBool("verbose") {
fmt.Printf(" UUID: %v\n", account.ID())
if pathProvider, isProvider := account.(e2wtypes.AccountPathProvider); isProvider {
if pathProvider.Path() != "" {

View File

@@ -47,8 +47,8 @@ func init() {
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
}
func walletCreateBindings() {
if err := viper.BindPFlag("type", walletCreateCmd.Flags().Lookup("type")); err != nil {
func walletCreateBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("type", cmd.Flags().Lookup("type")); err != nil {
panic(err)
}
}

View File

@@ -48,11 +48,11 @@ func init() {
walletImportCmd.Flags().Bool("verify", false, "Verify the wallet can be imported, but do not import it")
}
func walletImportBindings() {
if err := viper.BindPFlag("data", walletImportCmd.Flags().Lookup("data")); err != nil {
func walletImportBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("data", cmd.Flags().Lookup("data")); err != nil {
panic(err)
}
if err := viper.BindPFlag("verify", walletImportCmd.Flags().Lookup("verify")); err != nil {
if err := viper.BindPFlag("verify", cmd.Flags().Lookup("verify")); err != nil {
panic(err)
}
}

View File

@@ -42,13 +42,13 @@ In quiet mode this will return 0 if the wallet exists, otherwise 1.`,
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
errCheck(err, "unknown wallet")
if quiet {
if viper.GetBool("quiet") {
os.Exit(0)
}
outputIf(verbose, fmt.Sprintf("UUID: %v", wallet.ID()))
outputIf(viper.GetBool("verbose"), fmt.Sprintf("UUID: %v", wallet.ID()))
fmt.Printf("Type: %s\n", wallet.Type())
if verbose {
if viper.GetBool("verbose") {
if storeProvider, ok := wallet.(wtypes.StoreProvider); ok {
store := storeProvider.Store()
fmt.Printf("Store: %s\n", store.Name())

View File

@@ -37,8 +37,8 @@ In quiet mode this will return 0 if any wallets are found, otherwise 1.`,
walletsFound := false
for w := range e2wallet.Wallets() {
walletsFound = true
outputIf(!quiet && !verbose, w.Name())
outputIf(verbose, fmt.Sprintf("%s\n UUID: %s", w.Name(), w.ID().String()))
outputIf(!viper.GetBool("quiet") && !viper.GetBool("verbose"), w.Name())
outputIf(viper.GetBool("verbose"), fmt.Sprintf("%s\n UUID: %s", w.Name(), w.ID().String()))
}
if !walletsFound {

View File

@@ -47,14 +47,14 @@ func init() {
walletSharedExportCmd.Flags().String("file", "", "Name of the file that stores the export")
}
func walletSharedExportBindings() {
if err := viper.BindPFlag("participants", walletSharedExportCmd.Flags().Lookup("participants")); err != nil {
func walletSharedExportBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("participants", cmd.Flags().Lookup("participants")); err != nil {
panic(err)
}
if err := viper.BindPFlag("threshold", walletSharedExportCmd.Flags().Lookup("threshold")); err != nil {
if err := viper.BindPFlag("threshold", cmd.Flags().Lookup("threshold")); err != nil {
panic(err)
}
if err := viper.BindPFlag("file", walletSharedExportCmd.Flags().Lookup("file")); err != nil {
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
panic(err)
}
}

View File

@@ -48,11 +48,11 @@ func init() {
walletSharedImportCmd.Flags().String("shares", "", "Shares required to decrypt the export, separated with spaces")
}
func walletSharedImportBindings() {
if err := viper.BindPFlag("file", walletSharedImportCmd.Flags().Lookup("file")); err != nil {
func walletSharedImportBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
panic(err)
}
if err := viper.BindPFlag("shares", walletSharedImportCmd.Flags().Lookup("shares")); err != nil {
if err := viper.BindPFlag("shares", cmd.Flags().Lookup("shares")); err != nil {
panic(err)
}
}

View File

@@ -1,6 +1,6 @@
# ethdo commands
ethdo provides features to manage wallets and accounts, as well as interacting with Ethereum 2 nodes and remote signers. Below are a list of all available commands.
ethdo provides features to manage wallets and accounts, as well as interacting with Ethereum consensus nodes and remote signers. Below are a list of all available commands.
Note that the below provides a list of commands rather than a howto guide. Please follow the
@@ -272,9 +272,9 @@ $ ethdo version
### `block` commands
Block commands focus on providing information about Ethereum 2 blocks.
Block commands focus on providing information about Ethereum consensus blocks.
#### `analyze`
`ethdo block info` obtains information about a block in Ethereum 2. Options include:
`ethdo block info` obtains information about a block in the Ethereum consensus chain. Options include:
- `blockid`: the ID (slot, root, 'head') of the block to obtain
```sh
@@ -297,7 +297,7 @@ Value for block 80: 488.531
#### `info`
`ethdo block info` obtains information about a block in Ethereum 2. Options include:
`ethdo block info` obtains information about a block in the Ethereum consensus chain. Options include:
- `blockid`: the ID (slot, root, 'head') of the block to obtain
```sh
@@ -335,7 +335,7 @@ Voluntary exits: 0
### `chain` commands
Chain commands focus on providing information about Ethereum 2 chains.
Chain commands focus on providing information about Ethereum consensus chains.
#### `eth1votes`
@@ -355,7 +355,7 @@ Additional information is supplied when using `--verbose`
#### `info`
`ethdo chain info` obtains information about an Ethereum 2 chain.
`ethdo chain info` obtains information about an Ethereum consensus chain.
```sh
$ ethdo chain info
@@ -383,9 +383,21 @@ $ ethdo chain queues
Activation queue: 14798
```
#### `spec`
`ethdo chain spec` obtains the specification of an Ethereum consensus chain from the nod.
```sh
$ ethdo chain spec
ALTAIR_FORK_EPOCH: 74240
ALTAIR_FORK_VERSION: 0x01000000
BASE_REWARD_FACTOR: 64
...
```
#### `status`
`ethdo chain status` obtains the status of an Ethereum 2 chain from the node's point of view. Options include:
`ethdo chain status` obtains the status of an Ethereum consensus chain from the node's point of view. Options include:
- `slot` show output in terms of slots rather than epochs
```sh
@@ -410,7 +422,7 @@ Prior justified epoch distance: 4
#### `time`
`ethdo chain time` calculates the time period of Ethereum 2 epochs and slots. Options include:
`ethdo chain time` calculates the time period of Ethereum consensus epochs and slots. Options include:
- `epoch` show epoch and slot times for the given epoch
- `slot` show epoch and slot times for the given slot
- `timestamp` show epoch and slot times for the given timestamp
@@ -489,11 +501,11 @@ $ ethdo exit verify --signed-operation=${HOME}/exit.json
### `node` commands
Node commands focus on information from an Ethereum 2 node.
Node commands focus on information from an Ethereum consensus node.
#### `events`
`ethdo node events` displays events emitted by an Ethereum 2 node.
`ethdo node events` displays events emitted by an Ethereum consensus node.
```sh
$ ethdo node events --topics=head,chain_reorg
@@ -504,7 +516,7 @@ $ ethdo node events --topics=head,chain_reorg
#### `info`
`ethdo node info` obtains the information about an Ethereum 2 node.
`ethdo node info` obtains the information about an Ethereum consensus node.
```sh
$ ethdo node info
@@ -526,7 +538,7 @@ Genesis timestamp: 1587020563
### `slot` commands
Slot commands focus on information about Ethereum 2 slots.
Slot commands focus on information about Ethereum consensus slots.
#### `slottime`
@@ -572,7 +584,7 @@ $ ethdo synccommittee members
### `validator` commands
Validator commands focus on interaction with Ethereum 2 validators.
Validator commands focus on interaction with Ethereum consensus validators.
#### `credentials get`
@@ -593,7 +605,7 @@ $ ethdo validator credentials set --validator=Validators/1 --withdrawal-address=
#### `depositdata`
`ethdo validator depositdata` generates the data required to deposit one or more Ethereum 2 validators. Options include:
`ethdo validator depositdata` generates the data required to deposit one or more Ethereum consensus validators. Options include:
- `withdrawalaccount` specify the account to be used for the withdrawal credentials (if withdrawalpubkey is not supplied)
- `withdrawaladdress` specify the Ethereum execution address to be used for the withdrawal credentials (if withdrawalpubkey is not supplied)
- `withdrawalpubkey` specify the public key to be used for the withdrawal credentials (if withdrawalaccount is not supplied)
@@ -676,7 +688,7 @@ Expected time between sync committees: 1 year 27 weeks
### `attester` commands
Attester commands focus on Ethereum 2 validators' actions as attesters.
Attester commands focus on Ethereum consensus validators' actions as attesters.
#### `duties`
@@ -700,6 +712,16 @@ $ ethdo attester inclusion --validator=Validators/1 --epoch=6484
Attestation included in block 207492 (inclusion delay 1)
```
#### `withdrawal`
`ethdo validator withdrawal` provides information about the next withdrawal for the given validator. Options include:
- `validator`: the list of validators for which to provide a summary
- `json`: provide JSON output
```sh
$ ethdo validator withdrawal --validator=12345
Withdrawal expected at 2023-04-17T15:08:35 in block 6243041
```
#### `yield`
`ethdo validator yield` calculates the expected yield given the number of validators. Options include:
@@ -719,7 +741,7 @@ Yield: 4.64%
### `proposer` commands
Proposer commands focus on Ethereum 2 validators' actions as proposers.
Proposer commands focus on Ethereum consensus validators' actions as proposers.
#### `duties`