diff --git a/cmd/root.go b/cmd/root.go index 502cde3..394fe4d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -158,7 +158,7 @@ func init() { if err := viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug")); err != nil { panic(err) } - RootCmd.PersistentFlags().String("connection", "", "connection to Ethereum 2 node via GRPC") + RootCmd.PersistentFlags().String("connection", "localhost:4000", "connection to Ethereum 2 node via GRPC") if err := viper.BindPFlag("connection", RootCmd.PersistentFlags().Lookup("connection")); err != nil { panic(err) } diff --git a/cmd/validatorinfo.go b/cmd/validatorinfo.go new file mode 100644 index 0000000..e478e55 --- /dev/null +++ b/cmd/validatorinfo.go @@ -0,0 +1,99 @@ +// Copyright © 2020 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" + "os" + "strings" + "time" + + ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/spf13/cobra" + "github.com/wealdtech/ethdo/grpc" + string2eth "github.com/wealdtech/go-string2eth" +) + +var validatorInfoCmd = &cobra.Command{ + Use: "info", + Short: "Obtain information about a validator", + Long: `Obtain information about validator. For example: + + ethdo validator info --account=primary/validator + +In quiet mode this will return 0 if the validator information can be obtained, otherwise 1.`, + Run: func(cmd *cobra.Command, args []string) { + // Sanity checking and setup. + assert(rootAccount != "", "--account is required") + account, err := accountFromPath(rootAccount) + errCheck(err, "Failed to access account") + err = connect() + errCheck(err, "Failed to obtain connection to Ethereum 2 beacon chain node") + + validatorInfo, err := grpc.FetchValidatorInfo(eth2GRPCConn, account) + errCheck(err, "Failed to obtain validator information") + validatorDef, err := grpc.FetchValidator(eth2GRPCConn, account) + if validatorInfo.Status != ethpb.ValidatorStatus_DEPOSITED && + validatorInfo.Status != ethpb.ValidatorStatus_UNKNOWN_STATUS { + errCheck(err, "Failed to obtain validator definition") + } + + assert(validatorInfo.Status != ethpb.ValidatorStatus_UNKNOWN_STATUS, "Not known as a validator") + + if quiet { + os.Exit(_exit_success) + } + + outputIf(verbose, fmt.Sprintf("Index:\t\t\t%v", validatorInfo.Index)) + outputIf(verbose, fmt.Sprintf("Public key:\t\t%#x", validatorInfo.PublicKey)) + fmt.Printf("Status:\t\t\t%s\n", strings.Title(strings.ToLower(validatorInfo.Status.String()))) + fmt.Printf("Balance:\t\t%s\n", string2eth.GWeiToString(validatorInfo.Balance, true)) + if validatorInfo.Status == ethpb.ValidatorStatus_ACTIVE || + validatorInfo.Status == ethpb.ValidatorStatus_EXITING || + validatorInfo.Status == ethpb.ValidatorStatus_SLASHING { + fmt.Printf("Effective balance:\t%s\n", string2eth.GWeiToString(validatorInfo.EffectiveBalance, true)) + } + outputIf(verbose, fmt.Sprintf("Withdrawal credentials:\t%#x", validatorDef.WithdrawalCredentials)) + transition := time.Unix(int64(validatorInfo.TransitionTimestamp), 0) + transitionPassed := int64(validatorInfo.TransitionTimestamp) <= time.Now().Unix() + switch validatorInfo.Status { + case ethpb.ValidatorStatus_DEPOSITED: + // TODO test + if validatorInfo.TransitionTimestamp != 0 { + fmt.Printf("Inclusion in chain:\t%s\n", transition) + } + case ethpb.ValidatorStatus_PENDING: + // TODO test + fmt.Printf("Activation:\t\t%s\n", transition) + case ethpb.ValidatorStatus_EXITING: + case ethpb.ValidatorStatus_SLASHING: + // TODO test + fmt.Printf("Attesting finishes:\t%s\n", transition) + case ethpb.ValidatorStatus_EXITED: + if transitionPassed { + fmt.Printf("Funds withdrawable:\tNow\n") + } else { + // TODO test + fmt.Printf("Funds withdrawable:\t%s\n", transition) + } + } + + os.Exit(_exit_success) + }, +} + +func init() { + validatorCmd.AddCommand(validatorInfoCmd) + validatorFlags(validatorInfoCmd) +} diff --git a/cmd/version.go b/cmd/version.go index 5c624fc..a67e2bc 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -30,7 +30,7 @@ var versionCmd = &cobra.Command{ ethdo version.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("1.2.1") + fmt.Println("1.2.2") if viper.GetBool("verbose") { buildInfo, ok := dbg.ReadBuildInfo() if ok { diff --git a/grpc/beaconchain.go b/grpc/beaconchain.go index e35dbcf..54375c1 100644 --- a/grpc/beaconchain.go +++ b/grpc/beaconchain.go @@ -72,7 +72,7 @@ func FetchChainConfig(conn *grpc.ClientConn) (map[string]interface{}, error) { return results, nil } -// FetchValidator fetches validator information from the beacon node. +// FetchValidator fetches the validator definition from the beacon node. func FetchValidator(conn *grpc.ClientConn, account wtypes.Account) (*ethpb.Validator, error) { beaconClient := ethpb.NewBeaconChainClient(conn) ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) @@ -85,3 +85,25 @@ func FetchValidator(conn *grpc.ClientConn, account wtypes.Account) (*ethpb.Valid } return beaconClient.GetValidator(ctx, req) } + +// FetchValidatorInfo fetches current validator info from the beacon node. +func FetchValidatorInfo(conn *grpc.ClientConn, account wtypes.Account) (*ethpb.ValidatorInfo, error) { + beaconClient := ethpb.NewBeaconChainClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + + stream, err := beaconClient.StreamValidatorsInfo(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to contact beacon node") + } + + changeSet := ðpb.ValidatorChangeSet{ + Action: ethpb.SetAction_SET_VALIDATOR_KEYS, + PublicKeys: [][]byte{account.PublicKey().Marshal()}, + } + err = stream.Send(changeSet) + if err != nil { + return nil, errors.Wrap(err, "failed to send validator public key") + } + return stream.Recv() +}