mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 22:18:01 -05:00
Add validator withdrawal.
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
dev:
|
||||||
|
- add "chain spec" command
|
||||||
|
- add "validator withdrawal" command
|
||||||
|
|
||||||
1.29.2:
|
1.29.2:
|
||||||
- fix regression where validator index could not be used as an account specifier
|
- fix regression where validator index could not be used as an account specifier
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func init() {
|
|||||||
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
|
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountCreateBindings() {
|
func accountCreateBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("participants", accountCreateCmd.Flags().Lookup("participants")); err != nil {
|
if err := viper.BindPFlag("participants", cmd.Flags().Lookup("participants")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func init() {
|
|||||||
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
|
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountDeriveBindings() {
|
func accountDeriveBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("show-private-key", accountDeriveCmd.Flags().Lookup("show-private-key")); err != nil {
|
if err := viper.BindPFlag("show-private-key", cmd.Flags().Lookup("show-private-key")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ func init() {
|
|||||||
accountImportCmd.Flags().String("keystore-passphrase", "", "Passphrase of keystore")
|
accountImportCmd.Flags().String("keystore-passphrase", "", "Passphrase of keystore")
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountImportBindings() {
|
func accountImportBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("key", accountImportCmd.Flags().Lookup("key")); err != nil {
|
if err := viper.BindPFlag("key", cmd.Flags().Lookup("key")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
|
|||||||
// Disallow wildcards (for now)
|
// Disallow wildcards (for now)
|
||||||
assert(fmt.Sprintf("%s/%s", wallet.Name(), account.Name()) == viper.GetString("account"), "Mismatched account name")
|
assert(fmt.Sprintf("%s/%s", wallet.Name(), account.Name()) == viper.GetString("account"), "Mismatched account name")
|
||||||
|
|
||||||
if quiet {
|
if viper.GetBool("quiet") {
|
||||||
os.Exit(_exitSuccess)
|
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
|
var withdrawalPubKey e2types.PublicKey
|
||||||
if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||||
fmt.Printf("Public key: %#x\n", pubKeyProvider.PublicKey().Marshal())
|
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 {
|
if distributedAccount, ok := account.(e2wtypes.DistributedAccount); ok {
|
||||||
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
|
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
|
||||||
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
|
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
fmt.Printf("Participants:\n")
|
fmt.Printf("Participants:\n")
|
||||||
for k, v := range distributedAccount.Participants() {
|
for k, v := range distributedAccount.Participants() {
|
||||||
fmt.Printf(" %d: %s\n", k, v)
|
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()
|
withdrawalPubKey = distributedAccount.CompositePublicKey()
|
||||||
}
|
}
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
withdrawalCredentials := util.SHA256(withdrawalPubKey.Marshal())
|
withdrawalCredentials := util.SHA256(withdrawalPubKey.Marshal())
|
||||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||||
fmt.Printf("Withdrawal credentials: %#x\n", withdrawalCredentials)
|
fmt.Printf("Withdrawal credentials: %#x\n", withdrawalCredentials)
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func init() {
|
|||||||
attesterDutiesCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
attesterDutiesCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
||||||
}
|
}
|
||||||
|
|
||||||
func attesterDutiesBindings() {
|
func attesterDutiesBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", attesterDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ func init() {
|
|||||||
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
|
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
|
||||||
}
|
}
|
||||||
|
|
||||||
func attesterInclusionBindings() {
|
func attesterInclusionBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func init() {
|
|||||||
blockAnalyzeCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
blockAnalyzeCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
||||||
}
|
}
|
||||||
|
|
||||||
func blockAnalyzeBindings() {
|
func blockAnalyzeBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("blockid", blockAnalyzeCmd.Flags().Lookup("blockid")); err != nil {
|
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ func init() {
|
|||||||
blockInfoCmd.Flags().Bool("ssz", false, "output data in SSZ format")
|
blockInfoCmd.Flags().Bool("ssz", false, "output data in SSZ format")
|
||||||
}
|
}
|
||||||
|
|
||||||
func blockInfoBindings() {
|
func blockInfoBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("blockid", blockInfoCmd.Flags().Lookup("blockid")); err != nil {
|
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ func init() {
|
|||||||
chainEth1VotesCmd.Flags().String("period", "", "period for which to fetch the votes")
|
chainEth1VotesCmd.Flags().String("period", "", "period for which to fetch the votes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainEth1VotesBindings() {
|
func chainEth1VotesBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", chainEth1VotesCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
||||||
errCheck(err, "Failed to obtain current fork")
|
errCheck(err, "Failed to obtain current fork")
|
||||||
|
|
||||||
if quiet {
|
if viper.GetBool("quiet") {
|
||||||
os.Exit(_exitSuccess)
|
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")
|
fmt.Println("Genesis time: undefined")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Genesis time: %s\n", genesis.GenesisTime.Format(time.UnixDate))
|
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 validators root: %#x\n", genesis.GenesisValidatorsRoot)
|
||||||
fmt.Printf("Genesis fork version: %#x\n", config["GENESIS_FORK_VERSION"].(spec.Version))
|
fmt.Printf("Genesis fork version: %#x\n", config["GENESIS_FORK_VERSION"].(spec.Version))
|
||||||
fmt.Printf("Current fork version: %#x\n", fork.CurrentVersion)
|
fmt.Printf("Current fork version: %#x\n", fork.CurrentVersion)
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
forkData := &spec.ForkData{
|
forkData := &spec.ForkData{
|
||||||
CurrentVersion: fork.CurrentVersion,
|
CurrentVersion: fork.CurrentVersion,
|
||||||
GenesisValidatorsRoot: genesis.GenesisValidatorsRoot,
|
GenesisValidatorsRoot: genesis.GenesisValidatorsRoot,
|
||||||
@@ -91,5 +91,5 @@ func init() {
|
|||||||
chainFlags(chainInfoCmd)
|
chainFlags(chainInfoCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainInfoBindings() {
|
func chainInfoBindings(_ *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ func init() {
|
|||||||
chainQueuesCmd.Flags().String("epoch", "", "epoch for which to fetch the queues")
|
chainQueuesCmd.Flags().String("epoch", "", "epoch for which to fetch the queues")
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainQueuesBindings() {
|
func chainQueuesBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", chainQueuesCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
cmd/chainspec.go
Normal file
97
cmd/chainspec.go
Normal 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) {
|
||||||
|
}
|
||||||
@@ -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(fmt.Sprintf("%d", epoch))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
|
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
res.WriteString("Epoch slots: ")
|
res.WriteString("Epoch slots: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", epochStartSlot))
|
res.WriteString(fmt.Sprintf("%d", epochStartSlot))
|
||||||
res.WriteString("-")
|
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("Justified epoch: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", finality.Justified.Epoch))
|
res.WriteString(fmt.Sprintf("%d", finality.Justified.Epoch))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
distance := epoch - finality.Justified.Epoch
|
distance := epoch - finality.Justified.Epoch
|
||||||
res.WriteString("Justified epoch distance: ")
|
res.WriteString("Justified epoch distance: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", 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("Finalized epoch: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", finality.Finalized.Epoch))
|
res.WriteString(fmt.Sprintf("%d", finality.Finalized.Epoch))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
distance := epoch - finality.Finalized.Epoch
|
distance := epoch - finality.Finalized.Epoch
|
||||||
res.WriteString("Finalized epoch distance: ")
|
res.WriteString("Finalized epoch distance: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", distance))
|
res.WriteString(fmt.Sprintf("%d", distance))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
|
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
|
||||||
if isProvider {
|
if isProvider {
|
||||||
validators, err := validatorsProvider.Validators(ctx, "head", nil)
|
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(fmt.Sprintf("%d", period))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
|
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
res.WriteString("Sync committee epochs: ")
|
res.WriteString("Sync committee epochs: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", periodStartEpoch))
|
res.WriteString(fmt.Sprintf("%d", periodStartEpoch))
|
||||||
res.WriteString("-")
|
res.WriteString("-")
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ func init() {
|
|||||||
chainTimeCmd.Flags().String("timestamp", "", "The timestamp for which to obtain information (format YYYY-MM-DDTHH:MM:SS+ZZZZ)")
|
chainTimeCmd.Flags().String("timestamp", "", "The timestamp for which to obtain information (format YYYY-MM-DDTHH:MM:SS+ZZZZ)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainTimeBindings() {
|
func chainTimeBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("slot", chainTimeCmd.Flags().Lookup("slot")); err != nil {
|
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||||
eth2util "github.com/wealdtech/go-eth2-util"
|
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
|
withdrawalCredentials[0] = 0x01 // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||||
copy(withdrawalCredentials[12:], withdrawalAddressBytes)
|
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)
|
depositAmount := uint64(0)
|
||||||
if depositVerifyDepositAmount != "" {
|
if depositVerifyDepositAmount != "" {
|
||||||
@@ -120,9 +121,9 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
|
|||||||
}
|
}
|
||||||
if !verified {
|
if !verified {
|
||||||
failures = true
|
failures = true
|
||||||
outputIf(!quiet, fmt.Sprintf("%s failed verification", depositName))
|
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%s failed verification", depositName))
|
||||||
} else {
|
} 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) {
|
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
|
||||||
if withdrawalCredentials == nil {
|
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 {
|
} else {
|
||||||
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
|
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
|
||||||
outputIf(!quiet, "Withdrawal credentials incorrect")
|
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
outputIf(!quiet, "Withdrawal credentials verified")
|
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials verified")
|
||||||
}
|
}
|
||||||
if amount == 0 {
|
if amount == 0 {
|
||||||
outputIf(!quiet, "Amount not supplied; NOT checked")
|
outputIf(!viper.GetBool("quiet"), "Amount not supplied; NOT checked")
|
||||||
} else {
|
} else {
|
||||||
if deposit.Amount != amount {
|
if deposit.Amount != amount {
|
||||||
outputIf(!quiet, "Amount incorrect")
|
outputIf(!viper.GetBool("quiet"), "Amount incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
outputIf(!quiet, "Amount verified")
|
outputIf(!viper.GetBool("quiet"), "Amount verified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(validatorPubKeys) == 0 {
|
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 {
|
} else {
|
||||||
var key [48]byte
|
var key [48]byte
|
||||||
copy(key[:], deposit.PublicKey)
|
copy(key[:], deposit.PublicKey)
|
||||||
if _, exists := validatorPubKeys[key]; !exists {
|
if _, exists := validatorPubKeys[key]; !exists {
|
||||||
outputIf(!quiet, "Validator public key incorrect")
|
outputIf(!viper.GetBool("quiet"), "Validator public key incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
outputIf(!quiet, "Validator public key verified")
|
outputIf(!viper.GetBool("quiet"), "Validator public key verified")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubKey phase0.BLSPubKey
|
var pubKey phase0.BLSPubKey
|
||||||
@@ -237,33 +238,33 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
|
if bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
|
||||||
outputIf(!quiet, "Deposit data root verified")
|
outputIf(!viper.GetBool("quiet"), "Deposit data root verified")
|
||||||
} else {
|
} else {
|
||||||
outputIf(!quiet, "Deposit data root incorrect")
|
outputIf(!viper.GetBool("quiet"), "Deposit data root incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deposit.ForkVersion) == 0 {
|
if len(deposit.ForkVersion) == 0 {
|
||||||
if depositVerifyForkVersion != "" {
|
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 {
|
} else {
|
||||||
if depositVerifyForkVersion == "" {
|
if depositVerifyForkVersion == "" {
|
||||||
outputIf(!quiet, "fork version not supplied; not checked")
|
outputIf(!viper.GetBool("quiet"), "fork version not supplied; not checked")
|
||||||
} else {
|
} else {
|
||||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(depositVerifyForkVersion, "0x"))
|
forkVersion, err := hex.DecodeString(strings.TrimPrefix(depositVerifyForkVersion, "0x"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.Wrap(err, "failed to decode fork version")
|
return false, errors.Wrap(err, "failed to decode fork version")
|
||||||
}
|
}
|
||||||
if bytes.Equal(deposit.ForkVersion, forkVersion) {
|
if bytes.Equal(deposit.ForkVersion, forkVersion) {
|
||||||
outputIf(!quiet, "Fork version verified")
|
outputIf(!viper.GetBool("quiet"), "Fork version verified")
|
||||||
} else {
|
} else {
|
||||||
outputIf(!quiet, "Fork version incorrect")
|
outputIf(!viper.GetBool("quiet"), "Fork version incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deposit.DepositMessageRoot) != 32 {
|
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 {
|
} else {
|
||||||
// We can also verify the deposit message signature.
|
// We can also verify the deposit message signature.
|
||||||
depositMessage := &phase0.DepositMessage{
|
depositMessage := &phase0.DepositMessage{
|
||||||
@@ -277,9 +278,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(deposit.DepositMessageRoot, depositMessageRoot[:]) {
|
if bytes.Equal(deposit.DepositMessageRoot, depositMessageRoot[:]) {
|
||||||
outputIf(!quiet, "Deposit message root verified")
|
outputIf(!viper.GetBool("quiet"), "Deposit message root verified")
|
||||||
} else {
|
} else {
|
||||||
outputIf(!quiet, "Deposit message root incorrect")
|
outputIf(!viper.GetBool("quiet"), "Deposit message root incorrect")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +306,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
|||||||
}
|
}
|
||||||
signatureVerified := blsSig.Verify(containerRoot[:], validatorPubKey)
|
signatureVerified := blsSig.Verify(containerRoot[:], validatorPubKey)
|
||||||
if signatureVerified {
|
if signatureVerified {
|
||||||
outputIf(!quiet, "Deposit message signature verified")
|
outputIf(!viper.GetBool("quiet"), "Deposit message signature verified")
|
||||||
} else {
|
} else {
|
||||||
outputIf(!quiet, "Deposit message signature NOT verified")
|
outputIf(!viper.GetBool("quiet"), "Deposit message signature NOT verified")
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)")
|
epochSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func epochBindings() {
|
func epochBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", epochSummaryCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,6 @@ func init() {
|
|||||||
epochFlags(epochSummaryCmd)
|
epochFlags(epochSummaryCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func epochSummaryBindings() {
|
func epochSummaryBindings(cmd *cobra.Command) {
|
||||||
epochBindings()
|
epochBindings(cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// errCheck checks for an error and quits if it is present.
|
// errCheck checks for an error and quits if it is present.
|
||||||
func errCheck(err error, msg string) {
|
func errCheck(err error, msg string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !quiet {
|
if !viper.GetBool("quiet") {
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +59,7 @@ func assert(condition bool, msg string) {
|
|||||||
|
|
||||||
// die prints an error and quits.
|
// die prints an error and quits.
|
||||||
func die(msg string) {
|
func die(msg string) {
|
||||||
if msg != "" && !quiet {
|
if msg != "" && !viper.GetBool("quiet") {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||||
}
|
}
|
||||||
os.Exit(_exitFailure)
|
os.Exit(_exitFailure)
|
||||||
|
|||||||
@@ -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")
|
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
||||||
|
|
||||||
outputIf(verbose, "Verified")
|
outputIf(viper.GetBool("verbose"), "Verified")
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -133,8 +133,8 @@ func init() {
|
|||||||
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
|
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitVerifyBindings() {
|
func exitVerifyBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("signed-operation", exitVerifyCmd.Flags().Lookup("signed-operation")); err != nil {
|
if err := viper.BindPFlag("signed-operation", cmd.Flags().Lookup("signed-operation")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)")
|
nodeEventsCmd.Flags().StringSlice("topics", nil, "The topics of events for which to listen (attestation,block,chain_reorg,finalized_checkpoint,head,voluntary_exit)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeEventsBindings() {
|
func nodeEventsBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("topics", nodeEventsCmd.Flags().Lookup("topics")); err != nil {
|
if err := viper.BindPFlag("topics", cmd.Flags().Lookup("topics")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
if quiet {
|
if viper.GetBool("quiet") {
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
version, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx)
|
version, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx)
|
||||||
errCheck(err, "Failed to obtain node version")
|
errCheck(err, "Failed to obtain node version")
|
||||||
fmt.Printf("Version: %s\n", version)
|
fmt.Printf("Version: %s\n", version)
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ func init() {
|
|||||||
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
|
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
|
||||||
}
|
}
|
||||||
|
|
||||||
func proposerDutiesBindings() {
|
func proposerDutiesBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", proposerDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
139
cmd/root.go
139
cmd/root.go
@@ -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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -33,21 +33,55 @@ import (
|
|||||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var cfgFile string
|
||||||
cfgFile string
|
|
||||||
quiet bool
|
|
||||||
verbose bool
|
|
||||||
debug bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands.
|
// RootCmd represents the base command when called without any subcommands.
|
||||||
var RootCmd = &cobra.Command{
|
var RootCmd = &cobra.Command{
|
||||||
Use: "ethdo",
|
Use: "ethdo",
|
||||||
Short: "Ethereum 2 CLI",
|
Short: "Ethereum consensus layer CLI",
|
||||||
Long: `Manage common Ethereum 2 tasks from the command line.`,
|
Long: `Manage common Ethereum consensus layer tasks from the command line.`,
|
||||||
PersistentPreRunE: persistentPreRunE,
|
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 {
|
func persistentPreRunE(cmd *cobra.Command, _ []string) error {
|
||||||
if cmd.Name() == "help" {
|
if cmd.Name() == "help" {
|
||||||
// User just wants help
|
// User just wants help
|
||||||
@@ -63,11 +97,14 @@ func persistentPreRunE(cmd *cobra.Command, _ []string) error {
|
|||||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||||
|
|
||||||
// We bind viper here so that we bind to the correct command.
|
// We bind viper here so that we bind to the correct command.
|
||||||
quiet = viper.GetBool("quiet")
|
quiet := viper.GetBool("quiet")
|
||||||
verbose = viper.GetBool("verbose")
|
verbose := viper.GetBool("verbose")
|
||||||
debug = viper.GetBool("debug")
|
debug := viper.GetBool("debug")
|
||||||
|
|
||||||
includeCommandBindings(cmd)
|
// Command-specific bindings.
|
||||||
|
if bindingsFunc, exists := bindings[commandPath(cmd)]; exists {
|
||||||
|
bindingsFunc(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
if quiet && verbose {
|
if quiet && verbose {
|
||||||
fmt.Println("Cannot supply both quiet and verbose flags")
|
fmt.Println("Cannot supply both quiet and verbose flags")
|
||||||
@@ -79,78 +116,6 @@ func persistentPreRunE(cmd *cobra.Command, _ []string) error {
|
|||||||
return util.SetupStore()
|
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.
|
// 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.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
@@ -167,8 +132,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
addPersistentFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPersistentFlags() {
|
||||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ethdo.yaml)")
|
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")
|
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 {
|
if err := viper.BindPFlag("log", RootCmd.PersistentFlags().Lookup("log")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/herumi/bls-eth-go-binary/bls"
|
"github.com/herumi/bls-eth-go-binary/bls"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"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")
|
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)
|
os.Exit(_exitSuccess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
errCheck(err, "Failed to parse domain")
|
||||||
assert(len(domain) == 32, "Domain data invalid")
|
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
|
var account e2wtypes.Account
|
||||||
switch {
|
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)
|
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
|
||||||
errCheck(err, "Failed to sign")
|
errCheck(err, "Failed to sign")
|
||||||
|
|
||||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Marshal()))
|
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%#x", signature.Marshal()))
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
account, err = util.ParseAccount(ctx, viper.GetString("public-key"), nil, false)
|
||||||
}
|
}
|
||||||
errCheck(err, "Failed to obtain account")
|
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
|
var specDomain spec.Domain
|
||||||
copy(specDomain[:], 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")
|
errCheck(err, "Failed to verify data")
|
||||||
assert(verified, "Failed to verify")
|
assert(verified, "Failed to verify")
|
||||||
|
|
||||||
outputIf(verbose, "Verified")
|
outputIf(viper.GetBool("verbose"), "Verified")
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ func init() {
|
|||||||
slotTimeCmd.Flags().String("slot", "", "the ID of the slot to fetch")
|
slotTimeCmd.Flags().String("slot", "", "the ID of the slot to fetch")
|
||||||
}
|
}
|
||||||
|
|
||||||
func slotTimeBindings() {
|
func slotTimeBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("slot", slotTimeCmd.Flags().Lookup("slot")); err != nil {
|
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ func init() {
|
|||||||
synccommitteeInclusionCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
synccommitteeInclusionCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
||||||
}
|
}
|
||||||
|
|
||||||
func synccommitteeInclusionBindings() {
|
func synccommitteeInclusionBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", synccommitteeInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ func init() {
|
|||||||
synccommitteeMembersCmd.Flags().String("period", "", "the sync committee period for which to fetch sync committees ('current', 'next')")
|
synccommitteeMembersCmd.Flags().String("period", "", "the sync committee period for which to fetch sync committees ('current', 'next')")
|
||||||
}
|
}
|
||||||
|
|
||||||
func synccommitteeMembersBindings() {
|
func synccommitteeMembersBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", synccommitteeMembersCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
cmd/validator/withdrawal/command.go
Normal file
108
cmd/validator/withdrawal/command.go
Normal 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)
|
||||||
|
}
|
||||||
39
cmd/validator/withdrawal/output.go
Normal file
39
cmd/validator/withdrawal/output.go
Normal 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
|
||||||
|
}
|
||||||
155
cmd/validator/withdrawal/process.go
Normal file
155
cmd/validator/withdrawal/process.go
Normal 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
|
||||||
|
}
|
||||||
50
cmd/validator/withdrawal/run.go
Normal file
50
cmd/validator/withdrawal/run.go
Normal 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
|
||||||
|
}
|
||||||
@@ -50,8 +50,8 @@ func init() {
|
|||||||
validatorCredentialsGetCmd.Flags().String("validator", "", "Validator for which to get validator credentials")
|
validatorCredentialsGetCmd.Flags().String("validator", "", "Validator for which to get validator credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorCredentialsGetBindings() {
|
func validatorCredentialsGetBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validator", validatorCredentialsGetCmd.Flags().Lookup("validator")); err != nil {
|
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,29 +66,29 @@ func init() {
|
|||||||
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorCredentialsSetBindings() {
|
func validatorCredentialsSetBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("prepare-offline", validatorCredentialsSetCmd.Flags().Lookup("prepare-offline")); err != nil {
|
if err := viper.BindPFlag("prepare-offline", cmd.Flags().Lookup("prepare-offline")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,29 +59,29 @@ func init() {
|
|||||||
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
|
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorDepositdataBindings() {
|
func validatorDepositdataBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validatoraccount", validatorDepositDataCmd.Flags().Lookup("validatoraccount")); err != nil {
|
if err := viper.BindPFlag("validatoraccount", cmd.Flags().Lookup("validatoraccount")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func init() {
|
|||||||
validatorDutiesCmd.Flags().String("index", "", "validator index for duties")
|
validatorDutiesCmd.Flags().String("index", "", "validator index for duties")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorDutiesBindings() {
|
func validatorDutiesBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("pubkey", validatorDutiesCmd.Flags().Lookup("pubkey")); err != nil {
|
if err := viper.BindPFlag("pubkey", cmd.Flags().Lookup("pubkey")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,26 +63,26 @@ func init() {
|
|||||||
validatorExitCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
validatorExitCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorExitBindings() {
|
func validatorExitBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("epoch", validatorExitCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ func init() {
|
|||||||
validatorExpectationCmd.Flags().Int64("validators", 1, "Number of validators")
|
validatorExpectationCmd.Flags().Int64("validators", 1, "Number of validators")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorExpectationBindings() {
|
func validatorExpectationBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validators", validatorExpectationCmd.Flags().Lookup("validators")); err != nil {
|
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), "head")
|
||||||
errCheck(err, "Failed to obtain validator")
|
errCheck(err, "Failed to obtain validator")
|
||||||
|
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
network, err := util.Network(ctx, eth2Client)
|
network, err := util.Network(ctx, eth2Client)
|
||||||
errCheck(err, "Failed to obtain network")
|
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)
|
pubKey, err := validator.PubKey(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
deposits, totalDeposited, err := graphData(network, pubKey[:])
|
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)
|
os.Exit(_exitSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
if validator.Status.IsPending() || validator.Status.HasActivated() {
|
if validator.Status.IsPending() || validator.Status.HasActivated() {
|
||||||
fmt.Printf("Index: %d\n", validator.Index)
|
fmt.Printf("Index: %d\n", validator.Index)
|
||||||
}
|
}
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
if validator.Status.IsPending() {
|
if validator.Status.IsPending() {
|
||||||
fmt.Printf("Activation eligibility epoch: %d\n", validator.Validator.ActivationEligibilityEpoch)
|
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() {
|
if validator.Status.IsActive() {
|
||||||
fmt.Printf("Effective balance: %s\n", string2eth.GWeiToString(uint64(validator.Validator.EffectiveBalance), true))
|
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)
|
fmt.Printf("Withdrawal credentials: %#x\n", validator.Validator.WithdrawalCredentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +174,8 @@ func init() {
|
|||||||
validatorFlags(validatorInfoCmd)
|
validatorFlags(validatorInfoCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorInfoBindings() {
|
func validatorInfoBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validator", validatorInfoCmd.Flags().Lookup("validator")); err != nil {
|
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ func init() {
|
|||||||
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
|
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorKeycheckBindings() {
|
func validatorKeycheckBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("withdrawal-credentials", validatorKeycheckCmd.Flags().Lookup("withdrawal-credentials")); err != nil {
|
if err := viper.BindPFlag("withdrawal-credentials", cmd.Flags().Lookup("withdrawal-credentials")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ func init() {
|
|||||||
validatorSummaryCmd.Flags().StringSlice("validators", nil, "the list of validators for which to obtain information")
|
validatorSummaryCmd.Flags().StringSlice("validators", nil, "the list of validators for which to obtain information")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorSummaryBindings() {
|
func validatorSummaryBindings(cmd *cobra.Command) {
|
||||||
validatorBindings()
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
cmd/validatorwithdrawal.go
Normal file
57
cmd/validatorwithdrawal.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,8 +50,8 @@ func init() {
|
|||||||
validatorYieldCmd.Flags().String("validators", "", "Number of active validators (default fetches from chain)")
|
validatorYieldCmd.Flags().String("validators", "", "Number of active validators (default fetches from chain)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorYieldBindings() {
|
func validatorYieldBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validators", validatorYieldCmd.Flags().Lookup("validators")); err != nil {
|
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ In quiet mode this will return 0 if the wallet holds any addresses, otherwise 1.
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
outputIf(!quiet, account.Name())
|
outputIf(!viper.GetBool("quiet"), account.Name())
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
fmt.Printf(" UUID: %v\n", account.ID())
|
fmt.Printf(" UUID: %v\n", account.ID())
|
||||||
if pathProvider, isProvider := account.(e2wtypes.AccountPathProvider); isProvider {
|
if pathProvider, isProvider := account.(e2wtypes.AccountPathProvider); isProvider {
|
||||||
if pathProvider.Path() != "" {
|
if pathProvider.Path() != "" {
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ func init() {
|
|||||||
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
|
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletCreateBindings() {
|
func walletCreateBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("type", walletCreateCmd.Flags().Lookup("type")); err != nil {
|
if err := viper.BindPFlag("type", cmd.Flags().Lookup("type")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ func init() {
|
|||||||
walletImportCmd.Flags().Bool("verify", false, "Verify the wallet can be imported, but do not import it")
|
walletImportCmd.Flags().Bool("verify", false, "Verify the wallet can be imported, but do not import it")
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletImportBindings() {
|
func walletImportBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("data", walletImportCmd.Flags().Lookup("data")); err != nil {
|
if err := viper.BindPFlag("data", cmd.Flags().Lookup("data")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ In quiet mode this will return 0 if the wallet exists, otherwise 1.`,
|
|||||||
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
|
wallet, err := walletFromPath(ctx, viper.GetString("wallet"))
|
||||||
errCheck(err, "unknown wallet")
|
errCheck(err, "unknown wallet")
|
||||||
|
|
||||||
if quiet {
|
if viper.GetBool("quiet") {
|
||||||
os.Exit(0)
|
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())
|
fmt.Printf("Type: %s\n", wallet.Type())
|
||||||
if verbose {
|
if viper.GetBool("verbose") {
|
||||||
if storeProvider, ok := wallet.(wtypes.StoreProvider); ok {
|
if storeProvider, ok := wallet.(wtypes.StoreProvider); ok {
|
||||||
store := storeProvider.Store()
|
store := storeProvider.Store()
|
||||||
fmt.Printf("Store: %s\n", store.Name())
|
fmt.Printf("Store: %s\n", store.Name())
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ In quiet mode this will return 0 if any wallets are found, otherwise 1.`,
|
|||||||
walletsFound := false
|
walletsFound := false
|
||||||
for w := range e2wallet.Wallets() {
|
for w := range e2wallet.Wallets() {
|
||||||
walletsFound = true
|
walletsFound = true
|
||||||
outputIf(!quiet && !verbose, w.Name())
|
outputIf(!viper.GetBool("quiet") && !viper.GetBool("verbose"), w.Name())
|
||||||
outputIf(verbose, fmt.Sprintf("%s\n UUID: %s", w.Name(), w.ID().String()))
|
outputIf(viper.GetBool("verbose"), fmt.Sprintf("%s\n UUID: %s", w.Name(), w.ID().String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !walletsFound {
|
if !walletsFound {
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ func init() {
|
|||||||
walletSharedExportCmd.Flags().String("file", "", "Name of the file that stores the export")
|
walletSharedExportCmd.Flags().String("file", "", "Name of the file that stores the export")
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletSharedExportBindings() {
|
func walletSharedExportBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("participants", walletSharedExportCmd.Flags().Lookup("participants")); err != nil {
|
if err := viper.BindPFlag("participants", cmd.Flags().Lookup("participants")); err != nil {
|
||||||
panic(err)
|
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)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ func init() {
|
|||||||
walletSharedImportCmd.Flags().String("shares", "", "Shares required to decrypt the export, separated with spaces")
|
walletSharedImportCmd.Flags().String("shares", "", "Shares required to decrypt the export, separated with spaces")
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletSharedImportBindings() {
|
func walletSharedImportBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("file", walletSharedImportCmd.Flags().Lookup("file")); err != nil {
|
if err := viper.BindPFlag("file", cmd.Flags().Lookup("file")); err != nil {
|
||||||
panic(err)
|
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)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ethdo commands
|
# 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
|
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
|
||||||
|
|
||||||
Block commands focus on providing information about Ethereum 2 blocks.
|
Block commands focus on providing information about Ethereum consensus blocks.
|
||||||
#### `analyze`
|
#### `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
|
- `blockid`: the ID (slot, root, 'head') of the block to obtain
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -297,7 +297,7 @@ Value for block 80: 488.531
|
|||||||
|
|
||||||
#### `info`
|
#### `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
|
- `blockid`: the ID (slot, root, 'head') of the block to obtain
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -335,7 +335,7 @@ Voluntary exits: 0
|
|||||||
|
|
||||||
### `chain` commands
|
### `chain` commands
|
||||||
|
|
||||||
Chain commands focus on providing information about Ethereum 2 chains.
|
Chain commands focus on providing information about Ethereum consensus chains.
|
||||||
|
|
||||||
#### `eth1votes`
|
#### `eth1votes`
|
||||||
|
|
||||||
@@ -355,7 +355,7 @@ Additional information is supplied when using `--verbose`
|
|||||||
|
|
||||||
#### `info`
|
#### `info`
|
||||||
|
|
||||||
`ethdo chain info` obtains information about an Ethereum 2 chain.
|
`ethdo chain info` obtains information about an Ethereum consensus chain.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo chain info
|
$ ethdo chain info
|
||||||
@@ -383,9 +383,21 @@ $ ethdo chain queues
|
|||||||
Activation queue: 14798
|
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`
|
#### `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
|
- `slot` show output in terms of slots rather than epochs
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -410,7 +422,7 @@ Prior justified epoch distance: 4
|
|||||||
|
|
||||||
#### `time`
|
#### `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
|
- `epoch` show epoch and slot times for the given epoch
|
||||||
- `slot` show epoch and slot times for the given slot
|
- `slot` show epoch and slot times for the given slot
|
||||||
- `timestamp` show epoch and slot times for the given timestamp
|
- `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
|
||||||
|
|
||||||
Node commands focus on information from an Ethereum 2 node.
|
Node commands focus on information from an Ethereum consensus node.
|
||||||
|
|
||||||
#### `events`
|
#### `events`
|
||||||
|
|
||||||
`ethdo node events` displays events emitted by an Ethereum 2 node.
|
`ethdo node events` displays events emitted by an Ethereum consensus node.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo node events --topics=head,chain_reorg
|
$ ethdo node events --topics=head,chain_reorg
|
||||||
@@ -504,7 +516,7 @@ $ ethdo node events --topics=head,chain_reorg
|
|||||||
|
|
||||||
#### `info`
|
#### `info`
|
||||||
|
|
||||||
`ethdo node info` obtains the information about an Ethereum 2 node.
|
`ethdo node info` obtains the information about an Ethereum consensus node.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo node info
|
$ ethdo node info
|
||||||
@@ -526,7 +538,7 @@ Genesis timestamp: 1587020563
|
|||||||
|
|
||||||
### `slot` commands
|
### `slot` commands
|
||||||
|
|
||||||
Slot commands focus on information about Ethereum 2 slots.
|
Slot commands focus on information about Ethereum consensus slots.
|
||||||
|
|
||||||
#### `slottime`
|
#### `slottime`
|
||||||
|
|
||||||
@@ -572,7 +584,7 @@ $ ethdo synccommittee members
|
|||||||
|
|
||||||
### `validator` commands
|
### `validator` commands
|
||||||
|
|
||||||
Validator commands focus on interaction with Ethereum 2 validators.
|
Validator commands focus on interaction with Ethereum consensus validators.
|
||||||
|
|
||||||
#### `credentials get`
|
#### `credentials get`
|
||||||
|
|
||||||
@@ -593,7 +605,7 @@ $ ethdo validator credentials set --validator=Validators/1 --withdrawal-address=
|
|||||||
|
|
||||||
#### `depositdata`
|
#### `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)
|
- `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)
|
- `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)
|
- `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
|
||||||
|
|
||||||
Attester commands focus on Ethereum 2 validators' actions as attesters.
|
Attester commands focus on Ethereum consensus validators' actions as attesters.
|
||||||
|
|
||||||
#### `duties`
|
#### `duties`
|
||||||
|
|
||||||
@@ -700,6 +712,16 @@ $ ethdo attester inclusion --validator=Validators/1 --epoch=6484
|
|||||||
Attestation included in block 207492 (inclusion delay 1)
|
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`
|
#### `yield`
|
||||||
|
|
||||||
`ethdo validator yield` calculates the expected yield given the number of validators. Options include:
|
`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
|
||||||
|
|
||||||
Proposer commands focus on Ethereum 2 validators' actions as proposers.
|
Proposer commands focus on Ethereum consensus validators' actions as proposers.
|
||||||
|
|
||||||
#### `duties`
|
#### `duties`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user