Compare commits

..

1 Commits

Author SHA1 Message Date
Jim McDonald
bf1d265742 Support 12 and 18-word mnemonics with passphrases.
Only supports single-word (no whitespace) passphrases for 12 and 18 word
mnemonics.

Fixes #87
2023-05-18 23:54:26 +01:00
185 changed files with 1782 additions and 3355 deletions

View File

@@ -1,58 +0,0 @@
name: Docker
on:
push:
jobs:
# Set variables that will be available to all builds.
env_vars:
runs-on: ubuntu-latest
outputs:
release_version: ${{ steps.release_version.outputs.release_version }}
binary: ${{ steps.binary.outputs.binary }}
steps:
- id: release_version
run: |
RELEASE_VERSION=$(echo ${{ github.ref_name }} | sed -e 's/^[vt]//')
echo "release_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
- id: binary
run: |
BINARY=$(basename ${{ github.repository }})
echo "binary=${BINARY}" >> $GITHUB_OUTPUT
# Build.
build:
runs-on: ubuntu-latest
needs: [env_vars]
steps:
- name: Check out repository into the Go module directory
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64/v8
push: true
tags: wealdtech/ethdo:latest
- name: build and push on release
uses: docker/build-push-action@v4
if: ${{ github.event.release.tag_name != '' }}
with:
context: .
platforms: linux/amd64,linux/arm64/v8
push: true
tags: wealdtech/ethdo:${{ github.event.release.tag_name }}

View File

@@ -1,23 +1,23 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- master
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v4
- uses: actions/setup-go@v3
with:
go-version: '^1.21'
- uses: actions/checkout@v4
go-version: '1.20'
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
uses: golangci/golangci-lint-action@v3
with:
only-new-issues: true
args: --timeout=60m

View File

@@ -44,12 +44,12 @@ jobs:
needs: [create_release, env_vars]
steps:
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v3
with:
go-version: '^1.21'
go-version: '^1.20'
- name: Check out repository into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Select correct tag
run: git checkout ${{ github.ref_name }}
@@ -119,12 +119,12 @@ jobs:
needs: [create_release, env_vars]
steps:
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v3
with:
go-version: '^1.21'
go-version: '^1.20'
- name: Check out repository into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Select correct tag
run: git checkout ${{ github.ref_name }}
@@ -164,12 +164,12 @@ jobs:
needs: [create_release, env_vars]
steps:
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v3
with:
go-version: '^1.21'
go-version: '^1.20'
- name: Check out repository into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Select correct tag
run: git checkout ${{ github.ref_name }}

View File

@@ -6,10 +6,10 @@ on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/setup-go@v4
- uses: actions/setup-go@v3
with:
go-version: '^1.21'
- uses: actions/checkout@v4
go-version: '1.20'
- uses: actions/checkout@v3
- uses: n8maninger/action-golang-test@v1

View File

@@ -4,16 +4,6 @@
# This file is not a configuration example,
# it contains the exhaustive configuration with explanations of the options.
issues:
# Which files to exclude: they will be analyzed, but issues from them won't be reported.
# There is no need to include all autogenerated files,
# we confidently recognize autogenerated files.
# If it's not, please let us know.
# "/" will be replaced by current OS file path separator to properly work on Windows.
# Default: []
exclude-files:
- ".*_ssz\\.go$"
# Options for analysis running.
run:
# The default concurrency value is the number of available CPU.
@@ -49,6 +39,15 @@ run:
# Default: true
# skip-dirs-use-default: false
# Which files to skip: they will be analyzed, but issues from them won't be reported.
# Default value is empty list,
# but there is no need to include all autogenerated files,
# we confidently recognize autogenerated files.
# If it's not please let us know.
# "/" will be replaced by current OS file path separator to properly work on Windows.
skip-files:
- ".*_ssz\\.go$"
# If set we pass it to "go list -mod={option}". From "go help modules":
# If invoked with -mod=readonly, the go command is disallowed from the implicit
# automatic updating of go.mod described above. Instead, it fails when any changes
@@ -69,7 +68,7 @@ run:
# Define the Go version limit.
# Mainly related to generics support since go1.18.
# Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18
# go: '1.19'
go: '1.19'
# output configuration options
@@ -129,11 +128,8 @@ linters:
- contextcheck
- cyclop
- deadcode
- depguard
- dupl
- err113
- errorlint
- execinquery
- exhaustive
- exhaustivestruct
- exhaustruct
@@ -145,6 +141,7 @@ linters:
- gochecknoinits
- gocognit
- goconst
- goerr113
- goheader
- golint
- gomnd
@@ -154,14 +151,12 @@ linters:
- lll
- maintidx
- maligned
- mnd
- musttag
- nestif
- nilnil
- nlreturn
- nolintlint
- nosnakecase
- perfsprint
- promlinter
- rowserrcheck
- scopelint

View File

@@ -1,56 +1,8 @@
dev:
- provide better error message on context deadlline exceeded
- update launchpad output to match latest version
- add deposit contract address to "chain info"
1.35.2:
- update dependencies
1.35.1:
- fix output for various commands that may encounter an empty slot
1.35.0:
- support Deneb
- add start and end dates for eth1votes period
1.34.1:
- fix period parsing for "synccommittee members" command
1.34.0:
- update dependencies
- use Capella fork for all exits
- support Deneb beta 5
1.33.2:
- fix windows build
1.33.1:
- add "slot" to "proposer duties" command
- add activation epoch and time to "validator info" command where applicable
- add "holesky" to the list of supported networks
- avoid crash when requesting validators from beacon node without debug enabled
1.33.0:
- show all slots with 'synccommittee inclusion'
- add "wallet batch" command
1.32.0:
- fix incorrect error when "deposit verify" is not given a withdrawal address
- allow truncated mnemonics (first four characters of each word)
- add deneb information to "block info"
- add epoch parameter to "validator yield"
- add proposer index to "block info"
- "block info" honours "--quiet" flag
- "block info" accepts "--block-time" option
- increase default operation timeout from 10s to 30s
- "epoch summary" JSON lists number of blobs
1.31.0:
- initial support for deneb
- add "--generate-keystore" option for "account derive"
- update "validator exit" command to be able to generate multiple exits
- support for 12-word and 18-word mnemonics with single-word (no whitespace) passphrases
- add JSON output for "validator expectation"
1.30.0:
- add "chain spec" command

View File

@@ -1,4 +1,4 @@
FROM golang:1.21-bookworm as builder
FROM golang:1.20-bullseye as builder
WORKDIR /app
@@ -10,7 +10,7 @@ COPY . .
RUN go build
FROM debian:bookworm-slim
FROM debian:bullseye-slim
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt install -y ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists/*

View File

@@ -38,7 +38,7 @@ docker pull wealdtech/ethdo
go install github.com/wealdtech/ethdo@latest
```
Note that `ethdo` requires at least version 1.20 of go to operate. The version of go can be found with `go version`.
Note that `ethdo` requires at least version 1.13 of go to operate. The version of go can be found with `go version`.
If this does not work please see the [troubleshooting](https://github.com/wealdtech/ethdo/blob/master/docs/troubleshooting.md) page.

View File

@@ -18,12 +18,10 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/services/chaintime"
@@ -36,7 +34,6 @@ type ChainInfo struct {
GenesisValidatorsRoot phase0.Root
Epoch phase0.Epoch
GenesisForkVersion phase0.Version
ExitForkVersion phase0.Version
CurrentForkVersion phase0.Version
BLSToExecutionChangeDomainType phase0.DomainType
VoluntaryExitDomainType phase0.DomainType
@@ -48,7 +45,6 @@ type chainInfoJSON struct {
GenesisValidatorsRoot string `json:"genesis_validators_root"`
Epoch string `json:"epoch"`
GenesisForkVersion string `json:"genesis_fork_version"`
ExitForkVersion string `json:"exit_fork_version"`
CurrentForkVersion string `json:"current_fork_version"`
BLSToExecutionChangeDomainType string `json:"bls_to_execution_change_domain_type"`
VoluntaryExitDomainType string `json:"voluntary_exit_domain_type"`
@@ -61,12 +57,11 @@ type chainInfoVersionJSON struct {
// MarshalJSON implements json.Marshaler.
func (c *ChainInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&chainInfoJSON{
Version: strconv.FormatUint(c.Version, 10),
Version: fmt.Sprintf("%d", c.Version),
Validators: c.Validators,
GenesisValidatorsRoot: fmt.Sprintf("%#x", c.GenesisValidatorsRoot),
Epoch: fmt.Sprintf("%d", c.Epoch),
GenesisForkVersion: fmt.Sprintf("%#x", c.GenesisForkVersion),
ExitForkVersion: fmt.Sprintf("%#x", c.ExitForkVersion),
CurrentForkVersion: fmt.Sprintf("%#x", c.CurrentForkVersion),
BLSToExecutionChangeDomainType: fmt.Sprintf("%#x", c.BLSToExecutionChangeDomainType),
VoluntaryExitDomainType: fmt.Sprintf("%#x", c.VoluntaryExitDomainType),
@@ -87,7 +82,7 @@ func (c *ChainInfo) UnmarshalJSON(input []byte) error {
if err != nil {
return errors.Wrap(err, "version invalid")
}
if version < 3 {
if version < 2 {
return errors.New("outdated version; please regenerate your offline data")
}
c.Version = version
@@ -135,18 +130,6 @@ func (c *ChainInfo) UnmarshalJSON(input []byte) error {
}
copy(c.GenesisForkVersion[:], genesisForkVersionBytes)
if data.ExitForkVersion == "" {
return errors.New("exit fork version missing")
}
exitForkVersionBytes, err := hex.DecodeString(strings.TrimPrefix(data.ExitForkVersion, "0x"))
if err != nil {
return errors.Wrap(err, "exit fork version invalid")
}
if len(exitForkVersionBytes) != phase0.ForkVersionLength {
return errors.New("exit fork version incorrect length")
}
copy(c.ExitForkVersion[:], exitForkVersionBytes)
if data.CurrentForkVersion == "" {
return errors.New("current fork version missing")
}
@@ -252,18 +235,18 @@ func ObtainChainInfoFromNode(ctx context.Context,
error,
) {
res := &ChainInfo{
Version: 3,
Version: 2,
Validators: make([]*ValidatorInfo, 0),
Epoch: chainTime.CurrentEpoch(),
}
// Obtain validators.
validatorsResponse, err := consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{State: "head"})
validators, err := consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, "head", nil)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validators")
}
for _, validator := range validatorsResponse.Data {
for _, validator := range validators {
res.Validators = append(res.Validators, &ValidatorInfo{
Index: validator.Index,
Pubkey: validator.Validator.PublicKey,
@@ -271,24 +254,20 @@ func ObtainChainInfoFromNode(ctx context.Context,
State: validator.Status,
})
}
// Order validators by index.
sort.Slice(res.Validators, func(i int, j int) bool {
return res.Validators[i].Index < res.Validators[j].Index
})
// Genesis validators root obtained from beacon node.
genesisResponse, err := consensusClient.(consensusclient.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
genesis, err := consensusClient.(consensusclient.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain genesis information")
}
res.GenesisValidatorsRoot = genesisResponse.Data.GenesisValidatorsRoot
res.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot
// Fetch the genesis fork version from the specification.
specResponse, err := consensusClient.(consensusclient.SpecProvider).Spec(ctx, &api.SpecOpts{})
spec, err := consensusClient.(consensusclient.SpecProvider).Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := specResponse.Data["GENESIS_FORK_VERSION"]
tmp, exists := spec["GENESIS_FORK_VERSION"]
if !exists {
return nil, errors.New("genesis fork version not known by chain")
}
@@ -298,34 +277,24 @@ func ObtainChainInfoFromNode(ctx context.Context,
return nil, errors.New("could not obtain GENESIS_FORK_VERSION")
}
// Fetch the exit fork version (Capella) from the specification.
tmp, exists = specResponse.Data["CAPELLA_FORK_VERSION"]
if !exists {
return nil, errors.New("capella fork version not known by chain")
}
res.ExitForkVersion, isForkVersion = tmp.(phase0.Version)
if !isForkVersion {
return nil, errors.New("could not obtain CAPELLA_FORK_VERSION")
}
// Fetch the current fork version from the fork schedule.
forkScheduleResponse, err := consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx, &api.ForkScheduleOpts{})
forkSchedule, err := consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain fork schedule")
}
for i := range forkScheduleResponse.Data {
if forkScheduleResponse.Data[i].Epoch <= res.Epoch {
res.CurrentForkVersion = forkScheduleResponse.Data[i].CurrentVersion
for i := range forkSchedule {
if forkSchedule[i].Epoch <= res.Epoch {
res.CurrentForkVersion = forkSchedule[i].CurrentVersion
}
}
blsToExecutionChangeDomainType, exists := specResponse.Data["DOMAIN_BLS_TO_EXECUTION_CHANGE"].(phase0.DomainType)
blsToExecutionChangeDomainType, exists := spec["DOMAIN_BLS_TO_EXECUTION_CHANGE"].(phase0.DomainType)
if !exists {
return nil, errors.New("failed to obtain DOMAIN_BLS_TO_EXECUTION_CHANGE")
}
copy(res.BLSToExecutionChangeDomainType[:], blsToExecutionChangeDomainType[:])
voluntaryExitDomainType, exists := specResponse.Data["DOMAIN_VOLUNTARY_EXIT"].(phase0.DomainType)
voluntaryExitDomainType, exists := spec["DOMAIN_VOLUNTARY_EXIT"].(phase0.DomainType)
if !exists {
return nil, errors.New("failed to obtain DOMAIN_VOLUNTARY_EXIT")
}

View File

@@ -86,7 +86,7 @@ func processPathed(ctx context.Context, data *dataIn) (*dataOut, error) {
if data.passphrase == "" {
return nil, errors.New("passphrase is required")
}
match, err := regexp.MatchString("^m/[0-9]+/[0-9]+(/[0-9+])+", data.path)
match, err := regexp.Match("^m/[0-9]+/[0-9]+(/[0-9+])+", []byte(data.path))
if err != nil {
return nil, errors.Wrap(err, "unable to match path to regular expression")
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package accountcreate
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if !viper.GetBool("verbose") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -15,7 +15,6 @@ package accountderive
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
@@ -85,7 +84,7 @@ func outputKeystore(_ context.Context, data *dataOut) (string, error) {
}
ks := make(map[string]interface{})
ks["uuid"] = uuid.String()
ks["pubkey"] = hex.EncodeToString(data.key.PublicKey().Marshal())
ks["pubkey"] = fmt.Sprintf("%x", data.key.PublicKey().Marshal())
ks["version"] = 4
ks["path"] = data.path
ks["crypto"] = crypto
@@ -99,5 +98,5 @@ func outputKeystore(_ context.Context, data *dataOut) (string, error) {
if err := os.WriteFile(keystoreFilename, out, 0o600); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", keystoreFilename))
}
return "", nil
return "", errors.New("not implemented")
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2020, 2024 Weald Technology Trading.
// Copyright © 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,9 +15,9 @@ package accountderive
import (
"context"
"errors"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain input"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if dataIn.quiet {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return strings.TrimSuffix(results, "\n"), nil

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package accountimport
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain input"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if !viper.GetBool("verbose") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package accountkey
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var accountCreateCmd = &cobra.Command{
ethdo account create --account="primary/operations" --passphrase="my secret"
In quiet mode this will return 0 if the account is created successfully, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := accountcreate.Run(cmd)
if err != nil {
return err

View File

@@ -29,7 +29,7 @@ var accountDeriveCmd = &cobra.Command{
ethdo account derive --mnemonic="..." --path="m/12381/3600/0/0"
In quiet mode this will return 0 if the inputs can derive an account account, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := accountderive.Run(cmd)
if err != nil {
return err

View File

@@ -24,12 +24,12 @@ import (
var accountImportCmd = &cobra.Command{
Use: "import",
Short: "Import an account",
Long: `Import an account from its private key or keystore. For example:
Long: `Import an account from its private key. For example:
ethdo account import --account="primary/testing" --key="0x..." --passphrase="my secret"
In quiet mode this will return 0 if the account is imported successfully, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := accountimport.Run(cmd)
if err != nil {
return err

View File

@@ -33,7 +33,7 @@ var accountInfoCmd = &cobra.Command{
ethdo account info --account="primary/my funds"
In quiet mode this will return 0 if the account exists, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()

View File

@@ -30,7 +30,7 @@ var accountKeyCmd = &cobra.Command{
ethdo account key --account="Personal wallet/Operations" --passphrase="my account passphrase"
In quiet mode this will return 0 if the key can be obtained, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := accountkey.Run(cmd)
if err != nil {
return err

View File

@@ -29,7 +29,7 @@ var accountLockCmd = &cobra.Command{
ethdo account lock --account="primary/my funds"
In quiet mode this will return 0 if the account is locked, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()

View File

@@ -30,7 +30,7 @@ var accountUnlockCmd = &cobra.Command{
ethdo account unlock --account="primary/my funds" --passphrase="secret"
In quiet mode this will return 0 if the account is unlocked, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()

View File

@@ -61,11 +61,10 @@ func input(ctx context.Context) (*dataIn, error) {
// Ethereum 2 client.
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err
@@ -73,7 +72,7 @@ func input(ctx context.Context) (*dataIn, error) {
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(data.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -17,8 +17,7 @@ import (
"context"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
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"
@@ -50,16 +49,12 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return results, nil
}
func duty(ctx context.Context, eth2Client eth2client.Service, validator *apiv1.Validator, epoch spec.Epoch) (*apiv1.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.
dutiesResponse, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, &api.AttesterDutiesOpts{
Epoch: epoch,
Indices: []spec.ValidatorIndex{validator.Index},
})
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []spec.ValidatorIndex{validator.Index})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain attester duties")
}
duties := dutiesResponse.Data
if len(duties) == 0 {
return nil, errors.New("validator does not have duty for that epoch")

View File

@@ -37,7 +37,7 @@ func TestProcess(t *testing.T) {
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
)
require.NoError(t, err)

View File

@@ -1,4 +1,4 @@
// Copyright © 2021, 2024 Weald Technology Trading.
// Copyright © 2021 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,8 +15,8 @@ package attesterduties
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -58,11 +58,10 @@ func input(ctx context.Context) (*dataIn, error) {
// Ethereum 2 client.
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err
@@ -70,7 +69,7 @@ func input(ctx context.Context) (*dataIn, error) {
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(data.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -16,7 +16,6 @@ package attesterinclusion
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/attestantio/go-eth2-client/spec/phase0"
@@ -50,7 +49,7 @@ func output(_ context.Context, data *dataOut) (string, error) {
buf.WriteString("Attestation included in block ")
buf.WriteString(fmt.Sprintf("%d", data.slot))
buf.WriteString(", index ")
buf.WriteString(strconv.FormatUint(data.attestationIndex, 10))
buf.WriteString(fmt.Sprintf("%d", data.attestationIndex))
if data.verbose {
buf.WriteString("\nInclusion delay: ")
buf.WriteString(fmt.Sprintf("%d", data.inclusionDelay))

View File

@@ -17,11 +17,9 @@ import (
"bytes"
"context"
"fmt"
"net/http"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
@@ -40,7 +38,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(data.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to set up chaintime service")
@@ -63,22 +61,14 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
startSlot := duty.Slot + 1
endSlot := startSlot + 32
for slot := startSlot; slot < endSlot; slot++ {
blockResponse, err := data.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: fmt.Sprintf("%d", slot),
})
signedBlock, err := data.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
// No block for this slot, that's fine.
continue
}
return nil, errors.Wrap(err, "failed to obtain block")
}
block := blockResponse.Data
if block == nil {
if signedBlock == nil {
continue
}
blockSlot, err := block.Slot()
blockSlot, err := signedBlock.Slot()
if err != nil {
return nil, errors.Wrap(err, "failed to obtain block slot")
}
@@ -88,38 +78,28 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
if data.debug {
fmt.Printf("Fetched block for slot %d\n", slot)
}
attestations, err := block.Attestations()
attestations, err := signedBlock.Attestations()
if err != nil {
return nil, errors.Wrap(err, "failed to obtain block attestations")
}
for i, attestation := range attestations {
attestationData, err := attestation.Data()
if err != nil {
return nil, errors.Wrap(err, "failed to obtain attestation data")
}
aggregationBits, err := attestation.AggregationBits()
if err != nil {
return nil, errors.Wrap(err, "failed to obtain attestation aggregation bits")
}
if attestationData.Slot == duty.Slot &&
attestationData.Index == duty.CommitteeIndex &&
aggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
if attestation.Data.Slot == duty.Slot &&
attestation.Data.Index == duty.CommitteeIndex &&
attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
headCorrect := false
targetCorrect := false
if data.verbose {
headCorrect, err = calcHeadCorrect(ctx, data, attestationData)
headCorrect, err = calcHeadCorrect(ctx, data, attestation)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain head correct result")
}
targetCorrect, err = calcTargetCorrect(ctx, data, attestationData)
targetCorrect, err = calcTargetCorrect(ctx, data, attestation)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain target correct result")
}
}
results.found = true
// TODO fix.
// results.attestation = attestation
results.attestation = attestation
results.slot = slot
results.attestationIndex = uint64(i)
results.inclusionDelay = slot - duty.Slot
@@ -138,67 +118,55 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return results, nil
}
func calcHeadCorrect(ctx context.Context, data *dataIn, attestationData *phase0.AttestationData) (bool, error) {
slot := attestationData.Slot
func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *phase0.Attestation) (bool, error) {
slot := attestation.Data.Slot
for {
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
header, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
// No block.
slot--
continue
}
return false, err
}
if !response.Data.Canonical {
if header == nil {
// No block.
slot--
continue
}
if !header.Canonical {
// Not canonical.
slot--
continue
}
return bytes.Equal(response.Data.Root[:], attestationData.BeaconBlockRoot[:]), nil
return bytes.Equal(header.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
}
}
func calcTargetCorrect(ctx context.Context, data *dataIn, attestationData *phase0.AttestationData) (bool, error) {
func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.Attestation) (bool, error) {
// Start with first slot of the target epoch.
slot := data.chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
slot := data.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
for {
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
header, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
// No block.
slot--
continue
}
return false, err
}
if !response.Data.Canonical {
if header == nil {
// No block.
slot--
continue
}
if !header.Canonical {
// Not canonical.
slot--
continue
}
return bytes.Equal(response.Data.Root[:], attestationData.Target.Root[:]), nil
return bytes.Equal(header.Root[:], attestation.Data.Target.Root[:]), nil
}
}
func duty(ctx context.Context, eth2Client eth2client.Service, validator *apiv1.Validator, epoch phase0.Epoch) (*apiv1.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.
dutiesResponse, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, &api.AttesterDutiesOpts{
Epoch: epoch,
Indices: []phase0.ValidatorIndex{validator.Index},
})
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []phase0.ValidatorIndex{validator.Index})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain attester duties")
}
duties := dutiesResponse.Data
if len(duties) == 0 {
return nil, errors.New("validator does not have duty for that epoch")

View File

@@ -37,7 +37,7 @@ func TestProcess(t *testing.T) {
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
)
require.NoError(t, err)

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package attesterinclusion
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var attesterDutiesCmd = &cobra.Command{
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, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := attesterduties.Run(cmd)
if err != nil {
return err

View File

@@ -29,7 +29,7 @@ var attesterInclusionCmd = &cobra.Command{
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, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := attesterinclusion.Run(cmd)
if err != nil {
return err

View File

@@ -34,7 +34,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Operation.
blockID string
@@ -125,7 +124,6 @@ func newCommand(_ context.Context) (*command, error) {
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.customSpecSupport = viper.GetBool("custom-spec")
c.blockID = viper.GetString("blockid")
c.stream = viper.GetBool("stream")

View File

@@ -17,7 +17,6 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
)
@@ -83,26 +82,26 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
for i, attestation := range c.analysis.Attestations {
if c.verbose {
builder.WriteString("Attestation ")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(fmt.Sprintf("%d", i))
builder.WriteString(": ")
builder.WriteString("distance ")
builder.WriteString(strconv.Itoa(attestation.Distance))
builder.WriteString(fmt.Sprintf("%d", attestation.Distance))
builder.WriteString(", ")
if attestation.Duplicate != nil {
builder.WriteString("duplicate of attestation ")
builder.WriteString(strconv.Itoa(attestation.Duplicate.Index))
builder.WriteString(fmt.Sprintf("%d", attestation.Duplicate.Index))
builder.WriteString(" in block ")
builder.WriteString(fmt.Sprintf("%d", attestation.Duplicate.Block))
builder.WriteString("\n")
continue
}
builder.WriteString(strconv.Itoa(attestation.NewVotes))
builder.WriteString(fmt.Sprintf("%d", attestation.NewVotes))
builder.WriteString("/")
builder.WriteString(strconv.Itoa(attestation.Votes))
builder.WriteString(fmt.Sprintf("%d", attestation.Votes))
builder.WriteString("/")
builder.WriteString(strconv.Itoa(attestation.PossibleVotes))
builder.WriteString(fmt.Sprintf("%d", attestation.PossibleVotes))
builder.WriteString(" new/total/possible votes")
if attestation.NewVotes == 0 {
builder.WriteString("\n")
@@ -138,7 +137,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
if c.analysis.SyncCommitee.Contributions > 0 {
if c.verbose {
builder.WriteString("Sync committee contributions: ")
builder.WriteString(strconv.Itoa(c.analysis.SyncCommitee.Contributions))
builder.WriteString(fmt.Sprintf("%d", c.analysis.SyncCommitee.Contributions))
builder.WriteString(" contributions, score ")
builder.WriteString(fmt.Sprintf("%0.3f", c.analysis.SyncCommitee.Score))
builder.WriteString(", value ")

View File

@@ -19,9 +19,7 @@ import (
"fmt"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/electra"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
@@ -35,17 +33,13 @@ func (c *command) process(ctx context.Context) error {
return err
}
blockResponse, err := c.blocksProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: c.blockID,
})
block, err := c.blocksProvider.SignedBeaconBlock(ctx, c.blockID)
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
return errors.New("empty beacon block")
}
return errors.Wrap(err, "failed to obtain beacon block")
}
block := blockResponse.Data
if block == nil {
return errors.New("empty beacon block")
}
slot, err := block.Slot()
if err != nil {
@@ -63,12 +57,8 @@ func (c *command) process(ctx context.Context) error {
// Calculate how many parents we need to fetch.
minSlot := slot
for _, attestation := range attestations {
attestationData, err := attestation.Data()
if err != nil {
continue
}
if attestationData.Slot < minSlot {
minSlot = attestationData.Slot
if attestation.Data.Slot < minSlot {
minSlot = attestation.Data.Slot
}
}
if c.debug {
@@ -107,27 +97,68 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
if c.debug {
fmt.Printf("Processing attestation %d\n", i)
}
var analysis *attestationAnalysis
switch attestation.Version {
case spec.DataVersionPhase0:
analysis, err = c.phase0Analysis(ctx, slot, attestation.Phase0, blockVotes, spec.DataVersionPhase0)
case spec.DataVersionAltair:
analysis, err = c.phase0Analysis(ctx, slot, attestation.Altair, blockVotes, spec.DataVersionAltair)
case spec.DataVersionBellatrix:
analysis, err = c.phase0Analysis(ctx, slot, attestation.Bellatrix, blockVotes, spec.DataVersionBellatrix)
case spec.DataVersionCapella:
analysis, err = c.phase0Analysis(ctx, slot, attestation.Capella, blockVotes, spec.DataVersionCapella)
case spec.DataVersionDeneb:
analysis, err = c.phase0Analysis(ctx, slot, attestation.Deneb, blockVotes, spec.DataVersionDeneb)
case spec.DataVersionElectra:
analysis, err = c.electraAnalysis(ctx, slot, attestation.Electra, blockVotes, spec.DataVersionElectra)
default:
return fmt.Errorf("unknown version %s", attestation.Version)
analysis := &attestationAnalysis{
Head: attestation.Data.BeaconBlockRoot,
Target: attestation.Data.Target.Root,
Distance: int(slot - attestation.Data.Slot),
}
root, err := attestation.HashTreeRoot()
if err != nil {
return err
}
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
analysis.Duplicate = info
} else {
data := attestation.Data
_, exists := blockVotes[data.Slot]
if !exists {
blockVotes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
}
_, exists = blockVotes[data.Slot][data.Index]
if !exists {
blockVotes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
}
// Count new votes.
analysis.PossibleVotes = int(attestation.AggregationBits.Len())
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
if attestation.AggregationBits.BitAt(j) {
analysis.Votes++
if blockVotes[data.Slot][data.Index].BitAt(j) {
// Already attested to in this block; skip.
continue
}
if c.votes[data.Slot][data.Index].BitAt(j) {
// Already attested to in a previous block; skip.
continue
}
analysis.NewVotes++
blockVotes[data.Slot][data.Index].SetBitAt(j, true)
}
}
// Calculate head correct.
var err error
analysis.HeadCorrect, err = c.calcHeadCorrect(ctx, attestation)
if err != nil {
return err
}
// Calculate head timely.
analysis.HeadTimely = attestation.Data.Slot == slot-1
// Calculate source timely.
analysis.SourceTimely = attestation.Data.Slot >= slot-5
// Calculate target correct.
analysis.TargetCorrect, err = c.calcTargetCorrect(ctx, attestation)
if err != nil {
return err
}
// Calculate target timely.
analysis.TargetTimely = attestation.Data.Slot >= slot-32
}
// Calculate score and value.
if analysis.TargetCorrect && analysis.TargetTimely {
@@ -148,194 +179,17 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
return nil
}
func (c *command) phase0Analysis(ctx context.Context,
slot phase0.Slot,
attestation *phase0.Attestation,
blockVotes map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist,
version spec.DataVersion,
) (
*attestationAnalysis,
error,
) {
analysis := &attestationAnalysis{
Head: attestation.Data.BeaconBlockRoot,
Target: attestation.Data.Target.Root,
Distance: int(slot - attestation.Data.Slot),
}
// TODO.
root, err := attestation.HashTreeRoot()
if err != nil {
return nil, err
}
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
analysis.Duplicate = info
} else {
data := attestation.Data
_, exists := blockVotes[data.Slot]
if !exists {
blockVotes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
}
_, exists = blockVotes[data.Slot][data.Index]
if !exists {
blockVotes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
}
// Count new votes.
analysis.PossibleVotes = int(attestation.AggregationBits.Len())
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
if attestation.AggregationBits.BitAt(j) {
analysis.Votes++
if blockVotes[data.Slot][data.Index].BitAt(j) {
// Already attested to in this block; skip.
continue
}
if c.votes[data.Slot][data.Index].BitAt(j) {
// Already attested to in a previous block; skip.
continue
}
analysis.NewVotes++
blockVotes[data.Slot][data.Index].SetBitAt(j, true)
}
}
// Calculate head correct.
analysis.HeadCorrect, err = c.calcHeadCorrect(ctx, attestation)
if err != nil {
return nil, err
}
// Calculate head timely.
analysis.HeadTimely = analysis.HeadCorrect && attestation.Data.Slot == slot-1
// Calculate source timely.
analysis.SourceTimely = attestation.Data.Slot >= slot-5
// Calculate target correct.
analysis.TargetCorrect, err = c.calcTargetCorrect(ctx, attestation)
if err != nil {
return nil, err
}
// Calculate target timely.
if version < spec.DataVersionDeneb {
analysis.TargetTimely = attestation.Data.Slot >= slot-32
} else {
analysis.TargetTimely = true
}
}
return analysis, nil
}
func (c *command) electraAnalysis(ctx context.Context,
slot phase0.Slot,
attestation *electra.Attestation,
blockVotes map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist,
version spec.DataVersion,
) (
*attestationAnalysis,
error,
) {
analysis := &attestationAnalysis{
Head: attestation.Data.BeaconBlockRoot,
Target: attestation.Data.Target.Root,
Distance: int(slot - attestation.Data.Slot),
}
// TODO.
root, err := attestation.HashTreeRoot()
if err != nil {
return nil, err
}
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
analysis.Duplicate = info
} else {
data := attestation.Data
_, exists := blockVotes[data.Slot]
if !exists {
blockVotes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
}
_, exists = blockVotes[data.Slot][data.Index]
if !exists {
blockVotes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
}
// Count new votes.
analysis.PossibleVotes = int(attestation.AggregationBits.Len())
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
if attestation.AggregationBits.BitAt(j) {
analysis.Votes++
if blockVotes[data.Slot][data.Index].BitAt(j) {
// Already attested to in this block; skip.
continue
}
if c.votes[data.Slot][data.Index].BitAt(j) {
// Already attested to in a previous block; skip.
continue
}
analysis.NewVotes++
blockVotes[data.Slot][data.Index].SetBitAt(j, true)
}
}
// Calculate head correct.
var err error
analysis.HeadCorrect, err = c.calcElectraHeadCorrect(ctx, attestation)
if err != nil {
return nil, err
}
// Calculate head timely.
analysis.HeadTimely = analysis.HeadCorrect && attestation.Data.Slot == slot-1
// Calculate source timely.
analysis.SourceTimely = attestation.Data.Slot >= slot-5
// Calculate target correct.
analysis.TargetCorrect, err = c.calcElectraTargetCorrect(ctx, attestation)
if err != nil {
return nil, err
}
// Calculate target timely.
if version < spec.DataVersionDeneb {
analysis.TargetTimely = attestation.Data.Slot >= slot-32
} else {
analysis.TargetTimely = true
}
}
return analysis, nil
}
func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedBeaconBlock, minSlot phase0.Slot) error {
parentRoot, err := block.ParentRoot()
if err != nil {
return err
}
root, err := block.Deneb.HashTreeRoot()
if err != nil {
panic(err)
}
slot, err := block.Slot()
if err != nil {
panic(err)
}
if c.debug {
fmt.Printf("Parent root of %#x@%d is %#x\n", root, slot, parentRoot)
}
// Obtain the parent block.
parentBlockResponse, err := c.blocksProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: fmt.Sprintf("%#x", parentRoot),
})
parentBlock, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%#x", parentRoot))
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
return errors.New("empty beacon block")
}
return err
}
parentBlock := parentBlockResponse.Data
if parentBlock == nil {
return fmt.Errorf("unable to obtain parent block %s", parentBlock)
}
@@ -356,10 +210,10 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
}
func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
// attestations, err := block.Attestations()
// if err != nil {
// return err
// }
attestations, err := block.Attestations()
if err != nil {
return err
}
slot, err := block.Slot()
if err != nil {
return err
@@ -368,40 +222,31 @@ func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSig
fmt.Printf("Processing block %d\n", slot)
}
// TODO reinstate.
// for i, attestation := range attestations {
// root, err := attestation.HashTreeRoot()
// if err != nil {
// return err
// }
// c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationData{
// Block: slot,
// Index: i,
// }
for i, attestation := range attestations {
root, err := attestation.HashTreeRoot()
if err != nil {
return err
}
c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationData{
Block: slot,
Index: i,
}
// data, err := attestation.Data()
// if err != nil {
// return err
// }
// aggregationBits, err := attestation.AggregationBits()
// if err != nil {
// return err
// }
// _, exists := c.votes[data.Slot]
// if !exists {
// c.votes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
// }
// _, exists = c.votes[data.Slot][data.Index]
// if !exists {
// c.votes[data.Slot][data.Index] = bitfield.NewBitlist(aggregationBits.Len())
// }
// for j := uint64(0); j < aggregationBits.Len(); j++ {
// if aggregationBits.BitAt(j) {
// c.votes[data.Slot][data.Index].SetBitAt(j, true)
// }
// }
//}
data := attestation.Data
_, exists := c.votes[data.Slot]
if !exists {
c.votes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
}
_, exists = c.votes[data.Slot][data.Index]
if !exists {
c.votes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
}
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
if attestation.AggregationBits.BitAt(j) {
c.votes[data.Slot][data.Index].SetBitAt(j, true)
}
}
}
return nil
}
@@ -411,11 +256,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -423,7 +267,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")
@@ -445,12 +289,12 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("connection does not provide spec information")
}
specResponse, err := specProvider.Spec(ctx, &api.SpecOpts{})
spec, err := specProvider.Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := specResponse.Data["TIMELY_SOURCE_WEIGHT"]
tmp, exists := spec["TIMELY_SOURCE_WEIGHT"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(14)
@@ -461,7 +305,7 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("TIMELY_SOURCE_WEIGHT of unexpected type")
}
tmp, exists = specResponse.Data["TIMELY_TARGET_WEIGHT"]
tmp, exists = spec["TIMELY_TARGET_WEIGHT"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(26)
@@ -471,7 +315,7 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("TIMELY_TARGET_WEIGHT of unexpected type")
}
tmp, exists = specResponse.Data["TIMELY_HEAD_WEIGHT"]
tmp, exists = spec["TIMELY_HEAD_WEIGHT"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(14)
@@ -481,7 +325,7 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("TIMELY_HEAD_WEIGHT of unexpected type")
}
tmp, exists = specResponse.Data["SYNC_REWARD_WEIGHT"]
tmp, exists = spec["SYNC_REWARD_WEIGHT"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(2)
@@ -491,7 +335,7 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("SYNC_REWARD_WEIGHT of unexpected type")
}
tmp, exists = specResponse.Data["PROPOSER_WEIGHT"]
tmp, exists = spec["PROPOSER_WEIGHT"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(8)
@@ -501,7 +345,7 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("PROPOSER_WEIGHT of unexpected type")
}
tmp, exists = specResponse.Data["WEIGHT_DENOMINATOR"]
tmp, exists = spec["WEIGHT_DENOMINATOR"]
if !exists {
// Set a default value based on the Altair spec.
tmp = uint64(64)
@@ -518,31 +362,22 @@ func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attes
root, exists := c.headRoots[slot]
if !exists {
for {
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
header, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
if c.debug {
fmt.Printf("No block available for slot %d, assuming not in canonical chain", slot)
}
return false, nil
}
return false, err
}
if response.Data == nil {
if header == nil {
// No block.
slot--
continue
}
if !response.Data.Canonical {
if !header.Canonical {
// Not canonical.
slot--
continue
}
c.headRoots[attestation.Data.Slot] = response.Data.Root
root = response.Data.Root
c.headRoots[attestation.Data.Slot] = header.Root
root = header.Root
break
}
}
@@ -556,103 +391,22 @@ func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Att
// Start with first slot of the target epoch.
slot := c.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
for {
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
header, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
if c.debug {
fmt.Printf("No block available for slot %d, assuming not in canonical chain", slot)
}
return false, nil
}
}
if response.Data == nil {
// No block.
slot--
continue
}
if !response.Data.Canonical {
// Not canonical.
slot--
continue
}
c.targetRoots[attestation.Data.Slot] = response.Data.Root
root = response.Data.Root
break
}
}
return bytes.Equal(root[:], attestation.Data.Target.Root[:]), nil
}
func (c *command) calcElectraHeadCorrect(ctx context.Context, attestation *electra.Attestation) (bool, error) {
slot := attestation.Data.Slot
root, exists := c.headRoots[slot]
if !exists {
for {
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
if c.debug {
fmt.Printf("No block available for slot %d, assuming not in canonical chain", slot)
}
return false, nil
}
return false, err
}
if response.Data == nil {
if header == nil {
// No block.
slot--
continue
}
if !response.Data.Canonical {
if !header.Canonical {
// Not canonical.
slot--
continue
}
c.headRoots[attestation.Data.Slot] = response.Data.Root
root = response.Data.Root
break
}
}
return bytes.Equal(root[:], attestation.Data.BeaconBlockRoot[:]), nil
}
func (c *command) calcElectraTargetCorrect(ctx context.Context, attestation *electra.Attestation) (bool, error) {
root, exists := c.targetRoots[attestation.Data.Slot]
if !exists {
// Start with first slot of the target epoch.
slot := c.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
for {
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
Block: fmt.Sprintf("%d", slot),
})
if err != nil {
var apiError *api.Error
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
if c.debug {
fmt.Printf("No block available for slot %d, assuming not in canonical chain", slot)
}
return false, nil
}
}
if response.Data == nil {
// No block.
slot--
continue
}
if !response.Data.Canonical {
// Not canonical.
slot--
continue
}
c.targetRoots[attestation.Data.Slot] = response.Data.Root
root = response.Data.Root
c.targetRoots[attestation.Data.Slot] = header.Root
root = header.Root
break
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package blockanalyze
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2023 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -34,9 +34,8 @@ type dataIn struct {
jsonOutput bool
sszOutput bool
// Chain information.
blockID string
blockTime string
stream bool
blockID string
stream bool
}
func input(ctx context.Context) (*dataIn, error) {
@@ -51,21 +50,26 @@ func input(ctx context.Context) (*dataIn, error) {
data.debug = viper.GetBool("debug")
data.jsonOutput = viper.GetBool("json")
data.sszOutput = viper.GetBool("ssz")
data.blockID = viper.GetString("blockid")
data.blockTime = viper.GetString("block-time")
data.stream = viper.GetBool("stream")
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err
}
if viper.GetString("blockid") == "" {
data.blockID = "head"
} else {
// Specific slot.
data.blockID = viper.GetString("blockid")
}
return data, nil
}

View File

@@ -20,18 +20,15 @@ import (
"fmt"
"math/big"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/attestantio/go-eth2-client/spec/electra"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
@@ -58,7 +55,6 @@ func output(_ context.Context, data *dataOut) (string, error) {
func outputBlockGeneral(_ context.Context,
verbose bool,
slot phase0.Slot,
proposerIndex phase0.ValidatorIndex,
blockRoot phase0.Root,
bodyRoot phase0.Root,
parentRoot phase0.Root,
@@ -74,7 +70,6 @@ func outputBlockGeneral(_ context.Context,
res := strings.Builder{}
res.WriteString(fmt.Sprintf("Slot: %d\n", slot))
res.WriteString(fmt.Sprintf("Proposing validator index: %d\n", proposerIndex))
res.WriteString(fmt.Sprintf("Epoch: %d\n", phase0.Epoch(uint64(slot)/slotsPerEpoch)))
res.WriteString(fmt.Sprintf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(slot)*int64(slotDuration.Seconds()), 0)))
res.WriteString(fmt.Sprintf("Block root: %#x\n", blockRoot))
@@ -119,14 +114,12 @@ func outputBlockAttestations(ctx context.Context, eth2Client eth2client.Service,
// Fetch committees for this epoch if not already obtained.
committees, exists := validatorCommittees[att.Data.Slot]
if !exists {
response, err := beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
State: fmt.Sprintf("%d", att.Data.Slot),
})
beaconCommittees, err := beaconCommitteesProvider.BeaconCommittees(ctx, fmt.Sprintf("%d", att.Data.Slot))
if err != nil {
// Failed to get it; create an empty committee to stop us continually attempting to re-fetch.
validatorCommittees[att.Data.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
} else {
for _, beaconCommittee := range response.Data {
for _, beaconCommittee := range beaconCommittees {
if _, exists := validatorCommittees[beaconCommittee.Slot]; !exists {
validatorCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
}
@@ -171,128 +164,11 @@ func outputBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Ser
res.WriteString(fmt.Sprintf(" %d:\n", i))
res.WriteString(fmt.Sprintln(" Slashed validators:"))
response, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
State: "head",
Indices: slashedIndices,
})
validators, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, "head", slashedIndices)
if err != nil {
return "", errors.Wrap(err, "failed to obtain beacon committees")
}
for k, v := range response.Data {
res.WriteString(fmt.Sprintf(" %#x (%d)\n", v.Validator.PublicKey[:], k))
}
// Say what caused the slashing.
if att1.Data.Target.Epoch == att2.Data.Target.Epoch {
res.WriteString(fmt.Sprintf(" Double voted for same target epoch (%d):\n", att1.Data.Target.Epoch))
if !bytes.Equal(att1.Data.Target.Root[:], att2.Data.Target.Root[:]) {
res.WriteString(fmt.Sprintf(" Attestation 1 target epoch root: %#x\n", att1.Data.Target.Root))
res.WriteString(fmt.Sprintf(" Attestation 2target epoch root: %#x\n", att2.Data.Target.Root))
}
if !bytes.Equal(att1.Data.BeaconBlockRoot[:], att2.Data.BeaconBlockRoot[:]) {
res.WriteString(fmt.Sprintf(" Attestation 1 beacon block root: %#x\n", att1.Data.BeaconBlockRoot))
res.WriteString(fmt.Sprintf(" Attestation 2 beacon block root: %#x\n", att2.Data.BeaconBlockRoot))
}
} else if att1.Data.Source.Epoch < att2.Data.Source.Epoch &&
att1.Data.Target.Epoch > att2.Data.Target.Epoch {
res.WriteString(" Surround voted:\n")
res.WriteString(fmt.Sprintf(" Attestation 1 vote: %d->%d\n", att1.Data.Source.Epoch, att1.Data.Target.Epoch))
res.WriteString(fmt.Sprintf(" Attestation 2 vote: %d->%d\n", att2.Data.Source.Epoch, att2.Data.Target.Epoch))
}
}
}
return res.String(), nil
}
func outputElectraBlockAttestations(ctx context.Context,
eth2Client eth2client.Service,
verbose bool,
attestations []*electra.Attestation,
) (
string,
error,
) {
res := strings.Builder{}
validatorCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
res.WriteString(fmt.Sprintf("Attestations: %d\n", len(attestations)))
if verbose {
beaconCommitteesProvider, isProvider := eth2Client.(eth2client.BeaconCommitteesProvider)
if isProvider {
for i, att := range attestations {
res.WriteString(fmt.Sprintf(" %d:\n", i))
// Fetch committees for this epoch if not already obtained.
committees, exists := validatorCommittees[att.Data.Slot]
if !exists {
response, err := beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
State: fmt.Sprintf("%d", att.Data.Slot),
})
if err != nil {
// Failed to get it; create an empty committee to stop us continually attempting to re-fetch.
validatorCommittees[att.Data.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
} else {
for _, beaconCommittee := range response.Data {
if _, exists := validatorCommittees[beaconCommittee.Slot]; !exists {
validatorCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
}
validatorCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
}
}
committees = validatorCommittees[att.Data.Slot]
}
res.WriteString(fmt.Sprintf(" Committee index: %d\n", att.Data.Index))
res.WriteString(fmt.Sprintf(" Attesters: %d/%d\n", att.AggregationBits.Count(), att.AggregationBits.Len()))
res.WriteString(fmt.Sprintf(" Aggregation bits: %s\n", bitlistToString(att.AggregationBits)))
if _, exists := committees[att.Data.Index]; exists {
res.WriteString(fmt.Sprintf(" Attesting indices: %s\n", attestingIndices(att.AggregationBits, committees[att.Data.Index])))
}
res.WriteString(fmt.Sprintf(" Slot: %d\n", att.Data.Slot))
res.WriteString(fmt.Sprintf(" Beacon block root: %#x\n", att.Data.BeaconBlockRoot))
res.WriteString(fmt.Sprintf(" Source epoch: %d\n", att.Data.Source.Epoch))
res.WriteString(fmt.Sprintf(" Source root: %#x\n", att.Data.Source.Root))
res.WriteString(fmt.Sprintf(" Target epoch: %d\n", att.Data.Target.Epoch))
res.WriteString(fmt.Sprintf(" Target root: %#x\n", att.Data.Target.Root))
}
}
}
return res.String(), nil
}
func outputElectraBlockAttesterSlashings(ctx context.Context,
eth2Client eth2client.Service,
verbose bool,
attesterSlashings []*electra.AttesterSlashing,
) (
string,
error,
) {
res := strings.Builder{}
res.WriteString(fmt.Sprintf("Attester slashings: %d\n", len(attesterSlashings)))
if verbose {
for i, slashing := range attesterSlashings {
// Say what was slashed.
att1 := slashing.Attestation1
att2 := slashing.Attestation2
slashedIndices := intersection(att1.AttestingIndices, att2.AttestingIndices)
if len(slashedIndices) == 0 {
continue
}
res.WriteString(fmt.Sprintf(" %d:\n", i))
res.WriteString(fmt.Sprintln(" Slashed validators:"))
response, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
State: "head",
Indices: slashedIndices,
})
if err != nil {
return "", errors.Wrap(err, "failed to obtain beacon committees")
}
for k, v := range response.Data {
for k, v := range validators {
res.WriteString(fmt.Sprintf(" %#x (%d)\n", v.Validator.PublicKey[:], k))
}
@@ -345,14 +221,11 @@ func outputBlockVoluntaryExits(ctx context.Context, eth2Client eth2client.Servic
if verbose {
for i, voluntaryExit := range voluntaryExits {
res.WriteString(fmt.Sprintf(" %d:\n", i))
response, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
State: "head",
Indices: []phase0.ValidatorIndex{voluntaryExit.Message.ValidatorIndex},
})
validators, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, "head", []phase0.ValidatorIndex{voluntaryExit.Message.ValidatorIndex})
if err != nil {
res.WriteString(fmt.Sprintf(" Error: failed to obtain validators: %v\n", err))
} else {
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", response.Data[voluntaryExit.Message.ValidatorIndex].Validator.PublicKey, voluntaryExit.Message.ValidatorIndex))
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[voluntaryExit.Message.ValidatorIndex].Validator.PublicKey, voluntaryExit.Message.ValidatorIndex))
res.WriteString(fmt.Sprintf(" Epoch: %d\n", voluntaryExit.Message.Epoch))
}
}
@@ -368,14 +241,11 @@ func outputBlockBLSToExecutionChanges(ctx context.Context, eth2Client eth2client
if verbose {
for i, op := range ops {
res.WriteString(fmt.Sprintf(" %d:\n", i))
response, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
State: "head",
Indices: []phase0.ValidatorIndex{op.Message.ValidatorIndex},
})
validators, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, "head", []phase0.ValidatorIndex{op.Message.ValidatorIndex})
if err != nil {
res.WriteString(fmt.Sprintf(" Error: failed to obtain validators: %v\n", err))
} else {
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", response.Data[op.Message.ValidatorIndex].Validator.PublicKey, op.Message.ValidatorIndex))
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[op.Message.ValidatorIndex].Validator.PublicKey, op.Message.ValidatorIndex))
res.WriteString(fmt.Sprintf(" BLS public key: %#x\n", op.Message.FromBLSPubkey))
res.WriteString(fmt.Sprintf(" Execution address: %s\n", op.Message.ToExecutionAddress.String()))
}
@@ -393,9 +263,9 @@ func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service
if verbose {
specProvider, isProvider := eth2Client.(eth2client.SpecProvider)
if isProvider {
specResponse, err := specProvider.Spec(ctx, &api.SpecOpts{})
config, err := specProvider.Spec(ctx)
if err == nil {
slotsPerEpoch := specResponse.Data["SLOTS_PER_EPOCH"].(uint64)
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
res.WriteString(" Contributions: ")
res.WriteString(bitvectorToString(syncAggregate.SyncCommitteeBits))
@@ -403,16 +273,14 @@ func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service
syncCommitteesProvider, isProvider := eth2Client.(eth2client.SyncCommitteesProvider)
if isProvider {
syncCommitteeResponse, err := syncCommitteesProvider.SyncCommittee(ctx, &api.SyncCommitteeOpts{
State: strconv.FormatUint(uint64(epoch)*slotsPerEpoch, 10),
})
syncCommittee, err := syncCommitteesProvider.SyncCommittee(ctx, fmt.Sprintf("%d", uint64(epoch)*slotsPerEpoch))
if err != nil {
res.WriteString(fmt.Sprintf(" Error: failed to obtain sync committee: %v\n", err))
} else {
res.WriteString(" Contributing validators:")
for i := uint64(0); i < syncAggregate.SyncCommitteeBits.Len(); i++ {
if syncAggregate.SyncCommitteeBits.BitAt(i) {
res.WriteString(fmt.Sprintf(" %d", syncCommitteeResponse.Data.Validators[i]))
res.WriteString(fmt.Sprintf(" %d", syncCommittee.Validators[i]))
}
}
res.WriteString("\n")
@@ -447,7 +315,6 @@ func outputCapellaBlockText(ctx context.Context, data *dataOut, signedBlock *cap
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
@@ -522,14 +389,7 @@ func outputCapellaBlockText(ctx context.Context, data *dataOut, signedBlock *cap
return res.String(), nil
}
func outputDenebBlockText(ctx context.Context,
data *dataOut,
signedBlock *deneb.SignedBeaconBlock,
blobs []*deneb.BlobSidecar,
) (
string,
error,
) {
func outputDenebBlockText(ctx context.Context, data *dataOut, signedBlock *deneb.SignedBeaconBlock) (string, error) {
if signedBlock == nil {
return "", errors.New("no block supplied")
}
@@ -551,7 +411,6 @@ func outputDenebBlockText(ctx context.Context,
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
@@ -623,117 +482,7 @@ func outputDenebBlockText(ctx context.Context,
}
res.WriteString(tmp)
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
if err != nil {
return "", err
}
res.WriteString(tmp)
return res.String(), nil
}
func outputElectraBlockText(ctx context.Context,
data *dataOut,
signedBlock *electra.SignedBeaconBlock,
blobs []*deneb.BlobSidecar,
) (
string,
error,
) {
if signedBlock == nil {
return "", errors.New("no block supplied")
}
body := signedBlock.Message.Body
res := strings.Builder{}
// General info.
blockRoot, err := signedBlock.Message.HashTreeRoot()
if err != nil {
return "", errors.Wrap(err, "failed to obtain block root")
}
bodyRoot, err := signedBlock.Message.Body.HashTreeRoot()
if err != nil {
return "", errors.Wrap(err, "failed to generate body root")
}
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
signedBlock.Message.StateRoot,
signedBlock.Message.Body.Graffiti[:],
data.genesisTime,
data.slotDuration,
data.slotsPerEpoch)
if err != nil {
return "", err
}
res.WriteString(tmp)
// Eth1 data.
if data.verbose {
tmp, err := outputBlockETH1Data(ctx, body.ETH1Data)
if err != nil {
return "", err
}
res.WriteString(tmp)
}
// Sync aggregate.
tmp, err = outputBlockSyncAggregate(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.SyncAggregate, phase0.Epoch(uint64(signedBlock.Message.Slot)/data.slotsPerEpoch))
if err != nil {
return "", err
}
res.WriteString(tmp)
// Attestations.
tmp, err = outputElectraBlockAttestations(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.Attestations)
if err != nil {
return "", err
}
res.WriteString(tmp)
// Attester slashings.
tmp, err = outputElectraBlockAttesterSlashings(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.AttesterSlashings)
if err != nil {
return "", err
}
res.WriteString(tmp)
res.WriteString(fmt.Sprintf("Proposer slashings: %d\n", len(body.ProposerSlashings)))
// Add verbose proposer slashings.
tmp, err = outputBlockDeposits(ctx, data.verbose, signedBlock.Message.Body.Deposits)
if err != nil {
return "", err
}
res.WriteString(tmp)
// Voluntary exits.
tmp, err = outputBlockVoluntaryExits(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.VoluntaryExits)
if err != nil {
return "", err
}
res.WriteString(tmp)
tmp, err = outputBlockBLSToExecutionChanges(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.BLSToExecutionChanges)
if err != nil {
return "", err
}
res.WriteString(tmp)
tmp, err = outputElectraBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
if err != nil {
return "", err
}
res.WriteString(tmp)
tmp, err = outputElectraBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body)
if err != nil {
return "", err
}
@@ -764,7 +513,6 @@ func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *b
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
@@ -855,7 +603,6 @@ func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *alta
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
@@ -939,7 +686,6 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
tmp, err := outputBlockGeneral(ctx,
data.verbose,
signedBlock.Message.Slot,
signedBlock.Message.ProposerIndex,
blockRoot,
bodyRoot,
signedBlock.Message.ParentRoot,
@@ -1121,73 +867,9 @@ func outputDenebBlockExecutionPayload(_ context.Context,
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
res.WriteString(" Withdrawals: ")
res.WriteString(fmt.Sprintf("%d\n", len(payload.Withdrawals)))
res.WriteString(" Excess blob gas: ")
res.WriteString(fmt.Sprintf("%d\n", payload.ExcessBlobGas))
}
return res.String(), nil
}
func outputElectraBlockExecutionPayload(_ context.Context,
verbose bool,
payload *electra.ExecutionPayload,
) (
string,
error,
) {
if payload == nil {
return "", nil
}
// If the block number is 0 then we're before the merge.
if payload.BlockNumber == 0 {
return "", nil
}
res := strings.Builder{}
if !verbose {
res.WriteString("Execution block number: ")
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
res.WriteString("Transactions: ")
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
} else {
res.WriteString("Execution payload:\n")
res.WriteString(" Execution block number: ")
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
res.WriteString(" Base fee per gas: ")
res.WriteString(string2eth.WeiToString(payload.BaseFeePerGas.ToBig(), true))
res.WriteString("\n Block hash: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.BlockHash))
res.WriteString(" Parent hash: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash))
res.WriteString(" Fee recipient: ")
res.WriteString(payload.FeeRecipient.String())
res.WriteString(" Gas limit: ")
res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit))
res.WriteString(" Gas used: ")
res.WriteString(fmt.Sprintf("%d\n", payload.GasUsed))
res.WriteString(" Timestamp: ")
res.WriteString(fmt.Sprintf("%s (%d)\n", time.Unix(int64(payload.Timestamp), 0).String(), payload.Timestamp))
res.WriteString(" Prev RANDAO: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.PrevRandao))
res.WriteString(" Receipts root: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.ReceiptsRoot))
res.WriteString(" State root: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.StateRoot))
res.WriteString(" Extra data: ")
if utf8.Valid(payload.ExtraData) {
res.WriteString(fmt.Sprintf("%s\n", string(payload.ExtraData)))
} else {
res.WriteString(fmt.Sprintf("%#x\n", payload.ExtraData))
}
res.WriteString(" Logs bloom: ")
res.WriteString(fmt.Sprintf("%#x\n", payload.LogsBloom))
res.WriteString(" Transactions: ")
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
res.WriteString(" Withdrawals: ")
res.WriteString(fmt.Sprintf("%d\n", len(payload.Withdrawals)))
res.WriteString(" Excess blob gas: ")
res.WriteString(fmt.Sprintf("%d\n", payload.ExcessBlobGas))
res.WriteString(" Excess data gas: ")
res.WriteString(payload.ExcessDataGas.Dec())
res.WriteString("\n")
}
return res.String(), nil
@@ -1196,7 +878,6 @@ func outputElectraBlockExecutionPayload(_ context.Context,
func outputDenebBlobInfo(_ context.Context,
verbose bool,
body *deneb.BeaconBlockBody,
blobs []*deneb.BlobSidecar,
) (
string,
error,
@@ -1205,47 +886,15 @@ func outputDenebBlobInfo(_ context.Context,
return "", nil
}
if !verbose {
return fmt.Sprintf("Blobs: %d\n", len(body.BlobKZGCommitments)), nil
}
res := strings.Builder{}
for i, blob := range blobs {
if i == 0 {
res.WriteString("Blobs\n")
}
res.WriteString(fmt.Sprintf(" Index: %d\n", blob.Index))
res.WriteString(fmt.Sprintf(" KZG commitment: %s\n", body.BlobKZGCommitments[i].String()))
}
return res.String(), nil
}
func outputElectraBlobInfo(_ context.Context,
verbose bool,
body *electra.BeaconBlockBody,
blobs []*deneb.BlobSidecar,
) (
string,
error,
) {
if body == nil {
return "", nil
}
if !verbose {
return fmt.Sprintf("Blobs: %d\n", len(body.BlobKZGCommitments)), nil
}
res := strings.Builder{}
for i, blob := range blobs {
if i == 0 {
res.WriteString("Blobs\n")
res.WriteString(fmt.Sprintf("Blob KZG commitments: %d\n", len(body.BlobKzgCommitments)))
} else if len(body.BlobKzgCommitments) > 0 {
res.WriteString("Blob KZG commitments:\n")
for i := range body.BlobKzgCommitments {
res.WriteString(fmt.Sprintf(" %s\n", body.BlobKzgCommitments[i].String()))
}
res.WriteString(fmt.Sprintf(" Index: %d\n", blob.Index))
res.WriteString(fmt.Sprintf(" KZG commitment: %s\n", body.BlobKZGCommitments[i].String()))
}
return res.String(), nil

View File

@@ -17,24 +17,17 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/attestantio/go-eth2-client/spec/electra"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
)
var (
@@ -47,8 +40,8 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
if data == nil {
return nil, errors.New("no data")
}
if data.blockID == "" && data.blockTime == "" {
return nil, errors.New("no block ID or block time")
if data.blockID == "" {
return nil, errors.New("no block ID")
}
results = &dataOut{
@@ -57,83 +50,44 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
eth2Client: data.eth2Client,
}
specResponse, err := results.eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
config, err := results.eth2Client.(eth2client.SpecProvider).Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect to obtain configuration information")
}
genesisResponse, err := results.eth2Client.(eth2client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
genesis, err := results.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect to obtain genesis information")
}
genesis := genesisResponse.Data
results.genesisTime = genesis.GenesisTime
results.slotDuration = specResponse.Data["SECONDS_PER_SLOT"].(time.Duration)
results.slotsPerEpoch = specResponse.Data["SLOTS_PER_EPOCH"].(uint64)
results.slotDuration = config["SECONDS_PER_SLOT"].(time.Duration)
results.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
if data.blockTime != "" {
data.blockID, err = timeToBlockID(ctx, data.eth2Client, data.blockTime)
if err != nil {
return nil, err
}
}
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: data.blockID,
})
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, data.blockID)
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
if data.quiet {
os.Exit(1)
}
return nil, errors.New("empty beacon block")
}
return nil, errors.Wrap(err, "failed to obtain beacon block")
}
block := blockResponse.Data
if data.quiet {
os.Exit(0)
if signedBlock == nil {
return nil, errors.New("empty beacon block")
}
switch block.Version {
switch signedBlock.Version {
case spec.DataVersionPhase0:
if err := outputPhase0Block(ctx, data.jsonOutput, block.Phase0); err != nil {
if err := outputPhase0Block(ctx, data.jsonOutput, signedBlock.Phase0); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
case spec.DataVersionAltair:
if err := outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, block.Altair); err != nil {
if err := outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Altair); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
case spec.DataVersionBellatrix:
if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, block.Bellatrix); err != nil {
if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Bellatrix); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
case spec.DataVersionCapella:
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, block.Capella); err != nil {
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Capella); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
case spec.DataVersionDeneb:
blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
Block: data.blockID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain blob sidecars")
}
blobSidecars := blobSidecarsResponse.Data
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, block.Deneb, blobSidecars); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
case spec.DataVersionElectra:
blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
Block: data.blockID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain blob sidecars")
}
blobSidecars := blobSidecarsResponse.Data
if err := outputElectraBlock(ctx, data.jsonOutput, data.sszOutput, block.Electra, blobSidecars); err != nil {
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Deneb); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
default:
@@ -156,57 +110,38 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return &dataOut{}, nil
}
func headEventHandler(event *apiv1.Event) {
ctx := context.Background()
func headEventHandler(event *api.Event) {
// Only interested in head events.
if event.Topic != "head" {
return
}
blockID := fmt.Sprintf("%#x", event.Data.(*apiv1.HeadEvent).Block[:])
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: blockID,
})
blockID := fmt.Sprintf("%#x", event.Data.(*api.HeadEvent).Block[:])
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(context.Background(), blockID)
if err != nil {
if !jsonOutput && !sszOutput {
fmt.Printf("Failed to obtain block: %v\n", err)
}
return
}
block := blockResponse.Data
if block == nil {
if signedBlock == nil {
if !jsonOutput && !sszOutput {
fmt.Println("Empty beacon block")
}
return
}
switch block.Version {
switch signedBlock.Version {
case spec.DataVersionPhase0:
err = outputPhase0Block(ctx, jsonOutput, block.Phase0)
err = outputPhase0Block(context.Background(), jsonOutput, signedBlock.Phase0)
case spec.DataVersionAltair:
err = outputAltairBlock(ctx, jsonOutput, sszOutput, block.Altair)
err = outputAltairBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Altair)
case spec.DataVersionBellatrix:
err = outputBellatrixBlock(ctx, jsonOutput, sszOutput, block.Bellatrix)
err = outputBellatrixBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Bellatrix)
case spec.DataVersionCapella:
err = outputCapellaBlock(ctx, jsonOutput, sszOutput, block.Capella)
err = outputCapellaBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Capella)
case spec.DataVersionDeneb:
var blobSidecarsResponse *api.Response[[]*deneb.BlobSidecar]
blobSidecarsResponse, err = results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
Block: blockID,
})
if err == nil {
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, block.Deneb, blobSidecarsResponse.Data)
}
case spec.DataVersionElectra:
var blobSidecarsResponse *api.Response[[]*deneb.BlobSidecar]
blobSidecarsResponse, err = results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
Block: blockID,
})
if err == nil {
err = outputElectraBlock(context.Background(), jsonOutput, sszOutput, block.Electra, blobSidecarsResponse.Data)
}
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Deneb)
default:
err = errors.New("unknown block version")
}
@@ -310,12 +245,7 @@ func outputCapellaBlock(ctx context.Context, jsonOutput bool, sszOutput bool, si
return nil
}
func outputDenebBlock(ctx context.Context,
jsonOutput bool,
sszOutput bool,
signedBlock *deneb.SignedBeaconBlock,
blobs []*deneb.BlobSidecar,
) error {
func outputDenebBlock(ctx context.Context, jsonOutput bool, sszOutput bool, signedBlock *deneb.SignedBeaconBlock) error {
switch {
case jsonOutput:
data, err := json.Marshal(signedBlock)
@@ -330,7 +260,7 @@ func outputDenebBlock(ctx context.Context,
}
fmt.Printf("%x\n", data)
default:
data, err := outputDenebBlockText(ctx, results, signedBlock, blobs)
data, err := outputDenebBlockText(ctx, results, signedBlock)
if err != nil {
return errors.Wrap(err, "failed to generate text")
}
@@ -338,70 +268,3 @@ func outputDenebBlock(ctx context.Context,
}
return nil
}
func outputElectraBlock(ctx context.Context,
jsonOutput bool,
sszOutput bool,
signedBlock *electra.SignedBeaconBlock,
blobs []*deneb.BlobSidecar,
) error {
switch {
case jsonOutput:
data, err := json.Marshal(signedBlock)
if err != nil {
return errors.Wrap(err, "failed to generate JSON")
}
fmt.Printf("%s\n", string(data))
case sszOutput:
data, err := signedBlock.MarshalSSZ()
if err != nil {
return errors.Wrap(err, "failed to generate SSZ")
}
fmt.Printf("%x\n", data)
default:
data, err := outputElectraBlockText(ctx, results, signedBlock, blobs)
if err != nil {
return errors.Wrap(err, "failed to generate text")
}
fmt.Print(data)
}
return nil
}
func timeToBlockID(ctx context.Context, eth2Client eth2client.Service, input string) (string, error) {
var timestamp time.Time
switch {
case strings.HasPrefix(input, "0x"):
// Hex string.
hexTime, err := strconv.ParseInt(strings.TrimPrefix(input, "0x"), 16, 64)
if err != nil {
return "", errors.Wrap(err, "failed to parse block time as hex string")
}
timestamp = time.Unix(hexTime, 0)
case !strings.Contains(input, ":"):
// No colon, assume decimal string.
decTime, err := strconv.ParseInt(input, 10, 64)
if err != nil {
return "", errors.Wrap(err, "failed to parse block time as decimal string")
}
timestamp = time.Unix(decTime, 0)
default:
dateTime, err := time.Parse("2006-01-02T15:04:05", input)
if err != nil {
return "", errors.Wrap(err, "failed to parse block time as datetime")
}
timestamp = dateTime
}
// Assume timestamp.
chainTime, err := standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
)
if err != nil {
return "", errors.Wrap(err, "failed to set up chaintime service")
}
return fmt.Sprintf("%d", chainTime.TimestampToSlot(timestamp)), nil
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package blockinfo
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var blockAnalyzeCmd = &cobra.Command{
ethdo block analyze --blockid=12345
In quiet mode this will return 0 if the block information is present and not skipped, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := blockanalyze.Run(cmd)
if err != nil {
return err

View File

@@ -29,7 +29,7 @@ var blockInfoCmd = &cobra.Command{
ethdo block info --blockid=12345
In quiet mode this will return 0 if the block information is present and not skipped, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := blockinfo.Run(cmd)
if err != nil {
return err
@@ -48,7 +48,6 @@ func init() {
blockCmd.AddCommand(blockInfoCmd)
blockFlags(blockInfoCmd)
blockInfoCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
blockInfoCmd.Flags().String("block-time", "", "the time of the block to fetch (format YYYY-MM-DDTHH:MM:SS, or a hex or decimal timestamp")
blockInfoCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
blockInfoCmd.Flags().Bool("ssz", false, "output data in SSZ format")
}
@@ -57,9 +56,6 @@ func blockInfoBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
panic(err)
}
if err := viper.BindPFlag("block-time", cmd.Flags().Lookup("block-time")); err != nil {
panic(err)
}
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
panic(err)
}

View File

@@ -34,7 +34,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Input.
xepoch string
@@ -51,8 +50,6 @@ type command struct {
slot phase0.Slot
epoch phase0.Epoch
period uint64
periodStart time.Time
periodEnd time.Time
incumbent *phase0.ETH1Data
eth1DataVotes []*phase0.ETH1Data
votes map[string]*vote
@@ -65,11 +62,10 @@ type vote struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
json: viper.GetBool("json"),
customSpecSupport: viper.GetBool("custom-spec"),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
json: viper.GetBool("json"),
}
// Timeout.

View File

@@ -24,13 +24,11 @@ import (
)
type jsonOutput struct {
Period uint64 `json:"period"`
PeriodStart int64 `json:"period_start"`
PeriodEnd int64 `json:"period_end"`
Epoch phase0.Epoch `json:"epoch"`
Slot phase0.Slot `json:"slot"`
Incumbent *phase0.ETH1Data `json:"incumbent"`
Votes []*vote `json:"votes"`
Period uint64 `json:"period"`
Epoch phase0.Epoch `json:"epoch"`
Slot phase0.Slot `json:"slot"`
Incumbent *phase0.ETH1Data `json:"incumbent"`
Votes []*vote `json:"votes"`
}
func (c *command) output(ctx context.Context) (string, error) {
@@ -59,13 +57,11 @@ func (c *command) outputJSON(_ context.Context) (string, error) {
})
output := &jsonOutput{
Period: c.period,
PeriodStart: c.periodStart.Unix(),
PeriodEnd: c.periodEnd.Unix(),
Epoch: c.epoch,
Slot: c.slot,
Incumbent: c.incumbent,
Votes: votes,
Period: c.period,
Epoch: c.epoch,
Slot: c.slot,
Incumbent: c.incumbent,
Votes: votes,
}
data, err := json.Marshal(output)
if err != nil {
@@ -82,11 +78,6 @@ func (c *command) outputText(_ context.Context) (string, error) {
builder.WriteString(fmt.Sprintf("%d\n", c.period))
if c.verbose {
builder.WriteString("Period start: ")
builder.WriteString(fmt.Sprintf("%s\n", c.periodStart))
builder.WriteString("Period end: ")
builder.WriteString(fmt.Sprintf("%s\n", c.periodEnd))
builder.WriteString("Incumbent: ")
builder.WriteString(fmt.Sprintf("block %#x, deposit count %d\n", c.incumbent.BlockHash, c.incumbent.DepositCount))
}

View File

@@ -20,7 +20,6 @@ import (
"strconv"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
@@ -59,13 +58,10 @@ func (c *command) process(ctx context.Context) error {
if fetchSlot > c.chainTime.CurrentSlot() {
fetchSlot = c.chainTime.CurrentSlot()
}
stateResponse, err := c.beaconStateProvider.BeaconState(ctx, &api.BeaconStateOpts{
State: fmt.Sprintf("%d", fetchSlot),
})
state, err := c.beaconStateProvider.BeaconState(ctx, fmt.Sprintf("%d", fetchSlot))
if err != nil {
return errors.Wrap(err, "failed to obtain state")
}
state := stateResponse.Data
if state == nil {
return errors.New("state not returned by beacon node")
}
@@ -77,36 +73,32 @@ func (c *command) process(ctx context.Context) error {
}
}
c.slot, err = state.Slot()
if err != nil {
return errors.Wrap(err, "failed to obtain slot")
}
switch state.Version {
case spec.DataVersionPhase0:
c.slot = state.Phase0.Slot
c.incumbent = state.Phase0.ETH1Data
c.eth1DataVotes = state.Phase0.ETH1DataVotes
case spec.DataVersionAltair:
c.slot = state.Altair.Slot
c.incumbent = state.Altair.ETH1Data
c.eth1DataVotes = state.Altair.ETH1DataVotes
case spec.DataVersionBellatrix:
c.slot = state.Bellatrix.Slot
c.incumbent = state.Bellatrix.ETH1Data
c.eth1DataVotes = state.Bellatrix.ETH1DataVotes
case spec.DataVersionCapella:
c.slot = state.Capella.Slot
c.incumbent = state.Capella.ETH1Data
c.eth1DataVotes = state.Capella.ETH1DataVotes
case spec.DataVersionDeneb:
c.slot = state.Deneb.Slot
c.incumbent = state.Deneb.ETH1Data
c.eth1DataVotes = state.Deneb.ETH1DataVotes
case spec.DataVersionElectra:
c.incumbent = state.Electra.ETH1Data
c.eth1DataVotes = state.Electra.ETH1DataVotes
default:
return fmt.Errorf("unhandled beacon state version %v", state.Version)
}
c.period = uint64(c.epoch) / c.epochsPerEth1VotingPeriod
c.periodStart = c.chainTime.StartOfEpoch(phase0.Epoch(c.period * c.epochsPerEth1VotingPeriod))
c.periodEnd = c.chainTime.StartOfEpoch(phase0.Epoch((c.period + 1) * c.epochsPerEth1VotingPeriod))
c.votes = make(map[string]*vote)
for _, eth1Vote := range c.eth1DataVotes {
@@ -127,11 +119,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -139,7 +130,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")
@@ -155,12 +146,12 @@ func (c *command) setup(ctx context.Context) error {
return errors.New("connection does not provide spec information")
}
specResponse, err := specProvider.Spec(ctx, &api.SpecOpts{})
spec, err := specProvider.Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := specResponse.Data["SLOTS_PER_EPOCH"]
tmp, exists := spec["SLOTS_PER_EPOCH"]
if !exists {
return errors.New("spec did not contain SLOTS_PER_EPOCH")
}
@@ -169,7 +160,7 @@ func (c *command) setup(ctx context.Context) error {
if !good {
return errors.New("SLOTS_PER_EPOCH value invalid")
}
tmp, exists = specResponse.Data["EPOCHS_PER_ETH1_VOTING_PERIOD"]
tmp, exists = spec["EPOCHS_PER_ETH1_VOTING_PERIOD"]
if !exists {
return errors.New("spec did not contain EPOCHS_PER_ETH1_VOTING_PERIOD")
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package chaineth1votes
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -33,7 +33,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Input.
epoch string
@@ -50,11 +49,10 @@ type command struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
json: viper.GetBool("json"),
customSpecSupport: viper.GetBool("custom-spec"),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
json: viper.GetBool("json"),
}
// Timeout.

View File

@@ -18,7 +18,6 @@ import (
"fmt"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
@@ -35,14 +34,12 @@ func (c *command) process(ctx context.Context) error {
return err
}
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
State: fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(epoch)),
})
validators, err := c.validatorsProvider.Validators(ctx, fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(epoch)), nil)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
for _, validator := range response.Data {
for _, validator := range validators {
if validator.Validator == nil {
continue
}
@@ -62,11 +59,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -74,7 +70,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package chainqueues
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -31,7 +31,6 @@ type dataIn struct {
// Input
connection string
allowInsecureConnections bool
customSpecSupport bool
timestamp string
slot string
epoch string
@@ -74,7 +73,6 @@ func input(_ context.Context) (*dataIn, error) {
data.connection = viper.GetString("connection")
data.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
data.customSpecSupport = viper.GetBool("custom-spec")
return data, nil
}

View File

@@ -16,7 +16,6 @@ package chaintime
import (
"context"
"fmt"
"strconv"
"strings"
"time"
@@ -60,14 +59,14 @@ func output(_ context.Context, data *dataOut) (string, error) {
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
if data.verbose {
builder.WriteString(" (")
builder.WriteString(strconv.FormatInt(data.epochStart.Unix(), 10))
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(strconv.FormatInt(data.epochEnd.Unix(), 10))
builder.WriteString(fmt.Sprintf("%d", data.epochEnd.Unix()))
builder.WriteString(")")
}
@@ -77,27 +76,27 @@ func output(_ context.Context, data *dataOut) (string, error) {
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
if data.verbose {
builder.WriteString(" (")
builder.WriteString(strconv.FormatInt(data.slotStart.Unix(), 10))
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(strconv.FormatInt(data.slotEnd.Unix(), 10))
builder.WriteString(fmt.Sprintf("%d", data.slotEnd.Unix()))
builder.WriteString(")")
}
if data.hasSyncCommittees {
builder.WriteString("\nSync committee period ")
builder.WriteString(strconv.FormatUint(data.syncCommitteePeriod, 10))
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(strconv.FormatInt(data.syncCommitteePeriodStart.Unix(), 10))
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"))
@@ -105,7 +104,7 @@ func output(_ context.Context, data *dataOut) (string, error) {
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
if data.verbose {
builder.WriteString(", ")
builder.WriteString(strconv.FormatInt(data.syncCommitteePeriodEnd.Unix(), 10))
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEnd.Unix()))
}
builder.WriteString(")")
}

View File

@@ -31,11 +31,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
}
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: data.connection,
Timeout: data.timeout,
AllowInsecure: data.allowInsecureConnections,
CustomSpecSupport: data.customSpecSupport,
LogFallback: !data.quiet,
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")
@@ -43,7 +42,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
chainTime, err := standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -1,4 +1,4 @@
// Copyright © 2021, 2024 Weald Technology Trading.
// Copyright © 2021 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,8 +15,8 @@ package chaintime
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -33,7 +33,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Input.
data string
@@ -61,10 +60,9 @@ type command struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
customSpecSupport: viper.GetBool("custom-spec"),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
}
// Timeout.

View File

@@ -22,7 +22,6 @@ import (
"os"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
@@ -87,11 +86,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -105,32 +103,29 @@ func (c *command) setup(ctx context.Context) error {
}
stateID := fmt.Sprintf("%d", c.item.Message.Contribution.Slot)
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
State: stateID,
Indices: []phase0.ValidatorIndex{c.item.Message.AggregatorIndex},
})
validators, err := c.validatorsProvider.Validators(ctx,
stateID,
[]phase0.ValidatorIndex{c.item.Message.AggregatorIndex},
)
if err != nil {
return errors.Wrap(err, "failed to obtain validator information")
}
if len(response.Data) == 0 || response.Data[c.item.Message.AggregatorIndex] == nil {
if len(validators) == 0 || validators[c.item.Message.AggregatorIndex] == nil {
return nil
}
c.validatorKnown = true
c.validator = response.Data[c.item.Message.AggregatorIndex]
c.validator = validators[c.item.Message.AggregatorIndex]
// Obtain the sync committee
syncCommitteesProvider, isProvider := c.eth2Client.(eth2client.SyncCommitteesProvider)
if !isProvider {
return errors.New("connection does not provide sync committee information")
}
syncCommitteeResponse, err := syncCommitteesProvider.SyncCommittee(ctx, &api.SyncCommitteeOpts{
State: stateID,
})
c.syncCommittee, err = syncCommitteesProvider.SyncCommittee(ctx, stateID)
if err != nil {
return errors.Wrap(err, "failed to obtain sync committee information")
}
c.syncCommittee = syncCommitteeResponse.Data
return nil
}
@@ -142,11 +137,11 @@ func (c *command) isAggregator(ctx context.Context) (bool, error) {
if !isProvider {
return false, errors.New("connection does not provide spec information")
}
specResponse, err := specProvider.Spec(ctx, &api.SpecOpts{})
var err error
c.spec, err = specProvider.Spec(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to obtain spec information")
}
c.spec = specResponse.Data
tmp, exists := c.spec["SYNC_COMMITTEE_SIZE"]
if !exists {
@@ -231,19 +226,16 @@ func (c *command) confirmContributionSignature(ctx context.Context) error {
fmt.Fprintf(os.Stderr, "Contribution validator indices: %v (%d)\n", includedIndices, len(includedIndices))
}
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
State: "head",
Indices: includedIndices,
})
includedValidators, err := c.validatorsProvider.Validators(ctx, "head", includedIndices)
if err != nil {
return errors.Wrap(err, "failed to obtain subcommittee validators")
}
if len(response.Data) == 0 {
if len(includedValidators) == 0 {
return errors.New("obtained empty subcommittee validator list")
}
var aggregatePubKey *e2types.BLSPublicKey
for _, v := range response.Data {
for _, v := range includedValidators {
pubKeyBytes := make([]byte, 48)
copy(pubKeyBytes, v.Validator.PublicKey[:])
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)

View File

@@ -1,4 +1,4 @@
// Copyright © 2021, 2024 Weald Technology Trading.
// Copyright © 2021 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,8 +15,8 @@ package chainverifysignedcontributionandproof
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -31,7 +31,7 @@ var chainEth1VotesCmd = &cobra.Command{
Note that this will fetch the votes made in blocks up to the end of the provided epoch.
In quiet mode this will return 0 if there is a majority for the votes, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := chaineth1votes.Run(cmd)
if err != nil {
return err

View File

@@ -1,4 +1,4 @@
// Copyright © 2020 - 2024 Weald Technology Trading
// Copyright © 2020, 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
@@ -20,8 +20,6 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -36,45 +34,43 @@ var chainInfoCmd = &cobra.Command{
ethdo chain info
In quiet mode this will return 0 if the chain information can be obtained, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
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"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !viper.GetBool("quiet"),
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")
specResponse, err := eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
errCheck(err, "Failed to obtain beacon chain specification")
genesisResponse, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
errCheck(err, "Failed to obtain beacon chain genesis")
forkResponse, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, &api.ForkOpts{State: "head"})
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
errCheck(err, "Failed to obtain current fork")
if viper.GetBool("quiet") {
os.Exit(_exitSuccess)
}
if genesisResponse.Data.GenesisTime.Unix() == 0 {
if genesis.GenesisTime.Unix() == 0 {
fmt.Println("Genesis time: undefined")
} else {
fmt.Printf("Genesis time: %s\n", genesisResponse.Data.GenesisTime.Format(time.UnixDate))
outputIf(viper.GetBool("verbose"), fmt.Sprintf("Genesis timestamp: %v", genesisResponse.Data.GenesisTime.Unix()))
fmt.Printf("Genesis time: %s\n", genesis.GenesisTime.Format(time.UnixDate))
outputIf(viper.GetBool("verbose"), fmt.Sprintf("Genesis timestamp: %v", genesis.GenesisTime.Unix()))
}
fmt.Printf("Genesis validators root: %#x\n", genesisResponse.Data.GenesisValidatorsRoot)
fmt.Printf("Genesis fork version: %#x\n", specResponse.Data["GENESIS_FORK_VERSION"].(spec.Version))
fmt.Printf("Current fork version: %#x\n", forkResponse.Data.CurrentVersion)
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 viper.GetBool("verbose") {
forkData := &spec.ForkData{
CurrentVersion: forkResponse.Data.CurrentVersion,
GenesisValidatorsRoot: genesisResponse.Data.GenesisValidatorsRoot,
CurrentVersion: fork.CurrentVersion,
GenesisValidatorsRoot: genesis.GenesisValidatorsRoot,
}
forkDataRoot, err := forkData.HashTreeRoot()
if err == nil {
@@ -83,12 +79,8 @@ In quiet mode this will return 0 if the chain information can be obtained, other
fmt.Printf("Fork digest: %#x\n", forkDigest)
}
}
fmt.Printf("Seconds per slot: %d\n", int(specResponse.Data["SECONDS_PER_SLOT"].(time.Duration).Seconds()))
fmt.Printf("Slots per epoch: %d\n", specResponse.Data["SLOTS_PER_EPOCH"].(uint64))
depositContractAddress := bellatrix.ExecutionAddress{}
copy(depositContractAddress[:], specResponse.Data["DEPOSIT_CONTRACT_ADDRESS"].([]byte))
fmt.Printf("Deposit contract address: %s\n", depositContractAddress.String())
fmt.Printf("Seconds per slot: %d\n", int(config["SECONDS_PER_SLOT"].(time.Duration).Seconds()))
fmt.Printf("Slots per epoch: %d\n", config["SLOTS_PER_EPOCH"].(uint64))
os.Exit(_exitSuccess)
},

View File

@@ -29,7 +29,7 @@ var chainQueuesCmd = &cobra.Command{
ethdo chain queues
In quiet mode this will return 0 if the entry and exit queues are 0, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := chainqueues.Run(cmd)
if err != nil {
return err

View File

@@ -18,11 +18,9 @@ import (
"encoding/json"
"fmt"
"sort"
"strconv"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -37,19 +35,18 @@ var chainSpecCmd = &cobra.Command{
ethdo chain spec
In quiet mode this will return 0 if the chain specification can be obtained, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
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"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !viper.GetBool("quiet"),
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")
specResponse, err := eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
spec, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
errCheck(err, "Failed to obtain chain specification")
if viper.GetBool("quiet") {
@@ -57,35 +54,35 @@ In quiet mode this will return 0 if the chain specification can be obtained, oth
}
// Tweak the spec for output.
for k, v := range specResponse.Data {
for k, v := range spec {
switch t := v.(type) {
case phase0.Version:
specResponse.Data[k] = fmt.Sprintf("%#x", t)
spec[k] = fmt.Sprintf("%#x", t)
case phase0.DomainType:
specResponse.Data[k] = fmt.Sprintf("%#x", t)
spec[k] = fmt.Sprintf("%#x", t)
case time.Time:
specResponse.Data[k] = strconv.FormatInt(t.Unix(), 10)
spec[k] = fmt.Sprintf("%d", t.Unix())
case time.Duration:
specResponse.Data[k] = strconv.FormatUint(uint64(t.Seconds()), 10)
spec[k] = fmt.Sprintf("%d", uint64(t.Seconds()))
case []byte:
specResponse.Data[k] = fmt.Sprintf("%#x", t)
spec[k] = fmt.Sprintf("%#x", t)
case uint64:
specResponse.Data[k] = strconv.FormatUint(t, 10)
spec[k] = fmt.Sprintf("%d", t)
}
}
if viper.GetBool("json") {
data, err := json.Marshal(specResponse.Data)
data, err := json.Marshal(spec)
errCheck(err, "Failed to marshal JSON")
fmt.Printf("%s\n", string(data))
} else {
keys := make([]string, 0, len(specResponse.Data))
for k := range specResponse.Data {
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, specResponse.Data[key])
fmt.Printf("%s: %v\n", key, spec[key])
}
}
},

View File

@@ -17,12 +17,10 @@ import (
"context"
"fmt"
"os"
"strconv"
"strings"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/cobra"
@@ -40,31 +38,27 @@ var chainStatusCmd = &cobra.Command{
ethdo chain status
In quiet mode this will return 0 if the chain status can be obtained, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
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"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !viper.GetBool("quiet"),
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,
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
)
errCheck(err, "Failed to configure chaintime service")
finalityProvider, isProvider := eth2Client.(eth2client.FinalityProvider)
assert(isProvider, "beacon node does not provide finality; cannot report on chain status")
finalityResponse, err := finalityProvider.Finality(ctx, &api.FinalityOpts{
State: "head",
})
finality, err := finalityProvider.Finality(ctx, "head")
errCheck(err, "Failed to obtain finality information")
finality := finalityResponse.Data
slot := chainTime.CurrentSlot()
@@ -132,13 +126,13 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
if viper.GetBool("verbose") {
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
if isProvider {
validatorsResponse, err := validatorsProvider.Validators(ctx, &api.ValidatorsOpts{State: "head"})
validators, err := validatorsProvider.Validators(ctx, "head", nil)
errCheck(err, "Failed to obtain validators information")
// Stats of inteest.
totalBalance := phase0.Gwei(0)
activeEffectiveBalance := phase0.Gwei(0)
validatorCount := make(map[apiv1.ValidatorState]int)
for _, validator := range validatorsResponse.Data {
for _, validator := range validators {
validatorCount[validator.Status]++
totalBalance += validator.Balance
if validator.Status.IsActive() {
@@ -168,7 +162,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
nextPeriodTimestamp := chainTime.StartOfEpoch(nextPeriodStartEpoch)
res.WriteString("Sync committee period: ")
res.WriteString(strconv.FormatUint(period, 10))
res.WriteString(fmt.Sprintf("%d", period))
res.WriteString("\n")
if viper.GetBool("verbose") {

View File

@@ -27,7 +27,7 @@ var chainTimeCmd = &cobra.Command{
Long: `Obtain info about the chain at a given time. For example:
ethdo chain time --slot=12345`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := chaintime.Run(cmd)
if err != nil {
return err

View File

@@ -28,7 +28,7 @@ var chainVerifySignedContributionAndProofCmd = &cobra.Command{
ethdo chain verify signedcontributionandproof --data=... --validator=...
validator can be an account, a public key or an index.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := chainverifysignedcontributionandproof.Run(cmd)
if err != nil {
return err

View File

@@ -16,7 +16,6 @@ package cmd
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
@@ -50,7 +49,7 @@ var depositVerifyCmd = &cobra.Command{
The deposit data is compared to the supplied withdrawal account/public key, validator public key, and value to ensure they match.
In quiet mode this will return 0 if the data is verified correctly, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
assert(depositVerifyData != "", "--data is required")
var data []byte
var err error
@@ -76,12 +75,6 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
deposits, err := util.DepositInfoFromJSON(data)
errCheck(err, "Failed to fetch deposit data")
if viper.GetBool("debug") {
data, err := json.Marshal(deposits)
if err == nil {
fmt.Fprintf(os.Stderr, "Deposit data is %s\n", string(data))
}
}
var withdrawalCredentials []byte
if depositVerifyWithdrawalPubKey != "" {
@@ -270,12 +263,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
return false, nil
}
switch {
case len(deposit.DepositMessageRoot) != 32:
if len(deposit.DepositMessageRoot) != 32 {
outputIf(!viper.GetBool("quiet"), "Deposit message root not supplied; not checked")
case len(withdrawalCredentials) != 32:
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials not available; cannot recreate deposit message")
default:
} else {
// We can also verify the deposit message signature.
depositMessage := &phase0.DepositMessage{
PublicKey: pubKey,

View File

@@ -18,7 +18,6 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
@@ -34,7 +33,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Operation.
epoch string
@@ -51,9 +49,6 @@ type command struct {
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
// Caches.
blocksCache map[string]*spec.VersionedSignedBeaconBlock
// Results.
summary *epochSummary
}
@@ -72,7 +67,6 @@ type epochSummary struct {
TargetCorrectValidators int `json:"target_correct_validators"`
TargetTimelyValidators int `json:"target_timely_validators"`
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
Blobs int `json:"blobs"`
}
type epochProposal struct {
@@ -94,11 +88,10 @@ type nonParticipatingValidator struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
summary: &epochSummary{},
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
summary: &epochSummary{},
}
// Timeout.
@@ -109,7 +102,6 @@ func newCommand(_ context.Context) (*command, error) {
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.customSpecSupport = viper.GetBool("custom-spec")
c.epoch = viper.GetString("epoch")
c.stream = viper.GetBool("stream")

View File

@@ -16,13 +16,12 @@ package epochsummary
import (
"context"
"fmt"
"net/http"
"sort"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"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"
@@ -49,22 +48,19 @@ 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 c.processBlobs(ctx)
return c.processSyncCommitteeDuties(ctx)
}
func (c *command) processProposerDuties(ctx context.Context) error {
response, err := c.proposerDutiesProvider.ProposerDuties(ctx, &api.ProposerDutiesOpts{
Epoch: c.summary.Epoch,
})
duties, err := c.proposerDutiesProvider.ProposerDuties(ctx, c.summary.Epoch, nil)
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}
for _, duty := range response.Data {
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", duty.Slot))
if duties == nil {
return errors.New("empty proposer duties")
}
for _, duty := range duties {
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", duty.Slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
}
@@ -80,14 +76,12 @@ func (c *command) processProposerDuties(ctx context.Context) error {
}
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
State: fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)),
})
validators, err := c.validatorsProvider.Validators(ctx, fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)), nil)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
}
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
for _, validator := range response.Data {
for _, validator := range validators {
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
activeValidators[validator.Index] = validator
}
@@ -167,7 +161,7 @@ func (c *command) processSlots(ctx context.Context,
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
for slot := firstSlot; slot <= lastSlot; slot++ {
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
}
@@ -184,23 +178,17 @@ func (c *command) processSlots(ctx context.Context,
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
for _, attestation := range attestations {
attestationData, err := attestation.Data()
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain attestation data for slot %d", slot))
}
if attestationData.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestationData.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
// Outside of this epoch's range.
continue
}
slotCommittees, exists := allCommittees[attestationData.Slot]
slotCommittees, exists := allCommittees[attestation.Data.Slot]
if !exists {
response, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
State: fmt.Sprintf("%d", attestationData.Slot),
})
beaconCommittees, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, fmt.Sprintf("%d", attestation.Data.Slot))
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestationData.Slot))
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
}
for _, beaconCommittee := range response.Data {
for _, beaconCommittee := range beaconCommittees {
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
}
@@ -213,26 +201,22 @@ func (c *command) processSlots(ctx context.Context,
}
}
}
slotCommittees = allCommittees[attestationData.Slot]
slotCommittees = allCommittees[attestation.Data.Slot]
}
committee := slotCommittees[attestationData.Index]
committee := slotCommittees[attestation.Data.Index]
inclusionDistance := slot - attestationData.Slot
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestationData)
inclusionDistance := slot - attestation.Data.Slot
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestationData)
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
aggregationBits, err := attestation.AggregationBits()
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
for i := uint64(0); i < aggregationBits.Len(); i++ {
if aggregationBits.BitAt(i) {
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
if attestation.AggregationBits.BitAt(i) {
votes[committee[int(i)]] = struct{}{}
if _, exists := headCorrects[committee[int(i)]]; !exists && headCorrect {
headCorrects[committee[int(i)]] = struct{}{}
@@ -253,7 +237,6 @@ func (c *command) processSlots(ctx context.Context,
}
}
}
return len(votes),
len(headCorrects),
len(headTimelys),
@@ -271,13 +254,10 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
return nil
}
committeeResponse, err := c.syncCommitteesProvider.SyncCommittee(ctx, &api.SyncCommitteeOpts{
State: fmt.Sprintf("%d", c.summary.FirstSlot),
})
committee, err := c.syncCommitteesProvider.SyncCommittee(ctx, fmt.Sprintf("%d", c.summary.FirstSlot))
if err != nil {
return errors.Wrap(err, "failed to obtain sync committee")
}
committee := committeeResponse.Data
if len(committee.Validators) == 0 {
return errors.Wrap(err, "empty sync committee")
}
@@ -288,7 +268,7 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
}
for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
}
@@ -296,14 +276,21 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
// If the block is missed we don't count the sync aggregate miss.
continue
}
if block.Version == spec.DataVersionPhase0 {
var aggregate *altair.SyncAggregate
switch block.Version {
case spec.DataVersionPhase0:
// No sync committees in this fork.
return nil
}
aggregate, err := block.SyncAggregate()
if err != nil {
return errors.Wrapf(err, "failed to obtain sync aggregate for slot %d", slot)
case spec.DataVersionAltair:
aggregate = block.Altair.Message.Body.SyncAggregate
case spec.DataVersionBellatrix:
aggregate = block.Bellatrix.Message.Body.SyncAggregate
case spec.DataVersionCapella:
aggregate = block.Capella.Message.Body.SyncAggregate
case spec.DataVersionDeneb:
aggregate = block.Deneb.Message.Body.SyncAggregate
default:
return fmt.Errorf("unhandled block version %v", block.Version)
}
for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
if !aggregate.SyncCommitteeBits.BitAt(i) {
@@ -340,11 +327,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -352,7 +338,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")
@@ -386,54 +372,3 @@ func (c *command) setup(ctx context.Context) error {
return nil
}
func (c *command) processBlobs(ctx context.Context) error {
for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
}
if block == nil {
continue
}
switch block.Version {
case spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix, spec.DataVersionCapella:
// No blobs in these forks.
case spec.DataVersionDeneb:
c.summary.Blobs += len(block.Deneb.Message.Body.BlobKZGCommitments)
case spec.DataVersionElectra:
c.summary.Blobs += len(block.Electra.Message.Body.BlobKZGCommitments)
default:
return fmt.Errorf("unhandled block version %v", block.Version)
}
}
return nil
}
func (c *command) fetchBlock(ctx context.Context,
blockID string,
) (
*spec.VersionedSignedBeaconBlock,
error,
) {
block, exists := c.blocksCache[blockID]
if !exists {
var err error
blockResponse, err := c.blocksProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: blockID,
})
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
// No block for this slot, that's okay.
return nil, nil
}
return nil, errors.Wrap(err, "failed to fetch block")
}
block = blockResponse.Data
c.blocksCache[blockID] = block
}
return block, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package epochsummary
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var epochSummaryCmd = &cobra.Command{
ethdo epoch summary --epoch=12345
In quiet mode this will return 0 if information for the epoch is found, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := epochsummary.Run(cmd)
if err != nil {
return err

View File

@@ -21,7 +21,6 @@ import (
"strings"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
v1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
@@ -39,7 +38,7 @@ var exitVerifyCmd = &cobra.Command{
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(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
assert(viper.GetString("signed-operation") != "", "signed-operation is required")
@@ -47,11 +46,10 @@ In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.
errCheck(err, "Failed to obtain signed operation")
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !viper.GetBool("quiet"),
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")
@@ -69,11 +67,10 @@ In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.
opRoot, err := signedOp.Message.HashTreeRoot()
errCheck(err, "Failed to obtain exit hash tree root")
genesisResponse, err := eth2Client.(consensusclient.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
genesis, err := eth2Client.(consensusclient.GenesisProvider).Genesis(ctx)
errCheck(err, "Failed to obtain beacon chain genesis")
genesis := genesisResponse.Data
response, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, &api.ForkOpts{State: "head"})
fork, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, "head")
errCheck(err, "Failed to obtain fork information")
// Check against current and prior fork versions.
@@ -86,14 +83,14 @@ In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.
// Try with the current fork.
domain := phase0.Domain{}
currentExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, response.Data.CurrentVersion[:], genesis.GenesisValidatorsRoot[:])
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")
if !verified {
// Try again with the previous fork.
previousExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, response.Data.PreviousVersion[:], genesis.GenesisValidatorsRoot[:])
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)

View File

@@ -51,11 +51,10 @@ func input(ctx context.Context) (*dataIn, error) {
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2024 Weald Technology Trading.
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,8 +15,8 @@ package nodeevents
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -25,19 +25,14 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
if err := process(ctx, dataIn); err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
// Process generates all output.

View File

@@ -27,7 +27,7 @@ var nodeEventsCmd = &cobra.Command{
Long: `Report events from a node. For example:
ethdo node events --events=head,chain_reorg.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := nodeevents.Run(cmd)
if err != nil {
return err

View File

@@ -19,7 +19,6 @@ import (
"os"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
@@ -33,15 +32,14 @@ var nodeInfoCmd = &cobra.Command{
ethdo node info
In quiet mode this will return 0 if the node information can be obtained, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
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"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !viper.GetBool("quiet"),
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")
@@ -50,14 +48,14 @@ In quiet mode this will return 0 if the node information can be obtained, otherw
}
if viper.GetBool("verbose") {
versionResponse, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx, &api.NodeVersionOpts{})
version, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx)
errCheck(err, "Failed to obtain node version")
fmt.Printf("Version: %s\n", versionResponse.Data)
fmt.Printf("Version: %s\n", version)
}
syncStateResponse, err := eth2Client.(eth2client.NodeSyncingProvider).NodeSyncing(ctx, &api.NodeSyncingOpts{})
syncState, err := eth2Client.(eth2client.NodeSyncingProvider).NodeSyncing(ctx)
errCheck(err, "failed to obtain node sync state")
fmt.Printf("Syncing: %t\n", syncStateResponse.Data.SyncDistance != 0)
fmt.Printf("Syncing: %t\n", syncState.SyncDistance != 0)
os.Exit(_exitSuccess)
},

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2023 Weald Technology Trading.
// 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
@@ -34,11 +34,9 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Operation.
epoch string
slot string
jsonOutput bool
// Data access.
@@ -57,23 +55,23 @@ type results struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
timeout: viper.GetDuration("timeout"),
connection: viper.GetString("connection"),
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
customSpecSupport: viper.GetBool("custom-spec"),
epoch: viper.GetString("epoch"),
slot: viper.GetString("slot"),
jsonOutput: viper.GetBool("json"),
results: &results{},
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
results: &results{},
}
// Timeout.
if c.timeout == 0 {
if viper.GetDuration("timeout") == 0 {
return nil, errors.New("timeout is required")
}
c.timeout = viper.GetDuration("timeout")
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.epoch = viper.GetString("epoch")
c.jsonOutput = viper.GetBool("json")
return c, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2023 Weald Technology Trading.
// 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
@@ -43,32 +43,19 @@ func (c *command) outputJSON(_ context.Context) (string, error) {
func (c *command) outputTxt(_ context.Context) (string, error) {
builder := strings.Builder{}
if len(c.results.Duties) == 1 {
// Only have a single slot, just print the validator.
duty := c.results.Duties[0]
builder.WriteString("Validator ")
builder.WriteString("Epoch ")
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))
for _, duty := range c.results.Duties {
builder.WriteString(" Slot ")
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
builder.WriteString("validator ")
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
if c.verbose {
builder.WriteString(" (pubkey ")
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
}
builder.WriteString("\n")
} else {
// Have multiple slots, print per-slot information.
builder.WriteString("Epoch ")
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))
for _, duty := range c.results.Duties {
builder.WriteString(" Slot ")
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
builder.WriteString("validator ")
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
if c.verbose {
builder.WriteString(" (pubkey ")
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
}
builder.WriteString("\n")
}
}
return strings.TrimSuffix(builder.String(), "\n"), nil

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2023 Weald Technology Trading.
// 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
@@ -17,8 +17,6 @@ import (
"context"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
@@ -26,59 +24,20 @@ import (
func (c *command) process(ctx context.Context) error {
// Obtain information we need to process.
if err := c.setup(ctx); err != nil {
err := c.setup(ctx)
if err != nil {
return err
}
if c.slot != "" {
return c.processSlot(ctx)
}
return c.processEpoch(ctx)
}
func (c *command) processSlot(ctx context.Context) error {
var err error
slot, err := util.ParseSlot(ctx, c.chainTime, c.slot)
if err != nil {
return errors.Wrap(err, "failed to parse slot")
}
c.results.Epoch = c.chainTime.SlotToEpoch(slot)
response, err := c.proposerDutiesProvider.ProposerDuties(ctx, &api.ProposerDutiesOpts{
Epoch: c.results.Epoch,
})
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}
c.results.Duties = make([]*apiv1.ProposerDuty, 0, 1)
for _, duty := range response.Data {
if duty.Slot == slot {
c.results.Duties = append(c.results.Duties, duty)
break
}
}
return nil
}
func (c *command) processEpoch(ctx context.Context) error {
var err error
c.results.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
if err != nil {
return errors.Wrap(err, "failed to parse epoch")
}
dutiesResponse, err := c.proposerDutiesProvider.ProposerDuties(ctx, &api.ProposerDutiesOpts{
Epoch: c.results.Epoch,
})
c.results.Duties, err = c.proposerDutiesProvider.ProposerDuties(ctx, c.results.Epoch, nil)
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}
c.results.Duties = dutiesResponse.Data
return nil
}
@@ -88,11 +47,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
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")
@@ -100,7 +58,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package proposerduties
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var proposerDutiesCmd = &cobra.Command{
ethdo proposer duties --epoch=12345
In quiet mode this will return 0 if duties can be obtained, otherwise 1.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := proposerduties.Run(cmd)
if err != nil {
return err
@@ -48,14 +48,10 @@ func init() {
proposerCmd.AddCommand(proposerDutiesCmd)
proposerFlags(proposerDutiesCmd)
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
proposerDutiesCmd.Flags().String("slot", "", "the slot for which to fetch duties")
}
func proposerDutiesBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
panic(err)
}
}

View File

@@ -76,7 +76,6 @@ var bindings = map[string]func(cmd *cobra.Command){
"validator/yield": validatorYieldBindings,
"validator/expectation": validatorExpectationBindings,
"validator/withdrawal": validatorWithdrawalBindings,
"wallet/batch": walletBatchBindings,
"wallet/create": walletCreateBindings,
"wallet/import": walletImportBindings,
"wallet/sharedexport": walletSharedExportBindings,
@@ -136,7 +135,6 @@ func init() {
addPersistentFlags()
}
//nolint:gocyclo
func addPersistentFlags() {
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ethdo.yaml)")
@@ -160,7 +158,7 @@ func addPersistentFlags() {
if err := viper.BindPFlag("path", RootCmd.PersistentFlags().Lookup("path")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("private-key", "", "Private key to provide access to an account or validator")
RootCmd.PersistentFlags().String("private-key", "", "Private key to provide access to an account or validaotr")
if err := viper.BindPFlag("private-key", RootCmd.PersistentFlags().Lookup("private-key")); err != nil {
panic(err)
}
@@ -225,7 +223,7 @@ func addPersistentFlags() {
if err := viper.BindPFlag("connection", RootCmd.PersistentFlags().Lookup("connection")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().Duration("timeout", 30*time.Second, "the time after which a network request will be considered failed. Increase this if you are running on an error-prone, high-latency or low-bandwidth connection")
RootCmd.PersistentFlags().Duration("timeout", 10*time.Second, "the time after which a network request will be considered failed. Increase this if you are running on an error-prone, high-latency or low-bandwidth connection")
if err := viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout")); err != nil {
panic(err)
}
@@ -253,10 +251,6 @@ func addPersistentFlags() {
if err := viper.BindPFlag("allow-insecure-connections", RootCmd.PersistentFlags().Lookup("allow-insecure-connections")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().Bool("custom-spec", false, "allow custom (non-mainnet) beacon node specifications")
if err := viper.BindPFlag("custom-spec", RootCmd.PersistentFlags().Lookup("custom-spec")); err != nil {
panic(err)
}
}
// initConfig reads in config file and ENV variables if set.

View File

@@ -40,7 +40,7 @@ var signatureAggregateCmd = &cobra.Command{
Signatures are specified as "signature" for simple aggregation, and as "id:signature" for threshold aggregation.
In quiet mode this will return 0 if the signatures can be aggregated, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
assert(len(signatureAggregateSignatures) > 1, "multiple signatures required to aggregate")
var signature *bls.Sign
var err error

View File

@@ -36,7 +36,7 @@ var signatureSignCmd = &cobra.Command{
ethdo signature sign --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --account="Personal wallet/Operations" --passphrase="my account passphrase"
In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
@@ -66,7 +66,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
copy(specDomain[:], domain)
var fixedSizeData [32]byte
copy(fixedSizeData[:], data)
outputIf(viper.GetBool("debug"), fmt.Sprintf("Signing %#x with domain %#x by public key %#x", fixedSizeData, specDomain, account.PublicKey().Marshal()))
fmt.Printf("Signing %#x with domain %#x by public key %#x\n", fixedSizeData, specDomain, account.PublicKey().Marshal())
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
errCheck(err, "Failed to sign")

View File

@@ -41,7 +41,7 @@ var signatureVerifyCmd = &cobra.Command{
ethdo signature verify --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --signature=0x8888... --account="Personal wallet/Operations"
In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()

View File

@@ -53,11 +53,10 @@ func input(ctx context.Context) (*dataIn, error) {
// Ethereum 2 client.
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err

View File

@@ -19,7 +19,6 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/pkg/errors"
)
@@ -42,18 +41,17 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return nil, errors.New("slot must be a positive integer")
}
genesisResponse, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain genesis information")
}
genesis := genesisResponse.Data
specResponse, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain chain specifications")
}
slotDuration := specResponse.Data["SECONDS_PER_SLOT"].(time.Duration)
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
results.startTime = genesis.GenesisTime.Add((time.Duration(slot*int64(slotDuration.Seconds())) * time.Second))
results.endTime = results.startTime.Add(slotDuration)

View File

@@ -1,4 +1,4 @@
// Copyright © 2021, 2024 Weald Technology Trading.
// Copyright © 2021 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,8 +15,8 @@ package slottime
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -29,7 +29,7 @@ var slotTimeCmd = &cobra.Command{
ethdo slot time --slot=12345
In quiet mode this will return 0.`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := slottime.Run(cmd)
if err != nil {
return err

View File

@@ -33,7 +33,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Input.
validator string
@@ -52,10 +51,9 @@ type command struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
customSpecSupport: viper.GetBool("custom-spec"),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
}
// Timeout.

View File

@@ -16,7 +16,6 @@ package inclusion
import (
"context"
"fmt"
"strconv"
"strings"
)
@@ -54,13 +53,13 @@ func (c *command) output(_ context.Context) (string, error) {
}
}
builder.WriteString("Expected: ")
builder.WriteString(strconv.Itoa(len(c.inclusions)))
builder.WriteString(fmt.Sprintf("%d", len(c.inclusions)))
builder.WriteString("\nIncluded: ")
builder.WriteString(strconv.Itoa(included))
builder.WriteString(fmt.Sprintf("%d", included))
builder.WriteString("\nMissed: ")
builder.WriteString(strconv.Itoa(missed))
builder.WriteString(fmt.Sprintf("%d", missed))
builder.WriteString("\nNo block: ")
builder.WriteString(strconv.Itoa(noBlock))
builder.WriteString(fmt.Sprintf("%d", noBlock))
builder.WriteString("\nPer-slot result: ")
for i, inclusion := range c.inclusions {

View File

@@ -16,10 +16,8 @@ package inclusion
import (
"context"
"fmt"
"net/http"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/pkg/errors"
@@ -43,14 +41,10 @@ func (c *command) process(ctx context.Context) error {
return err
}
syncCommitteeResponse, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommittee(ctx, &api.SyncCommitteeOpts{
State: "head",
Epoch: &c.epoch,
})
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", c.epoch)
if err != nil {
return errors.Wrap(err, "failed to obtain sync committee information")
}
syncCommittee := syncCommitteeResponse.Data
if syncCommittee == nil {
return errors.New("no sync committee returned")
@@ -72,20 +66,11 @@ func (c *command) process(ctx context.Context) error {
if lastSlot > c.chainTime.CurrentSlot() {
lastSlot = c.chainTime.CurrentSlot()
}
for slot := firstSlot; slot <= lastSlot; slot++ {
blockResponse, err := c.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
Block: fmt.Sprintf("%d", slot),
})
for slot := firstSlot; slot < lastSlot; slot++ {
block, err := c.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
var apiErr *api.Error
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
c.inclusions = append(c.inclusions, 0)
continue
}
return errors.Wrap(err, "failed to obtain beacon block")
return err
}
block := blockResponse.Data
if block == nil {
c.inclusions = append(c.inclusions, 0)
continue
@@ -120,13 +105,6 @@ func (c *command) process(ctx context.Context) error {
} else {
c.inclusions = append(c.inclusions, 2)
}
case spec.DataVersionElectra:
aggregate = block.Electra.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)
}
@@ -141,11 +119,10 @@ func (c *command) setup(ctx context.Context) error {
// Connect to the client.
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
CustomSpecSupport: c.customSpecSupport,
LogFallback: !c.quiet,
Address: c.connection,
Timeout: c.timeout,
AllowInsecure: c.allowInsecureConnections,
LogFallback: !c.quiet,
})
if err != nil {
return err
@@ -153,7 +130,7 @@ func (c *command) setup(ctx context.Context) error {
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithGenesisProvider(c.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")

View File

@@ -1,4 +1,4 @@
// Copyright © 2022, 2024 Weald Technology Trading.
// 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
@@ -15,8 +15,8 @@ package inclusion
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -27,19 +27,14 @@ func Run(cmd *cobra.Command) (string, error) {
c, err := newCommand(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
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 {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := c.output(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -52,11 +52,10 @@ func input(ctx context.Context) (*dataIn, error) {
// Ethereum 2 client.
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
CustomSpecSupport: viper.GetBool("custom-spec"),
LogFallback: !data.quiet,
Address: viper.GetString("connection"),
Timeout: viper.GetDuration("timeout"),
AllowInsecure: viper.GetBool("allow-insecure-connections"),
LogFallback: !data.quiet,
})
if err != nil {
return nil, err
@@ -64,7 +63,7 @@ func input(ctx context.Context) (*dataIn, error) {
// Chain time.
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithGenesisProvider(data.eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
)
if err != nil {

View File

@@ -50,7 +50,7 @@ func TestOutput(t *testing.T) {
json: true,
validators: []phase0.ValidatorIndex{1, 2, 3},
},
res: `["1","2","3"]`,
res: "[1,2,3]",
},
}

View File

@@ -19,7 +19,6 @@ import (
"strings"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/util"
@@ -35,14 +34,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return nil, err
}
syncCommitteeResponse, err := data.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommittee(ctx, &api.SyncCommitteeOpts{
State: "head",
Epoch: &epoch,
})
syncCommittee, err := data.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", epoch)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain sync committee information")
}
syncCommittee := syncCommitteeResponse.Data
if syncCommittee == nil {
return nil, errors.New("no sync committee returned")

View File

@@ -36,7 +36,7 @@ func TestProcess(t *testing.T) {
require.NoError(t, err)
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithGenesisProvider(eth2Client.(eth2client.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
)
require.NoError(t, err)

View File

@@ -1,4 +1,4 @@
// Copyright © 2021, 2024 Weald Technology Trading.
// Copyright © 2021 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,8 +15,8 @@ package members
import (
"context"
"errors"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@@ -26,7 +26,7 @@ func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
if err != nil {
return "", errors.Join(errors.New("failed to set up command"), err)
return "", errors.Wrap(err, "failed to obtain input")
}
// Further errors do not need a usage report.
@@ -34,12 +34,7 @@ func Run(cmd *cobra.Command) (string, error) {
dataOut, err := process(ctx, dataIn)
if err != nil {
switch {
case errors.Is(err, context.DeadlineExceeded):
return "", errors.New("operation timed out; try increasing with --timeout option")
default:
return "", errors.Join(errors.New("failed to process"), err)
}
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
@@ -48,7 +43,7 @@ func Run(cmd *cobra.Command) (string, error) {
results, err := output(ctx, dataOut)
if err != nil {
return "", errors.Join(errors.New("failed to obtain output"), err)
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil

View File

@@ -31,7 +31,7 @@ var synccommitteeInclusionCmd = &cobra.Command{
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, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := synccommitteeinclusion.Run(cmd)
if err != nil {
return err

View File

@@ -31,7 +31,7 @@ var synccommitteeMembersCmd = &cobra.Command{
In quiet mode this will return 0 if the synccommittee members are found, otherwise 1.
epoch can be a specific epoch. period can be 'current' for the current sync period or 'next' for the next sync period`,
RunE: func(cmd *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
res, err := synccommitteemembers.Run(cmd)
if err != nil {
return err
@@ -49,7 +49,7 @@ epoch can be a specific epoch. period can be 'current' for the current sync per
func init() {
synccommitteeCmd.AddCommand(synccommitteeMembersCmd)
synccommitteeFlags(synccommitteeMembersCmd)
synccommitteeMembersCmd.Flags().String("epoch", "", "the epoch for which to fetch sync committees")
synccommitteeMembersCmd.Flags().Int64("epoch", -1, "the epoch for which to fetch sync committees")
synccommitteeMembersCmd.Flags().String("period", "", "the sync committee period for which to fetch sync committees ('current', 'next')")
}

View File

@@ -35,7 +35,6 @@ type command struct {
timeout time.Duration
connection string
allowInsecureConnections bool
customSpecSupport bool
// Data access.
consensusClient eth2client.Service
@@ -47,10 +46,9 @@ type command struct {
func newCommand(_ context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
customSpecSupport: viper.GetBool("custom-spec"),
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
}
// Timeout.

View File

@@ -15,7 +15,6 @@ package validatorcredentialsget
import (
"context"
"encoding/hex"
"fmt"
"strings"
@@ -48,7 +47,7 @@ func (c *command) output(_ context.Context) (string, error) {
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
func addressBytesToEIP55(address []byte) string {
bytes := []byte(hex.EncodeToString(address))
bytes := []byte(fmt.Sprintf("%x", address))
hash := ethutil.Keccak256(bytes)
for i := 0; i < len(bytes); i++ {
hashByte := hash[i/2]

Some files were not shown because too many files have changed in this diff Show More