mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df34cef2bd | ||
|
|
e8513e60b2 | ||
|
|
4aadee3fad | ||
|
|
c7adf03aeb | ||
|
|
35944ca8c5 | ||
|
|
083d625813 | ||
|
|
263db812b3 | ||
|
|
2c01d18195 | ||
|
|
df99d43415 | ||
|
|
d81546f2de | ||
|
|
3d87a917af | ||
|
|
9240ed4857 | ||
|
|
18f9e8dca2 | ||
|
|
fbc24b81d2 | ||
|
|
f898466395 | ||
|
|
e496fa1977 | ||
|
|
d016326779 | ||
|
|
ee14b5ee8e | ||
|
|
9ae927feab |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
||||
1.30.0:
|
||||
- add "chain spec" command
|
||||
- add "validator withdrawal" command
|
||||
|
||||
1.29.2:
|
||||
- fix regression where validator index could not be used as an account specifier
|
||||
|
||||
1.29.0:
|
||||
- allow use of keystores with validator credentials set
|
||||
- tidy up various command options to provide more standard usage
|
||||
- add mainnet fallback beacon node
|
||||
|
||||
1.28.4:
|
||||
- allow validator exit to use a keystore as its validator parameter
|
||||
|
||||
|
||||
@@ -86,6 +86,11 @@ Teku disables the REST API by default. To enable it, the beacon node must be st
|
||||
|
||||
The default port for the REST API is 5051, which can be changed with the `--rest-api-port` parameter.
|
||||
|
||||
### Lodestar
|
||||
Lodestar enables the REST API by default and should just work locally. If you want to access the REST API from a remote server then you should also look to change the `--rest.address` to `0.0.0.0` as per the Lodestar documentation.
|
||||
|
||||
The default port for the REST API is 9596, which can be changed with the `--rest.port` parameter.
|
||||
|
||||
## Usage
|
||||
|
||||
`ethdo` contains a large number of features that are useful for day-to-day interactions with the different consensus clients.
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(accountCmd)
|
||||
}
|
||||
|
||||
func accountFlags(cmd *cobra.Command) {
|
||||
func accountFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type dataOut struct {
|
||||
account e2wtypes.Account
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type dataIn struct {
|
||||
showWithdrawalCredentials bool
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
func input(_ context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
// Quiet.
|
||||
|
||||
@@ -29,7 +29,7 @@ type dataOut struct {
|
||||
key *e2types.BLSPrivateKey
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type dataOut struct {
|
||||
account e2wtypes.Account
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type dataOut struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(attesterCmd)
|
||||
}
|
||||
|
||||
func attesterFlags(cmd *cobra.Command) {
|
||||
func attesterFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import (
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -31,13 +33,11 @@ type dataIn struct {
|
||||
verbose bool
|
||||
debug bool
|
||||
json bool
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
account string
|
||||
pubKey string
|
||||
validator string
|
||||
eth2Client eth2client.Service
|
||||
epoch spec.Epoch
|
||||
chainTime chaintime.Service
|
||||
epoch phase0.Epoch
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -52,38 +52,37 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
data.json = viper.GetBool("json")
|
||||
|
||||
// Account or pubkey.
|
||||
if viper.GetString("account") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account or pubkey is required")
|
||||
// Validator.
|
||||
data.validator = viper.GetString("validator")
|
||||
if data.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
data.account = viper.GetString("account")
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Required data.
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
// Epoch
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
||||
// Epoch.
|
||||
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "AccountMissing",
|
||||
name: "ValidatorMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "account or pubkey is required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"timeout": "5s",
|
||||
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type dataOut struct {
|
||||
duty *api.AttesterDuty
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@ package attesterduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
@@ -32,43 +28,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if data.account != "" {
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
_, account, err = util.WalletAndAccountFromPath(ctx, data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", data.pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", data.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch validator
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
@@ -77,7 +39,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
@@ -87,7 +49,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch spec.Epoch, slotsPerEpoch uint64) (*api.AttesterDuty, error) {
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch spec.Epoch) (*api.AttesterDuty, error) {
|
||||
// Find the attesting slot for the given epoch.
|
||||
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []spec.ValidatorIndex{validator.Index})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,9 +18,11 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
@@ -33,6 +35,12 @@ func TestProcess(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
chainTime, err := standardchaintime.New(context.Background(),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
||||
{
|
||||
name: "Client",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ package attesterinclusion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -32,15 +32,11 @@ type dataIn struct {
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
epoch spec.Epoch
|
||||
account string
|
||||
pubKey string
|
||||
index string
|
||||
validator string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -54,48 +50,35 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.verbose = viper.GetBool("verbose")
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Account.
|
||||
data.account = viper.GetString("account")
|
||||
|
||||
// PubKey.
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// ID.
|
||||
data.index = viper.GetString("index")
|
||||
|
||||
if viper.GetString("account") == "" && viper.GetString("index") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account, index or pubkey is required")
|
||||
data.validator = viper.GetString("validator")
|
||||
if data.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
// Epoch.
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
||||
if epoch > 0 {
|
||||
epoch--
|
||||
}
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
if data.debug {
|
||||
fmt.Printf("Epoch is %d\n", data.epoch)
|
||||
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "IndexMissing",
|
||||
name: "ValidatorMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "account, index or pubkey is required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"timeout": "5s",
|
||||
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ type dataOut struct {
|
||||
targetTimely bool
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
buf := strings.Builder{}
|
||||
if data == nil {
|
||||
return buf.String(), errors.New("no data")
|
||||
|
||||
@@ -31,7 +31,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var err error
|
||||
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
@@ -41,30 +44,13 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
validatorIndex, err := util.ValidatorIndex(ctx, data.eth2Client, data.account, data.pubKey, data.index)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator index")
|
||||
}
|
||||
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), []phase0.ValidatorIndex{validatorIndex})
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
@@ -175,7 +161,7 @@ func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.At
|
||||
}
|
||||
}
|
||||
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch phase0.Epoch, slotsPerEpoch uint64) (*api.AttesterDuty, error) {
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch phase0.Epoch) (*api.AttesterDuty, error) {
|
||||
// Find the attesting slot for the given epoch.
|
||||
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []phase0.ValidatorIndex{validator.Index})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,18 +18,26 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/http"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
eth2Client, err := auto.New(context.Background(),
|
||||
auto.WithLogLevel(zerolog.Disabled),
|
||||
auto.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||
eth2Client, err := http.New(context.Background(),
|
||||
http.WithLogLevel(zerolog.Disabled),
|
||||
http.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
chainTime, err := standardchaintime.New(context.Background(),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
||||
{
|
||||
name: "Client",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var attesterDutiesCmd = &cobra.Command{
|
||||
Short: "Obtain information about duties of an attester",
|
||||
Long: `Obtain information about dutes of an attester. For example:
|
||||
|
||||
ethdo attester duties --account=Validators/00001 --epoch=12345
|
||||
ethdo attester duties --validator=Validators/00001 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if a duty from the attester is found, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,19 +47,15 @@ In quiet mode this will return 0 if a duty from the attester is found, otherwise
|
||||
func init() {
|
||||
attesterCmd.AddCommand(attesterDutiesCmd)
|
||||
attesterFlags(attesterDutiesCmd)
|
||||
attesterDutiesCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
||||
attesterDutiesCmd.Flags().String("pubkey", "", "the public key of the attester")
|
||||
attesterDutiesCmd.Flags().Bool("json", false, "Generate JSON data for an exit; do not broadcast to network")
|
||||
attesterDutiesCmd.Flags().String("epoch", "head", "the epoch for which to obtain the duties")
|
||||
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("pubkey", attesterDutiesCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", attesterDutiesCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var attesterInclusionCmd = &cobra.Command{
|
||||
Short: "Obtain information about attester inclusion",
|
||||
Long: `Obtain information about attester inclusion. For example:
|
||||
|
||||
ethdo attester inclusion --account=Validators/00001 --epoch=12345
|
||||
ethdo attester inclusion --validator=Validators/00001 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if an attestation from the attester is found on the block of the given epoch, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,19 +47,19 @@ In quiet mode this will return 0 if an attestation from the attester is found on
|
||||
func init() {
|
||||
attesterCmd.AddCommand(attesterInclusionCmd)
|
||||
attesterFlags(attesterInclusionCmd)
|
||||
attesterInclusionCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
||||
attesterInclusionCmd.Flags().String("pubkey", "", "the public key of the attester")
|
||||
attesterInclusionCmd.Flags().String("epoch", "-1", "the epoch for which to obtain the inclusion")
|
||||
attesterInclusionCmd.Flags().String("validator", "", "the index, public key, or account of the validator")
|
||||
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("pubkey", attesterInclusionCmd.Flags().Lookup("pubkey")); 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(blockCmd)
|
||||
}
|
||||
|
||||
func blockFlags(cmd *cobra.Command) {
|
||||
func blockFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ type attestationData struct {
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -106,9 +106,8 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
if attestation.NewVotes == 0 {
|
||||
builder.WriteString("\n")
|
||||
continue
|
||||
} else {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
switch {
|
||||
case !attestation.HeadCorrect:
|
||||
builder.WriteString("head vote incorrect, ")
|
||||
|
||||
@@ -77,11 +77,7 @@ func (c *command) analyze(ctx context.Context, block *spec.VersionedSignedBeacon
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.analyzeSyncCommittees(ctx, block); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.analyzeSyncCommittees(ctx, block)
|
||||
}
|
||||
|
||||
func (c *command) analyzeAttestations(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
@@ -213,7 +209,7 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
|
||||
return c.fetchParents(ctx, parentBlock, minSlot)
|
||||
}
|
||||
|
||||
func (c *command) processParentBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -259,7 +255,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
@@ -412,7 +413,7 @@ func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Att
|
||||
return bytes.Equal(root[:], attestation.Data.Target.Root[:]), nil
|
||||
}
|
||||
|
||||
func (c *command) analyzeSyncCommittees(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
c.analysis.SyncCommitee = &syncCommitteeAnalysis{}
|
||||
switch block.Version {
|
||||
case spec.DataVersionPhase0:
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestProcess(t *testing.T) {
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"blockid": "invalid",
|
||||
},
|
||||
err: "failed to obtain beacon block: failed to request signed beacon block: GET failed with status 400: {\"code\":400,\"message\":\"Invalid block: invalid\"}",
|
||||
err: "failed to obtain beacon block: failed to request signed beacon block: GET failed with status 400: {\"code\":400,\"message\":\"BAD_REQUEST: Unsupported endpoint version: v2\",\"stacktraces\":[]}",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.stream = viper.GetBool("stream")
|
||||
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionBad",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -43,7 +43,7 @@ type dataOut struct {
|
||||
slotsPerEpoch uint64
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func outputBlockGeneral(ctx context.Context,
|
||||
func outputBlockGeneral(_ context.Context,
|
||||
verbose bool,
|
||||
slot phase0.Slot,
|
||||
blockRoot phase0.Root,
|
||||
@@ -89,7 +89,7 @@ func outputBlockGeneral(ctx context.Context,
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockETH1Data(ctx context.Context, eth1Data *phase0.ETH1Data) (string, error) {
|
||||
func outputBlockETH1Data(_ context.Context, eth1Data *phase0.ETH1Data) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString(fmt.Sprintf("Ethereum 1 deposit count: %d\n", eth1Data.DepositCount))
|
||||
@@ -194,7 +194,7 @@ func outputBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Ser
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockDeposits(ctx context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||
func outputBlockDeposits(_ context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
// Deposits.
|
||||
@@ -638,7 +638,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
func outputCapellaBlockExecutionPayload(_ context.Context,
|
||||
verbose bool,
|
||||
payload *capella.ExecutionPayload,
|
||||
) (
|
||||
@@ -706,7 +706,7 @@ func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockExecutionPayload(ctx context.Context,
|
||||
func outputBellatrixBlockExecutionPayload(_ context.Context,
|
||||
verbose bool,
|
||||
payload *bellatrix.ExecutionPayload,
|
||||
) (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 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
|
||||
@@ -39,6 +39,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
if data.blockID == "" {
|
||||
return nil, errors.New("no block ID")
|
||||
}
|
||||
|
||||
results = &dataOut{
|
||||
debug: data.debug,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 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
|
||||
@@ -43,11 +43,11 @@ func TestProcess(t *testing.T) {
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Client",
|
||||
name: "NoBlockID",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
},
|
||||
err: "empty beacon block",
|
||||
err: "no block ID",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -49,17 +49,13 @@ func init() {
|
||||
blockFlags(blockAnalyzeCmd)
|
||||
blockAnalyzeCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
|
||||
blockAnalyzeCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
||||
blockAnalyzeCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", blockAnalyzeCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,21 +49,17 @@ func init() {
|
||||
blockFlags(blockInfoCmd)
|
||||
blockInfoCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
|
||||
blockInfoCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
||||
blockInfoCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
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("json", blockInfoCmd.Flags().Lookup("json")); 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(chainCmd)
|
||||
}
|
||||
|
||||
func chainFlags(cmd *cobra.Command) {
|
||||
func chainFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ type vote struct {
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -42,7 +42,7 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
return c.outputText(ctx)
|
||||
}
|
||||
|
||||
func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
votes := make([]*vote, 0, len(c.votes))
|
||||
totalVotes := 0
|
||||
for _, vote := range c.votes {
|
||||
@@ -71,7 +71,7 @@ func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *command) outputText(ctx context.Context) (string, error) {
|
||||
func (c *command) outputText(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
builder.WriteString("Voting period: ")
|
||||
|
||||
@@ -114,7 +114,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ type command struct {
|
||||
exitQueue int
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -36,7 +36,7 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
return c.outputText(ctx)
|
||||
}
|
||||
|
||||
func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
output := &jsonOutput{
|
||||
ActivationQueue: c.activationQueue,
|
||||
ExitQueue: c.exitQueue,
|
||||
@@ -49,7 +49,7 @@ func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *command) outputText(ctx context.Context) (string, error) {
|
||||
func (c *command) outputText(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
if c.activationQueue > 0 {
|
||||
|
||||
@@ -58,7 +58,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type dataIn struct {
|
||||
epoch string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
func input(_ context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
|
||||
@@ -34,6 +34,7 @@ type dataOut struct {
|
||||
slot spec.Slot
|
||||
slotStart time.Time
|
||||
slotEnd time.Time
|
||||
hasSyncCommittees bool
|
||||
syncCommitteePeriod uint64
|
||||
syncCommitteePeriodStart time.Time
|
||||
syncCommitteePeriodEpochStart spec.Epoch
|
||||
@@ -41,7 +42,7 @@ type dataOut struct {
|
||||
syncCommitteePeriodEpochEnd spec.Epoch
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
@@ -56,27 +57,59 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epoch))
|
||||
builder.WriteString("\n Epoch start ")
|
||||
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epochStart.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n Epoch end ")
|
||||
builder.WriteString(data.epochEnd.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epochEnd.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\nSlot ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slot))
|
||||
builder.WriteString("\n Slot start ")
|
||||
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slotStart.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n Slot end ")
|
||||
builder.WriteString(data.slotEnd.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slotEnd.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\nSync committee period ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
||||
builder.WriteString("\n Sync committee period start ")
|
||||
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
||||
builder.WriteString(")\n Sync committee period end ")
|
||||
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
||||
builder.WriteString(")\n")
|
||||
if data.hasSyncCommittees {
|
||||
builder.WriteString("\nSync committee period ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
||||
builder.WriteString("\n Sync committee period start ")
|
||||
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
||||
if data.verbose {
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodStart.Unix()))
|
||||
}
|
||||
builder.WriteString(")\n Sync committee period end ")
|
||||
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
||||
if data.verbose {
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEnd.Unix()))
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -29,22 +30,22 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.connection, data.timeout, data.allowInsecureConnections)
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: data.connection,
|
||||
Timeout: data.timeout,
|
||||
AllowInsecure: data.allowInsecureConnections,
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
}
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
|
||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
epochsPerSyncCommitteePeriod := config["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"].(uint64)
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
@@ -62,34 +63,33 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
results.slot = phase0.Slot(slot)
|
||||
case data.epoch != "":
|
||||
epoch, err := strconv.ParseUint(data.epoch, 10, 64)
|
||||
epoch, err := util.ParseEpoch(ctx, chainTime, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse epoch")
|
||||
}
|
||||
results.slot = phase0.Slot(epoch * slotsPerEpoch)
|
||||
results.slot = chainTime.FirstSlotOfEpoch(epoch)
|
||||
case data.timestamp != "":
|
||||
timestamp, err := time.Parse("2006-01-02T15:04:05-0700", data.timestamp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse timestamp")
|
||||
}
|
||||
secs := timestamp.Sub(genesis.GenesisTime)
|
||||
if secs < 0 {
|
||||
return nil, errors.New("timestamp prior to genesis")
|
||||
}
|
||||
results.slot = phase0.Slot(secs / slotDuration)
|
||||
results.slot = chainTime.TimestampToSlot(timestamp)
|
||||
}
|
||||
|
||||
// Fill in the info given the slot.
|
||||
results.slotStart = genesis.GenesisTime.Add(time.Duration(results.slot) * slotDuration)
|
||||
results.slotEnd = genesis.GenesisTime.Add(time.Duration(results.slot+1) * slotDuration)
|
||||
results.epoch = phase0.Epoch(uint64(results.slot) / slotsPerEpoch)
|
||||
results.epochStart = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch)*slotsPerEpoch) * slotDuration)
|
||||
results.epochEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch+1)*slotsPerEpoch) * slotDuration)
|
||||
results.syncCommitteePeriod = uint64(results.epoch) / epochsPerSyncCommitteePeriod
|
||||
results.syncCommitteePeriodEpochStart = phase0.Epoch(results.syncCommitteePeriod * epochsPerSyncCommitteePeriod)
|
||||
results.syncCommitteePeriodEpochEnd = phase0.Epoch((results.syncCommitteePeriod+1)*epochsPerSyncCommitteePeriod) - 1
|
||||
results.syncCommitteePeriodStart = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochStart)*slotsPerEpoch) * slotDuration)
|
||||
results.syncCommitteePeriodEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochEnd)*slotsPerEpoch) * slotDuration)
|
||||
results.slotStart = chainTime.StartOfSlot(results.slot)
|
||||
results.slotEnd = chainTime.StartOfSlot(results.slot + 1)
|
||||
results.epoch = chainTime.SlotToEpoch(results.slot)
|
||||
results.epochStart = chainTime.StartOfEpoch(results.epoch)
|
||||
results.epochEnd = chainTime.StartOfEpoch(results.epoch + 1)
|
||||
if results.epoch >= chainTime.FirstEpochOfSyncPeriod(chainTime.AltairInitialSyncCommitteePeriod()) {
|
||||
results.hasSyncCommittees = true
|
||||
results.syncCommitteePeriod = chainTime.SlotToSyncCommitteePeriod(results.slot)
|
||||
results.syncCommitteePeriodEpochStart = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod)
|
||||
results.syncCommitteePeriodEpochEnd = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod + 1)
|
||||
results.syncCommitteePeriodStart = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochStart)
|
||||
results.syncCommitteePeriodEnd = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochEnd)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021 - 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
|
||||
@@ -15,7 +15,6 @@ package chaintime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -47,16 +46,11 @@ func TestProcess(t *testing.T) {
|
||||
slot: "1",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epochStart: time.Unix(1606824023, 0),
|
||||
epochEnd: time.Unix(1606824407, 0),
|
||||
slot: 1,
|
||||
slotStart: time.Unix(1606824035, 0),
|
||||
slotEnd: time.Unix(1606824047, 0),
|
||||
syncCommitteePeriod: 0,
|
||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
||||
syncCommitteePeriodEpochStart: 0,
|
||||
syncCommitteePeriodEpochEnd: 255,
|
||||
epochStart: time.Unix(1606824023, 0),
|
||||
epochEnd: time.Unix(1606824407, 0),
|
||||
slot: 1,
|
||||
slotStart: time.Unix(1606824035, 0),
|
||||
slotEnd: time.Unix(1606824047, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,17 +62,12 @@ func TestProcess(t *testing.T) {
|
||||
epoch: "2",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 2,
|
||||
epochStart: time.Unix(1606824791, 0),
|
||||
epochEnd: time.Unix(1606825175, 0),
|
||||
slot: 64,
|
||||
slotStart: time.Unix(1606824791, 0),
|
||||
slotEnd: time.Unix(1606824803, 0),
|
||||
syncCommitteePeriod: 0,
|
||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
||||
syncCommitteePeriodEpochStart: 0,
|
||||
syncCommitteePeriodEpochEnd: 255,
|
||||
epoch: 2,
|
||||
epochStart: time.Unix(1606824791, 0),
|
||||
epochEnd: time.Unix(1606825175, 0),
|
||||
slot: 64,
|
||||
slotStart: time.Unix(1606824791, 0),
|
||||
slotEnd: time.Unix(1606824803, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,20 +76,21 @@ func TestProcess(t *testing.T) {
|
||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
timeout: 10 * time.Second,
|
||||
allowInsecureConnections: true,
|
||||
timestamp: "2021-01-01T00:00:00+0000",
|
||||
timestamp: "2023-01-01T00:00:00+0000",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 6862,
|
||||
epochStart: time.Unix(1609459031, 0),
|
||||
epochEnd: time.Unix(1609459415, 0),
|
||||
slot: 219598,
|
||||
slotStart: time.Unix(1609459199, 0),
|
||||
slotEnd: time.Unix(1609459211, 0),
|
||||
syncCommitteePeriod: 26,
|
||||
syncCommitteePeriodStart: time.Unix(1609379927, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1609477847, 0),
|
||||
syncCommitteePeriodEpochStart: 6656,
|
||||
syncCommitteePeriodEpochEnd: 6911,
|
||||
epoch: 171112,
|
||||
epochStart: time.Unix(1672531031, 0),
|
||||
epochEnd: time.Unix(1672531415, 0),
|
||||
slot: 5475598,
|
||||
slotStart: time.Unix(1672531199, 0),
|
||||
slotEnd: time.Unix(1672531211, 0),
|
||||
hasSyncCommittees: true,
|
||||
syncCommitteePeriod: 668,
|
||||
syncCommitteePeriodStart: time.Unix(1672491095, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1672589399, 0),
|
||||
syncCommitteePeriodEpochStart: 171008,
|
||||
syncCommitteePeriodEpochEnd: 171264,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -112,7 +102,6 @@ func TestProcess(t *testing.T) {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
fmt.Printf("****** %d %d\n", res.syncCommitteePeriodStart.Unix(), res.syncCommitteePeriodEnd.Unix())
|
||||
require.Equal(t, test.expected, res)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ type command struct {
|
||||
additionalInfo string
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
func (c *command) output(_ context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -85,7 +85,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
@@ -51,17 +51,13 @@ func init() {
|
||||
chainFlags(chainEth1VotesCmd)
|
||||
chainEth1VotesCmd.Flags().String("epoch", "", "epoch for which to fetch the votes")
|
||||
chainEth1VotesCmd.Flags().String("period", "", "period for which to fetch the votes")
|
||||
chainEth1VotesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", chainEth1VotesCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("period", cmd.Flags().Lookup("period")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,12 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
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 2 beacon node")
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
@@ -49,12 +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 {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if viper.GetBool("prepare-offline") {
|
||||
fmt.Printf("Add the following to your command to run it offline:\n --offline --genesis-validators=root=%#x --fork-version=%#x\n", genesis.GenesisValidatorsRoot, fork.CurrentVersion)
|
||||
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,
|
||||
@@ -89,11 +89,7 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainInfoCmd)
|
||||
chainFlags(chainInfoCmd)
|
||||
chainInfoCmd.Flags().Bool("prepare-offline", false, "Provide information useful for offline commands")
|
||||
}
|
||||
|
||||
func chainInfoBindings() {
|
||||
if err := viper.BindPFlag("prepare-offline", chainInfoCmd.Flags().Lookup("prepare-offline")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func chainInfoBindings(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -48,14 +48,10 @@ func init() {
|
||||
chainCmd.AddCommand(chainQueuesCmd)
|
||||
chainFlags(chainQueuesCmd)
|
||||
chainQueuesCmd.Flags().String("epoch", "", "epoch for which to fetch the queues")
|
||||
chainQueuesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func chainQueuesBindings() {
|
||||
if err := viper.BindPFlag("epoch", chainQueuesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", chainQueuesCmd.Flags().Lookup("json")); 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) {
|
||||
}
|
||||
@@ -41,7 +41,12 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
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 2 beacon node")
|
||||
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
@@ -78,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("-")
|
||||
@@ -101,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))
|
||||
@@ -111,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)
|
||||
@@ -160,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(depositCmd)
|
||||
}
|
||||
|
||||
func depositFlags(cmd *cobra.Command) {
|
||||
func depositFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ func init() {
|
||||
RootCmd.AddCommand(epochCmd)
|
||||
}
|
||||
|
||||
func epochFlags(cmd *cobra.Command) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ type nonParticipatingValidator struct {
|
||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -48,11 +48,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
if err := c.processAttesterDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.processSyncCommitteeDuties(ctx)
|
||||
}
|
||||
|
||||
func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
@@ -328,7 +324,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
@@ -47,12 +47,8 @@ In quiet mode this will return 0 if information for the epoch is found, otherwis
|
||||
func init() {
|
||||
epochCmd.AddCommand(epochSummaryCmd)
|
||||
epochFlags(epochSummaryCmd)
|
||||
epochSummaryCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func epochSummaryBindings() {
|
||||
epochBindings()
|
||||
if err := viper.BindPFlag("json", epochSummaryCmd.Flags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(exitCmd)
|
||||
}
|
||||
|
||||
func exitFlags(cmd *cobra.Command) {
|
||||
func exitFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -14,80 +14,97 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
consensusclient "github.com/attestantio/go-eth2-client"
|
||||
v1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"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"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var exitVerifyPubKey string
|
||||
|
||||
var exitVerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify exit data is valid",
|
||||
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
||||
|
||||
ethdo exit verify --data=exitdata.json --account=primary/current
|
||||
ethdo exit verify --signed-operation=exitdata.json --validator=primary/current
|
||||
|
||||
In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
assert(viper.GetString("account") != "" || exitVerifyPubKey != "", "account or public key is required")
|
||||
account, err := exitVerifyAccount(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
assert(viper.GetString("signed-operation") != "", "signed-operation is required")
|
||||
signedOp, err := obtainSignedOperation(viper.GetString("signed-operation"))
|
||||
errCheck(err, "Failed to obtain signed operation")
|
||||
|
||||
assert(viper.GetString("exit") != "", "exit is required")
|
||||
data, err := obtainExitData(viper.GetString("exit"))
|
||||
errCheck(err, "Failed to obtain exit data")
|
||||
|
||||
// Confirm signature is good.
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
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 2 beacon node")
|
||||
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
validator, err := util.ParseValidator(ctx, eth2Client.(consensusclient.ValidatorsProvider), fmt.Sprintf("%d", signedOp.Message.ValidatorIndex), "head")
|
||||
errCheck(err, "failed to obtain validator")
|
||||
pubkey, err := validator.PubKey(ctx)
|
||||
errCheck(err, "failed to obtain validator public key")
|
||||
account, err := util.ParseAccount(ctx, pubkey.String(), nil, false)
|
||||
errCheck(err, "failed to obtain account")
|
||||
|
||||
// Ensure the validator is in a suitable state.
|
||||
assert(validator.Status == v1.ValidatorStateActiveOngoing, "validator not in a suitable state to exit")
|
||||
|
||||
// Obtain the hash tree root of the message to check the signature.
|
||||
opRoot, err := signedOp.Message.HashTreeRoot()
|
||||
errCheck(err, "Failed to obtain exit hash tree root")
|
||||
|
||||
genesis, err := eth2Client.(consensusclient.GenesisProvider).Genesis(ctx)
|
||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||
|
||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
var exitDomain spec.Domain
|
||||
copy(exitDomain[:], domain)
|
||||
exit := &spec.VoluntaryExit{
|
||||
Epoch: data.Exit.Message.Epoch,
|
||||
ValidatorIndex: data.Exit.Message.ValidatorIndex,
|
||||
}
|
||||
exitRoot, err := exit.HashTreeRoot()
|
||||
errCheck(err, "Failed to obtain exit hash tree root")
|
||||
fork, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, "head")
|
||||
errCheck(err, "Failed to obtain fork information")
|
||||
|
||||
// Check against current and prior fork versions.
|
||||
signatureBytes := make([]byte, 96)
|
||||
copy(signatureBytes, data.Exit.Signature[:])
|
||||
copy(signatureBytes, signedOp.Signature[:])
|
||||
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
||||
errCheck(err, "Invalid signature")
|
||||
verified, err := util.VerifyRoot(account, exitRoot, exitDomain, sig)
|
||||
|
||||
verified := false
|
||||
|
||||
// Try with the current fork.
|
||||
domain := phase0.Domain{}
|
||||
currentExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.CurrentVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
errCheck(err, "Failed to compute domain")
|
||||
copy(domain[:], currentExitDomain)
|
||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
assert(verified, "Voluntary exit failed to verify")
|
||||
if !verified {
|
||||
// Try again with the previous fork.
|
||||
previousExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.PreviousVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
copy(domain[:], previousExitDomain)
|
||||
errCheck(err, "Failed to compute domain")
|
||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
}
|
||||
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
||||
|
||||
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
||||
errCheck(err, "Failed to obtain current fork")
|
||||
assert(bytes.Equal(data.ForkVersion[:], fork.CurrentVersion[:]) || bytes.Equal(data.ForkVersion[:], fork.PreviousVersion[:]), "Exit is for an old fork version and is no longer valid")
|
||||
|
||||
outputIf(verbose, "Verified")
|
||||
outputIf(viper.GetBool("verbose"), "Verified")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// obtainExitData obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||
func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
||||
// obtainSignedOperation obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||
func obtainSignedOperation(input string) (*phase0.SignedVoluntaryExit, error) {
|
||||
var err error
|
||||
var data []byte
|
||||
// Input could be JSON or a path to JSON
|
||||
@@ -101,46 +118,23 @@ func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||
}
|
||||
}
|
||||
exitData := &util.ValidatorExitData{}
|
||||
err = json.Unmarshal(data, exitData)
|
||||
signedOp := &phase0.SignedVoluntaryExit{}
|
||||
err = json.Unmarshal(data, signedOp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "data is not valid JSON")
|
||||
}
|
||||
|
||||
return exitData, nil
|
||||
}
|
||||
|
||||
// exitVerifyAccount obtains the account for the exitVerify command.
|
||||
func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(exitVerifyPubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", exitVerifyPubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", exitVerifyPubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
return signedOp, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
exitCmd.AddCommand(exitVerifyCmd)
|
||||
exitFlags(exitVerifyCmd)
|
||||
exitVerifyCmd.Flags().String("exit", "", "JSON data, or path to JSON data")
|
||||
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
||||
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
|
||||
}
|
||||
|
||||
func exitVerifyBindings() {
|
||||
if err := viper.BindPFlag("exit", exitVerifyCmd.Flags().Lookup("exit")); err != nil {
|
||||
func exitVerifyBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("signed-operation", cmd.Flags().Lookup("signed-operation")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(nodeCmd)
|
||||
}
|
||||
|
||||
func nodeFlags(cmd *cobra.Command) {
|
||||
func nodeFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -50,7 +50,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.topics = viper.GetStringSlice("topics")
|
||||
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionBad",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@ import (
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
// defaultBeaconNode is used if no other connection is supplied.
|
||||
var defaultBeaconNode = "http://mainnet-consensus.attestant.io/"
|
||||
|
||||
var nodeInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a node",
|
||||
@@ -38,33 +35,19 @@ In quiet mode this will return 0 if the node information can be obtained, otherw
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
if viper.GetString("connection") != "" {
|
||||
// The user provided a connection, so don't second-guess them by using a different node.
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return
|
||||
}
|
||||
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 2 beacon node")
|
||||
|
||||
// The user did not provide a connection, so attempt to use the default node.
|
||||
if viper.GetBool("debug") {
|
||||
fmt.Fprintf(os.Stderr, "No node connection, attempting to use %s\n", defaultBeaconNode)
|
||||
}
|
||||
eth2Client, err = util.ConnectToBeaconNode(ctx, defaultBeaconNode, viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return
|
||||
}
|
||||
if !viper.GetBool("quiet") {
|
||||
fmt.Fprintf(os.Stderr, "No connection supplied; using mainnet public access endpoint\n")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(proposerCmd)
|
||||
}
|
||||
|
||||
func proposerFlags(cmd *cobra.Command) {
|
||||
func proposerFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ type results struct {
|
||||
Duties []*apiv1.ProposerDuty `json:"duties"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -46,7 +46,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
@@ -48,14 +48,10 @@ func init() {
|
||||
proposerCmd.AddCommand(proposerDutiesCmd)
|
||||
proposerFlags(proposerDutiesCmd)
|
||||
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
|
||||
proposerDutiesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func proposerDutiesBindings() {
|
||||
if err := viper.BindPFlag("epoch", proposerDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", proposerDutiesCmd.Flags().Lookup("json")); err != nil {
|
||||
func proposerDutiesBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
145
cmd/root.go
145
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,22 +33,56 @@ 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,
|
||||
}
|
||||
|
||||
func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
// 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
|
||||
return nil
|
||||
@@ -63,11 +97,14 @@ func persistentPreRunE(cmd *cobra.Command, args []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, args []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/eth1votes":
|
||||
chainEth1VotesBindings()
|
||||
case "chain/info":
|
||||
chainInfoBindings()
|
||||
case "chain/queues":
|
||||
chainQueuesBindings()
|
||||
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)
|
||||
@@ -242,6 +211,10 @@ func init() {
|
||||
if err := viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().Bool("json", false, "generate JSON output where available")
|
||||
if err := viper.BindPFlag("json", RootCmd.PersistentFlags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().Bool("debug", false, "generate debug output")
|
||||
if err := viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug")); 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)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(slotCmd)
|
||||
}
|
||||
|
||||
func slotFlags(cmd *cobra.Command) {
|
||||
func slotFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -52,7 +52,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -67,14 +67,6 @@ func TestInput(t *testing.T) {
|
||||
},
|
||||
err: "slot is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"slot": "1",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -29,7 +29,7 @@ type dataOut struct {
|
||||
endTime time.Time
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(synccommitteeCmd)
|
||||
}
|
||||
|
||||
func synccommitteeFlags(cmd *cobra.Command) {
|
||||
func synccommitteeFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
eth2client "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"
|
||||
@@ -34,22 +35,21 @@ type command struct {
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Input.
|
||||
account string
|
||||
pubKey string
|
||||
index string
|
||||
epoch int64
|
||||
validator string
|
||||
epochStr string
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
|
||||
// Output.
|
||||
epoch phase0.Epoch
|
||||
inCommittee bool
|
||||
committeeIndex uint64
|
||||
inclusions []int
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
@@ -67,15 +67,13 @@ func newCommand(ctx context.Context) (*command, error) {
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
// Validator.
|
||||
c.account = viper.GetString("account")
|
||||
c.pubKey = viper.GetString("pubkey")
|
||||
c.index = viper.GetString("index")
|
||||
if c.account == "" && c.pubKey == "" && c.index == "" {
|
||||
return nil, errors.New("account, pubkey or index required")
|
||||
c.validator = viper.GetString("validator")
|
||||
if c.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
|
||||
// Epoch.
|
||||
c.epoch = viper.GetInt64("epoch")
|
||||
c.epochStr = viper.GetString("epoch")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ func TestInput(t *testing.T) {
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
err: "account, pubkey or index required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"validators": "1",
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
func (c *command) output(_ context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"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"
|
||||
@@ -32,14 +31,17 @@ func (c *command) process(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
firstSlot, lastSlot := c.calculateSlots(ctx)
|
||||
|
||||
validatorIndex, err := util.ValidatorIndex(ctx, c.eth2Client, c.account, c.pubKey, c.index)
|
||||
validator, err := util.ParseValidator(ctx, c.eth2Client.(eth2client.ValidatorsProvider), c.validator, "head")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", phase0.Epoch(c.epoch))
|
||||
c.epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epochStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", c.epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain sync committee information")
|
||||
}
|
||||
@@ -49,7 +51,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for i := range syncCommittee.Validators {
|
||||
if syncCommittee.Validators[i] == validatorIndex {
|
||||
if syncCommittee.Validators[i] == validator.Index {
|
||||
c.inCommittee = true
|
||||
c.committeeIndex = uint64(i)
|
||||
break
|
||||
@@ -57,6 +59,8 @@ func (c *command) process(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if c.inCommittee {
|
||||
firstSlot := c.chainTime.FirstSlotOfEpoch(c.epoch)
|
||||
lastSlot := c.chainTime.LastSlotOfEpoch(c.epoch)
|
||||
// This validator is in the sync committee. Check blocks to see where it has been included.
|
||||
c.inclusions = make([]int, 0)
|
||||
if lastSlot > c.chainTime.CurrentSlot() {
|
||||
@@ -107,7 +111,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -122,15 +131,3 @@ func (c *command) setup(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) calculateSlots(ctx context.Context) (phase0.Slot, phase0.Slot) {
|
||||
var firstSlot phase0.Slot
|
||||
var lastSlot phase0.Slot
|
||||
if c.epoch == -1 {
|
||||
c.epoch = int64(c.chainTime.CurrentEpoch()) - 1
|
||||
}
|
||||
firstSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch))
|
||||
lastSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch) + 1)
|
||||
|
||||
return firstSlot, lastSlot
|
||||
}
|
||||
|
||||
@@ -32,28 +32,20 @@ func TestProcess(t *testing.T) {
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "MissingConnection",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "InvalidConnection",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"connection": "invalid",
|
||||
},
|
||||
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid: no such host",
|
||||
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid on 127.0.0.53:53: no such host",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"epoch": "-1",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ type dataIn struct {
|
||||
// Operation.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
epoch int64
|
||||
epoch string
|
||||
period string
|
||||
}
|
||||
|
||||
@@ -51,7 +51,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,7 +71,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
}
|
||||
|
||||
// Epoch
|
||||
data.epoch = viper.GetInt64("epoch")
|
||||
data.epoch = viper.GetString("epoch")
|
||||
data.period = viper.GetString("period")
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -60,13 +60,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionInvalid",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -31,7 +31,7 @@ type dataOut struct {
|
||||
validators []phase0.ValidatorIndex
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
@@ -54,8 +55,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
|
||||
func calculateEpoch(ctx context.Context, data *dataIn) (phase0.Epoch, error) {
|
||||
var epoch phase0.Epoch
|
||||
if data.epoch != -1 {
|
||||
epoch = phase0.Epoch(data.epoch)
|
||||
var err error
|
||||
if data.epoch != "" {
|
||||
epoch, err = util.ParseEpoch(ctx, data.chainTime, data.epoch)
|
||||
} else {
|
||||
switch strings.ToLower(data.period) {
|
||||
case "", "current":
|
||||
@@ -68,6 +70,9 @@ func calculateEpoch(ctx context.Context, data *dataIn) (phase0.Epoch, error) {
|
||||
return 0, fmt.Errorf("period %s not known", data.period)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if data.debug {
|
||||
fmt.Printf("epoch is %d\n", epoch)
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestProcess(t *testing.T) {
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
epoch: -1,
|
||||
epoch: "-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,19 +49,15 @@ epoch can be a specific epoch; If not supplied all slots for the current sync co
|
||||
func init() {
|
||||
synccommitteeCmd.AddCommand(synccommitteeInclusionCmd)
|
||||
synccommitteeFlags(synccommitteeInclusionCmd)
|
||||
synccommitteeInclusionCmd.Flags().Int64("epoch", -1, "the epoch for which to fetch sync committee inclusion")
|
||||
synccommitteeInclusionCmd.Flags().String("pubkey", "", "validator public key for sync committee")
|
||||
synccommitteeInclusionCmd.Flags().String("index", "", "validator index for sync committee")
|
||||
synccommitteeInclusionCmd.Flags().String("epoch", "", "the epoch for which to fetch sync committee inclusion")
|
||||
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("pubkey", synccommitteeInclusionCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("index", synccommitteeInclusionCmd.Flags().Lookup("index")); 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func init() {
|
||||
RootCmd.AddCommand(validatorCmd)
|
||||
}
|
||||
|
||||
func validatorFlags(cmd *cobra.Command) {
|
||||
func validatorFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
func validatorBindings() {
|
||||
|
||||
@@ -44,7 +44,7 @@ type command struct {
|
||||
validatorInfo *apiv1.Validator
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -43,24 +43,14 @@ func TestInput(t *testing.T) {
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
err: "one of account, index or pubkey required",
|
||||
},
|
||||
{
|
||||
name: "MultipleValidatorInfo",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"index": "1",
|
||||
"pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
err: "only one of account, index and pubkey allowed",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user