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
174 changed files with 1564 additions and 2604 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
@@ -128,12 +127,11 @@ linters:
disable:
- contextcheck
- cyclop
- depguard
- deadcode
- dupl
- err113
- errorlint
- execinquery
- exhaustive
- exhaustivestruct
- exhaustruct
- forbidigo
- forcetypeassert
@@ -143,22 +141,29 @@ linters:
- gochecknoinits
- gocognit
- goconst
- goerr113
- goheader
- golint
- gomnd
- ifshort
- interfacer
- ireturn
- lll
- maintidx
- mnd
- maligned
- musttag
- nestif
- nilnil
- nlreturn
- nolintlint
- perfsprint
- nosnakecase
- promlinter
- rowserrcheck
- scopelint
- sqlclosecheck
- structcheck
- unparam
- varcheck
- varnamelen
- wastedassign
- wrapcheck

View File

@@ -1,62 +1,8 @@
1.35.5:
- allow keystore to be output to the console
1.35.4:
- provide consensus and execution client info in block info output
1.35.3:
- 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:
dev:
- 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.
@@ -151,13 +151,9 @@ Amazon S3-compatible stores have additional options available, which can be conf
{
"stores": {
"s3": {
"region": "us-west-1",
"bucket": "my-s3-store",
"path": "/wallets",
"credentials": {
"id": "ABCDEF123",
"secret": "XXXXXXXXX"
}
"bucket":"mybucketname",
"path":"path/in/bucket",
"passphrase":"secret"
}
}
}

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

@@ -22,7 +22,6 @@ import (
type dataIn struct {
quiet bool
json bool
// Derivation information.
mnemonic string
path string
@@ -38,9 +37,6 @@ func input(_ context.Context) (*dataIn, error) {
// Quiet.
data.quiet = viper.GetBool("quiet")
// JSON.
data.json = viper.GetBool("json")
// Mnemonic.
if viper.GetString("mnemonic") == "" {
return nil, errors.New("mnemonic is required")

View File

@@ -15,7 +15,6 @@ package accountderive
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
@@ -31,7 +30,6 @@ import (
)
type dataOut struct {
json bool
showPrivateKey bool
showWithdrawalCredentials bool
generateKeystore bool
@@ -86,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
@@ -95,14 +93,10 @@ func outputKeystore(_ context.Context, data *dataOut) (string, error) {
return "", errors.Wrap(err, "failed to marshal keystore JSON")
}
if data.json {
fmt.Fprintf(os.Stdout, "%s\n", string(out))
} else {
keystoreFilename := fmt.Sprintf("keystore-%s-%d.json", strings.ReplaceAll(data.path, "/", "_"), time.Now().Unix())
keystoreFilename := fmt.Sprintf("keystore-%s-%d.json", strings.ReplaceAll(data.path, "/", "_"), time.Now().Unix())
if err := os.WriteFile(keystoreFilename, out, 0o600); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", keystoreFilename))
}
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

@@ -38,7 +38,6 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
}
results := &dataOut{
json: data.json,
showPrivateKey: data.showPrivateKey,
showWithdrawalCredentials: data.showWithdrawalCredentials,
generateKeystore: data.generateKeystore,

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
@@ -50,7 +50,6 @@ func init() {
accountDeriveCmd.Flags().Bool("show-private-key", false, "show private key for derived account")
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
accountDeriveCmd.Flags().Bool("generate-keystore", false, "generate a keystore for the derived account")
accountDeriveCmd.Flags().Bool("json", false, "display the JSON keystore for the derived account on stdout")
}
func accountDeriveBindings(cmd *cobra.Command) {
@@ -63,7 +62,4 @@ func accountDeriveBindings(cmd *cobra.Command) {
if err := viper.BindPFlag("generate-keystore", cmd.Flags().Lookup("generate-keystore")); err != nil {
panic(err)
}
if err := viper.BindPFlag("json", cmd.Flags().Lookup("json")); err != nil {
panic(err)
}
}

View File

@@ -29,7 +29,7 @@ var accountImportCmd = &cobra.Command{
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

@@ -72,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

@@ -69,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,7 +78,7 @@ 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")
}
@@ -131,25 +121,21 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
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[:], attestation.Data.BeaconBlockRoot[:]), nil
return bytes.Equal(header.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
}
}
@@ -157,38 +143,30 @@ func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.At
// Start with first slot of the 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[:], attestation.Data.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

@@ -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

@@ -17,10 +17,8 @@ import (
"bytes"
"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/phase0"
"github.com/pkg/errors"
@@ -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 == http.StatusNotFound {
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 {
@@ -151,7 +145,7 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
}
// Calculate head timely.
analysis.HeadTimely = analysis.HeadCorrect && attestation.Data.Slot == slot-1
analysis.HeadTimely = attestation.Data.Slot == slot-1
// Calculate source timely.
analysis.SourceTimely = attestation.Data.Slot >= slot-5
@@ -163,11 +157,7 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
}
// Calculate target timely.
if block.Version < spec.DataVersionDeneb {
analysis.TargetTimely = attestation.Data.Slot >= slot-32
} else {
analysis.TargetTimely = true
}
analysis.TargetTimely = attestation.Data.Slot >= slot-32
}
// Calculate score and value.
@@ -194,30 +184,12 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
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 == http.StatusNotFound {
return errors.New("empty beacon block")
}
return err
}
parentBlock := parentBlockResponse.Data
if parentBlock == nil {
return fmt.Errorf("unable to obtain parent block %s", parentBlock)
}
@@ -295,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")
@@ -317,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)
@@ -333,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)
@@ -343,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)
@@ -353,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)
@@ -363,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)
@@ -373,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)
@@ -390,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 == http.StatusNotFound {
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
}
}
@@ -428,30 +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 == http.StatusNotFound {
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.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,8 +50,7 @@ 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
@@ -66,5 +64,12 @@ func input(ctx context.Context) (*dataIn, error) {
return nil, err
}
if viper.GetString("blockid") == "" {
data.blockID = "head"
} else {
// Specific slot.
data.blockID = viper.GetString("blockid")
}
return data, nil
}

View File

@@ -19,15 +19,12 @@ import (
"encoding/hex"
"fmt"
"math/big"
"regexp"
"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"
@@ -55,10 +52,9 @@ func output(_ context.Context, data *dataOut) (string, error) {
return "", nil
}
func outputBlockGeneral(ctx context.Context,
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(ctx 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))
@@ -83,7 +78,14 @@ func outputBlockGeneral(ctx context.Context,
res.WriteString(fmt.Sprintf("Parent root: %#x\n", parentRoot))
res.WriteString(fmt.Sprintf("State root: %#x\n", stateRoot))
}
res.WriteString(blockGraffiti(ctx, graffiti))
if len(graffiti) > 0 && hex.EncodeToString(graffiti) != "0000000000000000000000000000000000000000000000000000000000000000" {
graffiti = bytes.TrimRight(graffiti, "\u0000")
if utf8.Valid(graffiti) {
res.WriteString(fmt.Sprintf("Graffiti: %s\n", string(graffiti)))
} else {
res.WriteString(fmt.Sprintf("Graffiti: %#x\n", graffiti))
}
}
return res.String(), nil
}
@@ -112,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)
}
@@ -164,14 +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 {
for k, v := range validators {
res.WriteString(fmt.Sprintf(" %#x (%d)\n", v.Validator.PublicKey[:], k))
}
@@ -224,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))
}
}
@@ -247,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()))
}
@@ -272,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))
@@ -282,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")
@@ -326,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,
@@ -401,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")
}
@@ -430,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,
@@ -502,7 +482,7 @@ func outputDenebBlockText(ctx context.Context,
}
res.WriteString(tmp)
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body)
if err != nil {
return "", err
}
@@ -533,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,
@@ -624,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,
@@ -708,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,
@@ -890,8 +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))
res.WriteString(" Excess data gas: ")
res.WriteString(payload.ExcessDataGas.Dec())
res.WriteString("\n")
}
return res.String(), nil
@@ -900,7 +878,6 @@ func outputDenebBlockExecutionPayload(_ context.Context,
func outputDenebBlobInfo(_ context.Context,
verbose bool,
body *deneb.BeaconBlockBody,
blobs []*deneb.BlobSidecar,
) (
string,
error,
@@ -909,18 +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")
if !verbose {
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
@@ -1056,94 +1030,3 @@ func attestingIndices(input bitfield.Bitlist, indices []phase0.ValidatorIndex) s
}
return strings.TrimSpace(res)
}
func blockGraffiti(_ context.Context, graffiti []byte) string {
if len(graffiti) == 0 || hex.EncodeToString(graffiti) == "0000000000000000000000000000000000000000000000000000000000000000" {
// No graffiti.
return ""
}
// Remove any trailing null characters.
graffiti = bytes.TrimRight(graffiti, "\u0000")
if !utf8.Valid(graffiti) {
// Graffiti is not valid UTF-8, return hex.
return fmt.Sprintf("Graffiti: %#x\n", graffiti)
}
// See if there is client identification information present in the graffiti.
// The client identification will always be the last entry in the graffiti, with a space beforehand.
parts := bytes.Split(graffiti, []byte{' '})
// Consensus and execution client values come from
// https://github.com/ethereum/execution-apis/blob/main/src/engine/identification.md
consensusClients := map[string]string{
"GR": "grandine",
"LH": "lighthouse",
"LS": "lodestar",
"NB": "nimbus",
"PM": "prysm",
"TK": "teku",
}
consensusRegex := regexp.MustCompile(`(GR|LH|LS|NB|PM|TK)([0-9a-f]*)`)
consensusData := consensusRegex.Find(parts[len(parts)-1])
executionClients := map[string]string{
"BU": "besu",
"EG": "erigon",
"EJ": "ethereumJS",
"GE": "go-ethereum",
"NM": "nethermind",
"RH": "reth",
}
executionRegex := regexp.MustCompile(`(BU|EG|EJ|GE|NM|RH)([0-9a-f]*)`)
executionData := executionRegex.Find(parts[len(parts)-1])
if len(consensusData) == 0 && len(executionData) == 0 {
// There is no identifier; return the graffiti as-is.
return fmt.Sprintf("Graffiti: %s\n", string(graffiti))
}
res := strings.Builder{}
truncatedGraffiti := bytes.Join(parts[0:len(parts)-1], []byte(" "))
if len(truncatedGraffiti) > 0 {
res.WriteString(fmt.Sprintf("Graffiti: %s\n", string(truncatedGraffiti)))
}
if len(consensusData) > 0 {
consensusClient := consensusData[0:2]
consensusHash := ""
if len(consensusData) > 2 {
consensusHash = string(consensusData[2:])
}
res.WriteString("Consensus client: ")
res.WriteString(consensusClients[string(consensusClient)])
if consensusHash != "" {
res.WriteString(" (version hash ")
res.WriteString(consensusHash)
res.WriteString(")")
}
res.WriteString("\n")
}
if len(executionData) > 0 {
executionClient := executionData[0:2]
executionHash := ""
if len(executionData) > 2 {
executionHash = string(executionData[2:])
}
res.WriteString("Execution client: ")
res.WriteString(executionClients[string(executionClient)])
if executionHash != "" {
res.WriteString(" (version hash ")
res.WriteString(executionHash)
res.WriteString(")")
}
res.WriteString("\n")
}
return res.String()
}

View File

@@ -175,58 +175,3 @@ func TestOutputBlockETH1Data(t *testing.T) {
})
}
}
func TestBlockGraffiti(t *testing.T) {
tests := []struct {
name string
graffiti []byte
res string
}{
{
name: "Empty",
graffiti: []byte(""),
},
{
name: "NoID",
graffiti: []byte("No identifier"),
res: "Graffiti: No identifier\n",
},
{
name: "SingleClient",
graffiti: []byte("Graffiti TK"),
res: "Graffiti: Graffiti\nConsensus client: teku\n",
},
{
name: "SingleClientImmediate",
graffiti: []byte("TK"),
res: "Consensus client: teku\n",
},
{
name: "SingleClientAndHashImmediate",
graffiti: []byte("TKa9f98260"),
res: "Consensus client: teku (version hash a9f98260)\n",
},
{
name: "DualClients",
graffiti: []byte("LHGE"),
res: "Consensus client: lighthouse\nExecution client: go-ethereum\n",
},
{
name: "DualClientsReverseOrder",
graffiti: []byte("GELH"),
res: "Consensus client: lighthouse\nExecution client: go-ethereum\n",
},
{
name: "DualClientsTruncatedHash",
graffiti: []byte("Freedom To Transact TKa9f9NM220b"),
res: "Graffiti: Freedom To Transact\nConsensus client: teku (version hash a9f9)\nExecution client: nethermind (version hash 220b)\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := blockGraffiti(context.Background(), test.graffiti)
require.Equal(t, test.res, res)
})
}
}

View File

@@ -17,15 +17,10 @@ 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"
@@ -33,7 +28,6 @@ import (
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
)
var (
@@ -46,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{
@@ -56,72 +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 {
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Deneb); err != nil {
return nil, errors.Wrap(err, "failed to output block")
}
default:
@@ -144,49 +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)
}
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Deneb)
default:
err = errors.New("unknown block version")
}
@@ -290,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)
@@ -310,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")
}
@@ -318,41 +268,3 @@ func outputDenebBlock(ctx context.Context,
}
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

@@ -50,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

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,24 +73,25 @@ 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
default:
@@ -102,8 +99,6 @@ func (c *command) process(ctx context.Context) error {
}
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 {
@@ -135,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")
@@ -151,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")
}
@@ -165,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

@@ -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
}
@@ -73,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

@@ -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

@@ -42,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

@@ -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"
@@ -104,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
}
@@ -141,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 {
@@ -230,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,7 +34,7 @@ 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{
@@ -47,33 +45,32 @@ In quiet mode this will return 0 if the chain information can be obtained, other
})
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 {
@@ -82,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,7 +35,7 @@ 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{
@@ -48,7 +46,7 @@ In quiet mode this will return 0 if the chain specification can be obtained, oth
})
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") {
@@ -56,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,7 +38,7 @@ 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{
@@ -52,18 +50,15 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
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()
@@ -131,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() {
@@ -167,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"
@@ -50,9 +49,6 @@ type command struct {
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
// Caches.
blocksCache map[string]*spec.VersionedSignedBeaconBlock
// Results.
summary *epochSummary
}
@@ -71,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 {
@@ -93,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.

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))
}
@@ -190,13 +184,11 @@ func (c *command) processSlots(ctx context.Context,
}
slotCommittees, exists := allCommittees[attestation.Data.Slot]
if !exists {
response, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
State: fmt.Sprintf("%d", attestation.Data.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", 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)
}
@@ -262,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")
}
@@ -279,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))
}
@@ -287,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) {
@@ -342,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")
@@ -376,52 +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)
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")
@@ -68,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.
@@ -85,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

@@ -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,7 +32,7 @@ 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{
@@ -49,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
@@ -37,7 +37,6 @@ type command struct {
// Operation.
epoch string
slot string
jsonOutput bool
// Data access.
@@ -56,22 +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"),
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
}
@@ -99,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,
@@ -159,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)
}
@@ -224,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)
}

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

@@ -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

@@ -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
@@ -145,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

@@ -63,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

@@ -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]

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 validatorcredentialsget
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

@@ -48,7 +48,6 @@ type command struct {
genesisValidatorsRoot string
prepareOffline bool
signedOperationsInput string
maxDistance uint64
// Beacon node connection.
timeout time.Duration
@@ -91,7 +90,6 @@ func newCommand(_ context.Context) (*command, error) {
withdrawalAddressStr: viper.GetString("withdrawal-address"),
forkVersion: viper.GetString("fork-version"),
genesisValidatorsRoot: viper.GetString("genesis-validators-root"),
maxDistance: viper.GetUint64("max-distance"),
}
// Timeout is required.

View File

@@ -163,7 +163,7 @@ func (c *command) generateOperationFromMnemonicAndPath(ctx context.Context) erro
}
validatorKeyPath := c.path
match := validatorPath.MatchString(c.path)
match := validatorPath.Match([]byte(c.path))
if !match {
return fmt.Errorf("path %s does not match EIP-2334 format for a validator", c.path)
}
@@ -197,9 +197,6 @@ func (c *command) generateOperationFromMnemonicAndValidator(ctx context.Context)
// Scan the keys from the seed to find the path.
maxDistance := 1024
if c.maxDistance > 0 {
maxDistance = int(c.maxDistance)
}
// Start scanning the validator keys.
var withdrawalAccount e2wtypes.Account
for i := 0; ; i++ {
@@ -250,13 +247,10 @@ func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
}
maxDistance := 1024
// Start scanning the validator keys.
lastFoundIndex := 0
foundValidatorCount := 0
maxDistance := 1024
if c.maxDistance > 0 {
maxDistance = int(c.maxDistance)
}
for i := 0; ; i++ {
// If no validators have been found in the last maxDistance indices, stop scanning.
if i-lastFoundIndex > maxDistance {
@@ -738,7 +732,7 @@ func (c *command) setup(ctx context.Context) error {
// Set up chaintime.
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithGenesisProvider(c.consensusClient.(consensusclient.GenesisProvider)),
standardchaintime.WithGenesisTimeProvider(c.consensusClient.(consensusclient.GenesisTimeProvider)),
standardchaintime.WithSpecProvider(c.consensusClient.(consensusclient.SpecProvider)),
)
if err != nil {
@@ -834,7 +828,7 @@ func (c *command) obtainForkVersion(_ context.Context) (phase0.Version, 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]

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 validatorcredentialsset
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

@@ -109,10 +109,9 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
forkVersionMap := map[spec.Version]string{
[4]byte{0x00, 0x00, 0x00, 0x00}: "mainnet",
[4]byte{0x00, 0x00, 0x20, 0x09}: "pyrmont",
[4]byte{0x00, 0x00, 0x10, 0x20}: "goerli",
[4]byte{0x00, 0x00, 0x10, 0x20}: "prater",
[4]byte{0x80, 0x00, 0x00, 0x69}: "ropsten",
[4]byte{0x90, 0x00, 0x00, 0x69}: "sepolia",
[4]byte{0x01, 0x01, 0x70, 0x00}: "holesky",
}
if datum.validatorPubKey == nil {
@@ -138,7 +137,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
if network, exists := forkVersionMap[*datum.forkVersion]; exists {
networkName = network
}
output := fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x","network_name":"%s","deposit_cli_version":"2.7.0"}`,
output := fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x","eth2_network_name":"%s","deposit_cli_version":"1.1.0"}`,
*datum.validatorPubKey,
datum.withdrawalCredentials,
datum.amount,

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