diff --git a/CHANGELOG.md b/CHANGELOG.md index e4011f0..a227e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -dev: +1.16.0: - add sync committee information to "chain time" - add details of vote success to "attester inclusion --verbose" + - add "synccommittee inclusion" 1.15.1: - provide sync committee slots in "chain status" diff --git a/cmd/root.go b/cmd/root.go index ab7e3f6..25f8a58 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -101,6 +101,8 @@ func includeCommandBindings(cmd *cobra.Command) { nodeEventsBindings() case "slot/time": slotTimeBindings() + case "synccommittee/inclusion": + synccommitteeInclusionBindings() case "synccommittee/members": synccommitteeMembersBindings() case "validator/depositdata": diff --git a/cmd/synccommittee/inclusion/command.go b/cmd/synccommittee/inclusion/command.go new file mode 100644 index 0000000..2c2cac6 --- /dev/null +++ b/cmd/synccommittee/inclusion/command.go @@ -0,0 +1,83 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + "time" + + eth2client "github.com/attestantio/go-eth2-client" + "github.com/pkg/errors" + "github.com/spf13/viper" + "github.com/wealdtech/ethdo/services/chaintime" +) + +type command struct { + quiet bool + verbose bool + debug bool + + // Beacon node connection. + timeout time.Duration + connection string + allowInsecureConnections bool + + // Input. + account string + pubKey string + index string + epoch int64 + + // Data access. + eth2Client eth2client.Service + chainTime chaintime.Service + + // Output. + inCommittee bool + committeeIndex uint64 + inclusions []int +} + +func newCommand(ctx context.Context) (*command, error) { + c := &command{ + quiet: viper.GetBool("quiet"), + verbose: viper.GetBool("verbose"), + debug: viper.GetBool("debug"), + } + + // Timeout. + if viper.GetDuration("timeout") == 0 { + return nil, errors.New("timeout is required") + } + c.timeout = viper.GetDuration("timeout") + + if viper.GetString("connection") == "" { + return nil, errors.New("connection is required") + } + c.connection = viper.GetString("connection") + 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") + } + + // Epoch. + c.epoch = viper.GetInt64("epoch") + + return c, nil +} diff --git a/cmd/synccommittee/inclusion/command_internal_test.go b/cmd/synccommittee/inclusion/command_internal_test.go new file mode 100644 index 0000000..bffa667 --- /dev/null +++ b/cmd/synccommittee/inclusion/command_internal_test.go @@ -0,0 +1,82 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + "os" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestInput(t *testing.T) { + if os.Getenv("ETHDO_TEST_CONNECTION") == "" { + t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests") + } + + tests := []struct { + name string + vars map[string]interface{} + err string + }{ + { + name: "TimeoutMissing", + vars: map[string]interface{}{}, + err: "timeout is required", + }, + { + name: "ConnectionMissing", + vars: map[string]interface{}{ + "validators": "1", + "timeout": "5s", + }, + err: "connection is required", + }, + { + name: "NoValidator", + vars: map[string]interface{}{ + "timeout": "5s", + "connection": os.Getenv("ETHDO_TEST_CONNECTION"), + }, + err: "account, pubkey or index required", + }, + { + name: "Good", + vars: map[string]interface{}{ + "validators": "1", + "timeout": "5s", + "index": "1", + "connection": os.Getenv("ETHDO_TEST_CONNECTION"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + viper.Reset() + + for k, v := range test.vars { + viper.Set(k, v) + } + _, err := newCommand(context.Background()) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/cmd/synccommittee/inclusion/output.go b/cmd/synccommittee/inclusion/output.go new file mode 100644 index 0000000..1fac4eb --- /dev/null +++ b/cmd/synccommittee/inclusion/output.go @@ -0,0 +1,80 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + "fmt" + "strings" +) + +func (c *command) output(ctx context.Context) (string, error) { + if c.quiet { + return "", nil + } + + builder := strings.Builder{} + + builder.WriteString("Epoch: ") + builder.WriteString(fmt.Sprintf("%d", c.epoch)) + + if !c.inCommittee { + builder.WriteString("\nValidator not in sync committee\n") + } else { + if c.verbose { + builder.WriteString("Validator sync committee index ") + builder.WriteString(fmt.Sprintf("%d", c.committeeIndex)) + } + builder.WriteString("\n") + + noBlock := 0 + included := 0 + missed := 0 + for _, inclusion := range c.inclusions { + switch inclusion { + case 0: + noBlock++ + case 1: + included++ + case 2: + missed++ + } + } + builder.WriteString("Expected: ") + builder.WriteString(fmt.Sprintf("%d", len(c.inclusions))) + builder.WriteString("\nIncluded: ") + builder.WriteString(fmt.Sprintf("%d", included)) + builder.WriteString("\nMissed: ") + builder.WriteString(fmt.Sprintf("%d", missed)) + builder.WriteString("\nNo block: ") + builder.WriteString(fmt.Sprintf("%d", noBlock)) + + builder.WriteString("\nPer-slot result: ") + for i, inclusion := range c.inclusions { + switch inclusion { + case 0: + builder.WriteString("-") + case 1: + builder.WriteString("✓") + case 2: + builder.WriteString("✕") + } + if i%8 == 7 && i != len(c.inclusions)-1 { + builder.WriteString(" ") + } + } + } + + return builder.String(), nil +} diff --git a/cmd/synccommittee/inclusion/process.go b/cmd/synccommittee/inclusion/process.go new file mode 100644 index 0000000..56cb3c0 --- /dev/null +++ b/cmd/synccommittee/inclusion/process.go @@ -0,0 +1,178 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + "encoding/hex" + "fmt" + "strconv" + "strings" + + 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" + e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +func (c *command) process(ctx context.Context) error { + // Obtain information we need to process. + if err := c.setup(ctx); err != nil { + return err + } + + firstSlot, lastSlot := c.calculateSlots(ctx) + + validatorIndex, err := c.validatorIndex(ctx) + if err != nil { + return err + } + + syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", phase0.Epoch(c.epoch)) + if err != nil { + return errors.Wrap(err, "failed to obtain sync committee information") + } + + if syncCommittee == nil { + return errors.New("no sync committee returned") + } + + for i := range syncCommittee.Validators { + if syncCommittee.Validators[i] == validatorIndex { + c.inCommittee = true + c.committeeIndex = uint64(i) + break + } + } + + if c.inCommittee { + // 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() { + lastSlot = c.chainTime.CurrentSlot() + } + for slot := firstSlot; slot < lastSlot; slot++ { + block, err := c.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot)) + if err != nil { + return err + } + if block == nil { + c.inclusions = append(c.inclusions, 0) + continue + } + var aggregate *altair.SyncAggregate + switch block.Version { + case spec.DataVersionAltair: + aggregate = block.Altair.Message.Body.SyncAggregate + if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) { + c.inclusions = append(c.inclusions, 1) + } else { + c.inclusions = append(c.inclusions, 2) + } + default: + return fmt.Errorf("unhandled block version %v", block.Version) + } + } + } + + return nil +} + +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) + if err != nil { + return err + } + + c.chainTime, err = standardchaintime.New(ctx, + standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)), + standardchaintime.WithForkScheduleProvider(c.eth2Client.(eth2client.ForkScheduleProvider)), + standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)), + ) + if err != nil { + return errors.Wrap(err, "failed to set up chaintime service") + } + + return nil +} + +// validatorIndex obtains the index of a validator. +func (c *command) validatorIndex(ctx context.Context) (phase0.ValidatorIndex, error) { + switch { + case c.account != "": + ctx, cancel := context.WithTimeout(context.Background(), c.timeout) + defer cancel() + _, account, err := util.WalletAndAccountFromPath(ctx, c.account) + if err != nil { + return 0, errors.Wrap(err, "failed to obtain account") + } + return accountToIndex(ctx, account, c.eth2Client) + case c.pubKey != "": + pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(c.pubKey, "0x")) + if err != nil { + return 0, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", c.pubKey)) + } + account, err := util.NewScratchAccount(nil, pubKeyBytes) + if err != nil { + return 0, errors.Wrap(err, fmt.Sprintf("invalid public key %s", c.pubKey)) + } + return accountToIndex(ctx, account, c.eth2Client) + case c.index != "": + val, err := strconv.ParseUint(c.index, 10, 64) + if err != nil { + return 0, err + } + return phase0.ValidatorIndex(val), nil + default: + return 0, errors.New("no validator") + } +} + +func accountToIndex(ctx context.Context, account e2wtypes.Account, eth2Client eth2client.Service) (phase0.ValidatorIndex, error) { + pubKey, err := util.BestPublicKey(account) + if err != nil { + return 0, err + } + + pubKeys := make([]phase0.BLSPubKey, 1) + copy(pubKeys[0][:], pubKey.Marshal()) + validators, err := eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", pubKeys) + if err != nil { + return 0, err + } + + for index := range validators { + return index, nil + } + return 0, errors.New("validator not found") +} + +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 +} diff --git a/cmd/synccommittee/inclusion/process_internal_test.go b/cmd/synccommittee/inclusion/process_internal_test.go new file mode 100644 index 0000000..79e1749 --- /dev/null +++ b/cmd/synccommittee/inclusion/process_internal_test.go @@ -0,0 +1,72 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + "os" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +func TestProcess(t *testing.T) { + if os.Getenv("ETHDO_TEST_CONNECTION") == "" { + t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests") + } + + tests := []struct { + name string + vars map[string]interface{} + err string + }{ + { + name: "InvalidConnection", + vars: map[string]interface{}{ + "timeout": "5s", + "index": "1", + "connection": "invalid", + }, + err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method", + }, + { + name: "Good", + vars: map[string]interface{}{ + "timeout": "5s", + "index": "1", + "epoch": "-1", + "connection": os.Getenv("ETHDO_TEST_CONNECTION"), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + viper.Reset() + + for k, v := range test.vars { + viper.Set(k, v) + } + cmd, err := newCommand(context.Background()) + require.NoError(t, err) + err = cmd.process(context.Background()) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/cmd/synccommittee/inclusion/run.go b/cmd/synccommittee/inclusion/run.go new file mode 100644 index 0000000..29211d2 --- /dev/null +++ b/cmd/synccommittee/inclusion/run.go @@ -0,0 +1,50 @@ +// Copyright © 2022 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 inclusion + +import ( + "context" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// Run runs the command. +func Run(cmd *cobra.Command) (string, error) { + ctx := context.Background() + + c, err := newCommand(ctx) + if err != nil { + return "", errors.Wrap(err, "failed to set up command") + } + + // Further errors do not need a usage report. + cmd.SilenceUsage = true + + if err := c.process(ctx); err != nil { + return "", errors.Wrap(err, "failed to process") + } + + if viper.GetBool("quiet") { + return "", nil + } + + results, err := c.output(ctx) + if err != nil { + return "", errors.Wrap(err, "failed to obtain output") + } + + return results, nil +} diff --git a/cmd/synccommitteeinclusion.go b/cmd/synccommitteeinclusion.go new file mode 100644 index 0000000..7547969 --- /dev/null +++ b/cmd/synccommitteeinclusion.go @@ -0,0 +1,67 @@ +// Copyright © 2022 Weald Technology Trading. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + synccommitteeinclusion "github.com/wealdtech/ethdo/cmd/synccommittee/inclusion" +) + +var synccommitteeInclusionCmd = &cobra.Command{ + Use: "inclusion", + Short: "Obtain sync committee inclusion data for a validator", + Long: `Obtain sync committee inclusion data for a validator. For example: + + ethdo synccommittee inclusion --epoch=12345 --index=11111 + +In quiet mode this will return 0 if the validator was in the sync committee, otherwise 1. + +epoch can be a specific epoch; If not supplied all slots for the current sync committee period will be provided`, + RunE: func(cmd *cobra.Command, args []string) error { + res, err := synccommitteeinclusion.Run(cmd) + if err != nil { + return err + } + if viper.GetBool("quiet") { + return nil + } + if res != "" { + fmt.Println(res) + } + return nil + }, +} + +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") +} + +func synccommitteeInclusionBindings() { + if err := viper.BindPFlag("epoch", synccommitteeInclusionCmd.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 { + panic(err) + } +} diff --git a/cmd/version.go b/cmd/version.go index 2d8475b..7a0be8e 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,4 +1,4 @@ -// Copyright © 2019 - 2021 Weald Technology Trading. +// Copyright © 2019 - 2022 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 @@ -24,7 +24,7 @@ import ( // ReleaseVersion is the release version of the codebase. // Usually overridden by tag names when building binaries. -var ReleaseVersion = "local build (latest release 1.15.1)" +var ReleaseVersion = "local build (latest release 1.16.0)" // versionCmd represents the version command var versionCmd = &cobra.Command{ diff --git a/docs/usage.md b/docs/usage.md index 414f1b7..e15c157 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -451,6 +451,40 @@ $ ethdo slot time --slot=5 2020-12-01 12:01:23 +0000 GMT ``` +### `synccommittee` commands + +Sync committee commands focus on information about sync committees. + +#### `inclusion` + +`ethdo synccommittee inclusion` provides information about the inclusion, or not, of a validator's sync committee messages. Options include: + - `account` the account of the validator for which to print sync committee contributions + - `index` the index of the validator for which to print sync committee contributions + - `pubkey` the public key of the validator for which to print sync committee contributions + - `epoch` the specific epoch for which to print sync committee contributions. Defaults to the last complete epoch + + +```sh +$ ethdo synccommittee inclusion --index=274946 --epoch=91592 +Epoch: 91593 +Expected: 32 +Included: 30 +Missed: 1 +No block: 1 +Per-slot result: ✓✓✓✓✓✓✓✓ ✓✓✕✓✓✓✓✓ ✓✓✓✓-✓✓✓ ✓✓✓✓✓✓✓✓ +``` + +#### `members` + +`ethdo synccommittee members` provides information about the members of a sync committee. Options include: + - `epoch` the specific epoch for which to provide sync committee members. + - `period` the period for which to provide sync committee members. Can be 'current' or 'next'; dfeaults to 'current' + +```sh +$ ethdo synccommittee members +138334,116317,231736,65706,60046,148162,274946,34724,18051,122841,269578,121110,89733,154887,202118,243459,267543,82793,59504,238929,55360,272874,93917,83116,264342,244312,264907,79193,15443,27997,127175,140965,64416,66399,173906,268885,67779,48139,215005,191435,107954,225228,148630,169357,61091,223319,40668,184307,95903,81179,237461,41723,119710,243333,248243,42757,228686,252749,17546,231625,132030,15934,108465,104302,93026,191946,63738,80996,90679,227542,75463,64581,242030,5429,61623,157314,145363,224733,232492,45357,80674,198583,221422,48665,154803,128608,172512,261074,102835,129935,255726,40846,218932,139874,194575,17346,171565,76413,237859,103170,95661,83018,73902,246680,35795,257792,23836,136624,45745,190990,124229,37281,23818,233435,253903,37502,8669,31151,267179,27954,181019,145719,112270,1899,184844,175014,121769,41717,218760,44813,255860,64865,31985,231664,134296,88114,185542,27557,1698,62470,79182,184325,80380,8865,218456,178979,243886,9466,221389,131476,160857,62916,195389,160182,99293,100263,242371,144594,227527,275978,65714,74350,60121,46642,219334,157142,99379,203508,84367,251808,276456,92563,199831,215312,193875,129690,104234,44290,227725,194780,163061,162328,176517,278620,137355,212826,131615,125734,151873,18977,147927,272759,160537,210675,180411,24203,37266,247527,128678,270287,90352,23043,169645,5304,183412,237387,79751,37635,275139,95857,185990,235565,49425,255836,254314,77582,104172,168556,143653,64173,64504,130363,216602,218107,181130,191845,56454,2040,270365,161952,222409,45097,51611,219190,154903,162311,257460,106337,110775,42928,275709,202352,54724,272295,274470,35220,19694,10347,169585,104938,35121,212982,190582,77999,110201,141519,239881,81263,84314,148883,254649,256309,270013,254179,134009,149660,177127,201926,30533,164789,154343,57437,28958,135169,186415,218514,171355,165247,213526,100044,184264,93278,269329,159634,4092,224671,217236,123946,80703,85444,247742,17959,146473,128231,167559,133899,181532,33378,79060,119785,249443,180469,43692,169679,154421,114047,87877,28337,59072,19807,204598,220293,99461,55272,227923,4503,12580,27044,68955,157373,61321,265034,106833,31534,69137,264783,129588,70433,88338,113528,226211,123003,118982,131549,60350,78896,165715,119736,52639,93274,164295,278837,186453,69910,36768,249533,106205,184057,253232,88155,121377,242589,148236,250065,191526,277249,157463,226527,93000,64784,176880,176380,144301,52061,169803,134291,96648,211716,223000,157911,256737,100938,50434,41075,114894,259888,116872,218201,83617,76348,256832,17113,50270,96468,128448,36987,127511,42397,10154,49234,193346,126352,57719,17029,213127,157942,187829,2353,62462,73637,29053,120324,108515,254684,35982,188131,217092,256206,85802,105907,21204,147562,188961,154541,131147,16000,225112,58362,170375,42239,188309,60280,125472,220119,268946,65736,274053,223569,60454,239552,4401,139357,279634,162711,112016,90295,170641,239770,212067,213770,78311,49057,256295,28666,167207,166783,213148,30689,72118,55912,197733,205116,106169,40570,225057,122079,126423,217781,212897,147499,201774,10616,157826,155954,258431,212151,255318,97138,151907,181491,40236,272993,104430,178068,56089,10067,185066,93669,124108,12785,230215,67995,196282,248285,215370,167715,186183,238147,164161,15068,127990,166146,244578,195912,199812,248435,135597,143024,225304,27045,238140,87008,272550,165234,218128,160038,17697,25332,23446,265921,201045,241106 +``` + ### `validator` commands Validator commands focus on interaction with Ethereum 2 validators.