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