mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d3bd966e0 | ||
|
|
0488b13ba1 | ||
|
|
f8a611d63d | ||
|
|
772f07f8ec | ||
|
|
f6e23d803b | ||
|
|
86e872294d | ||
|
|
d7d9c66052 | ||
|
|
3e173f141e | ||
|
|
5d95e93b76 | ||
|
|
005eed1360 | ||
|
|
34b752edcc | ||
|
|
f17fe2f5cb | ||
|
|
8322353af5 | ||
|
|
7d0e607f96 | ||
|
|
fcafa037f8 | ||
|
|
8e1c8c5300 | ||
|
|
5af1476bc3 | ||
|
|
24a4220804 | ||
|
|
fa1d4d60fa | ||
|
|
7d00d1261f | ||
|
|
ac85a9539b | ||
|
|
099e434f43 | ||
|
|
5bab79bd79 | ||
|
|
1defa3b121 | ||
|
|
6cb7b034aa | ||
|
|
bd9659d71f | ||
|
|
68ca31e034 | ||
|
|
a37a5f4af3 | ||
|
|
47cf033feb | ||
|
|
72a9390f97 | ||
|
|
0276a72de6 | ||
|
|
fd0a89c258 | ||
|
|
1ac505f0bd | ||
|
|
b4124b7a27 | ||
|
|
f3ae2baf8f | ||
|
|
8aaf16ab3f | ||
|
|
61179045dd | ||
|
|
e50b4f015b | ||
|
|
6e522d6b5a | ||
|
|
d467a8ef9c | ||
|
|
d51683426c | ||
|
|
4879443e51 | ||
|
|
cf9d5718a3 | ||
|
|
7e09068d30 | ||
|
|
2362a1a058 | ||
|
|
80373ae237 | ||
|
|
bd2eea3a60 | ||
|
|
a23650a5ae | ||
|
|
327df13fba | ||
|
|
eaa0a19711 | ||
|
|
8aba766208 | ||
|
|
dd68af4884 | ||
|
|
564228ff00 | ||
|
|
697b6d4230 | ||
|
|
c548058190 | ||
|
|
daa2ac7b6e | ||
|
|
3b8a98bb83 | ||
|
|
f1021387b3 | ||
|
|
cd27401514 | ||
|
|
faf3c8afa4 |
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
jobs:
|
||||
# Set variables that will be available to all builds.
|
||||
env_vars:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_version: ${{ steps.release_version.outputs.release_version }}
|
||||
binary: ${{ steps.binary.outputs.binary }}
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
# Build.
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: [env_vars]
|
||||
steps:
|
||||
- name: Check out repository into the Go module directory
|
||||
|
||||
25
.github/workflows/golangci-lint.yml
vendored
25
.github/workflows/golangci-lint.yml
vendored
@@ -1,23 +1,24 @@
|
||||
name: golangci-lint
|
||||
name: 'golangci-lint'
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
- 'master'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: 'read'
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-22.04
|
||||
name: 'lint'
|
||||
runs-on: 'ubuntu-24.04'
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: 'actions/setup-go@v5'
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
cache: false
|
||||
go-version: '^1.22'
|
||||
- uses: 'actions/checkout@v4'
|
||||
- uses: 'golangci/golangci-lint-action@v6'
|
||||
with:
|
||||
args: --timeout=60m
|
||||
only-new-issues: true
|
||||
|
||||
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -3,8 +3,9 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- 't*'
|
||||
- 'v*'
|
||||
- 't*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# Set variables that will be available to all builds.
|
||||
@@ -44,12 +45,13 @@ jobs:
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
go-version: '^1.22'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
@@ -119,12 +121,13 @@ jobs:
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
go-version: '^1.22'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
@@ -164,12 +167,13 @@ jobs:
|
||||
needs: [create_release, env_vars]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
cache: false
|
||||
go-version: '^1.22'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Select correct tag
|
||||
run: git checkout ${{ github.ref_name }}
|
||||
|
||||
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@@ -1,15 +1,18 @@
|
||||
name: test
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- uses: actions/checkout@v3
|
||||
- uses: n8maninger/action-golang-test@v1
|
||||
cache: false
|
||||
go-version: '^1.22'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: n8maninger/action-golang-test@v2
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -15,6 +15,12 @@ coverage.html
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Intellij
|
||||
.idea/
|
||||
|
||||
# Makefile
|
||||
Makefile
|
||||
|
||||
# Vim
|
||||
*.sw?
|
||||
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
# 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.
|
||||
@@ -39,15 +49,6 @@ 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
|
||||
@@ -68,7 +69,7 @@ run:
|
||||
# Define the Go version limit.
|
||||
# Mainly related to generics support since go1.18.
|
||||
# Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.18
|
||||
go: '1.19'
|
||||
# go: '1.19'
|
||||
|
||||
|
||||
# output configuration options
|
||||
@@ -105,6 +106,11 @@ output:
|
||||
|
||||
# All available settings of specific linters.
|
||||
linters-settings:
|
||||
gosec:
|
||||
excludes:
|
||||
# Flags for potentially-unsafe casting of ints, but generates a lot of false positives.
|
||||
- 'G115'
|
||||
|
||||
lll:
|
||||
line-length: 132
|
||||
|
||||
@@ -127,13 +133,13 @@ linters:
|
||||
disable:
|
||||
- contextcheck
|
||||
- cyclop
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- err113
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exhaustivestruct
|
||||
- exhaustruct
|
||||
- exportloopref
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
@@ -142,29 +148,21 @@ linters:
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
- goconst
|
||||
- goerr113
|
||||
- goheader
|
||||
- golint
|
||||
- gomnd
|
||||
- ifshort
|
||||
- interfacer
|
||||
- ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- maligned
|
||||
- mnd
|
||||
- musttag
|
||||
- nestif
|
||||
- nilnil
|
||||
- nlreturn
|
||||
- nolintlint
|
||||
- nosnakecase
|
||||
- perfsprint
|
||||
- promlinter
|
||||
- rowserrcheck
|
||||
- scopelint
|
||||
- sqlclosecheck
|
||||
- structcheck
|
||||
- unparam
|
||||
- varcheck
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
- wrapcheck
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
||||
1.37.0:
|
||||
- support Electra
|
||||
- add `--compounding` flag when creating validator deposit data
|
||||
|
||||
1.36.6:
|
||||
- allow specification of blockid for validator info
|
||||
- validator depositdata orders deposits from an HD wallet by path
|
||||
|
||||
1.36.5:
|
||||
- avoid corner case mnemonic derivation with 25th word
|
||||
|
||||
1.36.2:
|
||||
- avoid crash when signing and verifing signatures using keys rather than accounts
|
||||
|
||||
1.36.1:
|
||||
- more JSON data for epoch summary
|
||||
- fix crash when block ifno had no blobs
|
||||
|
||||
1.36.0:
|
||||
- support keystore wallets
|
||||
|
||||
1.35.6:
|
||||
- provide more JSON data in "epoch summary"
|
||||
|
||||
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,4 +1,4 @@
|
||||
FROM golang:1.20-bookworm as builder
|
||||
FROM golang:1.23-bookworm AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -151,9 +151,13 @@ Amazon S3-compatible stores have additional options available, which can be conf
|
||||
{
|
||||
"stores": {
|
||||
"s3": {
|
||||
"bucket":"mybucketname",
|
||||
"path":"path/in/bucket",
|
||||
"passphrase":"secret"
|
||||
"region": "us-west-1",
|
||||
"bucket": "my-s3-store",
|
||||
"path": "/wallets",
|
||||
"credentials": {
|
||||
"id": "ABCDEF123",
|
||||
"secret": "XXXXXXXXX"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -270,6 +271,10 @@ 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{})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if !viper.GetBool("verbose") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
type dataIn struct {
|
||||
quiet bool
|
||||
json bool
|
||||
// Derivation information.
|
||||
mnemonic string
|
||||
path string
|
||||
@@ -37,6 +38,9 @@ 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")
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
json bool
|
||||
showPrivateKey bool
|
||||
showWithdrawalCredentials bool
|
||||
generateKeystore bool
|
||||
@@ -94,10 +95,14 @@ func outputKeystore(_ context.Context, data *dataOut) (string, error) {
|
||||
return "", errors.Wrap(err, "failed to marshal keystore JSON")
|
||||
}
|
||||
|
||||
keystoreFilename := fmt.Sprintf("keystore-%s-%d.json", strings.ReplaceAll(data.path, "/", "_"), time.Now().Unix())
|
||||
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())
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
json: data.json,
|
||||
showPrivateKey: data.showPrivateKey,
|
||||
showWithdrawalCredentials: data.showWithdrawalCredentials,
|
||||
generateKeystore: data.generateKeystore,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to obtain input"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if dataIn.quiet {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(results, "\n"), nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to obtain input"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if !viper.GetBool("verbose") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := accountcreate.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := accountderive.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,6 +50,7 @@ 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) {
|
||||
@@ -62,4 +63,7 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := accountimport.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := accountkey.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -27,7 +28,7 @@ type dataOut struct {
|
||||
debug bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
attestation *phase0.Attestation
|
||||
attestation *spec.VersionedAttestation
|
||||
slot phase0.Slot
|
||||
attestationIndex uint64
|
||||
inclusionDelay phase0.Slot
|
||||
|
||||
@@ -17,10 +17,12 @@ 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"
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
@@ -66,6 +68,11 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
Block: 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
|
||||
@@ -87,9 +94,18 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.Wrap(err, "failed to obtain block attestations")
|
||||
}
|
||||
for i, attestation := range attestations {
|
||||
if attestation.Data.Slot == duty.Slot &&
|
||||
attestation.Data.Index == duty.CommitteeIndex &&
|
||||
attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
aggregationBits, err := attestation.AggregationBits()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain attestation aggregation bits")
|
||||
}
|
||||
|
||||
if attestationData.Slot == duty.Slot &&
|
||||
attestationData.Index == duty.CommitteeIndex &&
|
||||
aggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||
headCorrect := false
|
||||
targetCorrect := false
|
||||
if data.verbose {
|
||||
@@ -122,50 +138,66 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *phase0.Attestation) (bool, error) {
|
||||
slot := attestation.Data.Slot
|
||||
func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *spec.VersionedAttestation) (bool, error) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
|
||||
slot := attestationData.Slot
|
||||
for {
|
||||
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||
Block: 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 == nil {
|
||||
// No block.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
if !response.Data.Canonical {
|
||||
// Not canonical.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
return bytes.Equal(response.Data.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
|
||||
|
||||
return bytes.Equal(response.Data.Root[:], attestationData.BeaconBlockRoot[:]), nil
|
||||
}
|
||||
}
|
||||
|
||||
func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.Attestation) (bool, error) {
|
||||
func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *spec.VersionedAttestation) (bool, error) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
|
||||
// Start with first slot of the target epoch.
|
||||
slot := data.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
||||
slot := data.chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||
for {
|
||||
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||
Block: 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 == nil {
|
||||
// No block.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
if !response.Data.Canonical {
|
||||
// Not canonical.
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
return bytes.Equal(response.Data.Root[:], attestation.Data.Target.Root[:]), nil
|
||||
|
||||
return bytes.Equal(response.Data.Root[:], attestationData.Target.Root[:]), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := attesterduties.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := attesterinclusion.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -55,7 +55,7 @@ type command struct {
|
||||
weightDenominator uint64
|
||||
|
||||
// Processing.
|
||||
priorAttestations map[string]*attestationData
|
||||
priorAttestations map[string]*attestationDataInfo
|
||||
// Head roots provides the root of the head slot at given slots.
|
||||
headRoots map[phase0.Slot]phase0.Root
|
||||
// Target roots provides the root of the target epoch at given slots.
|
||||
@@ -77,20 +77,20 @@ type blockAnalysis struct {
|
||||
}
|
||||
|
||||
type attestationAnalysis struct {
|
||||
Head phase0.Root `json:"head"`
|
||||
Target phase0.Root `json:"target"`
|
||||
Distance int `json:"distance"`
|
||||
Duplicate *attestationData `json:"duplicate,omitempty"`
|
||||
NewVotes int `json:"new_votes"`
|
||||
Votes int `json:"votes"`
|
||||
PossibleVotes int `json:"possible_votes"`
|
||||
HeadCorrect bool `json:"head_correct"`
|
||||
HeadTimely bool `json:"head_timely"`
|
||||
SourceTimely bool `json:"source_timely"`
|
||||
TargetCorrect bool `json:"target_correct"`
|
||||
TargetTimely bool `json:"target_timely"`
|
||||
Score float64 `json:"score"`
|
||||
Value float64 `json:"value"`
|
||||
Head phase0.Root `json:"head"`
|
||||
Target phase0.Root `json:"target"`
|
||||
Distance int `json:"distance"`
|
||||
Duplicate *attestationDataInfo `json:"duplicate,omitempty"`
|
||||
NewVotes int `json:"new_votes"`
|
||||
Votes int `json:"votes"`
|
||||
PossibleVotes int `json:"possible_votes"`
|
||||
HeadCorrect bool `json:"head_correct"`
|
||||
HeadTimely bool `json:"head_timely"`
|
||||
SourceTimely bool `json:"source_timely"`
|
||||
TargetCorrect bool `json:"target_correct"`
|
||||
TargetTimely bool `json:"target_timely"`
|
||||
Score float64 `json:"score"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type syncCommitteeAnalysis struct {
|
||||
@@ -100,7 +100,7 @@ type syncCommitteeAnalysis struct {
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
type attestationData struct {
|
||||
type attestationDataInfo struct {
|
||||
Block phase0.Slot `json:"block"`
|
||||
Index int `json:"index"`
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func newCommand(_ context.Context) (*command, error) {
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
priorAttestations: make(map[string]*attestationData),
|
||||
priorAttestations: make(map[string]*attestationDataInfo),
|
||||
headRoots: make(map[phase0.Slot]phase0.Root),
|
||||
targetRoots: make(map[phase0.Slot]phase0.Root),
|
||||
votes: make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist),
|
||||
|
||||
@@ -34,20 +34,20 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
}
|
||||
|
||||
type attestationAnalysisJSON struct {
|
||||
Head string `json:"head"`
|
||||
Target string `json:"target"`
|
||||
Distance int `json:"distance"`
|
||||
Duplicate *attestationData `json:"duplicate,omitempty"`
|
||||
NewVotes int `json:"new_votes"`
|
||||
Votes int `json:"votes"`
|
||||
PossibleVotes int `json:"possible_votes"`
|
||||
HeadCorrect bool `json:"head_correct"`
|
||||
HeadTimely bool `json:"head_timely"`
|
||||
SourceTimely bool `json:"source_timely"`
|
||||
TargetCorrect bool `json:"target_correct"`
|
||||
TargetTimely bool `json:"target_timely"`
|
||||
Score float64 `json:"score"`
|
||||
Value float64 `json:"value"`
|
||||
Head string `json:"head"`
|
||||
Target string `json:"target"`
|
||||
Distance int `json:"distance"`
|
||||
Duplicate *attestationDataInfo `json:"duplicate,omitempty"`
|
||||
NewVotes int `json:"new_votes"`
|
||||
Votes int `json:"votes"`
|
||||
PossibleVotes int `json:"possible_votes"`
|
||||
HeadCorrect bool `json:"head_correct"`
|
||||
HeadTimely bool `json:"head_timely"`
|
||||
SourceTimely bool `json:"source_timely"`
|
||||
TargetCorrect bool `json:"target_correct"`
|
||||
TargetTimely bool `json:"target_timely"`
|
||||
Score float64 `json:"score"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
func (a *attestationAnalysis) MarshalJSON() ([]byte, error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/api"
|
||||
@@ -39,7 +40,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
})
|
||||
if err != nil {
|
||||
var apiError *api.Error
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == http.StatusNotFound {
|
||||
return errors.New("empty beacon block")
|
||||
}
|
||||
return errors.Wrap(err, "failed to obtain beacon block")
|
||||
@@ -62,8 +63,12 @@ func (c *command) process(ctx context.Context) error {
|
||||
// Calculate how many parents we need to fetch.
|
||||
minSlot := slot
|
||||
for _, attestation := range attestations {
|
||||
if attestation.Data.Slot < minSlot {
|
||||
minSlot = attestation.Data.Slot
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
if attestationData.Slot < minSlot {
|
||||
minSlot = attestationData.Slot
|
||||
}
|
||||
}
|
||||
if c.debug {
|
||||
@@ -102,10 +107,16 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
|
||||
if c.debug {
|
||||
fmt.Printf("Processing attestation %d\n", i)
|
||||
}
|
||||
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
|
||||
analysis := &attestationAnalysis{
|
||||
Head: attestation.Data.BeaconBlockRoot,
|
||||
Target: attestation.Data.Target.Root,
|
||||
Distance: int(slot - attestation.Data.Slot),
|
||||
Head: attestationData.BeaconBlockRoot,
|
||||
Target: attestationData.Target.Root,
|
||||
Distance: int(slot - attestationData.Slot),
|
||||
}
|
||||
|
||||
root, err := attestation.HashTreeRoot()
|
||||
@@ -115,45 +126,47 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
|
||||
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
|
||||
analysis.Duplicate = info
|
||||
} else {
|
||||
data := attestation.Data
|
||||
_, exists := blockVotes[data.Slot]
|
||||
if !exists {
|
||||
blockVotes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
||||
aggregationBits, err := attestation.AggregationBits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, exists = blockVotes[data.Slot][data.Index]
|
||||
_, exists := blockVotes[attestationData.Slot]
|
||||
if !exists {
|
||||
blockVotes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
|
||||
blockVotes[attestationData.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
||||
}
|
||||
_, exists = blockVotes[attestationData.Slot][attestationData.Index]
|
||||
if !exists {
|
||||
blockVotes[attestationData.Slot][attestationData.Index] = bitfield.NewBitlist(aggregationBits.Len())
|
||||
}
|
||||
|
||||
// Count new votes.
|
||||
analysis.PossibleVotes = int(attestation.AggregationBits.Len())
|
||||
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
|
||||
if attestation.AggregationBits.BitAt(j) {
|
||||
analysis.PossibleVotes = int(aggregationBits.Len())
|
||||
for j := range aggregationBits.Len() {
|
||||
if aggregationBits.BitAt(j) {
|
||||
analysis.Votes++
|
||||
if blockVotes[data.Slot][data.Index].BitAt(j) {
|
||||
if blockVotes[attestationData.Slot][attestationData.Index].BitAt(j) {
|
||||
// Already attested to in this block; skip.
|
||||
continue
|
||||
}
|
||||
if c.votes[data.Slot][data.Index].BitAt(j) {
|
||||
if c.votes[attestationData.Slot][attestationData.Index].BitAt(j) {
|
||||
// Already attested to in a previous block; skip.
|
||||
continue
|
||||
}
|
||||
analysis.NewVotes++
|
||||
blockVotes[data.Slot][data.Index].SetBitAt(j, true)
|
||||
blockVotes[attestationData.Slot][attestationData.Index].SetBitAt(j, true)
|
||||
}
|
||||
}
|
||||
// Calculate head correct.
|
||||
var err error
|
||||
analysis.HeadCorrect, err = c.calcHeadCorrect(ctx, attestation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate head timely.
|
||||
analysis.HeadTimely = analysis.HeadCorrect && attestation.Data.Slot == slot-1
|
||||
analysis.HeadTimely = analysis.HeadCorrect && attestationData.Slot == slot-1
|
||||
|
||||
// Calculate source timely.
|
||||
analysis.SourceTimely = attestation.Data.Slot >= slot-5
|
||||
analysis.SourceTimely = attestationData.Slot >= slot-5
|
||||
|
||||
// Calculate target correct.
|
||||
analysis.TargetCorrect, err = c.calcTargetCorrect(ctx, attestation)
|
||||
@@ -163,7 +176,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
|
||||
analysis.TargetTimely = attestationData.Slot >= slot-32
|
||||
} else {
|
||||
analysis.TargetTimely = true
|
||||
}
|
||||
@@ -193,7 +206,7 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err := block.Deneb.HashTreeRoot()
|
||||
root, err := block.Root()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -211,7 +224,7 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
|
||||
})
|
||||
if err != nil {
|
||||
var apiError *api.Error
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == http.StatusNotFound {
|
||||
return errors.New("empty beacon block")
|
||||
}
|
||||
return err
|
||||
@@ -254,23 +267,31 @@ func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSig
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationData{
|
||||
c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationDataInfo{
|
||||
Block: slot,
|
||||
Index: i,
|
||||
}
|
||||
|
||||
data := attestation.Data
|
||||
_, exists := c.votes[data.Slot]
|
||||
if !exists {
|
||||
c.votes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
_, exists = c.votes[data.Slot][data.Index]
|
||||
if !exists {
|
||||
c.votes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
|
||||
aggregationBits, err := attestation.AggregationBits()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attestation aggregation bits")
|
||||
}
|
||||
for j := uint64(0); j < attestation.AggregationBits.Len(); j++ {
|
||||
if attestation.AggregationBits.BitAt(j) {
|
||||
c.votes[data.Slot][data.Index].SetBitAt(j, true)
|
||||
|
||||
_, exists := c.votes[attestationData.Slot]
|
||||
if !exists {
|
||||
c.votes[attestationData.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
||||
}
|
||||
_, exists = c.votes[attestationData.Slot][attestationData.Index]
|
||||
if !exists {
|
||||
c.votes[attestationData.Slot][attestationData.Index] = bitfield.NewBitlist(aggregationBits.Len())
|
||||
}
|
||||
for j := range aggregationBits.Len() {
|
||||
if aggregationBits.BitAt(j) {
|
||||
c.votes[attestationData.Slot][attestationData.Index].SetBitAt(j, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,8 +405,13 @@ func (c *command) setup(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attestation) (bool, error) {
|
||||
slot := attestation.Data.Slot
|
||||
func (c *command) calcHeadCorrect(ctx context.Context, attestation *spec.VersionedAttestation) (bool, error) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
|
||||
slot := attestationData.Slot
|
||||
root, exists := c.headRoots[slot]
|
||||
if !exists {
|
||||
for {
|
||||
@@ -394,7 +420,7 @@ func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attes
|
||||
})
|
||||
if err != nil {
|
||||
var apiError *api.Error
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
|
||||
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)
|
||||
}
|
||||
@@ -412,27 +438,32 @@ func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attes
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
c.headRoots[attestation.Data.Slot] = response.Data.Root
|
||||
c.headRoots[slot] = response.Data.Root
|
||||
root = response.Data.Root
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Equal(root[:], attestation.Data.BeaconBlockRoot[:]), nil
|
||||
return bytes.Equal(root[:], attestationData.BeaconBlockRoot[:]), nil
|
||||
}
|
||||
|
||||
func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Attestation) (bool, error) {
|
||||
root, exists := c.targetRoots[attestation.Data.Slot]
|
||||
func (c *command) calcTargetCorrect(ctx context.Context, attestation *spec.VersionedAttestation) (bool, error) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
|
||||
root, exists := c.targetRoots[attestationData.Slot]
|
||||
if !exists {
|
||||
// Start with first slot of the target epoch.
|
||||
slot := c.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
||||
slot := c.chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||
for {
|
||||
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||
Block: fmt.Sprintf("%d", slot),
|
||||
})
|
||||
if err != nil {
|
||||
var apiError *api.Error
|
||||
if errors.As(err, &apiError) && apiError.StatusCode == 404 {
|
||||
if 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)
|
||||
}
|
||||
@@ -449,12 +480,12 @@ func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Att
|
||||
slot--
|
||||
continue
|
||||
}
|
||||
c.targetRoots[attestation.Data.Slot] = response.Data.Root
|
||||
c.targetRoots[attestationData.Slot] = response.Data.Root
|
||||
root = response.Data.Root
|
||||
break
|
||||
}
|
||||
}
|
||||
return bytes.Equal(root[:], attestation.Data.Target.Root[:]), nil
|
||||
return bytes.Equal(root[:], attestationData.Target.Root[:]), nil
|
||||
}
|
||||
|
||||
func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
@@ -490,6 +521,13 @@ func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.Versioned
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
case spec.DataVersionElectra:
|
||||
c.analysis.SyncCommitee.Contributions = int(block.Electra.Message.Body.SyncAggregate.SyncCommitteeBits.Count())
|
||||
c.analysis.SyncCommitee.PossibleContributions = int(block.Electra.Message.Body.SyncAggregate.SyncCommitteeBits.Len())
|
||||
c.analysis.SyncCommitee.Score = float64(c.syncRewardWeight) / float64(c.weightDenominator)
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported block version %d", block.Version)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
||||
"github.com/attestantio/go-eth2-client/spec/electra"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
@@ -54,7 +56,7 @@ func output(_ context.Context, data *dataOut) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func outputBlockGeneral(_ context.Context,
|
||||
func outputBlockGeneral(ctx context.Context,
|
||||
verbose bool,
|
||||
slot phase0.Slot,
|
||||
proposerIndex phase0.ValidatorIndex,
|
||||
@@ -82,14 +84,7 @@ func outputBlockGeneral(_ context.Context,
|
||||
res.WriteString(fmt.Sprintf("Parent root: %#x\n", parentRoot))
|
||||
res.WriteString(fmt.Sprintf("State root: %#x\n", stateRoot))
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
res.WriteString(blockGraffiti(ctx, graffiti))
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
@@ -139,7 +134,61 @@ func outputBlockAttestations(ctx context.Context, eth2Client eth2client.Service,
|
||||
res.WriteString(fmt.Sprintf(" Attesters: %d/%d\n", att.AggregationBits.Count(), att.AggregationBits.Len()))
|
||||
res.WriteString(fmt.Sprintf(" Aggregation bits: %s\n", bitlistToString(att.AggregationBits)))
|
||||
if _, exists := committees[att.Data.Index]; exists {
|
||||
res.WriteString(fmt.Sprintf(" Attesting indices: %s\n", attestingIndices(att.AggregationBits, committees[att.Data.Index])))
|
||||
res.WriteString(fmt.Sprintf(" Attesting indices: %s\n", attestingIndices(att.AggregationBits, committees, []int{int(att.Data.Index)})))
|
||||
}
|
||||
res.WriteString(fmt.Sprintf(" Slot: %d\n", att.Data.Slot))
|
||||
res.WriteString(fmt.Sprintf(" Beacon block root: %#x\n", att.Data.BeaconBlockRoot))
|
||||
res.WriteString(fmt.Sprintf(" Source epoch: %d\n", att.Data.Source.Epoch))
|
||||
res.WriteString(fmt.Sprintf(" Source root: %#x\n", att.Data.Source.Root))
|
||||
res.WriteString(fmt.Sprintf(" Target epoch: %d\n", att.Data.Target.Epoch))
|
||||
res.WriteString(fmt.Sprintf(" Target root: %#x\n", att.Data.Target.Root))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputElectraBlockAttestations(ctx context.Context, eth2Client eth2client.Service, verbose bool, attestations []*electra.Attestation) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
validatorCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
res.WriteString(fmt.Sprintf("Attestations: %d\n", len(attestations)))
|
||||
if verbose {
|
||||
beaconCommitteesProvider, isProvider := eth2Client.(eth2client.BeaconCommitteesProvider)
|
||||
if isProvider {
|
||||
for i, att := range attestations {
|
||||
res.WriteString(fmt.Sprintf(" %d:\n", i))
|
||||
|
||||
// Fetch committees for this epoch if not already obtained.
|
||||
committees, exists := validatorCommittees[att.Data.Slot]
|
||||
if !exists {
|
||||
response, err := beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
|
||||
State: fmt.Sprintf("%d", att.Data.Slot),
|
||||
})
|
||||
if err != nil {
|
||||
// Failed to get it; create an empty committee to stop us continually attempting to re-fetch.
|
||||
validatorCommittees[att.Data.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
} else {
|
||||
for _, beaconCommittee := range response.Data {
|
||||
if _, exists := validatorCommittees[beaconCommittee.Slot]; !exists {
|
||||
validatorCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
}
|
||||
validatorCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
||||
}
|
||||
}
|
||||
committees = validatorCommittees[att.Data.Slot]
|
||||
}
|
||||
|
||||
committeeIndices := make([]phase0.CommitteeIndex, 0)
|
||||
for _, committeeIndex := range att.CommitteeBits.BitIndices() {
|
||||
committeeIndices = append(committeeIndices, phase0.CommitteeIndex(committeeIndex))
|
||||
}
|
||||
res.WriteString(fmt.Sprintf(" Committee indices: %d\n", committeeIndices))
|
||||
res.WriteString(fmt.Sprintf(" Attesters: %d/%d\n", att.AggregationBits.Count(), att.AggregationBits.Len()))
|
||||
res.WriteString(fmt.Sprintf(" Aggregation bits: %s\n", bitlistToString(att.AggregationBits)))
|
||||
if _, exists := committees[att.Data.Index]; exists {
|
||||
res.WriteString(fmt.Sprintf(" Attesting indices: %s\n", attestingIndices(att.AggregationBits, committees, att.CommitteeBits.BitIndices())))
|
||||
}
|
||||
res.WriteString(fmt.Sprintf(" Slot: %d\n", att.Data.Slot))
|
||||
res.WriteString(fmt.Sprintf(" Beacon block root: %#x\n", att.Data.BeaconBlockRoot))
|
||||
@@ -204,6 +253,56 @@ func outputBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Ser
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputElectraBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Service, verbose bool, attesterSlashings []*electra.AttesterSlashing) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString(fmt.Sprintf("Attester slashings: %d\n", len(attesterSlashings)))
|
||||
if verbose {
|
||||
for i, slashing := range attesterSlashings {
|
||||
// Say what was slashed.
|
||||
att1 := slashing.Attestation1
|
||||
att2 := slashing.Attestation2
|
||||
slashedIndices := intersection(att1.AttestingIndices, att2.AttestingIndices)
|
||||
if len(slashedIndices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
res.WriteString(fmt.Sprintf(" %d:\n", i))
|
||||
res.WriteString(fmt.Sprintln(" Slashed validators:"))
|
||||
response, err := eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
|
||||
State: "head",
|
||||
Indices: slashedIndices,
|
||||
})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain beacon committees")
|
||||
}
|
||||
for k, v := range response.Data {
|
||||
res.WriteString(fmt.Sprintf(" %#x (%d)\n", v.Validator.PublicKey[:], k))
|
||||
}
|
||||
|
||||
// Say what caused the slashing.
|
||||
if att1.Data.Target.Epoch == att2.Data.Target.Epoch {
|
||||
res.WriteString(fmt.Sprintf(" Double voted for same target epoch (%d):\n", att1.Data.Target.Epoch))
|
||||
if !bytes.Equal(att1.Data.Target.Root[:], att2.Data.Target.Root[:]) {
|
||||
res.WriteString(fmt.Sprintf(" Attestation 1 target epoch root: %#x\n", att1.Data.Target.Root))
|
||||
res.WriteString(fmt.Sprintf(" Attestation 2target epoch root: %#x\n", att2.Data.Target.Root))
|
||||
}
|
||||
if !bytes.Equal(att1.Data.BeaconBlockRoot[:], att2.Data.BeaconBlockRoot[:]) {
|
||||
res.WriteString(fmt.Sprintf(" Attestation 1 beacon block root: %#x\n", att1.Data.BeaconBlockRoot))
|
||||
res.WriteString(fmt.Sprintf(" Attestation 2 beacon block root: %#x\n", att2.Data.BeaconBlockRoot))
|
||||
}
|
||||
} else if att1.Data.Source.Epoch < att2.Data.Source.Epoch &&
|
||||
att1.Data.Target.Epoch > att2.Data.Target.Epoch {
|
||||
res.WriteString(" Surround voted:\n")
|
||||
res.WriteString(fmt.Sprintf(" Attestation 1 vote: %d->%d\n", att1.Data.Source.Epoch, att1.Data.Target.Epoch))
|
||||
res.WriteString(fmt.Sprintf(" Attestation 2 vote: %d->%d\n", att2.Data.Source.Epoch, att2.Data.Target.Epoch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockDeposits(_ context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
@@ -295,7 +394,7 @@ func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service
|
||||
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++ {
|
||||
for i := range syncAggregate.SyncCommitteeBits.Len() {
|
||||
if syncAggregate.SyncCommitteeBits.BitAt(i) {
|
||||
res.WriteString(fmt.Sprintf(" %d", syncCommitteeResponse.Data.Validators[i]))
|
||||
}
|
||||
@@ -508,7 +607,117 @@ func outputDenebBlockText(ctx context.Context,
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
|
||||
tmp, err = outputBlobInfo(ctx, data.verbose, blobs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputElectraBlockText(ctx context.Context,
|
||||
data *dataOut,
|
||||
signedBlock *electra.SignedBeaconBlock,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if signedBlock == nil {
|
||||
return "", errors.New("no block supplied")
|
||||
}
|
||||
|
||||
body := signedBlock.Message.Body
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
// General info.
|
||||
blockRoot, err := signedBlock.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain block root")
|
||||
}
|
||||
bodyRoot, err := signedBlock.Message.Body.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to generate body root")
|
||||
}
|
||||
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
signedBlock.Message.ProposerIndex,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
signedBlock.Message.Body.Graffiti[:],
|
||||
data.genesisTime,
|
||||
data.slotDuration,
|
||||
data.slotsPerEpoch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Eth1 data.
|
||||
if data.verbose {
|
||||
tmp, err := outputBlockETH1Data(ctx, body.ETH1Data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
}
|
||||
|
||||
// Sync aggregate.
|
||||
tmp, err = outputBlockSyncAggregate(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.SyncAggregate, phase0.Epoch(uint64(signedBlock.Message.Slot)/data.slotsPerEpoch))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Attestations.
|
||||
tmp, err = outputElectraBlockAttestations(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.Attestations)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Attester slashings.
|
||||
tmp, err = outputElectraBlockAttesterSlashings(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.AttesterSlashings)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
res.WriteString(fmt.Sprintf("Proposer slashings: %d\n", len(body.ProposerSlashings)))
|
||||
// Add verbose proposer slashings.
|
||||
|
||||
// Voluntary exits.
|
||||
tmp, err = outputBlockVoluntaryExits(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.VoluntaryExits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputBlockBLSToExecutionChanges(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.BLSToExecutionChanges)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputDenebBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputElectraBlockExecutionRequests(ctx, data.verbose, signedBlock.Message.Body.ExecutionRequests)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputBlobInfo(ctx, data.verbose, blobs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -797,7 +1006,7 @@ func outputCapellaBlockExecutionPayload(_ context.Context,
|
||||
res.WriteString(" Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
baseFeePerGasBEBytes := make([]byte, len(payload.BaseFeePerGas))
|
||||
for i := 0; i < 32; i++ {
|
||||
for i := range 32 {
|
||||
baseFeePerGasBEBytes[i] = payload.BaseFeePerGas[32-1-i]
|
||||
}
|
||||
baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBEBytes)
|
||||
@@ -903,30 +1112,69 @@ func outputDenebBlockExecutionPayload(_ context.Context,
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputDenebBlobInfo(_ context.Context,
|
||||
func outputElectraBlockExecutionRequests(_ context.Context,
|
||||
verbose bool,
|
||||
executionRequests *electra.ExecutionRequests,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if executionRequests == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
res.WriteString("Deposit requests: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(executionRequests.Deposits)))
|
||||
if verbose {
|
||||
for i, deposit := range executionRequests.Deposits {
|
||||
res.WriteString(fmt.Sprintf("%3d:\n", i))
|
||||
res.WriteString(fmt.Sprintf(" Public key: %#x\n", deposit.Pubkey))
|
||||
res.WriteString(fmt.Sprintf(" Withdrawal credentials: %#x\n", deposit.WithdrawalCredentials))
|
||||
res.WriteString(fmt.Sprintf(" Amount: %s\n", string2eth.GWeiToString(uint64(deposit.Amount), true)))
|
||||
}
|
||||
}
|
||||
res.WriteString("Withdrawal requests: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(executionRequests.Withdrawals)))
|
||||
if verbose {
|
||||
for i, withdrawal := range executionRequests.Withdrawals {
|
||||
res.WriteString(fmt.Sprintf("%3d:\n", i))
|
||||
res.WriteString(fmt.Sprintf(" Source address: %#x\n", withdrawal.SourceAddress))
|
||||
res.WriteString(fmt.Sprintf(" Validator public key: %#x\n", withdrawal.ValidatorPubkey))
|
||||
res.WriteString(fmt.Sprintf(" Amount: %s\n", string2eth.GWeiToString(uint64(withdrawal.Amount), true)))
|
||||
}
|
||||
}
|
||||
res.WriteString("Consolidation requests: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(executionRequests.Consolidations)))
|
||||
if verbose {
|
||||
for i, consolidation := range executionRequests.Consolidations {
|
||||
res.WriteString(fmt.Sprintf("%3d:\n", i))
|
||||
res.WriteString(fmt.Sprintf(" Source address: %#x\n", consolidation.SourceAddress))
|
||||
res.WriteString(fmt.Sprintf(" Source public key: %#x\n", consolidation.SourcePubkey))
|
||||
res.WriteString(fmt.Sprintf(" Target public key: %#x\n", consolidation.TargetPubkey))
|
||||
}
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlobInfo(_ context.Context,
|
||||
verbose bool,
|
||||
body *deneb.BeaconBlockBody,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if body == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !verbose {
|
||||
return fmt.Sprintf("Blobs: %d\n", len(body.BlobKZGCommitments)), nil
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
for i, blob := range blobs {
|
||||
if i == 0 {
|
||||
res.WriteString("Blobs\n")
|
||||
res.WriteString(fmt.Sprintf("Blobs: %d\n", len(blobs)))
|
||||
|
||||
if verbose {
|
||||
for i, blob := range blobs {
|
||||
res.WriteString(fmt.Sprintf("%3d:\n", i))
|
||||
res.WriteString(fmt.Sprintf(" KZG proof: %s\n", blob.KZGProof.String()))
|
||||
res.WriteString(fmt.Sprintf(" KZG commitment: %s\n", blob.KZGCommitment.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
|
||||
@@ -959,7 +1207,7 @@ func outputBellatrixBlockExecutionPayload(_ context.Context,
|
||||
res.WriteString(" Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
baseFeePerGasBEBytes := make([]byte, len(payload.BaseFeePerGas))
|
||||
for i := 0; i < 32; i++ {
|
||||
for i := range 32 {
|
||||
baseFeePerGasBEBytes[i] = payload.BaseFeePerGas[32-1-i]
|
||||
}
|
||||
baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBEBytes)
|
||||
@@ -1022,7 +1270,7 @@ func bitlistToString(input bitfield.Bitlist) string {
|
||||
bits := int(input.Len())
|
||||
|
||||
res := ""
|
||||
for i := 0; i < bits; i++ {
|
||||
for i := range bits {
|
||||
if input.BitAt(uint64(i)) {
|
||||
res = fmt.Sprintf("%s✓", res)
|
||||
} else {
|
||||
@@ -1039,7 +1287,7 @@ func bitvectorToString(input bitfield.Bitvector512) string {
|
||||
bits := int(input.Len())
|
||||
|
||||
res := strings.Builder{}
|
||||
for i := 0; i < bits; i++ {
|
||||
for i := range bits {
|
||||
if input.BitAt(uint64(i)) {
|
||||
res.WriteString("✓")
|
||||
} else {
|
||||
@@ -1052,13 +1300,118 @@ func bitvectorToString(input bitfield.Bitvector512) string {
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func attestingIndices(input bitfield.Bitlist, indices []phase0.ValidatorIndex) string {
|
||||
func attestingIndices(input bitfield.Bitlist,
|
||||
committees map[phase0.CommitteeIndex][]phase0.ValidatorIndex,
|
||||
includedCommittees []int,
|
||||
) string {
|
||||
bits := int(input.Len())
|
||||
res := ""
|
||||
for i := 0; i < bits; i++ {
|
||||
|
||||
// Build up the validator list from the included committees.
|
||||
validatorIndices := make([]phase0.ValidatorIndex, 0)
|
||||
for _, committeeIndex := range includedCommittees {
|
||||
validatorIndices = append(validatorIndices, committees[phase0.CommitteeIndex(committeeIndex)]...)
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
for i := range bits {
|
||||
if input.BitAt(uint64(i)) {
|
||||
res = fmt.Sprintf("%s%d ", res, indices[i])
|
||||
// Work out the committee and offset given the index.
|
||||
res.WriteString(fmt.Sprintf("%d ", validatorIndices[i]))
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(res)
|
||||
|
||||
return strings.TrimSpace(res.String())
|
||||
}
|
||||
|
||||
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",
|
||||
"TR": "trin-execution",
|
||||
}
|
||||
executionRegex := regexp.MustCompile(`(BU|EG|EJ|GE|NM|RH|TR)([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()
|
||||
}
|
||||
|
||||
@@ -175,3 +175,58 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
||||
"github.com/attestantio/go-eth2-client/spec/electra"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
@@ -55,18 +57,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
eth2Client: data.eth2Client,
|
||||
}
|
||||
|
||||
specResponse, err := results.eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
|
||||
err := populateResults(ctx, results)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to obtain configuration information")
|
||||
return nil, err
|
||||
}
|
||||
genesisResponse, err := results.eth2Client.(eth2client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
|
||||
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)
|
||||
|
||||
if data.blockTime != "" {
|
||||
data.blockID, err = timeToBlockID(ctx, data.eth2Client, data.blockTime)
|
||||
@@ -75,55 +69,33 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
}
|
||||
|
||||
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
||||
Block: data.blockID,
|
||||
})
|
||||
block, err := obtainBlock(ctx, data, results)
|
||||
if err != nil {
|
||||
var apiErr *api.Error
|
||||
if errors.As(err, &apiErr) && apiErr.StatusCode == 404 {
|
||||
if data.quiet {
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil, errors.New("empty beacon block")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon block")
|
||||
return nil, err
|
||||
}
|
||||
block := blockResponse.Data
|
||||
if data.quiet {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch block.Version {
|
||||
case spec.DataVersionPhase0:
|
||||
if err := outputPhase0Block(ctx, data.jsonOutput, block.Phase0); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
err = outputPhase0Block(ctx, data.jsonOutput, block.Phase0)
|
||||
case spec.DataVersionAltair:
|
||||
if err := outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, block.Altair); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
err = outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, block.Altair)
|
||||
case spec.DataVersionBellatrix:
|
||||
if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, block.Bellatrix); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
err = outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, block.Bellatrix)
|
||||
case spec.DataVersionCapella:
|
||||
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, block.Capella); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
err = outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, block.Capella)
|
||||
case spec.DataVersionDeneb:
|
||||
blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
|
||||
Block: data.blockID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain blob sidecars")
|
||||
}
|
||||
blobSidecars := blobSidecarsResponse.Data
|
||||
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, block.Deneb, blobSidecars); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
err = processDenebBlock(ctx, data, block)
|
||||
case spec.DataVersionElectra:
|
||||
err = processElectraBlock(ctx, data, block)
|
||||
default:
|
||||
return nil, errors.New("unknown block version")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to process block")
|
||||
}
|
||||
|
||||
if data.stream {
|
||||
jsonOutput = data.jsonOutput
|
||||
@@ -141,6 +113,97 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return &dataOut{}, nil
|
||||
}
|
||||
|
||||
func populateResults(ctx context.Context, results *dataOut) error {
|
||||
specResponse, err := results.eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to obtain configuration information")
|
||||
}
|
||||
genesisResponse, err := results.eth2Client.(eth2client.GenesisProvider).Genesis(ctx, &api.GenesisOpts{})
|
||||
if err != nil {
|
||||
return 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)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func obtainBlock(ctx context.Context, data *dataIn, results *dataOut,
|
||||
) (
|
||||
*spec.VersionedSignedBeaconBlock,
|
||||
error,
|
||||
) {
|
||||
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
||||
Block: 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")
|
||||
}
|
||||
|
||||
return blockResponse.Data, nil
|
||||
}
|
||||
|
||||
func processDenebBlock(ctx context.Context,
|
||||
data *dataIn,
|
||||
block *spec.VersionedSignedBeaconBlock,
|
||||
) error {
|
||||
var blobSidecars []*deneb.BlobSidecar
|
||||
kzgCommitments, err := block.BlobKZGCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(kzgCommitments) > 0 {
|
||||
blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
|
||||
Block: data.blockID,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain blob sidecars")
|
||||
}
|
||||
blobSidecars = blobSidecarsResponse.Data
|
||||
}
|
||||
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, block.Deneb, blobSidecars); err != nil {
|
||||
return errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processElectraBlock(ctx context.Context,
|
||||
data *dataIn,
|
||||
block *spec.VersionedSignedBeaconBlock,
|
||||
) error {
|
||||
var blobSidecars []*deneb.BlobSidecar
|
||||
kzgCommitments, err := block.BlobKZGCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(kzgCommitments) > 0 {
|
||||
blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
|
||||
Block: data.blockID,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain blob sidecars")
|
||||
}
|
||||
blobSidecars = blobSidecarsResponse.Data
|
||||
}
|
||||
if err := outputElectraBlock(ctx, data.jsonOutput, data.sszOutput, block.Electra, blobSidecars); err != nil {
|
||||
return errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func headEventHandler(event *apiv1.Event) {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -177,13 +240,25 @@ func headEventHandler(event *apiv1.Event) {
|
||||
case spec.DataVersionCapella:
|
||||
err = outputCapellaBlock(ctx, jsonOutput, sszOutput, block.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)
|
||||
var blobSidecars []*deneb.BlobSidecar
|
||||
var kzgCommitments []deneb.KZGCommitment
|
||||
kzgCommitments, err = block.BlobKZGCommitments()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain KZG commitments: %v\n", err)
|
||||
return
|
||||
}
|
||||
if len(kzgCommitments) > 0 {
|
||||
var blobSidecarsResponse *api.Response[[]*deneb.BlobSidecar]
|
||||
blobSidecarsResponse, err = results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{
|
||||
Block: blockID,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain blob sidecars: %v\n", err)
|
||||
return
|
||||
}
|
||||
blobSidecars = blobSidecarsResponse.Data
|
||||
}
|
||||
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, block.Deneb, blobSidecars)
|
||||
default:
|
||||
err = errors.New("unknown block version")
|
||||
}
|
||||
@@ -316,6 +391,35 @@ func outputDenebBlock(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputElectraBlock(ctx context.Context,
|
||||
jsonOutput bool,
|
||||
sszOutput bool,
|
||||
signedBlock *electra.SignedBeaconBlock,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) error {
|
||||
switch {
|
||||
case jsonOutput:
|
||||
data, err := json.Marshal(signedBlock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate JSON")
|
||||
}
|
||||
fmt.Printf("%s\n", string(data))
|
||||
case sszOutput:
|
||||
data, err := signedBlock.MarshalSSZ()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate SSZ")
|
||||
}
|
||||
fmt.Printf("%x\n", data)
|
||||
default:
|
||||
data, err := outputElectraBlockText(ctx, results, signedBlock, blobs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate text")
|
||||
}
|
||||
fmt.Print(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func timeToBlockID(ctx context.Context, eth2Client eth2client.Service, input string) (string, error) {
|
||||
var timestamp time.Time
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := blockanalyze.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := blockinfo.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -97,6 +97,9 @@ func (c *command) process(ctx context.Context) error {
|
||||
case spec.DataVersionDeneb:
|
||||
c.incumbent = state.Deneb.ETH1Data
|
||||
c.eth1DataVotes = state.Deneb.ETH1DataVotes
|
||||
case spec.DataVersionElectra:
|
||||
c.incumbent = state.Electra.ETH1Data
|
||||
c.eth1DataVotes = state.Electra.ETH1DataVotes
|
||||
default:
|
||||
return fmt.Errorf("unhandled beacon state version %v", state.Version)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -221,7 +221,7 @@ func (c *command) confirmContributionSignature(ctx context.Context) error {
|
||||
|
||||
subCommittee := c.syncCommittee.ValidatorAggregates[c.item.Message.Contribution.SubcommitteeIndex]
|
||||
includedIndices := make([]phase0.ValidatorIndex, 0, len(subCommittee))
|
||||
for i := uint64(0); i < c.item.Message.Contribution.AggregationBits.Len(); i++ {
|
||||
for i := range c.item.Message.Contribution.AggregationBits.Len() {
|
||||
if c.item.Message.Contribution.AggregationBits.BitAt(i) {
|
||||
includedIndices = append(includedIndices, subCommittee[int(i)])
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Copyright © 2021, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := chaineth1votes.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020, 2022 Weald Technology Trading
|
||||
// Copyright © 2020 - 2024 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
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
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"
|
||||
@@ -35,7 +36,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
@@ -65,6 +66,7 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
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 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)
|
||||
@@ -83,6 +85,10 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
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())
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := chainqueues.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -37,7 +37,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
|
||||
@@ -40,7 +40,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := chaintime.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := chainverifysignedcontributionandproof.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -50,7 +50,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
assert(depositVerifyData != "", "--data is required")
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
@@ -29,12 +29,12 @@ func init() {
|
||||
RootCmd.AddCommand(epochCmd)
|
||||
}
|
||||
|
||||
func epochFlags(_ *cobra.Command) {
|
||||
epochSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
||||
func epochFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
||||
}
|
||||
|
||||
func epochBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,11 @@ type command struct {
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Operation.
|
||||
epoch string
|
||||
stream bool
|
||||
jsonOutput bool
|
||||
epoch string
|
||||
validatorsStr []string
|
||||
validators map[phase0.ValidatorIndex]struct{}
|
||||
stream bool
|
||||
jsonOutput bool
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
@@ -58,45 +60,61 @@ type command struct {
|
||||
}
|
||||
|
||||
type epochSummary struct {
|
||||
Epoch phase0.Epoch `json:"epoch"`
|
||||
FirstSlot phase0.Slot `json:"first_slot"`
|
||||
LastSlot phase0.Slot `json:"last_slot"`
|
||||
Proposals []*epochProposal `json:"proposals"`
|
||||
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
||||
ActiveValidators int `json:"active_validators"`
|
||||
ParticipatingValidators int `json:"participating_validators"`
|
||||
HeadCorrectValidators int `json:"head_correct_validators"`
|
||||
HeadTimelyValidators int `json:"head_timely_validators"`
|
||||
SourceTimelyValidators int `json:"source_timely_validators"`
|
||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
|
||||
Blobs int `json:"blobs"`
|
||||
Epoch phase0.Epoch `json:"epoch"`
|
||||
FirstSlot phase0.Slot `json:"first_slot"`
|
||||
LastSlot phase0.Slot `json:"last_slot"`
|
||||
Blocks int `json:"blocks"`
|
||||
Proposals []*epochProposal `json:"proposals"`
|
||||
SyncCommitteeValidators int `json:"sync_committee_validators"`
|
||||
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
||||
ActiveValidators int `json:"active_validators"`
|
||||
ParticipatingValidators int `json:"participating_validators"`
|
||||
HeadCorrectValidators int `json:"head_correct_validators"`
|
||||
HeadTimelyValidators int `json:"head_timely_validators"`
|
||||
SourceTimelyValidators int `json:"source_timely_validators"`
|
||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||
NonParticipatingValidators []*attestingValidator `json:"nonparticipating_validators"`
|
||||
NonHeadCorrectValidators []*attestingValidator `json:"nonheadcorrect_validators"`
|
||||
NonHeadTimelyValidators []*attestingValidator `json:"nonheadtimely_validators"`
|
||||
NonTargetCorrectValidators []*attestingValidator `json:"nontargetcorrect_validators"`
|
||||
NonSourceTimelyValidators []*attestingValidator `json:"nonsourcetimely_validators"`
|
||||
Blobs int `json:"blobs"`
|
||||
}
|
||||
|
||||
type epochProposal struct {
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Proposer phase0.ValidatorIndex `json:"proposer"`
|
||||
Block bool `json:"block"`
|
||||
ValidatorIndex phase0.ValidatorIndex `json:"validator_index"`
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Block bool `json:"block"`
|
||||
}
|
||||
|
||||
type epochSyncCommittee struct {
|
||||
Index phase0.ValidatorIndex `json:"index"`
|
||||
Missed int `json:"missed"`
|
||||
ValidatorIndex phase0.ValidatorIndex `json:"validator_index"`
|
||||
Missed int `json:"missed"`
|
||||
MissedSlots []phase0.Slot `json:"missed_slots"`
|
||||
}
|
||||
|
||||
type nonParticipatingValidator struct {
|
||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||
type attestingValidator struct {
|
||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||
Slot phase0.Slot `json:"slot"`
|
||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||
HeadVote *phase0.Root `json:"head_vote,omitempty"`
|
||||
Head *phase0.Root `json:"head,omitempty"`
|
||||
TargetVote *phase0.Root `json:"target_vote,omitempty"`
|
||||
Target *phase0.Root `json:"target,omitempty"`
|
||||
InclusionSlot phase0.Slot `json:"inclusion_slot,omitempty"`
|
||||
}
|
||||
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
summary: &epochSummary{},
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
validatorsStr: viper.GetStringSlice("validators"),
|
||||
summary: &epochSummary{
|
||||
Proposals: make([]*epochProposal, 0),
|
||||
},
|
||||
validators: make(map[phase0.ValidatorIndex]struct{}),
|
||||
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
missedProposals := make([]string, 0, len(c.summary.Proposals))
|
||||
for _, proposal := range c.summary.Proposals {
|
||||
if !proposal.Block {
|
||||
missedProposals = append(missedProposals, fmt.Sprintf("\n Slot %d (validator %d)", proposal.Slot, proposal.Proposer))
|
||||
missedProposals = append(missedProposals, fmt.Sprintf("\n Slot %d (validator %d)", proposal.Slot, proposal.ValidatorIndex))
|
||||
} else {
|
||||
proposedBlocks++
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
builder.WriteString("\n Slot ")
|
||||
builder.WriteString(fmt.Sprintf("%d (%d/%d)", proposal.Slot, uint64(proposal.Slot)%uint64(len(c.summary.Proposals)), len(c.summary.Proposals)))
|
||||
builder.WriteString(" validator ")
|
||||
builder.WriteString(fmt.Sprintf("%d", proposal.Proposer))
|
||||
builder.WriteString(fmt.Sprintf("%d", proposal.ValidatorIndex))
|
||||
builder.WriteString(" not proposed or not included")
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
if c.verbose {
|
||||
for _, syncCommittee := range c.summary.SyncCommittee {
|
||||
builder.WriteString("\n Validator ")
|
||||
builder.WriteString(fmt.Sprintf("%d", syncCommittee.Index))
|
||||
builder.WriteString(fmt.Sprintf("%d", syncCommittee.ValidatorIndex))
|
||||
builder.WriteString(" included ")
|
||||
builder.WriteString(fmt.Sprintf("%d/%d", proposedBlocks-syncCommittee.Missed, proposedBlocks))
|
||||
builder.WriteString(fmt.Sprintf(" (%0.2f%%)", 100.0*float64(proposedBlocks-syncCommittee.Missed)/float64(proposedBlocks)))
|
||||
|
||||
@@ -16,6 +16,7 @@ package epochsummary
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
@@ -42,6 +43,14 @@ func (c *command) process(ctx context.Context) error {
|
||||
c.summary.FirstSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)
|
||||
c.summary.LastSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) - 1
|
||||
|
||||
validators, err := util.ParseValidators(ctx, c.validatorsProvider, c.validatorsStr, "head")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse validators")
|
||||
}
|
||||
for _, validator := range validators {
|
||||
c.validators[validator.Index] = struct{}{}
|
||||
}
|
||||
|
||||
if err := c.processProposerDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -51,6 +60,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.processBlobs(ctx)
|
||||
}
|
||||
|
||||
@@ -68,10 +78,20 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
||||
}
|
||||
present := block != nil
|
||||
if present {
|
||||
c.summary.Blocks++
|
||||
}
|
||||
|
||||
_, exists := c.validators[duty.ValidatorIndex]
|
||||
if len(c.validators) > 0 && !exists {
|
||||
// Not one of ours.
|
||||
continue
|
||||
}
|
||||
|
||||
c.summary.Proposals = append(c.summary.Proposals, &epochProposal{
|
||||
Slot: duty.Slot,
|
||||
Proposer: duty.ValidatorIndex,
|
||||
Block: present,
|
||||
Slot: duty.Slot,
|
||||
ValidatorIndex: duty.ValidatorIndex,
|
||||
Block: present,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,14 +99,25 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
|
||||
validatorIndices := make([]phase0.ValidatorIndex, 0, len(c.validators))
|
||||
for validator := range c.validators {
|
||||
validatorIndices = append(validatorIndices, validator)
|
||||
}
|
||||
|
||||
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
|
||||
State: fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)),
|
||||
State: "head",
|
||||
Indices: validatorIndices,
|
||||
})
|
||||
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 {
|
||||
_, exists := c.validators[validator.Index]
|
||||
if len(c.validators) > 0 && !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
|
||||
activeValidators[validator.Index] = validator
|
||||
}
|
||||
@@ -111,20 +142,45 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
lastSlot = c.chainTime.CurrentSlot()
|
||||
}
|
||||
|
||||
var votes map[phase0.ValidatorIndex]struct{}
|
||||
var participations map[phase0.ValidatorIndex]*nonParticipatingValidator
|
||||
c.summary.ParticipatingValidators, c.summary.HeadCorrectValidators, c.summary.HeadTimelyValidators, c.summary.SourceTimelyValidators, c.summary.TargetCorrectValidators, c.summary.TargetTimelyValidators, votes, participations, err = c.processSlots(ctx, firstSlot, lastSlot)
|
||||
participatingValidators, headCorrectValidators, headTimelyValidators, sourceTimelyValidators, targetCorrectValidators, targetTimelyValidators, participations, err := c.processSlots(ctx, firstSlot, lastSlot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0, len(activeValidators)-len(votes))
|
||||
c.summary.ParticipatingValidators = len(participatingValidators)
|
||||
c.summary.HeadCorrectValidators = len(headCorrectValidators)
|
||||
c.summary.HeadTimelyValidators = len(headTimelyValidators)
|
||||
c.summary.SourceTimelyValidators = len(sourceTimelyValidators)
|
||||
c.summary.TargetCorrectValidators = len(targetCorrectValidators)
|
||||
c.summary.TargetTimelyValidators = len(targetTimelyValidators)
|
||||
|
||||
c.summary.NonParticipatingValidators = make([]*attestingValidator, 0, len(activeValidators)-len(participatingValidators))
|
||||
for activeValidatorIndex := range activeValidators {
|
||||
if _, exists := votes[activeValidatorIndex]; !exists {
|
||||
if _, exists := participatingValidators[activeValidatorIndex]; !exists {
|
||||
if _, exists := participations[activeValidatorIndex]; exists {
|
||||
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
|
||||
}
|
||||
}
|
||||
if _, exists := headCorrectValidators[activeValidatorIndex]; !exists {
|
||||
if _, exists := participations[activeValidatorIndex]; exists {
|
||||
c.summary.NonHeadCorrectValidators = append(c.summary.NonHeadCorrectValidators, participations[activeValidatorIndex])
|
||||
}
|
||||
}
|
||||
if _, exists := headTimelyValidators[activeValidatorIndex]; !exists {
|
||||
if _, exists := participations[activeValidatorIndex]; exists {
|
||||
c.summary.NonHeadTimelyValidators = append(c.summary.NonHeadTimelyValidators, participations[activeValidatorIndex])
|
||||
}
|
||||
}
|
||||
if _, exists := targetCorrectValidators[activeValidatorIndex]; !exists {
|
||||
if _, exists := participations[activeValidatorIndex]; exists {
|
||||
c.summary.NonTargetCorrectValidators = append(c.summary.NonTargetCorrectValidators, participations[activeValidatorIndex])
|
||||
}
|
||||
}
|
||||
if _, exists := sourceTimelyValidators[activeValidatorIndex]; !exists {
|
||||
if _, exists := participations[activeValidatorIndex]; exists {
|
||||
c.summary.NonSourceTimelyValidators = append(c.summary.NonSourceTimelyValidators, participations[activeValidatorIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
|
||||
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
|
||||
@@ -139,18 +195,18 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (c *command) processSlots(ctx context.Context,
|
||||
firstSlot phase0.Slot,
|
||||
lastSlot phase0.Slot,
|
||||
) (
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
int,
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]*nonParticipatingValidator,
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]struct{},
|
||||
map[phase0.ValidatorIndex]*attestingValidator,
|
||||
error,
|
||||
) {
|
||||
votes := make(map[phase0.ValidatorIndex]struct{})
|
||||
@@ -160,7 +216,7 @@ func (c *command) processSlots(ctx context.Context,
|
||||
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
|
||||
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
|
||||
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
participations := make(map[phase0.ValidatorIndex]*nonParticipatingValidator)
|
||||
participations := make(map[phase0.ValidatorIndex]*attestingValidator)
|
||||
|
||||
// Need a cache of beacon block headers to reduce lookup times.
|
||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||
@@ -168,7 +224,7 @@ func (c *command) processSlots(ctx context.Context,
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
block, err := c.fetchBlock(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))
|
||||
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
if block == nil {
|
||||
// No block at this slot; that's fine.
|
||||
@@ -176,81 +232,126 @@ func (c *command) processSlots(ctx context.Context,
|
||||
}
|
||||
slot, err := block.Slot()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
if attestationData.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestationData.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
|
||||
// Outside of this epoch's range.
|
||||
continue
|
||||
}
|
||||
slotCommittees, exists := allCommittees[attestation.Data.Slot]
|
||||
slotCommittees, exists := allCommittees[attestationData.Slot]
|
||||
if !exists {
|
||||
response, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
|
||||
State: fmt.Sprintf("%d", attestation.Data.Slot),
|
||||
State: fmt.Sprintf("%d", attestationData.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))
|
||||
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestationData.Slot))
|
||||
}
|
||||
for _, beaconCommittee := range response.Data {
|
||||
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
||||
allCommittees[beaconCommittee.Slot] = make(map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||
}
|
||||
|
||||
allCommittees[beaconCommittee.Slot][beaconCommittee.Index] = beaconCommittee.Validators
|
||||
|
||||
for _, index := range beaconCommittee.Validators {
|
||||
participations[index] = &nonParticipatingValidator{
|
||||
Validator: index,
|
||||
Slot: beaconCommittee.Slot,
|
||||
Committee: beaconCommittee.Index,
|
||||
if len(c.validators) > 0 {
|
||||
if _, exists := c.validators[index]; !exists {
|
||||
// Not one of our validators.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := participations[index]; !exists {
|
||||
participations[index] = &attestingValidator{
|
||||
Validator: index,
|
||||
Slot: beaconCommittee.Slot,
|
||||
Committee: beaconCommittee.Index,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
slotCommittees = allCommittees[attestation.Data.Slot]
|
||||
slotCommittees = allCommittees[attestationData.Slot]
|
||||
}
|
||||
committee := slotCommittees[attestation.Data.Index]
|
||||
committee := slotCommittees[attestationData.Index]
|
||||
|
||||
inclusionDistance := slot - attestation.Data.Slot
|
||||
inclusionDistance := slot - attestationData.Slot
|
||||
|
||||
head, err := util.AttestationHead(ctx, headersCache, attestation)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
target, err := util.AttestationTarget(ctx, headersCache, c.chainTime, attestation)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
|
||||
if attestation.AggregationBits.BitAt(i) {
|
||||
votes[committee[int(i)]] = struct{}{}
|
||||
if _, exists := headCorrects[committee[int(i)]]; !exists && headCorrect {
|
||||
headCorrects[committee[int(i)]] = struct{}{}
|
||||
aggregationBits, err := attestation.AggregationBits()
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, "failed to obtain aggregation bits")
|
||||
}
|
||||
for i := range aggregationBits.Len() {
|
||||
if aggregationBits.BitAt(i) {
|
||||
validatorIndex := committee[int(i)]
|
||||
if len(c.validators) > 0 {
|
||||
if _, exists := c.validators[validatorIndex]; !exists {
|
||||
// Not one of our validators.
|
||||
continue
|
||||
}
|
||||
}
|
||||
if _, exists := headTimelys[committee[int(i)]]; !exists && headCorrect && inclusionDistance == 1 {
|
||||
headTimelys[committee[int(i)]] = struct{}{}
|
||||
|
||||
// Only set the information from the first attestation we find for this validator.
|
||||
if participations[validatorIndex].InclusionSlot == 0 {
|
||||
participations[validatorIndex].HeadVote = &attestationData.BeaconBlockRoot
|
||||
participations[validatorIndex].Head = &head
|
||||
participations[validatorIndex].TargetVote = &attestationData.Target.Root
|
||||
participations[validatorIndex].Target = &target
|
||||
participations[validatorIndex].InclusionSlot = slot
|
||||
}
|
||||
if _, exists := sourceTimelys[committee[int(i)]]; !exists && inclusionDistance <= 5 {
|
||||
sourceTimelys[committee[int(i)]] = struct{}{}
|
||||
|
||||
votes[validatorIndex] = struct{}{}
|
||||
if _, exists := headCorrects[validatorIndex]; !exists && headCorrect {
|
||||
headCorrects[validatorIndex] = struct{}{}
|
||||
}
|
||||
if _, exists := targetCorrects[committee[int(i)]]; !exists && targetCorrect {
|
||||
targetCorrects[committee[int(i)]] = struct{}{}
|
||||
if _, exists := headTimelys[validatorIndex]; !exists && headCorrect && inclusionDistance == 1 {
|
||||
headTimelys[validatorIndex] = struct{}{}
|
||||
}
|
||||
if _, exists := targetTimelys[committee[int(i)]]; !exists && targetCorrect && inclusionDistance <= 32 {
|
||||
targetTimelys[committee[int(i)]] = struct{}{}
|
||||
if _, exists := sourceTimelys[validatorIndex]; !exists && inclusionDistance <= 5 {
|
||||
sourceTimelys[validatorIndex] = struct{}{}
|
||||
}
|
||||
if _, exists := targetCorrects[validatorIndex]; !exists && targetCorrect {
|
||||
targetCorrects[validatorIndex] = struct{}{}
|
||||
}
|
||||
if _, exists := targetTimelys[validatorIndex]; !exists && targetCorrect && inclusionDistance <= 32 {
|
||||
targetTimelys[validatorIndex] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(votes),
|
||||
len(headCorrects),
|
||||
len(headTimelys),
|
||||
len(sourceTimelys),
|
||||
len(targetCorrects),
|
||||
len(targetTimelys),
|
||||
votes,
|
||||
|
||||
return votes,
|
||||
headCorrects,
|
||||
headTimelys,
|
||||
sourceTimelys,
|
||||
targetCorrects,
|
||||
targetTimelys,
|
||||
participations,
|
||||
nil
|
||||
}
|
||||
@@ -272,7 +373,18 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
return errors.Wrap(err, "empty sync committee")
|
||||
}
|
||||
|
||||
for _, validatorIndex := range committee.Validators {
|
||||
if len(c.validators) == 0 {
|
||||
c.summary.SyncCommitteeValidators++
|
||||
} else {
|
||||
if _, exists := c.validators[validatorIndex]; exists {
|
||||
c.summary.SyncCommitteeValidators++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
missed := make(map[phase0.ValidatorIndex]int)
|
||||
missedSlots := make(map[phase0.ValidatorIndex][]phase0.Slot)
|
||||
for _, index := range committee.Validators {
|
||||
missed[index] = 0
|
||||
}
|
||||
@@ -295,9 +407,15 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to obtain sync aggregate for slot %d", slot)
|
||||
}
|
||||
for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
|
||||
for i := range aggregate.SyncCommitteeBits.Len() {
|
||||
validatorIndex := committee.Validators[int(i)]
|
||||
if _, exists := c.validators[validatorIndex]; !exists {
|
||||
// Not one of ours.
|
||||
continue
|
||||
}
|
||||
if !aggregate.SyncCommitteeBits.BitAt(i) {
|
||||
missed[committee.Validators[int(i)]]++
|
||||
missed[validatorIndex]++
|
||||
missedSlots[validatorIndex] = append(missedSlots[validatorIndex], slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,8 +424,9 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
for index, count := range missed {
|
||||
if count > 0 {
|
||||
c.summary.SyncCommittee = append(c.summary.SyncCommittee, &epochSyncCommittee{
|
||||
Index: index,
|
||||
Missed: count,
|
||||
ValidatorIndex: index,
|
||||
Missed: count,
|
||||
MissedSlots: missedSlots[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -319,7 +438,7 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
return missedDiff > 0
|
||||
}
|
||||
// Then order by validator index.
|
||||
return c.summary.SyncCommittee[i].Index < c.summary.SyncCommittee[j].Index
|
||||
return c.summary.SyncCommittee[i].ValidatorIndex < c.summary.SyncCommittee[j].ValidatorIndex
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -377,10 +496,10 @@ func (c *command) setup(ctx context.Context) error {
|
||||
}
|
||||
|
||||
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))
|
||||
for _, proposal := range c.summary.Proposals {
|
||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", proposal.Slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", proposal.Slot))
|
||||
}
|
||||
if block == nil {
|
||||
continue
|
||||
@@ -390,6 +509,8 @@ func (c *command) processBlobs(ctx context.Context) error {
|
||||
// No blobs in these forks.
|
||||
case spec.DataVersionDeneb:
|
||||
c.summary.Blobs += len(block.Deneb.Message.Body.BlobKZGCommitments)
|
||||
case spec.DataVersionElectra:
|
||||
c.summary.Blobs += len(block.Electra.Message.Body.BlobKZGCommitments)
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
@@ -411,6 +532,12 @@ func (c *command) fetchBlock(ctx context.Context,
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := epochsummary.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -46,9 +46,17 @@ In quiet mode this will return 0 if information for the epoch is found, otherwis
|
||||
|
||||
func init() {
|
||||
epochCmd.AddCommand(epochSummaryCmd)
|
||||
epochFlags(epochSummaryCmd)
|
||||
epochSummaryFlags(epochSummaryCmd)
|
||||
}
|
||||
|
||||
func epochSummaryFlags(cmd *cobra.Command) {
|
||||
epochFlags(cmd)
|
||||
cmd.Flags().StringSlice("validators", nil, "the validators for which to obtain a summary")
|
||||
}
|
||||
|
||||
func epochSummaryBindings(cmd *cobra.Command) {
|
||||
epochBindings(cmd)
|
||||
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
assert(viper.GetString("signed-operation") != "", "signed-operation is required")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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,14 +25,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
dataIn, err := input(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := process(ctx, dataIn); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Process generates all output.
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := nodeevents.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -33,7 +33,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := proposerduties.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
assert(len(signatureAggregateSignatures) > 1, "multiple signatures required to aggregate")
|
||||
var signature *bls.Sign
|
||||
var err error
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
@@ -36,7 +37,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
@@ -59,6 +60,8 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
account, err = util.ParseAccount(ctx, viper.GetString("account"), util.GetPassphrases(), true)
|
||||
case viper.GetString("private-key") != "":
|
||||
account, err = util.ParseAccount(ctx, viper.GetString("private-key"), nil, true)
|
||||
default:
|
||||
err = errors.New("account or private key must be supplied")
|
||||
}
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
@@ -41,7 +42,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(cmd *cobra.Command, args []string) {
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
@@ -71,6 +72,10 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
account, err = util.ParseAccount(ctx, viper.GetString("private-key"), nil, false)
|
||||
case viper.GetString("public-key") != "":
|
||||
account, err = util.ParseAccount(ctx, viper.GetString("public-key"), nil, false)
|
||||
case signatureVerifySigner != "":
|
||||
account, err = util.ParseAccount(ctx, signatureVerifySigner, nil, false)
|
||||
default:
|
||||
err = errors.New("one of account, private-key, public-key, or signer must be supplied")
|
||||
}
|
||||
errCheck(err, "Failed to obtain account")
|
||||
outputIf(viper.GetBool("debug"), fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := slottime.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,6 +16,7 @@ package inclusion
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/api"
|
||||
@@ -76,8 +77,14 @@ func (c *command) process(ctx context.Context) error {
|
||||
Block: fmt.Sprintf("%d", slot),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
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")
|
||||
}
|
||||
|
||||
block := blockResponse.Data
|
||||
if block == nil {
|
||||
c.inclusions = append(c.inclusions, 0)
|
||||
@@ -113,6 +120,13 @@ func (c *command) process(ctx context.Context) error {
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
case spec.DataVersionElectra:
|
||||
aggregate = block.Electra.Message.Body.SyncAggregate
|
||||
if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) {
|
||||
c.inclusions = append(c.inclusions, 1)
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2024 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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := synccommitteeinclusion.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := synccommitteemembers.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -48,6 +48,7 @@ type command struct {
|
||||
genesisValidatorsRoot string
|
||||
prepareOffline bool
|
||||
signedOperationsInput string
|
||||
maxDistance uint64
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
@@ -90,6 +91,7 @@ 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.
|
||||
|
||||
@@ -197,6 +197,9 @@ 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++ {
|
||||
@@ -247,10 +250,13 @@ 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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -16,6 +16,8 @@ package depositdata
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -29,6 +31,7 @@ import (
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
debug bool
|
||||
format string
|
||||
timeout time.Duration
|
||||
withdrawalAccount string
|
||||
@@ -39,13 +42,16 @@ type dataIn struct {
|
||||
forkVersion *spec.Version
|
||||
domain *spec.Domain
|
||||
passphrases []string
|
||||
compounding bool
|
||||
}
|
||||
|
||||
func input() (*dataIn, error) {
|
||||
var err error
|
||||
data := &dataIn{
|
||||
debug: viper.GetBool("debug"),
|
||||
forkVersion: &spec.Version{},
|
||||
domain: &spec.Domain{},
|
||||
compounding: viper.GetBool("compounding"),
|
||||
}
|
||||
|
||||
if viper.GetString("validatoraccount") == "" {
|
||||
@@ -97,6 +103,9 @@ func input() (*dataIn, error) {
|
||||
if withdrawalDetailsPresent > 1 {
|
||||
return nil, errors.New("only one of withdrawal account, public key or address is allowed")
|
||||
}
|
||||
if data.compounding && data.withdrawalAddress == "" {
|
||||
return nil, errors.New("a compounding validator must be created with a withdrawal address")
|
||||
}
|
||||
|
||||
if viper.GetString("depositvalue") == "" {
|
||||
return nil, errors.New("deposit value is required")
|
||||
@@ -106,10 +115,6 @@ func input() (*dataIn, error) {
|
||||
return nil, errors.Wrap(err, "deposit value is invalid")
|
||||
}
|
||||
data.amount = spec.Gwei(amount)
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
if data.amount < 1000000000 { // MIN_DEPOSIT_AMOUNT
|
||||
return nil, errors.New("deposit value must be at least 1 Ether")
|
||||
}
|
||||
|
||||
data.forkVersion, err = inputForkVersion(ctx)
|
||||
if err != nil {
|
||||
@@ -117,6 +122,12 @@ func input() (*dataIn, error) {
|
||||
}
|
||||
|
||||
copy(data.domain[:], e2types.Domain(e2types.DomainDeposit, data.forkVersion[:], e2types.ZeroGenesisValidatorsRoot))
|
||||
if data.debug {
|
||||
fmt.Fprintf(os.Stderr, "Deposit domain is %#x\n", e2types.DomainDeposit)
|
||||
fmt.Fprintf(os.Stderr, "Fork version is %#x\n", *data.forkVersion)
|
||||
fmt.Fprintf(os.Stderr, "Genesis validators root is %#x\n", e2types.ZeroGenesisValidatorsRoot)
|
||||
fmt.Fprintf(os.Stderr, "Signature domain is %#x\n", *data.domain)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -190,17 +190,6 @@ func TestInput(t *testing.T) {
|
||||
},
|
||||
err: "deposit value is required",
|
||||
},
|
||||
{
|
||||
name: "DepositValueTooSmall",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "1000 Wei",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "deposit value must be at least 1 Ether",
|
||||
},
|
||||
{
|
||||
name: "DepositValueInvalid",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
type dataOut struct {
|
||||
format string
|
||||
account string
|
||||
path string
|
||||
validatorPubKey *spec.BLSPubKey
|
||||
withdrawalCredentials []byte
|
||||
amount spec.Gwei
|
||||
@@ -112,7 +113,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
[4]byte{0x00, 0x00, 0x10, 0x20}: "goerli",
|
||||
[4]byte{0x80, 0x00, 0x00, 0x69}: "ropsten",
|
||||
[4]byte{0x90, 0x00, 0x00, 0x69}: "sepolia",
|
||||
[4]byte{0x00, 0x01, 0x70, 0x00}: "holesky",
|
||||
[4]byte{0x01, 0x01, 0x70, 0x00}: "holesky",
|
||||
}
|
||||
|
||||
if datum.validatorPubKey == nil {
|
||||
@@ -138,7 +139,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","eth2_network_name":"%s","deposit_cli_version":"2.5.0"}`,
|
||||
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"}`,
|
||||
*datum.validatorPubKey,
|
||||
datum.withdrawalCredentials,
|
||||
datum.amount,
|
||||
@@ -170,7 +171,7 @@ func validatorDepositDataOutputJSON(datum *dataOut) (string, error) {
|
||||
if datum.depositDataRoot == nil {
|
||||
return "", errors.New("deposit data root required")
|
||||
}
|
||||
if datum.depositDataRoot == nil {
|
||||
if datum.depositMessageRoot == nil {
|
||||
return "", errors.New("deposit message root required")
|
||||
}
|
||||
if datum.forkVersion == nil {
|
||||
|
||||
@@ -423,7 +423,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"2.5.0"}]`,
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","network_name":"pyrmont","deposit_cli_version":"2.7.0"}]`,
|
||||
},
|
||||
{
|
||||
name: "SinglePrater",
|
||||
@@ -440,7 +440,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00001020","eth2_network_name":"goerli","deposit_cli_version":"2.5.0"}]`,
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00001020","network_name":"goerli","deposit_cli_version":"2.7.0"}]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
@@ -468,7 +468,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
depositMessageRoot: depositMessageRoot2,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"2.5.0"},{"pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","amount":32000000000,"signature":"911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","deposit_message_root":"bb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52","deposit_data_root":"3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"2.5.0"}]`,
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","network_name":"pyrmont","deposit_cli_version":"2.7.0"},{"pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","amount":32000000000,"signature":"911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","deposit_message_root":"bb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52","deposit_data_root":"3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","fork_version":"00002009","network_name":"pyrmont","deposit_cli_version":"2.7.0"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
@@ -40,6 +42,21 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// These values are hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
if data.amount < 1000000000 { // MIN_DEPOSIT_AMOUNT
|
||||
return nil, errors.New("deposit value must be at least 1 Ether")
|
||||
}
|
||||
switch data.compounding {
|
||||
case false:
|
||||
if data.amount > 32000000000 {
|
||||
return nil, errors.New("deposit value exceeds maximum for a non-compounding validator")
|
||||
}
|
||||
case true:
|
||||
if data.amount > 2048000000000 {
|
||||
return nil, errors.New("deposit value exceeds maximum for a compounding validator")
|
||||
}
|
||||
}
|
||||
|
||||
for _, validatorAccount := range data.validatorAccounts {
|
||||
validatorPubKey, err := ethdoutil.BestPublicKey(validatorAccount)
|
||||
if err != nil {
|
||||
@@ -80,7 +97,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
copy(depositDataRoot[:], root[:])
|
||||
|
||||
validatorWallet := validatorAccount.(e2wtypes.AccountWalletProvider).Wallet()
|
||||
results = append(results, &dataOut{
|
||||
result := &dataOut{
|
||||
format: data.format,
|
||||
account: fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()),
|
||||
validatorPubKey: &pubKey,
|
||||
@@ -90,8 +107,53 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
forkVersion: data.forkVersion,
|
||||
depositMessageRoot: &depositMessageRoot,
|
||||
depositDataRoot: &depositDataRoot,
|
||||
}
|
||||
if pathProvider, isPathProvider := validatorAccount.(e2wtypes.AccountPathProvider); isPathProvider {
|
||||
result.path = pathProvider.Path()
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Order the results
|
||||
if results[0].path != "" {
|
||||
// Order accounts by their path components.
|
||||
sort.Slice(results, func(i int, j int) bool {
|
||||
iBits := strings.Split(results[i].path, "/")
|
||||
jBits := strings.Split(results[j].path, "/")
|
||||
for index := range iBits {
|
||||
if iBits[index] == "m" && jBits[index] == "m" {
|
||||
continue
|
||||
}
|
||||
if len(jBits) <= index {
|
||||
return false
|
||||
}
|
||||
iBit, err := strconv.ParseUint(iBits[index], 10, 64)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
jBit, err := strconv.ParseUint(jBits[index], 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if iBit < jBit {
|
||||
return true
|
||||
}
|
||||
if iBit > jBit {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(jBits) > len(iBits)
|
||||
})
|
||||
} else {
|
||||
// Order accounts by their name.
|
||||
sort.Slice(results, func(i int, j int) bool {
|
||||
return strings.Compare(results[i].account, results[j].account) < 0
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -145,7 +207,11 @@ func createWithdrawalCredentials(data *dataIn) ([]byte, error) {
|
||||
withdrawalCredentials = make([]byte, 32)
|
||||
copy(withdrawalCredentials[12:32], withdrawalAddressBytes)
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(1) // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
if data.compounding {
|
||||
withdrawalCredentials[0] = byte(2) // COMPOUNDING_WITHDRAWAL_PREFIX
|
||||
} else {
|
||||
withdrawalCredentials[0] = byte(1) // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("withdrawal account, public key or address is required")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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
|
||||
@@ -14,7 +14,9 @@
|
||||
package depositdata
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -23,7 +25,7 @@ import (
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
dataIn, err := input()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -31,7 +33,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
return "", errors.New("operation timed out; try increasing with --timeout option")
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -40,7 +47,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2024 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 validatorduties
|
||||
|
||||
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.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -34,7 +34,12 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -44,6 +44,7 @@ type command struct {
|
||||
prepareOffline bool
|
||||
signedOperationsInput string
|
||||
epoch string
|
||||
maxDistance uint64
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
@@ -82,6 +83,7 @@ func newCommand(_ context.Context) (*command, error) {
|
||||
forkVersion: viper.GetString("fork-version"),
|
||||
genesisValidatorsRoot: viper.GetString("genesis-validators-root"),
|
||||
epoch: viper.GetString("epoch"),
|
||||
maxDistance: viper.GetUint64("max-distance"),
|
||||
signedOperations: make([]*phase0.SignedVoluntaryExit, 0),
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +177,9 @@ 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.
|
||||
for i := 0; ; i++ {
|
||||
if i == maxDistance {
|
||||
@@ -219,7 +222,11 @@ func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
|
||||
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
|
||||
}
|
||||
|
||||
// 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.
|
||||
lastFoundIndex := 0
|
||||
foundValidatorCount := 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2023 Weald Technology Trading.
|
||||
// Copyright © 2023, 2024 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 validatorexit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -27,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Copyright © 2021, 2024 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 validatorexpectation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -27,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -101,7 +101,7 @@ func checkMnemonic(ctx context.Context, debug bool, validatorWithdrawalCredentia
|
||||
// Create seed from mnemonic and passphrase.
|
||||
seed := bip39.NewSeed(mnemonic, mnemonicPassphrase)
|
||||
// Check first 1024 indices.
|
||||
for i := 0; i < 1024; i++ {
|
||||
for i := range 1024 {
|
||||
path := fmt.Sprintf("m/12381/3600/%d/0", i)
|
||||
if debug {
|
||||
fmt.Printf("Checking path %s\n", path)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021, 2024 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,10 +15,10 @@ package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
dataIn, err := input(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
@@ -35,12 +35,17 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
results, exitCode, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
if exitCode != 0 {
|
||||
fmt.Println(results)
|
||||
|
||||
@@ -16,6 +16,7 @@ package validatorsummary
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
@@ -96,6 +97,11 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
Block: fmt.Sprintf("%d", duty.Slot),
|
||||
})
|
||||
if err != nil {
|
||||
var apiErr *api.Error
|
||||
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
||||
}
|
||||
block := blockResponse.Data
|
||||
@@ -224,7 +230,12 @@ func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
||||
Block: fmt.Sprintf("%d", slot),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
var apiErr *api.Error
|
||||
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Wrap(err, "failed to obtain beacon block")
|
||||
}
|
||||
block := blockResponse.Data
|
||||
attestations, err := block.Attestations()
|
||||
@@ -232,16 +243,24 @@ func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
||||
return err
|
||||
}
|
||||
for _, attestation := range attestations {
|
||||
if _, exists := dutiesBySlot[attestation.Data.Slot]; !exists {
|
||||
attestationData, err := attestation.Data()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain attestation data")
|
||||
}
|
||||
if _, exists := dutiesBySlot[attestationData.Slot]; !exists {
|
||||
// We do not have any attestations for this slot.
|
||||
continue
|
||||
}
|
||||
if _, exists := dutiesBySlot[attestation.Data.Slot][attestation.Data.Index]; !exists {
|
||||
if _, exists := dutiesBySlot[attestationData.Slot][attestationData.Index]; !exists {
|
||||
// We do not have any attestations for this committee.
|
||||
continue
|
||||
}
|
||||
for _, duty := range dutiesBySlot[attestation.Data.Slot][attestation.Data.Index] {
|
||||
if attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||
for _, duty := range dutiesBySlot[attestationData.Slot][attestationData.Index] {
|
||||
aggregationBits, err := attestation.AggregationBits()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain aggregation bits")
|
||||
}
|
||||
if aggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||
// Found it.
|
||||
if _, exists := votes[duty.ValidatorIndex]; exists {
|
||||
// Duplicate; ignore.
|
||||
@@ -250,13 +269,13 @@ func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
||||
votes[duty.ValidatorIndex] = struct{}{}
|
||||
|
||||
// Update the metrics for the attestation.
|
||||
index := int(attestation.Data.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
|
||||
index := int(attestationData.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
|
||||
c.summary.Slots[index].Attestations.Included++
|
||||
inclusionDelay := slot - duty.Slot
|
||||
|
||||
fault := &validatorFault{
|
||||
Validator: duty.ValidatorIndex,
|
||||
AttestationData: attestation.Data,
|
||||
AttestationData: attestationData,
|
||||
InclusionDistance: int(inclusionDelay),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2024 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 validatorsummary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -27,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2023 Weald Technology Trading.
|
||||
// Copyright © 2023, 2024 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
|
||||
@@ -61,7 +61,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
return errors.Wrap(err, "failed to obtain block slot")
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "Slot is %d\n", slot)
|
||||
fmt.Fprintf(os.Stderr, "Current slot is %d\n", slot)
|
||||
}
|
||||
|
||||
response, err := c.consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, &api.ValidatorsOpts{
|
||||
@@ -88,6 +88,8 @@ func (c *command) process(ctx context.Context) error {
|
||||
fmt.Fprintf(os.Stderr, "Next withdrawal validator index is %d\n", nextWithdrawalValidatorIndex)
|
||||
}
|
||||
|
||||
withdrawalSlot := slot + 1
|
||||
withdrawalsInSlot := 0
|
||||
index := int(nextWithdrawalValidatorIndex)
|
||||
for {
|
||||
if index == len(validators) {
|
||||
@@ -97,11 +99,20 @@ func (c *command) process(ctx context.Context) error {
|
||||
break
|
||||
}
|
||||
if validators[index].Validator.WithdrawalCredentials[0] == ethWithdrawalPrefix &&
|
||||
validators[index].Validator.EffectiveBalance == c.maxEffectiveBalance {
|
||||
validators[index].Validator.EffectiveBalance == c.maxEffectiveBalance &&
|
||||
validators[index].Validator.ActivationEpoch <= c.chainTime.SlotToEpoch(withdrawalSlot) {
|
||||
c.res.WithdrawalsToGo++
|
||||
withdrawalsInSlot++
|
||||
if withdrawalsInSlot == int(c.maxWithdrawalsPerPayload) {
|
||||
withdrawalSlot++
|
||||
withdrawalsInSlot = 0
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "There are %d withdrawals to go until this validator\n", c.res.WithdrawalsToGo)
|
||||
}
|
||||
|
||||
c.res.BlocksToGo = c.res.WithdrawalsToGo / c.maxWithdrawalsPerPayload
|
||||
if c.res.WithdrawalsToGo%c.maxWithdrawalsPerPayload != 0 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2023 Weald Technology Trading.
|
||||
// Copyright © 2023, 2024 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 validatorwithdrawl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -27,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Copyright © 2021, 2024 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 validatoryield
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -27,14 +27,19 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
return "", errors.Join(errors.New("failed to set up command"), err)
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
@@ -43,7 +48,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -29,7 +29,7 @@ var validatorCredentialsGetCmd = &cobra.Command{
|
||||
ethdo validator credentials get --validator=primary/validator
|
||||
|
||||
In quiet mode this will return 0 if the validator exists, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := validatorcredentialsget.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,7 +38,7 @@ The validator account can be specified in one of a number of ways:
|
||||
- account and withdrawal account using --account and --withdrawal-account; this will generate a single operation
|
||||
|
||||
In quiet mode this will return 0 if the credentials operation has been generated (and successfully broadcast if online), otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
res, err := validatorcredentialsset.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,6 +64,7 @@ func init() {
|
||||
validatorCredentialsSetCmd.Flags().Bool("offline", false, "Do not attempt to connect to a beacon node to obtain information for the operation")
|
||||
validatorCredentialsSetCmd.Flags().String("fork-version", "", "Fork version to use for signing (overrides fetching from beacon node)")
|
||||
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
|
||||
validatorCredentialsSetCmd.Flags().Uint64("max-distance", 1024, "Maximum indices to scan for finding the validator.")
|
||||
}
|
||||
|
||||
func validatorCredentialsSetBindings(cmd *cobra.Command) {
|
||||
@@ -91,4 +92,7 @@ func validatorCredentialsSetBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("genesis-validators-root", cmd.Flags().Lookup("genesis-validators-root")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("max-distance", cmd.Flags().Lookup("max-distance")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user