mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-09 14:07:56 -05:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b450e96dde | ||
|
|
30d7f6989a | ||
|
|
00ea75e5c8 | ||
|
|
34647927ab | ||
|
|
44cfb68e2c | ||
|
|
70df67e6ab | ||
|
|
08fb16ff9e | ||
|
|
903ecc8581 | ||
|
|
93f4f6d68c | ||
|
|
1eee5a1349 | ||
|
|
9c4e9bcb2f | ||
|
|
83a950e5d1 | ||
|
|
b2c0ae3fa2 | ||
|
|
3059aa270f | ||
|
|
8b7aab7180 | ||
|
|
c46483586d | ||
|
|
e4f0b934d7 | ||
|
|
25a5bd917f | ||
|
|
889a884f6e | ||
|
|
1c23926295 | ||
|
|
992a969eaf | ||
|
|
58d8587471 | ||
|
|
b89154ada3 | ||
|
|
b9fff0dbde | ||
|
|
0238130895 | ||
|
|
60b6ce44f1 | ||
|
|
24486f0175 | ||
|
|
503bf9a996 | ||
|
|
1d14a57204 | ||
|
|
842d603524 | ||
|
|
c3471240a5 | ||
|
|
7b4ea7e27e | ||
|
|
4d3bd966e0 | ||
|
|
0488b13ba1 | ||
|
|
f8a611d63d | ||
|
|
772f07f8ec | ||
|
|
f6e23d803b | ||
|
|
86e872294d | ||
|
|
d7d9c66052 | ||
|
|
3e173f141e | ||
|
|
5d95e93b76 | ||
|
|
34b752edcc | ||
|
|
f17fe2f5cb | ||
|
|
8322353af5 | ||
|
|
7d0e607f96 |
55
.github/workflows/docker.yml
vendored
55
.github/workflows/docker.yml
vendored
@@ -1,58 +1,45 @@
|
|||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
|
# This workflow is triggered on a push to a tag that follows semantic versioning
|
||||||
|
# e.g., v1.2.3, v2.0.0-rc1
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Set variables that will be available to all builds.
|
# Build and push the Docker image
|
||||||
env_vars:
|
build-and-push:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
release_version: ${{ steps.release_version.outputs.release_version }}
|
|
||||||
binary: ${{ steps.binary.outputs.binary }}
|
|
||||||
steps:
|
steps:
|
||||||
- id: release_version
|
- name: Check out repository
|
||||||
run: |
|
uses: actions/checkout@v4
|
||||||
RELEASE_VERSION=$(echo ${{ github.ref_name }} | sed -e 's/^[vt]//')
|
|
||||||
echo "release_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
- id: binary
|
|
||||||
run: |
|
|
||||||
BINARY=$(basename ${{ github.repository }})
|
|
||||||
echo "binary=${BINARY}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Build.
|
# This step extracts the version number from the tag
|
||||||
build:
|
# e.g., if the tag is 'v1.2.3', this will output '1.2.3'
|
||||||
runs-on: ubuntu-latest
|
- name: Extract release version
|
||||||
needs: [env_vars]
|
id: release_version
|
||||||
steps:
|
run: |
|
||||||
- name: Check out repository into the Go module directory
|
echo "version=$(echo ${{ github.ref_name }} | sed -e 's/^v//')" >> $GITHUB_OUTPUT
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
push: true
|
push: true
|
||||||
tags: wealdtech/ethdo:latest
|
tags: |
|
||||||
|
wealdtech/ethdo:${{ steps.release_version.outputs.version }}
|
||||||
- name: build and push on release
|
wealdtech/ethdo:latest
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
if: ${{ github.event.release.tag_name != '' }}
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
|
||||||
push: true
|
|
||||||
tags: wealdtech/ethdo:${{ github.event.release.tag_name }}
|
|
||||||
|
|||||||
5
.github/workflows/golangci-lint.yml
vendored
5
.github/workflows/golangci-lint.yml
vendored
@@ -8,6 +8,8 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: 'read'
|
contents: 'read'
|
||||||
|
pull-requests: 'read'
|
||||||
|
checks: 'write'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
@@ -21,4 +23,7 @@ jobs:
|
|||||||
- uses: 'actions/checkout@v4'
|
- uses: 'actions/checkout@v4'
|
||||||
- uses: 'golangci/golangci-lint-action@v6'
|
- uses: 'golangci/golangci-lint-action@v6'
|
||||||
with:
|
with:
|
||||||
|
version: 'latest'
|
||||||
|
args: '--timeout=60m'
|
||||||
only-new-issues: true
|
only-new-issues: true
|
||||||
|
skip-cache: true
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -15,6 +15,12 @@ coverage.html
|
|||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Makefile
|
||||||
|
Makefile
|
||||||
|
|
||||||
# Vim
|
# Vim
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
|||||||
@@ -74,35 +74,9 @@ run:
|
|||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
|
formats:
|
||||||
#
|
- format: colored-line-number
|
||||||
# Multiple can be specified by separating them by comma, output can be provided
|
path: stderr
|
||||||
# for each of them by separating format name and path by colon symbol.
|
|
||||||
# Output path can be either `stdout`, `stderr` or path to the file to write to.
|
|
||||||
# Example: "checkstyle:report.json,colored-line-number"
|
|
||||||
#
|
|
||||||
# Default: colored-line-number
|
|
||||||
# format: json
|
|
||||||
|
|
||||||
# Print lines of code with issue.
|
|
||||||
# Default: true
|
|
||||||
# print-issued-lines: false
|
|
||||||
|
|
||||||
# Print linter name in the end of issue text.
|
|
||||||
# Default: true
|
|
||||||
# print-linter-name: false
|
|
||||||
|
|
||||||
# Make issues output unique by line.
|
|
||||||
# Default: true
|
|
||||||
# uniq-by-line: false
|
|
||||||
|
|
||||||
# Add a prefix to the output file references.
|
|
||||||
# Default is no prefix.
|
|
||||||
# path-prefix: ""
|
|
||||||
|
|
||||||
# Sort results by: filepath, line and column.
|
|
||||||
# sort-results: true
|
|
||||||
|
|
||||||
|
|
||||||
# All available settings of specific linters.
|
# All available settings of specific linters.
|
||||||
linters-settings:
|
linters-settings:
|
||||||
@@ -139,7 +113,6 @@ linters:
|
|||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- exportloopref
|
|
||||||
- forbidigo
|
- forbidigo
|
||||||
- forcetypeassert
|
- forcetypeassert
|
||||||
- funlen
|
- funlen
|
||||||
@@ -162,6 +135,7 @@ linters:
|
|||||||
- promlinter
|
- promlinter
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
|
- tenv
|
||||||
- unparam
|
- unparam
|
||||||
- varnamelen
|
- varnamelen
|
||||||
- wastedassign
|
- wastedassign
|
||||||
|
|||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
|||||||
|
dev:
|
||||||
|
|
||||||
|
1.39.0:
|
||||||
|
- support Fulu
|
||||||
|
|
||||||
|
1.38.0:
|
||||||
|
- update latest version of go-eth2-client to support complex Spec types
|
||||||
|
- adapt event handling to use new event handler structures in go-eth2-client
|
||||||
|
|
||||||
|
1.37.4:
|
||||||
|
- add support for eip-7044 in exit verify command
|
||||||
|
- provide ETH values as well as validator numbers in "epoch summary"
|
||||||
|
|
||||||
|
1.37.3:
|
||||||
|
- add "hoodi" to the list of supported networks
|
||||||
|
|
||||||
|
1.37.2:
|
||||||
|
- add "block trail"
|
||||||
|
|
||||||
|
1.37.1:
|
||||||
|
- handle missing blobs for block info
|
||||||
|
- fix `--epoch` flag for epoch summary
|
||||||
|
|
||||||
|
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:
|
1.36.5:
|
||||||
- avoid corner case mnemonic derivation with 25th word
|
- avoid corner case mnemonic derivation with 25th word
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22-bookworm as builder
|
FROM golang:1.23-bookworm AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/attestantio/go-eth2-client/spec"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -27,7 +28,7 @@ type dataOut struct {
|
|||||||
debug bool
|
debug bool
|
||||||
quiet bool
|
quiet bool
|
||||||
verbose bool
|
verbose bool
|
||||||
attestation *phase0.Attestation
|
attestation *spec.VersionedAttestation
|
||||||
slot phase0.Slot
|
slot phase0.Slot
|
||||||
attestationIndex uint64
|
attestationIndex uint64
|
||||||
inclusionDelay phase0.Slot
|
inclusionDelay phase0.Slot
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
"github.com/attestantio/go-eth2-client/api"
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
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/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
@@ -93,9 +94,18 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return nil, errors.Wrap(err, "failed to obtain block attestations")
|
return nil, errors.Wrap(err, "failed to obtain block attestations")
|
||||||
}
|
}
|
||||||
for i, attestation := range attestations {
|
for i, attestation := range attestations {
|
||||||
if attestation.Data.Slot == duty.Slot &&
|
attestationData, err := attestation.Data()
|
||||||
attestation.Data.Index == duty.CommitteeIndex &&
|
if err != nil {
|
||||||
attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
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
|
headCorrect := false
|
||||||
targetCorrect := false
|
targetCorrect := false
|
||||||
if data.verbose {
|
if data.verbose {
|
||||||
@@ -128,8 +138,13 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *phase0.Attestation) (bool, error) {
|
func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *spec.VersionedAttestation) (bool, error) {
|
||||||
slot := attestation.Data.Slot
|
attestationData, err := attestation.Data()
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := attestationData.Slot
|
||||||
for {
|
for {
|
||||||
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||||
Block: fmt.Sprintf("%d", slot),
|
Block: fmt.Sprintf("%d", slot),
|
||||||
@@ -149,13 +164,19 @@ func calcHeadCorrect(ctx context.Context, data *dataIn, attestation *phase0.Atte
|
|||||||
slot--
|
slot--
|
||||||
continue
|
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.
|
// Start with first slot of the target epoch.
|
||||||
slot := data.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
slot := data.chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||||
for {
|
for {
|
||||||
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
response, err := data.eth2Client.(eth2client.BeaconBlockHeadersProvider).BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||||
Block: fmt.Sprintf("%d", slot),
|
Block: fmt.Sprintf("%d", slot),
|
||||||
@@ -175,7 +196,8 @@ func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.At
|
|||||||
slot--
|
slot--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return bytes.Equal(response.Data.Root[:], attestation.Data.Target.Root[:]), nil
|
|
||||||
|
return bytes.Equal(response.Data.Root[:], attestationData.Target.Root[:]), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ type command struct {
|
|||||||
weightDenominator uint64
|
weightDenominator uint64
|
||||||
|
|
||||||
// Processing.
|
// Processing.
|
||||||
priorAttestations map[string]*attestationData
|
priorAttestations map[string]*attestationDataInfo
|
||||||
// Head roots provides the root of the head slot at given slots.
|
// Head roots provides the root of the head slot at given slots.
|
||||||
headRoots map[phase0.Slot]phase0.Root
|
headRoots map[phase0.Slot]phase0.Root
|
||||||
// Target roots provides the root of the target epoch at given slots.
|
// Target roots provides the root of the target epoch at given slots.
|
||||||
@@ -77,20 +77,20 @@ type blockAnalysis struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type attestationAnalysis struct {
|
type attestationAnalysis struct {
|
||||||
Head phase0.Root `json:"head"`
|
Head phase0.Root `json:"head"`
|
||||||
Target phase0.Root `json:"target"`
|
Target phase0.Root `json:"target"`
|
||||||
Distance int `json:"distance"`
|
Distance int `json:"distance"`
|
||||||
Duplicate *attestationData `json:"duplicate,omitempty"`
|
Duplicate *attestationDataInfo `json:"duplicate,omitempty"`
|
||||||
NewVotes int `json:"new_votes"`
|
NewVotes int `json:"new_votes"`
|
||||||
Votes int `json:"votes"`
|
Votes int `json:"votes"`
|
||||||
PossibleVotes int `json:"possible_votes"`
|
PossibleVotes int `json:"possible_votes"`
|
||||||
HeadCorrect bool `json:"head_correct"`
|
HeadCorrect bool `json:"head_correct"`
|
||||||
HeadTimely bool `json:"head_timely"`
|
HeadTimely bool `json:"head_timely"`
|
||||||
SourceTimely bool `json:"source_timely"`
|
SourceTimely bool `json:"source_timely"`
|
||||||
TargetCorrect bool `json:"target_correct"`
|
TargetCorrect bool `json:"target_correct"`
|
||||||
TargetTimely bool `json:"target_timely"`
|
TargetTimely bool `json:"target_timely"`
|
||||||
Score float64 `json:"score"`
|
Score float64 `json:"score"`
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type syncCommitteeAnalysis struct {
|
type syncCommitteeAnalysis struct {
|
||||||
@@ -100,7 +100,7 @@ type syncCommitteeAnalysis struct {
|
|||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type attestationData struct {
|
type attestationDataInfo struct {
|
||||||
Block phase0.Slot `json:"block"`
|
Block phase0.Slot `json:"block"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ func newCommand(_ context.Context) (*command, error) {
|
|||||||
quiet: viper.GetBool("quiet"),
|
quiet: viper.GetBool("quiet"),
|
||||||
verbose: viper.GetBool("verbose"),
|
verbose: viper.GetBool("verbose"),
|
||||||
debug: viper.GetBool("debug"),
|
debug: viper.GetBool("debug"),
|
||||||
priorAttestations: make(map[string]*attestationData),
|
priorAttestations: make(map[string]*attestationDataInfo),
|
||||||
headRoots: make(map[phase0.Slot]phase0.Root),
|
headRoots: make(map[phase0.Slot]phase0.Root),
|
||||||
targetRoots: make(map[phase0.Slot]phase0.Root),
|
targetRoots: make(map[phase0.Slot]phase0.Root),
|
||||||
votes: make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist),
|
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 {
|
type attestationAnalysisJSON struct {
|
||||||
Head string `json:"head"`
|
Head string `json:"head"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
Distance int `json:"distance"`
|
Distance int `json:"distance"`
|
||||||
Duplicate *attestationData `json:"duplicate,omitempty"`
|
Duplicate *attestationDataInfo `json:"duplicate,omitempty"`
|
||||||
NewVotes int `json:"new_votes"`
|
NewVotes int `json:"new_votes"`
|
||||||
Votes int `json:"votes"`
|
Votes int `json:"votes"`
|
||||||
PossibleVotes int `json:"possible_votes"`
|
PossibleVotes int `json:"possible_votes"`
|
||||||
HeadCorrect bool `json:"head_correct"`
|
HeadCorrect bool `json:"head_correct"`
|
||||||
HeadTimely bool `json:"head_timely"`
|
HeadTimely bool `json:"head_timely"`
|
||||||
SourceTimely bool `json:"source_timely"`
|
SourceTimely bool `json:"source_timely"`
|
||||||
TargetCorrect bool `json:"target_correct"`
|
TargetCorrect bool `json:"target_correct"`
|
||||||
TargetTimely bool `json:"target_timely"`
|
TargetTimely bool `json:"target_timely"`
|
||||||
Score float64 `json:"score"`
|
Score float64 `json:"score"`
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *attestationAnalysis) MarshalJSON() ([]byte, error) {
|
func (a *attestationAnalysis) MarshalJSON() ([]byte, error) {
|
||||||
|
|||||||
@@ -63,8 +63,12 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
// Calculate how many parents we need to fetch.
|
// Calculate how many parents we need to fetch.
|
||||||
minSlot := slot
|
minSlot := slot
|
||||||
for _, attestation := range attestations {
|
for _, attestation := range attestations {
|
||||||
if attestation.Data.Slot < minSlot {
|
attestationData, err := attestation.Data()
|
||||||
minSlot = attestation.Data.Slot
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to obtain attestation data")
|
||||||
|
}
|
||||||
|
if attestationData.Slot < minSlot {
|
||||||
|
minSlot = attestationData.Slot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.debug {
|
if c.debug {
|
||||||
@@ -103,10 +107,16 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
|
|||||||
if c.debug {
|
if c.debug {
|
||||||
fmt.Printf("Processing attestation %d\n", i)
|
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{
|
analysis := &attestationAnalysis{
|
||||||
Head: attestation.Data.BeaconBlockRoot,
|
Head: attestationData.BeaconBlockRoot,
|
||||||
Target: attestation.Data.Target.Root,
|
Target: attestationData.Target.Root,
|
||||||
Distance: int(slot - attestation.Data.Slot),
|
Distance: int(slot - attestationData.Slot),
|
||||||
}
|
}
|
||||||
|
|
||||||
root, err := attestation.HashTreeRoot()
|
root, err := attestation.HashTreeRoot()
|
||||||
@@ -116,45 +126,47 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
|
|||||||
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
|
if info, exists := c.priorAttestations[fmt.Sprintf("%#x", root)]; exists {
|
||||||
analysis.Duplicate = info
|
analysis.Duplicate = info
|
||||||
} else {
|
} else {
|
||||||
data := attestation.Data
|
aggregationBits, err := attestation.AggregationBits()
|
||||||
_, exists := blockVotes[data.Slot]
|
if err != nil {
|
||||||
if !exists {
|
return err
|
||||||
blockVotes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
|
||||||
}
|
}
|
||||||
_, exists = blockVotes[data.Slot][data.Index]
|
_, exists := blockVotes[attestationData.Slot]
|
||||||
if !exists {
|
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.
|
// Count new votes.
|
||||||
analysis.PossibleVotes = int(attestation.AggregationBits.Len())
|
analysis.PossibleVotes = int(aggregationBits.Len())
|
||||||
for j := range attestation.AggregationBits.Len() {
|
for j := range aggregationBits.Len() {
|
||||||
if attestation.AggregationBits.BitAt(j) {
|
if aggregationBits.BitAt(j) {
|
||||||
analysis.Votes++
|
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.
|
// Already attested to in this block; skip.
|
||||||
continue
|
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.
|
// Already attested to in a previous block; skip.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
analysis.NewVotes++
|
analysis.NewVotes++
|
||||||
blockVotes[data.Slot][data.Index].SetBitAt(j, true)
|
blockVotes[attestationData.Slot][attestationData.Index].SetBitAt(j, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Calculate head correct.
|
// Calculate head correct.
|
||||||
var err error
|
|
||||||
analysis.HeadCorrect, err = c.calcHeadCorrect(ctx, attestation)
|
analysis.HeadCorrect, err = c.calcHeadCorrect(ctx, attestation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate head timely.
|
// Calculate head timely.
|
||||||
analysis.HeadTimely = analysis.HeadCorrect && attestation.Data.Slot == slot-1
|
analysis.HeadTimely = analysis.HeadCorrect && attestationData.Slot == slot-1
|
||||||
|
|
||||||
// Calculate source timely.
|
// Calculate source timely.
|
||||||
analysis.SourceTimely = attestation.Data.Slot >= slot-5
|
analysis.SourceTimely = attestationData.Slot >= slot-5
|
||||||
|
|
||||||
// Calculate target correct.
|
// Calculate target correct.
|
||||||
analysis.TargetCorrect, err = c.calcTargetCorrect(ctx, attestation)
|
analysis.TargetCorrect, err = c.calcTargetCorrect(ctx, attestation)
|
||||||
@@ -164,7 +176,7 @@ func (c *command) analyzeAttestations(ctx context.Context, block *spec.Versioned
|
|||||||
|
|
||||||
// Calculate target timely.
|
// Calculate target timely.
|
||||||
if block.Version < spec.DataVersionDeneb {
|
if block.Version < spec.DataVersionDeneb {
|
||||||
analysis.TargetTimely = attestation.Data.Slot >= slot-32
|
analysis.TargetTimely = attestationData.Slot >= slot-32
|
||||||
} else {
|
} else {
|
||||||
analysis.TargetTimely = true
|
analysis.TargetTimely = true
|
||||||
}
|
}
|
||||||
@@ -194,7 +206,7 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
root, err := block.Deneb.HashTreeRoot()
|
root, err := block.Root()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -255,23 +267,31 @@ func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSig
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationData{
|
c.priorAttestations[fmt.Sprintf("%#x", root)] = &attestationDataInfo{
|
||||||
Block: slot,
|
Block: slot,
|
||||||
Index: i,
|
Index: i,
|
||||||
}
|
}
|
||||||
|
|
||||||
data := attestation.Data
|
attestationData, err := attestation.Data()
|
||||||
_, exists := c.votes[data.Slot]
|
if err != nil {
|
||||||
if !exists {
|
return errors.Wrap(err, "failed to obtain attestation data")
|
||||||
c.votes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist)
|
|
||||||
}
|
}
|
||||||
_, exists = c.votes[data.Slot][data.Index]
|
aggregationBits, err := attestation.AggregationBits()
|
||||||
if !exists {
|
if err != nil {
|
||||||
c.votes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len())
|
return errors.Wrap(err, "failed to obtain attestation aggregation bits")
|
||||||
}
|
}
|
||||||
for j := range attestation.AggregationBits.Len() {
|
|
||||||
if attestation.AggregationBits.BitAt(j) {
|
_, exists := c.votes[attestationData.Slot]
|
||||||
c.votes[data.Slot][data.Index].SetBitAt(j, true)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,8 +405,13 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attestation) (bool, error) {
|
func (c *command) calcHeadCorrect(ctx context.Context, attestation *spec.VersionedAttestation) (bool, error) {
|
||||||
slot := attestation.Data.Slot
|
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]
|
root, exists := c.headRoots[slot]
|
||||||
if !exists {
|
if !exists {
|
||||||
for {
|
for {
|
||||||
@@ -413,20 +438,25 @@ func (c *command) calcHeadCorrect(ctx context.Context, attestation *phase0.Attes
|
|||||||
slot--
|
slot--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.headRoots[attestation.Data.Slot] = response.Data.Root
|
c.headRoots[slot] = response.Data.Root
|
||||||
root = response.Data.Root
|
root = response.Data.Root
|
||||||
break
|
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) {
|
func (c *command) calcTargetCorrect(ctx context.Context, attestation *spec.VersionedAttestation) (bool, error) {
|
||||||
root, exists := c.targetRoots[attestation.Data.Slot]
|
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 {
|
if !exists {
|
||||||
// Start with first slot of the target epoch.
|
// Start with first slot of the target epoch.
|
||||||
slot := c.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
slot := c.chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||||
for {
|
for {
|
||||||
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
response, err := c.blockHeadersProvider.BeaconBlockHeader(ctx, &api.BeaconBlockHeaderOpts{
|
||||||
Block: fmt.Sprintf("%d", slot),
|
Block: fmt.Sprintf("%d", slot),
|
||||||
@@ -450,12 +480,12 @@ func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Att
|
|||||||
slot--
|
slot--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.targetRoots[attestation.Data.Slot] = response.Data.Root
|
c.targetRoots[attestationData.Slot] = response.Data.Root
|
||||||
root = response.Data.Root
|
root = response.Data.Root
|
||||||
break
|
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 {
|
func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||||
@@ -491,6 +521,20 @@ func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.Versioned
|
|||||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||||
return nil
|
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
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
c.analysis.SyncCommitee.Contributions = int(block.Fulu.Message.Body.SyncAggregate.SyncCommitteeBits.Count())
|
||||||
|
c.analysis.SyncCommitee.PossibleContributions = int(block.Fulu.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:
|
default:
|
||||||
return fmt.Errorf("unsupported block version %d", block.Version)
|
return fmt.Errorf("unsupported block version %d", block.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
"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/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prysmaticlabs/go-bitfield"
|
"github.com/prysmaticlabs/go-bitfield"
|
||||||
@@ -78,9 +79,9 @@ func outputBlockGeneral(ctx context.Context,
|
|||||||
res.WriteString(fmt.Sprintf("Epoch: %d\n", phase0.Epoch(uint64(slot)/slotsPerEpoch)))
|
res.WriteString(fmt.Sprintf("Epoch: %d\n", phase0.Epoch(uint64(slot)/slotsPerEpoch)))
|
||||||
res.WriteString(fmt.Sprintf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(slot)*int64(slotDuration.Seconds()), 0)))
|
res.WriteString(fmt.Sprintf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(slot)*int64(slotDuration.Seconds()), 0)))
|
||||||
res.WriteString(fmt.Sprintf("Block root: %#x\n", blockRoot))
|
res.WriteString(fmt.Sprintf("Block root: %#x\n", blockRoot))
|
||||||
|
res.WriteString(fmt.Sprintf("Parent root: %#x\n", parentRoot))
|
||||||
if verbose {
|
if verbose {
|
||||||
res.WriteString(fmt.Sprintf("Body root: %#x\n", bodyRoot))
|
res.WriteString(fmt.Sprintf("Body root: %#x\n", bodyRoot))
|
||||||
res.WriteString(fmt.Sprintf("Parent root: %#x\n", parentRoot))
|
|
||||||
res.WriteString(fmt.Sprintf("State root: %#x\n", stateRoot))
|
res.WriteString(fmt.Sprintf("State root: %#x\n", stateRoot))
|
||||||
}
|
}
|
||||||
res.WriteString(blockGraffiti(ctx, graffiti))
|
res.WriteString(blockGraffiti(ctx, graffiti))
|
||||||
@@ -133,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(" Attesters: %d/%d\n", att.AggregationBits.Count(), att.AggregationBits.Len()))
|
||||||
res.WriteString(fmt.Sprintf(" Aggregation bits: %s\n", bitlistToString(att.AggregationBits)))
|
res.WriteString(fmt.Sprintf(" Aggregation bits: %s\n", bitlistToString(att.AggregationBits)))
|
||||||
if _, exists := committees[att.Data.Index]; exists {
|
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(" Slot: %d\n", att.Data.Slot))
|
||||||
res.WriteString(fmt.Sprintf(" Beacon block root: %#x\n", att.Data.BeaconBlockRoot))
|
res.WriteString(fmt.Sprintf(" Beacon block root: %#x\n", att.Data.BeaconBlockRoot))
|
||||||
@@ -198,6 +253,56 @@ func outputBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Ser
|
|||||||
return res.String(), nil
|
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) {
|
func outputBlockDeposits(_ context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||||
res := strings.Builder{}
|
res := strings.Builder{}
|
||||||
|
|
||||||
@@ -502,7 +607,117 @@ func outputDenebBlockText(ctx context.Context,
|
|||||||
}
|
}
|
||||||
res.WriteString(tmp)
|
res.WriteString(tmp)
|
||||||
|
|
||||||
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
|
tmp, err = outputBlobInfo(ctx, data.verbose, signedBlock.Message.Body.BlobKZGCommitments, 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, signedBlock.Message.Body.BlobKZGCommitments, blobs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -897,30 +1112,73 @@ func outputDenebBlockExecutionPayload(_ context.Context,
|
|||||||
return res.String(), nil
|
return res.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputDenebBlobInfo(_ context.Context,
|
func outputElectraBlockExecutionRequests(_ context.Context,
|
||||||
verbose bool,
|
verbose bool,
|
||||||
body *deneb.BeaconBlockBody,
|
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,
|
||||||
|
commitments []deneb.KZGCommitment,
|
||||||
blobs []*deneb.BlobSidecar,
|
blobs []*deneb.BlobSidecar,
|
||||||
) (
|
) (
|
||||||
string,
|
string,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
if body == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !verbose {
|
|
||||||
return fmt.Sprintf("Blobs: %d\n", len(body.BlobKZGCommitments)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := strings.Builder{}
|
res := strings.Builder{}
|
||||||
|
|
||||||
for i, blob := range blobs {
|
if len(blobs) == 0 && len(commitments) > 0 {
|
||||||
if i == 0 {
|
res.WriteString(fmt.Sprintf("Blobs: %d (but no blobs obtained from the beacon node)\n", len(commitments)))
|
||||||
res.WriteString("Blobs\n")
|
} else {
|
||||||
|
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
|
return res.String(), nil
|
||||||
@@ -1046,15 +1304,28 @@ func bitvectorToString(input bitfield.Bitvector512) string {
|
|||||||
return res.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())
|
bits := int(input.Len())
|
||||||
res := ""
|
|
||||||
|
// 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 {
|
for i := range bits {
|
||||||
if input.BitAt(uint64(i)) {
|
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 {
|
func blockGraffiti(_ context.Context, graffiti []byte) string {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
"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/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
@@ -56,18 +57,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
eth2Client: data.eth2Client,
|
eth2Client: data.eth2Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
specResponse, err := results.eth2Client.(eth2client.SpecProvider).Spec(ctx, &api.SpecOpts{})
|
err := populateResults(ctx, results)
|
||||||
if err != nil {
|
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 != "" {
|
if data.blockTime != "" {
|
||||||
data.blockID, err = timeToBlockID(ctx, data.eth2Client, data.blockTime)
|
data.blockID, err = timeToBlockID(ctx, data.eth2Client, data.blockTime)
|
||||||
@@ -76,6 +69,77 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block, err := obtainBlock(ctx, data, results)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data.quiet {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch block.Version {
|
||||||
|
case spec.DataVersionPhase0:
|
||||||
|
err = outputPhase0Block(ctx, data.jsonOutput, block.Phase0)
|
||||||
|
case spec.DataVersionAltair:
|
||||||
|
err = outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, block.Altair)
|
||||||
|
case spec.DataVersionBellatrix:
|
||||||
|
err = outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, block.Bellatrix)
|
||||||
|
case spec.DataVersionCapella:
|
||||||
|
err = outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, block.Capella)
|
||||||
|
case spec.DataVersionDeneb:
|
||||||
|
err = processDenebBlock(ctx, data, block)
|
||||||
|
case spec.DataVersionElectra:
|
||||||
|
err = processElectraBlock(ctx, data, block)
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
err = processFuluBlock(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
|
||||||
|
sszOutput = data.sszOutput
|
||||||
|
if !jsonOutput && !sszOutput {
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
err := data.eth2Client.(eth2client.EventsProvider).Events(ctx, &api.EventsOpts{
|
||||||
|
Topics: []string{"head"},
|
||||||
|
HeadHandler: headEventHandler,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to start block stream")
|
||||||
|
}
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
||||||
Block: data.blockID,
|
Block: data.blockID,
|
||||||
})
|
})
|
||||||
@@ -91,75 +155,99 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain beacon block")
|
return nil, errors.Wrap(err, "failed to obtain beacon block")
|
||||||
}
|
}
|
||||||
block := blockResponse.Data
|
|
||||||
if data.quiet {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch block.Version {
|
return blockResponse.Data, nil
|
||||||
case spec.DataVersionPhase0:
|
|
||||||
if err := outputPhase0Block(ctx, data.jsonOutput, block.Phase0); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to output block")
|
|
||||||
}
|
|
||||||
case spec.DataVersionAltair:
|
|
||||||
if err := outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, block.Altair); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to output block")
|
|
||||||
}
|
|
||||||
case spec.DataVersionBellatrix:
|
|
||||||
if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, block.Bellatrix); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to output block")
|
|
||||||
}
|
|
||||||
case spec.DataVersionCapella:
|
|
||||||
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, block.Capella); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to output block")
|
|
||||||
}
|
|
||||||
case spec.DataVersionDeneb:
|
|
||||||
var blobSidecars []*deneb.BlobSidecar
|
|
||||||
kzgCommitments, err := block.BlobKZGCommitments()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(kzgCommitments) > 0 {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown block version")
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.stream {
|
|
||||||
jsonOutput = data.jsonOutput
|
|
||||||
sszOutput = data.sszOutput
|
|
||||||
if !jsonOutput && !sszOutput {
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
err := data.eth2Client.(eth2client.EventsProvider).Events(ctx, []string{"head"}, headEventHandler)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to start block stream")
|
|
||||||
}
|
|
||||||
<-ctx.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dataOut{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func headEventHandler(event *apiv1.Event) {
|
func processDenebBlock(ctx context.Context,
|
||||||
ctx := context.Background()
|
data *dataIn,
|
||||||
|
block *spec.VersionedSignedBeaconBlock,
|
||||||
// Only interested in head events.
|
) error {
|
||||||
if event.Topic != "head" {
|
var blobSidecars []*deneb.BlobSidecar
|
||||||
return
|
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 {
|
||||||
|
var apiErr *api.Error
|
||||||
|
if errors.As(err, &apiErr) && apiErr.StatusCode != http.StatusNotFound {
|
||||||
|
return errors.Wrap(err, "failed to obtain blob sidecars")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blobSidecars = blobSidecarsResponse.Data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, block.Deneb, blobSidecars); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to output block")
|
||||||
}
|
}
|
||||||
|
|
||||||
blockID := fmt.Sprintf("%#x", event.Data.(*apiv1.HeadEvent).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 {
|
||||||
|
var apiErr *api.Error
|
||||||
|
if errors.As(err, &apiErr) && apiErr.StatusCode != http.StatusNotFound {
|
||||||
|
return errors.Wrap(err, "failed to obtain blob sidecars")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 processFuluBlock(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 {
|
||||||
|
var apiErr *api.Error
|
||||||
|
if errors.As(err, &apiErr) && apiErr.StatusCode != http.StatusNotFound {
|
||||||
|
return errors.Wrap(err, "failed to obtain blob sidecars")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blobSidecars = blobSidecarsResponse.Data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := outputFuluBlock(ctx, data.jsonOutput, data.sszOutput, block.Fulu, blobSidecars); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to output block")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func headEventHandler(ctx context.Context, headEvent *apiv1.HeadEvent) {
|
||||||
|
blockID := fmt.Sprintf("%#x", headEvent.Block[:])
|
||||||
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
||||||
Block: blockID,
|
Block: blockID,
|
||||||
})
|
})
|
||||||
@@ -206,6 +294,46 @@ func headEventHandler(event *apiv1.Event) {
|
|||||||
blobSidecars = blobSidecarsResponse.Data
|
blobSidecars = blobSidecarsResponse.Data
|
||||||
}
|
}
|
||||||
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, block.Deneb, blobSidecars)
|
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, block.Deneb, blobSidecars)
|
||||||
|
case spec.DataVersionElectra:
|
||||||
|
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 = outputElectraBlock(context.Background(), jsonOutput, sszOutput, block.Electra, blobSidecars)
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
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 = outputFuluBlock(context.Background(), jsonOutput, sszOutput, block.Fulu, blobSidecars)
|
||||||
default:
|
default:
|
||||||
err = errors.New("unknown block version")
|
err = errors.New("unknown block version")
|
||||||
}
|
}
|
||||||
@@ -338,6 +466,64 @@ func outputDenebBlock(ctx context.Context,
|
|||||||
return nil
|
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 outputFuluBlock(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) {
|
func timeToBlockID(ctx context.Context, eth2Client eth2client.Service, input string) (string, error) {
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
|
|
||||||
|
|||||||
89
cmd/block/trail/command.go
Normal file
89
cmd/block/trail/command.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading.
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocktrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type command struct {
|
||||||
|
quiet bool
|
||||||
|
verbose bool
|
||||||
|
debug bool
|
||||||
|
|
||||||
|
// Beacon node connection.
|
||||||
|
timeout time.Duration
|
||||||
|
connection string
|
||||||
|
allowInsecureConnections bool
|
||||||
|
|
||||||
|
// Operation.
|
||||||
|
blockID string
|
||||||
|
jsonOutput bool
|
||||||
|
target string
|
||||||
|
maxBlocks int
|
||||||
|
|
||||||
|
// Data access.
|
||||||
|
consensusClient eth2client.Service
|
||||||
|
chainTime chaintime.Service
|
||||||
|
blocksProvider eth2client.SignedBeaconBlockProvider
|
||||||
|
blockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||||
|
|
||||||
|
// Processing.
|
||||||
|
justifiedCheckpoint *phase0.Checkpoint
|
||||||
|
finalizedCheckpoint *phase0.Checkpoint
|
||||||
|
|
||||||
|
// Results.
|
||||||
|
steps []*step
|
||||||
|
found bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type step struct {
|
||||||
|
Slot phase0.Slot `json:"slot"`
|
||||||
|
Root phase0.Root `json:"root"`
|
||||||
|
ParentRoot phase0.Root `json:"parent_root"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
// Not a slot, but we're using it to steal the JSON processing.
|
||||||
|
ExecutionBlock phase0.Slot `json:"execution_block"`
|
||||||
|
ExecutionHash phase0.Hash32 `json:"execution_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommand(_ context.Context) (*command, error) {
|
||||||
|
c := &command{
|
||||||
|
timeout: viper.GetDuration("timeout"),
|
||||||
|
quiet: viper.GetBool("quiet"),
|
||||||
|
verbose: viper.GetBool("verbose"),
|
||||||
|
debug: viper.GetBool("debug"),
|
||||||
|
jsonOutput: viper.GetBool("json"),
|
||||||
|
connection: viper.GetString("connection"),
|
||||||
|
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
|
||||||
|
blockID: viper.GetString("blockid"),
|
||||||
|
target: viper.GetString("target"),
|
||||||
|
maxBlocks: viper.GetInt("max-blocks"),
|
||||||
|
steps: make([]*step, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout.
|
||||||
|
if c.timeout == 0 {
|
||||||
|
return nil, errors.New("timeout is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
74
cmd/block/trail/output.go
Normal file
74
cmd/block/trail/output.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading.
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocktrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *command) output(ctx context.Context) (string, error) {
|
||||||
|
if c.quiet {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.jsonOutput {
|
||||||
|
return c.outputJSON(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.outputTxt(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleOut struct {
|
||||||
|
Start *step `json:"start"`
|
||||||
|
End *step `json:"end"`
|
||||||
|
Steps int `json:"distance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||||
|
var err error
|
||||||
|
var data []byte
|
||||||
|
if c.verbose {
|
||||||
|
data, err = json.Marshal(c.steps)
|
||||||
|
} else {
|
||||||
|
basic := &simpleOut{
|
||||||
|
Start: c.steps[0],
|
||||||
|
End: c.steps[len(c.steps)-1],
|
||||||
|
Steps: len(c.steps) - 1,
|
||||||
|
}
|
||||||
|
data, err = json.Marshal(basic)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||||
|
if !c.found {
|
||||||
|
return "Target not found", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString("Target '")
|
||||||
|
builder.WriteString(c.target)
|
||||||
|
builder.WriteString("' found at a distance of ")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", len(c.steps)-1))
|
||||||
|
builder.WriteString(" block(s)")
|
||||||
|
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
182
cmd/block/trail/process.go
Normal file
182
cmd/block/trail/process.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading.
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocktrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
|
"github.com/wealdtech/ethdo/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *command) process(ctx context.Context) error {
|
||||||
|
// Obtain information we need to process.
|
||||||
|
if err := c.setup(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
untilRoot := phase0.Root{}
|
||||||
|
var untilBlock phase0.Slot
|
||||||
|
switch {
|
||||||
|
case strings.ToLower(c.target) == "justified", strings.ToLower(c.target) == "finalized":
|
||||||
|
// Nothing to do.
|
||||||
|
case strings.HasPrefix(c.target, "0x"):
|
||||||
|
// Assume a root.
|
||||||
|
if err := json.Unmarshal([]byte(fmt.Sprintf("%q", c.target)), &untilRoot); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Assume a block number.
|
||||||
|
tmp, err := strconv.ParseUint(c.target, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
untilBlock = phase0.Slot(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockID := c.blockID
|
||||||
|
for range c.maxBlocks {
|
||||||
|
step := &step{}
|
||||||
|
|
||||||
|
blockResponse, err := c.blocksProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{
|
||||||
|
Block: blockID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
var apiError *api.Error
|
||||||
|
if errors.As(err, &apiError) && apiError.StatusCode == http.StatusNotFound {
|
||||||
|
return errors.New("empty beacon block")
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "failed to obtain beacon block")
|
||||||
|
}
|
||||||
|
block := blockResponse.Data
|
||||||
|
|
||||||
|
step.Slot, err = block.Slot()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
step.Root, err = block.Root()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
step.ParentRoot, err = block.ParentRoot()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
executionBlock, err := block.ExecutionBlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
step.ExecutionBlock = phase0.Slot(executionBlock)
|
||||||
|
step.ExecutionHash, err = block.ExecutionBlockHash()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.debug {
|
||||||
|
data, err := json.Marshal(step)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Step is %s\n", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.steps = append(c.steps, step)
|
||||||
|
|
||||||
|
blockID = step.ParentRoot.String()
|
||||||
|
|
||||||
|
if c.target == "justified" && bytes.Equal(step.Root[:], c.justifiedCheckpoint.Root[:]) {
|
||||||
|
c.found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c.target == "finalized" && bytes.Equal(step.Root[:], c.finalizedCheckpoint.Root[:]) {
|
||||||
|
c.found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if untilBlock > 0 && step.Slot == untilBlock {
|
||||||
|
c.found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!untilRoot.IsZero()) && bytes.Equal(step.Root[:], untilRoot[:]) {
|
||||||
|
c.found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *command) setup(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Connect to the client.
|
||||||
|
c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.chainTime, err = standardchaintime.New(ctx,
|
||||||
|
standardchaintime.WithSpecProvider(c.consensusClient.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisProvider(c.consensusClient.(eth2client.GenesisProvider)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to set up chaintime service")
|
||||||
|
}
|
||||||
|
|
||||||
|
var isProvider bool
|
||||||
|
c.blocksProvider, isProvider = c.consensusClient.(eth2client.SignedBeaconBlockProvider)
|
||||||
|
if !isProvider {
|
||||||
|
return errors.New("connection does not provide signed beacon block information")
|
||||||
|
}
|
||||||
|
c.blockHeadersProvider, isProvider = c.consensusClient.(eth2client.BeaconBlockHeadersProvider)
|
||||||
|
if !isProvider {
|
||||||
|
return errors.New("connection does not provide beacon block header information")
|
||||||
|
}
|
||||||
|
|
||||||
|
finalityProvider, isProvider := c.consensusClient.(eth2client.FinalityProvider)
|
||||||
|
if !isProvider {
|
||||||
|
return errors.New("connection does not provide finality information")
|
||||||
|
}
|
||||||
|
finalityResponse, err := finalityProvider.Finality(ctx, &api.FinalityOpts{
|
||||||
|
State: "head",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to obtain finality")
|
||||||
|
}
|
||||||
|
finality := finalityResponse.Data
|
||||||
|
c.justifiedCheckpoint = finality.Justified
|
||||||
|
if c.debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "Justified checkpoint is %d / %#x\n", c.justifiedCheckpoint.Epoch, c.justifiedCheckpoint.Root)
|
||||||
|
}
|
||||||
|
c.finalizedCheckpoint = finality.Finalized
|
||||||
|
if c.debug {
|
||||||
|
fmt.Fprintf(os.Stderr, "Finalized checkpoint is %d / %#x\n", c.finalizedCheckpoint.Epoch, c.finalizedCheckpoint.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
63
cmd/block/trail/process_internal_test.go
Normal file
63
cmd/block/trail/process_internal_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading.
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocktrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProcess(t *testing.T) {
|
||||||
|
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||||
|
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
vars map[string]interface{}
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "NoBlock",
|
||||||
|
vars: map[string]interface{}{
|
||||||
|
"timeout": "60s",
|
||||||
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
|
"blockid": "invalid",
|
||||||
|
},
|
||||||
|
err: "failed to obtain beacon block: failed to request signed beacon block: GET failed with status 400: {\"code\":400,\"message\":\"BAD_REQUEST: Unsupported endpoint version: v2\",\"stacktraces\":[]}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
viper.Reset()
|
||||||
|
|
||||||
|
for k, v := range test.vars {
|
||||||
|
viper.Set(k, v)
|
||||||
|
}
|
||||||
|
cmd, err := newCommand(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = cmd.process(context.Background())
|
||||||
|
if test.err != "" {
|
||||||
|
require.EqualError(t, err, test.err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
59
cmd/block/trail/run.go
Normal file
59
cmd/block/trail/run.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading.
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package blocktrail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run runs the command.
|
||||||
|
func Run(cmd *cobra.Command) (string, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
c, err := newCommand(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.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 {
|
||||||
|
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") {
|
||||||
|
if c.found {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := c.output(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Join(errors.New("failed to obtain output"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
65
cmd/blocktrail.go
Normal file
65
cmd/blocktrail.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright © 2025 Weald Technology Trading
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
blocktrail "github.com/wealdtech/ethdo/cmd/block/trail"
|
||||||
|
)
|
||||||
|
|
||||||
|
var blockTrailCmd = &cobra.Command{
|
||||||
|
Use: "trail",
|
||||||
|
Short: "Trail back in the chain from a given block.",
|
||||||
|
Long: `Trail back in the chain for a given block. For example:
|
||||||
|
|
||||||
|
ethdo block trail --blockid=12345 --target=finalized
|
||||||
|
|
||||||
|
In quiet mode this will return 0 if the block trail ends up at the finalized state, otherwise 1.`,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
res, err := blocktrail.Run(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if viper.GetBool("quiet") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if res != "" {
|
||||||
|
fmt.Println(res)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
blockCmd.AddCommand(blockTrailCmd)
|
||||||
|
blockFlags(blockTrailCmd)
|
||||||
|
blockTrailCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
|
||||||
|
blockTrailCmd.Flags().String("target", "justified", "the target block (block number, hash, justified or finalized)")
|
||||||
|
blockTrailCmd.Flags().Int("max-blocks", 16384, "the maximum number of blocks to look at before halting")
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockTrailBindings(cmd *cobra.Command) {
|
||||||
|
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := viper.BindPFlag("target", cmd.Flags().Lookup("target")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := viper.BindPFlag("max-blocks", cmd.Flags().Lookup("max-blocks")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,12 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
case spec.DataVersionDeneb:
|
case spec.DataVersionDeneb:
|
||||||
c.incumbent = state.Deneb.ETH1Data
|
c.incumbent = state.Deneb.ETH1Data
|
||||||
c.eth1DataVotes = state.Deneb.ETH1DataVotes
|
c.eth1DataVotes = state.Deneb.ETH1DataVotes
|
||||||
|
case spec.DataVersionElectra:
|
||||||
|
c.incumbent = state.Electra.ETH1Data
|
||||||
|
c.eth1DataVotes = state.Electra.ETH1DataVotes
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
c.incumbent = state.Fulu.ETH1Data
|
||||||
|
c.eth1DataVotes = state.Fulu.ETH1DataVotes
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled beacon state version %v", state.Version)
|
return fmt.Errorf("unhandled beacon state version %v", state.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
|||||||
errCheck(err, "Failed to obtain finality information")
|
errCheck(err, "Failed to obtain finality information")
|
||||||
finality := finalityResponse.Data
|
finality := finalityResponse.Data
|
||||||
|
|
||||||
|
blockProvider, isProvider := eth2Client.(eth2client.SignedBeaconBlockProvider)
|
||||||
|
assert(isProvider, "beacon node does not provide signed beacon blocks; cannot report on chain status")
|
||||||
|
blockResponse, err := blockProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{Block: "head"})
|
||||||
|
errCheck(err, "Failed to obtain block information")
|
||||||
|
block := blockResponse.Data
|
||||||
|
|
||||||
slot := chainTime.CurrentSlot()
|
slot := chainTime.CurrentSlot()
|
||||||
|
|
||||||
nextSlot := slot + 1
|
nextSlot := slot + 1
|
||||||
@@ -78,12 +84,28 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
|||||||
nextEpochStartSlot := chainTime.FirstSlotOfEpoch(nextEpoch)
|
nextEpochStartSlot := chainTime.FirstSlotOfEpoch(nextEpoch)
|
||||||
nextEpochTimestamp := chainTime.StartOfEpoch(nextEpoch)
|
nextEpochTimestamp := chainTime.StartOfEpoch(nextEpoch)
|
||||||
|
|
||||||
|
headSlot, err := block.Slot()
|
||||||
|
errCheck(err, "Failed to obtain block slot")
|
||||||
|
|
||||||
res := strings.Builder{}
|
res := strings.Builder{}
|
||||||
|
|
||||||
res.WriteString("Current slot: ")
|
res.WriteString("Current slot: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", slot))
|
res.WriteString(fmt.Sprintf("%d", slot))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
|
|
||||||
|
res.WriteString("Head slot: ")
|
||||||
|
res.WriteString(fmt.Sprintf("%d", headSlot))
|
||||||
|
if headSlot != slot {
|
||||||
|
if slot-headSlot == 1 {
|
||||||
|
res.WriteString(" (1 slot behind)")
|
||||||
|
} else {
|
||||||
|
res.WriteString(" (")
|
||||||
|
res.WriteString(fmt.Sprintf("%d", slot-headSlot))
|
||||||
|
res.WriteString(" slots behind)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.WriteString("\n")
|
||||||
|
|
||||||
res.WriteString("Current epoch: ")
|
res.WriteString("Current epoch: ")
|
||||||
res.WriteString(fmt.Sprintf("%d", epoch))
|
res.WriteString(fmt.Sprintf("%d", epoch))
|
||||||
res.WriteString("\n")
|
res.WriteString("\n")
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ var depositVerifyCmd = &cobra.Command{
|
|||||||
Short: "Verify deposit data matches the provided data",
|
Short: "Verify deposit data matches the provided data",
|
||||||
Long: `Verify deposit data matches the provided input data. For example:
|
Long: `Verify deposit data matches the provided input data. For example:
|
||||||
|
|
||||||
ethdo deposit verify --data=depositdata.json --withdrawalaccount=primary/current --value="32 Ether"
|
ethdo deposit verify --data=depositdata.json --withdrawalaccount=primary/current --depositvalue="32 Ether"
|
||||||
|
|
||||||
The deposit data is compared to the supplied withdrawal account/public key, validator public key, and value to ensure they match.
|
The deposit data is compared to the supplied withdrawal account/public key, validator public key, and value to ensure they match.
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func epochFlags(cmd *cobra.Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func epochBindings(cmd *cobra.Command) {
|
func epochBindings(cmd *cobra.Command) {
|
||||||
if err := viper.BindPFlag("validators", cmd.Flags().Lookup("validators")); err != nil {
|
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ package epochsummary
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
|
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
"github.com/attestantio/go-eth2-client/spec"
|
"github.com/attestantio/go-eth2-client/spec"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -52,6 +54,16 @@ type command struct {
|
|||||||
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
||||||
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||||
|
|
||||||
|
// Intermediate data.
|
||||||
|
validatorInfo map[phase0.ValidatorIndex]*apiv1.Validator
|
||||||
|
participatingValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
headCorrectValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
headTimelyValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
sourceTimelyValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
targetCorrectValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
targetTimelyValidators map[phase0.ValidatorIndex]struct{}
|
||||||
|
participations map[phase0.ValidatorIndex]*attestingValidator
|
||||||
|
|
||||||
// Caches.
|
// Caches.
|
||||||
blocksCache map[string]*spec.VersionedSignedBeaconBlock
|
blocksCache map[string]*spec.VersionedSignedBeaconBlock
|
||||||
|
|
||||||
@@ -68,12 +80,19 @@ type epochSummary struct {
|
|||||||
SyncCommitteeValidators int `json:"sync_committee_validators"`
|
SyncCommitteeValidators int `json:"sync_committee_validators"`
|
||||||
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
|
||||||
ActiveValidators int `json:"active_validators"`
|
ActiveValidators int `json:"active_validators"`
|
||||||
|
ActiveBalance *big.Int `json:"active_balance"`
|
||||||
ParticipatingValidators int `json:"participating_validators"`
|
ParticipatingValidators int `json:"participating_validators"`
|
||||||
|
ParticipatingBalance *big.Int `json:"participating_balance"`
|
||||||
HeadCorrectValidators int `json:"head_correct_validators"`
|
HeadCorrectValidators int `json:"head_correct_validators"`
|
||||||
|
HeadCorrectBalance *big.Int `json:"head_correct_balance"`
|
||||||
HeadTimelyValidators int `json:"head_timely_validators"`
|
HeadTimelyValidators int `json:"head_timely_validators"`
|
||||||
|
HeadTimelyBalance *big.Int `json:"head_timely_balance"`
|
||||||
SourceTimelyValidators int `json:"source_timely_validators"`
|
SourceTimelyValidators int `json:"source_timely_validators"`
|
||||||
|
SourceTimelyBalance *big.Int `json:"source_timely_balance"`
|
||||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||||
|
TargetCorrectBalance *big.Int `json:"target_correct_balance"`
|
||||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||||
|
TargetTimelyBalance *big.Int `json:"target_timely_balance"`
|
||||||
NonParticipatingValidators []*attestingValidator `json:"nonparticipating_validators"`
|
NonParticipatingValidators []*attestingValidator `json:"nonparticipating_validators"`
|
||||||
NonHeadCorrectValidators []*attestingValidator `json:"nonheadcorrect_validators"`
|
NonHeadCorrectValidators []*attestingValidator `json:"nonheadcorrect_validators"`
|
||||||
NonHeadTimelyValidators []*attestingValidator `json:"nonheadtimely_validators"`
|
NonHeadTimelyValidators []*attestingValidator `json:"nonheadtimely_validators"`
|
||||||
@@ -95,14 +114,15 @@ type epochSyncCommittee struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type attestingValidator struct {
|
type attestingValidator struct {
|
||||||
Validator phase0.ValidatorIndex `json:"validator_index"`
|
Validator phase0.ValidatorIndex `json:"validator_index"`
|
||||||
Slot phase0.Slot `json:"slot"`
|
EffectiveBalance phase0.Gwei `json:"effective_balance"`
|
||||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
Slot phase0.Slot `json:"slot"`
|
||||||
HeadVote *phase0.Root `json:"head_vote,omitempty"`
|
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||||
Head *phase0.Root `json:"head,omitempty"`
|
HeadVote *phase0.Root `json:"head_vote,omitempty"`
|
||||||
TargetVote *phase0.Root `json:"target_vote,omitempty"`
|
Head *phase0.Root `json:"head,omitempty"`
|
||||||
Target *phase0.Root `json:"target,omitempty"`
|
TargetVote *phase0.Root `json:"target_vote,omitempty"`
|
||||||
InclusionSlot phase0.Slot `json:"inclusion_slot,omitempty"`
|
Target *phase0.Root `json:"target,omitempty"`
|
||||||
|
InclusionSlot phase0.Slot `json:"inclusion_slot,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommand(_ context.Context) (*command, error) {
|
func newCommand(_ context.Context) (*command, error) {
|
||||||
@@ -112,10 +132,24 @@ func newCommand(_ context.Context) (*command, error) {
|
|||||||
debug: viper.GetBool("debug"),
|
debug: viper.GetBool("debug"),
|
||||||
validatorsStr: viper.GetStringSlice("validators"),
|
validatorsStr: viper.GetStringSlice("validators"),
|
||||||
summary: &epochSummary{
|
summary: &epochSummary{
|
||||||
Proposals: make([]*epochProposal, 0),
|
Proposals: make([]*epochProposal, 0),
|
||||||
|
ActiveBalance: big.NewInt(0),
|
||||||
|
ParticipatingBalance: big.NewInt(0),
|
||||||
|
HeadCorrectBalance: big.NewInt(0),
|
||||||
|
HeadTimelyBalance: big.NewInt(0),
|
||||||
|
SourceTimelyBalance: big.NewInt(0),
|
||||||
|
TargetCorrectBalance: big.NewInt(0),
|
||||||
|
TargetTimelyBalance: big.NewInt(0),
|
||||||
},
|
},
|
||||||
validators: make(map[phase0.ValidatorIndex]struct{}),
|
validators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
participatingValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
headCorrectValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
headTimelyValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
sourceTimelyValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
targetCorrectValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
targetTimelyValidators: make(map[phase0.ValidatorIndex]struct{}),
|
||||||
|
participations: make(map[phase0.ValidatorIndex]*attestingValidator),
|
||||||
|
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout.
|
// Timeout.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2022 Weald Technology Trading.
|
// Copyright © 2022, 2025 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,12 +70,80 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.WriteString(fmt.Sprintf("\n Attestations: %d/%d (%0.2f%%)", c.summary.ParticipatingValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.ParticipatingValidators)/float64(c.summary.ActiveValidators)))
|
gweiToEth := big.NewInt(1e9)
|
||||||
builder.WriteString(fmt.Sprintf("\n Source timely: %d/%d (%0.2f%%)", c.summary.SourceTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.SourceTimelyValidators)/float64(c.summary.ActiveValidators)))
|
mul := big.NewInt(10000)
|
||||||
builder.WriteString(fmt.Sprintf("\n Target correct: %d/%d (%0.2f%%)", c.summary.TargetCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetCorrectValidators)/float64(c.summary.ActiveValidators)))
|
participatingBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.ParticipatingBalance, mul), c.summary.ActiveBalance)
|
||||||
builder.WriteString(fmt.Sprintf("\n Target timely: %d/%d (%0.2f%%)", c.summary.TargetTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetTimelyValidators)/float64(c.summary.ActiveValidators)))
|
builder.WriteString(fmt.Sprintf("\n Attesting balance: %s/%s (%0.2f%%)",
|
||||||
builder.WriteString(fmt.Sprintf("\n Head correct: %d/%d (%0.2f%%)", c.summary.HeadCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadCorrectValidators)/float64(c.summary.ActiveValidators)))
|
new(big.Int).Div(c.summary.ParticipatingBalance, gweiToEth).String(),
|
||||||
builder.WriteString(fmt.Sprintf("\n Head timely: %d/%d (%0.2f%%)", c.summary.HeadTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadTimelyValidators)/float64(c.summary.ActiveValidators)))
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(participatingBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
sourceTimelyBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.SourceTimelyBalance, mul), c.summary.ActiveBalance)
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Source timely: %s/%s (%0.2f%%)",
|
||||||
|
new(big.Int).Div(c.summary.SourceTimelyBalance, gweiToEth).String(),
|
||||||
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(sourceTimelyBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
targetCorrectBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.TargetCorrectBalance, mul), c.summary.ActiveBalance)
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Target correct: %s/%s (%0.2f%%)",
|
||||||
|
new(big.Int).Div(c.summary.TargetCorrectBalance, gweiToEth).String(),
|
||||||
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(targetCorrectBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
targetTimelyBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.TargetTimelyBalance, mul), c.summary.ActiveBalance)
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Target timely: %s/%s (%0.2f%%)",
|
||||||
|
new(big.Int).Div(c.summary.TargetTimelyBalance, gweiToEth).String(),
|
||||||
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(targetTimelyBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
headCorrectBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.HeadCorrectBalance, mul), c.summary.ActiveBalance)
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Head correct: %s/%s (%0.2f%%)",
|
||||||
|
new(big.Int).Div(c.summary.HeadCorrectBalance, gweiToEth).String(),
|
||||||
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(headCorrectBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
headTimelyBalancePct := new(big.Int).Div(new(big.Int).Mul(c.summary.HeadTimelyBalance, mul), c.summary.ActiveBalance)
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Head timely: %s/%s (%0.2f%%)",
|
||||||
|
new(big.Int).Div(c.summary.HeadTimelyBalance, gweiToEth).String(),
|
||||||
|
new(big.Int).Div(c.summary.ActiveBalance, gweiToEth).String(),
|
||||||
|
float64(headTimelyBalancePct.Uint64())/100.0,
|
||||||
|
))
|
||||||
|
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Attesting validators: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.ParticipatingValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.ParticipatingValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Source timely: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.SourceTimelyValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.SourceTimelyValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Target correct: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.TargetCorrectValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.TargetCorrectValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Target timely: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.TargetTimelyValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.TargetTimelyValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Head correct: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.HeadCorrectValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.HeadCorrectValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
|
builder.WriteString(fmt.Sprintf("\n Head timely: %d/%d (%0.2f%%)",
|
||||||
|
c.summary.HeadTimelyValidators,
|
||||||
|
c.summary.ActiveValidators,
|
||||||
|
100.0*float64(c.summary.HeadTimelyValidators)/float64(c.summary.ActiveValidators),
|
||||||
|
))
|
||||||
if c.verbose {
|
if c.verbose {
|
||||||
// Sort list by validator index.
|
// Sort list by validator index.
|
||||||
for _, validator := range c.summary.NonParticipatingValidators {
|
for _, validator := range c.summary.NonParticipatingValidators {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2022, 2023 Weald Technology Trading.
|
// Copyright © 2022 - 2025 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -16,6 +16,7 @@ package epochsummary
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@@ -105,12 +106,14 @@ func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorInd
|
|||||||
}
|
}
|
||||||
|
|
||||||
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
|
response, err := c.validatorsProvider.Validators(ctx, &api.ValidatorsOpts{
|
||||||
State: "head",
|
State: fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)),
|
||||||
Indices: validatorIndices,
|
Indices: validatorIndices,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
|
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
|
||||||
}
|
}
|
||||||
|
c.validatorInfo = response.Data
|
||||||
|
|
||||||
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
|
||||||
for _, validator := range response.Data {
|
for _, validator := range response.Data {
|
||||||
_, exists := c.validators[validator.Index]
|
_, exists := c.validators[validator.Index]
|
||||||
@@ -133,6 +136,10 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
c.summary.ActiveValidators = len(activeValidators)
|
c.summary.ActiveValidators = len(activeValidators)
|
||||||
|
|
||||||
|
for _, validator := range activeValidators {
|
||||||
|
c.summary.ActiveBalance = c.summary.ActiveBalance.Add(c.summary.ActiveBalance, big.NewInt(int64(validator.Validator.EffectiveBalance)))
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain number of validators that voted for blocks in the epoch.
|
// Obtain number of validators that voted for blocks in the epoch.
|
||||||
// These votes can be included anywhere from the second slot of
|
// These votes can be included anywhere from the second slot of
|
||||||
// the epoch to the first slot of the next-but-one epoch.
|
// the epoch to the first slot of the next-but-one epoch.
|
||||||
@@ -142,43 +149,42 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
|||||||
lastSlot = c.chainTime.CurrentSlot()
|
lastSlot = c.chainTime.CurrentSlot()
|
||||||
}
|
}
|
||||||
|
|
||||||
participatingValidators, headCorrectValidators, headTimelyValidators, sourceTimelyValidators, targetCorrectValidators, targetTimelyValidators, participations, err := c.processSlots(ctx, firstSlot, lastSlot)
|
if err := c.processSlots(ctx, firstSlot, lastSlot); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.summary.ParticipatingValidators = len(participatingValidators)
|
c.summary.ParticipatingValidators = len(c.participatingValidators)
|
||||||
c.summary.HeadCorrectValidators = len(headCorrectValidators)
|
c.summary.HeadCorrectValidators = len(c.headCorrectValidators)
|
||||||
c.summary.HeadTimelyValidators = len(headTimelyValidators)
|
c.summary.HeadTimelyValidators = len(c.headTimelyValidators)
|
||||||
c.summary.SourceTimelyValidators = len(sourceTimelyValidators)
|
c.summary.SourceTimelyValidators = len(c.sourceTimelyValidators)
|
||||||
c.summary.TargetCorrectValidators = len(targetCorrectValidators)
|
c.summary.TargetCorrectValidators = len(c.targetCorrectValidators)
|
||||||
c.summary.TargetTimelyValidators = len(targetTimelyValidators)
|
c.summary.TargetTimelyValidators = len(c.targetTimelyValidators)
|
||||||
|
|
||||||
c.summary.NonParticipatingValidators = make([]*attestingValidator, 0, len(activeValidators)-len(participatingValidators))
|
c.summary.NonParticipatingValidators = make([]*attestingValidator, 0, len(activeValidators)-len(c.participatingValidators))
|
||||||
for activeValidatorIndex := range activeValidators {
|
for activeValidatorIndex := range activeValidators {
|
||||||
if _, exists := participatingValidators[activeValidatorIndex]; !exists {
|
if _, exists := c.participatingValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := c.participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
|
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, c.participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, exists := headCorrectValidators[activeValidatorIndex]; !exists {
|
if _, exists := c.headCorrectValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := c.participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonHeadCorrectValidators = append(c.summary.NonHeadCorrectValidators, participations[activeValidatorIndex])
|
c.summary.NonHeadCorrectValidators = append(c.summary.NonHeadCorrectValidators, c.participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, exists := headTimelyValidators[activeValidatorIndex]; !exists {
|
if _, exists := c.headTimelyValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := c.participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonHeadTimelyValidators = append(c.summary.NonHeadTimelyValidators, participations[activeValidatorIndex])
|
c.summary.NonHeadTimelyValidators = append(c.summary.NonHeadTimelyValidators, c.participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, exists := targetCorrectValidators[activeValidatorIndex]; !exists {
|
if _, exists := c.targetCorrectValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := c.participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonTargetCorrectValidators = append(c.summary.NonTargetCorrectValidators, participations[activeValidatorIndex])
|
c.summary.NonTargetCorrectValidators = append(c.summary.NonTargetCorrectValidators, c.participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, exists := sourceTimelyValidators[activeValidatorIndex]; !exists {
|
if _, exists := c.sourceTimelyValidators[activeValidatorIndex]; !exists {
|
||||||
if _, exists := participations[activeValidatorIndex]; exists {
|
if _, exists := c.participations[activeValidatorIndex]; exists {
|
||||||
c.summary.NonSourceTimelyValidators = append(c.summary.NonSourceTimelyValidators, participations[activeValidatorIndex])
|
c.summary.NonSourceTimelyValidators = append(c.summary.NonSourceTimelyValidators, c.participations[activeValidatorIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,24 +205,8 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
|
|||||||
func (c *command) processSlots(ctx context.Context,
|
func (c *command) processSlots(ctx context.Context,
|
||||||
firstSlot phase0.Slot,
|
firstSlot phase0.Slot,
|
||||||
lastSlot phase0.Slot,
|
lastSlot phase0.Slot,
|
||||||
) (
|
) error {
|
||||||
map[phase0.ValidatorIndex]struct{},
|
|
||||||
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{})
|
|
||||||
headCorrects := make(map[phase0.ValidatorIndex]struct{})
|
|
||||||
headTimelys := make(map[phase0.ValidatorIndex]struct{})
|
|
||||||
sourceTimelys := make(map[phase0.ValidatorIndex]struct{})
|
|
||||||
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
|
|
||||||
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
|
|
||||||
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
|
||||||
participations := make(map[phase0.ValidatorIndex]*attestingValidator)
|
|
||||||
|
|
||||||
// Need a cache of beacon block headers to reduce lookup times.
|
// Need a cache of beacon block headers to reduce lookup times.
|
||||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||||
@@ -224,7 +214,7 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, nil, 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", slot))
|
||||||
}
|
}
|
||||||
if block == nil {
|
if block == nil {
|
||||||
// No block at this slot; that's fine.
|
// No block at this slot; that's fine.
|
||||||
@@ -232,24 +222,28 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
}
|
}
|
||||||
slot, err := block.Slot()
|
slot, err := block.Slot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
attestations, err := block.Attestations()
|
attestations, err := block.Attestations()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
for _, attestation := range attestations {
|
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 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.
|
// Outside of this epoch's range.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slotCommittees, exists := allCommittees[attestation.Data.Slot]
|
slotCommittees, exists := allCommittees[attestationData.Slot]
|
||||||
if !exists {
|
if !exists {
|
||||||
response, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
|
response, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, &api.BeaconCommitteesOpts{
|
||||||
State: fmt.Sprintf("%d", attestation.Data.Slot),
|
State: fmt.Sprintf("%d", attestationData.Slot),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
|
return errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestationData.Slot))
|
||||||
}
|
}
|
||||||
for _, beaconCommittee := range response.Data {
|
for _, beaconCommittee := range response.Data {
|
||||||
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
|
||||||
@@ -266,86 +260,116 @@ func (c *command) processSlots(ctx context.Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := participations[index]; !exists {
|
if _, exists := c.participations[index]; !exists {
|
||||||
participations[index] = &attestingValidator{
|
c.participations[index] = &attestingValidator{
|
||||||
Validator: index,
|
Validator: index,
|
||||||
Slot: beaconCommittee.Slot,
|
EffectiveBalance: c.validatorInfo[index].Validator.EffectiveBalance,
|
||||||
Committee: beaconCommittee.Index,
|
Slot: beaconCommittee.Slot,
|
||||||
|
Committee: beaconCommittee.Index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slotCommittees = allCommittees[attestation.Data.Slot]
|
slotCommittees = allCommittees[attestationData.Slot]
|
||||||
}
|
}
|
||||||
committee := slotCommittees[attestation.Data.Index]
|
if err := c.extractAttestationData(ctx, attestation, attestationData, slotCommittees, slot, headersCache); err != nil {
|
||||||
|
return err
|
||||||
inclusionDistance := slot - attestation.Data.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 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 nil, nil, nil, nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range attestation.AggregationBits.Len() {
|
|
||||||
if attestation.AggregationBits.BitAt(i) {
|
|
||||||
validatorIndex := committee[int(i)]
|
|
||||||
if len(c.validators) > 0 {
|
|
||||||
if _, exists := c.validators[validatorIndex]; !exists {
|
|
||||||
// Not one of our validators.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only set the information from the first attestation we find for this validator.
|
|
||||||
if participations[validatorIndex].InclusionSlot == 0 {
|
|
||||||
participations[validatorIndex].HeadVote = &attestation.Data.BeaconBlockRoot
|
|
||||||
participations[validatorIndex].Head = &head
|
|
||||||
participations[validatorIndex].TargetVote = &attestation.Data.Target.Root
|
|
||||||
participations[validatorIndex].Target = &target
|
|
||||||
participations[validatorIndex].InclusionSlot = slot
|
|
||||||
}
|
|
||||||
|
|
||||||
votes[validatorIndex] = struct{}{}
|
|
||||||
if _, exists := headCorrects[validatorIndex]; !exists && headCorrect {
|
|
||||||
headCorrects[validatorIndex] = struct{}{}
|
|
||||||
}
|
|
||||||
if _, exists := headTimelys[validatorIndex]; !exists && headCorrect && inclusionDistance == 1 {
|
|
||||||
headTimelys[validatorIndex] = 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 votes,
|
return nil
|
||||||
headCorrects,
|
}
|
||||||
headTimelys,
|
|
||||||
sourceTimelys,
|
func (c *command) extractAttestationData(ctx context.Context,
|
||||||
targetCorrects,
|
attestation *spec.VersionedAttestation,
|
||||||
targetTimelys,
|
attestationData *phase0.AttestationData,
|
||||||
participations,
|
slotCommittees map[phase0.CommitteeIndex][]phase0.ValidatorIndex,
|
||||||
nil
|
slot phase0.Slot,
|
||||||
|
headersCache *util.BeaconBlockHeaderCache,
|
||||||
|
) error {
|
||||||
|
inclusionDistance := slot - attestationData.Slot
|
||||||
|
|
||||||
|
head, err := util.AttestationHead(ctx, headersCache, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target, err := util.AttestationTarget(ctx, headersCache, c.chainTime, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
committee := slotCommittees[attestationData.Index]
|
||||||
|
// Update with all of the committees if we have committee bits (from Electra onwards).
|
||||||
|
committeeBits, err := attestation.CommitteeBits()
|
||||||
|
if err == nil {
|
||||||
|
committee = make([]phase0.ValidatorIndex, 0)
|
||||||
|
for _, index := range committeeBits.BitIndices() {
|
||||||
|
committee = append(committee, slotCommittees[phase0.CommitteeIndex(index)]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationBits, err := attestation.AggregationBits()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to obtain aggregation bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range aggregationBits.Len() {
|
||||||
|
if aggregationBits.BitAt(i) {
|
||||||
|
validatorIndex := committee[i]
|
||||||
|
if len(c.validators) > 0 {
|
||||||
|
if _, exists := c.validators[validatorIndex]; !exists {
|
||||||
|
// Not one of our validators.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only set the information from the first attestation we find for this validator.
|
||||||
|
if c.participations[validatorIndex].InclusionSlot == 0 {
|
||||||
|
c.participations[validatorIndex].HeadVote = &attestationData.BeaconBlockRoot
|
||||||
|
c.participations[validatorIndex].Head = &head
|
||||||
|
c.participations[validatorIndex].TargetVote = &attestationData.Target.Root
|
||||||
|
c.participations[validatorIndex].Target = &target
|
||||||
|
c.participations[validatorIndex].InclusionSlot = slot
|
||||||
|
}
|
||||||
|
|
||||||
|
validatorBalance := big.NewInt(int64(c.validatorInfo[validatorIndex].Validator.EffectiveBalance))
|
||||||
|
if _, exists := c.participatingValidators[validatorIndex]; !exists {
|
||||||
|
c.summary.ParticipatingBalance = c.summary.ParticipatingBalance.Add(c.summary.ParticipatingBalance, validatorBalance)
|
||||||
|
c.participatingValidators[validatorIndex] = struct{}{}
|
||||||
|
}
|
||||||
|
if _, exists := c.headCorrectValidators[validatorIndex]; !exists && headCorrect {
|
||||||
|
c.headCorrectValidators[validatorIndex] = struct{}{}
|
||||||
|
c.summary.HeadCorrectBalance = c.summary.HeadCorrectBalance.Add(c.summary.HeadCorrectBalance, validatorBalance)
|
||||||
|
}
|
||||||
|
if _, exists := c.headTimelyValidators[validatorIndex]; !exists && headCorrect && inclusionDistance == 1 {
|
||||||
|
c.headTimelyValidators[validatorIndex] = struct{}{}
|
||||||
|
c.summary.HeadTimelyBalance = c.summary.HeadTimelyBalance.Add(c.summary.HeadTimelyBalance, validatorBalance)
|
||||||
|
}
|
||||||
|
if _, exists := c.sourceTimelyValidators[validatorIndex]; !exists && inclusionDistance <= 5 {
|
||||||
|
c.sourceTimelyValidators[validatorIndex] = struct{}{}
|
||||||
|
c.summary.SourceTimelyBalance = c.summary.SourceTimelyBalance.Add(c.summary.SourceTimelyBalance, validatorBalance)
|
||||||
|
}
|
||||||
|
if _, exists := c.targetCorrectValidators[validatorIndex]; !exists && targetCorrect {
|
||||||
|
c.targetCorrectValidators[validatorIndex] = struct{}{}
|
||||||
|
c.summary.TargetCorrectBalance = c.summary.TargetCorrectBalance.Add(c.summary.TargetCorrectBalance, validatorBalance)
|
||||||
|
}
|
||||||
|
if _, exists := c.targetTimelyValidators[validatorIndex]; !exists && targetCorrect && inclusionDistance <= 32 {
|
||||||
|
c.targetTimelyValidators[validatorIndex] = struct{}{}
|
||||||
|
c.summary.TargetTimelyBalance = c.summary.TargetTimelyBalance.Add(c.summary.TargetTimelyBalance, validatorBalance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||||
@@ -402,8 +426,10 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
|||||||
for i := range aggregate.SyncCommitteeBits.Len() {
|
for i := range aggregate.SyncCommitteeBits.Len() {
|
||||||
validatorIndex := committee.Validators[int(i)]
|
validatorIndex := committee.Validators[int(i)]
|
||||||
if _, exists := c.validators[validatorIndex]; !exists {
|
if _, exists := c.validators[validatorIndex]; !exists {
|
||||||
// Not one of ours.
|
if len(c.validators) > 0 {
|
||||||
continue
|
// Not one of ours.
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !aggregate.SyncCommitteeBits.BitAt(i) {
|
if !aggregate.SyncCommitteeBits.BitAt(i) {
|
||||||
missed[validatorIndex]++
|
missed[validatorIndex]++
|
||||||
@@ -501,6 +527,10 @@ func (c *command) processBlobs(ctx context.Context) error {
|
|||||||
// No blobs in these forks.
|
// No blobs in these forks.
|
||||||
case spec.DataVersionDeneb:
|
case spec.DataVersionDeneb:
|
||||||
c.summary.Blobs += len(block.Deneb.Message.Body.BlobKZGCommitments)
|
c.summary.Blobs += len(block.Deneb.Message.Body.BlobKZGCommitments)
|
||||||
|
case spec.DataVersionElectra:
|
||||||
|
c.summary.Blobs += len(block.Electra.Message.Body.BlobKZGCommitments)
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
c.summary.Blobs += len(block.Fulu.Message.Body.BlobKZGCommitments)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,32 +72,35 @@ In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.
|
|||||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||||
genesis := genesisResponse.Data
|
genesis := genesisResponse.Data
|
||||||
|
|
||||||
response, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, &api.ForkOpts{State: "head"})
|
response, err := eth2Client.(consensusclient.SpecProvider).Spec(ctx, &api.SpecOpts{})
|
||||||
errCheck(err, "Failed to obtain fork information")
|
errCheck(err, "Failed to obtain spec information")
|
||||||
|
|
||||||
// Check against current and prior fork versions.
|
// Check against Capella fork version (EIP-7044)
|
||||||
signatureBytes := make([]byte, 96)
|
signatureBytes := make([]byte, 96)
|
||||||
copy(signatureBytes, signedOp.Signature[:])
|
copy(signatureBytes, signedOp.Signature[:])
|
||||||
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
||||||
errCheck(err, "Invalid signature")
|
errCheck(err, "Invalid signature")
|
||||||
|
|
||||||
verified := false
|
|
||||||
|
|
||||||
// Try with the current fork.
|
|
||||||
domain := phase0.Domain{}
|
domain := phase0.Domain{}
|
||||||
currentExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, response.Data.CurrentVersion[:], genesis.GenesisValidatorsRoot[:])
|
forkRaw, ok := response.Data["CAPELLA_FORK_VERSION"]
|
||||||
errCheck(err, "Failed to compute domain")
|
if !ok {
|
||||||
copy(domain[:], currentExitDomain)
|
err = errors.New("failed to obtain Capella fork version")
|
||||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
|
||||||
errCheck(err, "Failed to verify voluntary exit")
|
|
||||||
if !verified {
|
|
||||||
// Try again with the previous fork.
|
|
||||||
previousExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, response.Data.PreviousVersion[:], genesis.GenesisValidatorsRoot[:])
|
|
||||||
copy(domain[:], previousExitDomain)
|
|
||||||
errCheck(err, "Failed to compute domain")
|
|
||||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
|
||||||
errCheck(err, "Failed to verify voluntary exit")
|
|
||||||
}
|
}
|
||||||
|
errCheck(err, "Failed to obtain fork version")
|
||||||
|
|
||||||
|
fork, ok := forkRaw.(phase0.Version)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("fork version is not of a valid type")
|
||||||
|
}
|
||||||
|
errCheck(err, "Failed to obtain fork version")
|
||||||
|
|
||||||
|
exitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork[:], genesis.GenesisValidatorsRoot[:])
|
||||||
|
errCheck(err, "Failed to compute domain")
|
||||||
|
|
||||||
|
copy(domain[:], exitDomain)
|
||||||
|
verified, err := util.VerifyRoot(account, opRoot, domain, sig)
|
||||||
|
errCheck(err, "Failed to verify voluntary exit")
|
||||||
|
|
||||||
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
||||||
|
|
||||||
outputIf(viper.GetBool("verbose"), "Verified")
|
outputIf(viper.GetBool("verbose"), "Verified")
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
|
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +29,10 @@ func process(ctx context.Context, data *dataIn) error {
|
|||||||
return errors.New("no data")
|
return errors.New("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := data.eth2Client.(eth2client.EventsProvider).Events(ctx, data.topics, eventHandler)
|
err := data.eth2Client.(eth2client.EventsProvider).Events(ctx, &api.EventsOpts{
|
||||||
|
Topics: data.topics,
|
||||||
|
Handler: eventHandler,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect for events")
|
return errors.Wrap(err, "failed to connect for events")
|
||||||
}
|
}
|
||||||
@@ -38,7 +42,7 @@ func process(ctx context.Context, data *dataIn) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventHandler(event *api.Event) {
|
func eventHandler(event *apiv1.Event) {
|
||||||
if event.Data == nil {
|
if event.Data == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ var bindings = map[string]func(cmd *cobra.Command){
|
|||||||
"attester/inclusion": attesterInclusionBindings,
|
"attester/inclusion": attesterInclusionBindings,
|
||||||
"block/analyze": blockAnalyzeBindings,
|
"block/analyze": blockAnalyzeBindings,
|
||||||
"block/info": blockInfoBindings,
|
"block/info": blockInfoBindings,
|
||||||
|
"block/trail": blockTrailBindings,
|
||||||
"chain/eth1votes": chainEth1VotesBindings,
|
"chain/eth1votes": chainEth1VotesBindings,
|
||||||
"chain/info": chainInfoBindings,
|
"chain/info": chainInfoBindings,
|
||||||
"chain/queues": chainQueuesBindings,
|
"chain/queues": chainQueuesBindings,
|
||||||
|
|||||||
@@ -120,6 +120,20 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
} else {
|
} else {
|
||||||
c.inclusions = append(c.inclusions, 2)
|
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)
|
||||||
|
}
|
||||||
|
case spec.DataVersionFulu:
|
||||||
|
aggregate = block.Fulu.Message.Body.SyncAggregate
|
||||||
|
if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) {
|
||||||
|
c.inclusions = append(c.inclusions, 1)
|
||||||
|
} else {
|
||||||
|
c.inclusions = append(c.inclusions, 2)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package depositdata
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type dataIn struct {
|
type dataIn struct {
|
||||||
|
debug bool
|
||||||
format string
|
format string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
withdrawalAccount string
|
withdrawalAccount string
|
||||||
@@ -39,13 +42,16 @@ type dataIn struct {
|
|||||||
forkVersion *spec.Version
|
forkVersion *spec.Version
|
||||||
domain *spec.Domain
|
domain *spec.Domain
|
||||||
passphrases []string
|
passphrases []string
|
||||||
|
compounding bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func input() (*dataIn, error) {
|
func input() (*dataIn, error) {
|
||||||
var err error
|
var err error
|
||||||
data := &dataIn{
|
data := &dataIn{
|
||||||
|
debug: viper.GetBool("debug"),
|
||||||
forkVersion: &spec.Version{},
|
forkVersion: &spec.Version{},
|
||||||
domain: &spec.Domain{},
|
domain: &spec.Domain{},
|
||||||
|
compounding: viper.GetBool("compounding"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if viper.GetString("validatoraccount") == "" {
|
if viper.GetString("validatoraccount") == "" {
|
||||||
@@ -97,6 +103,9 @@ func input() (*dataIn, error) {
|
|||||||
if withdrawalDetailsPresent > 1 {
|
if withdrawalDetailsPresent > 1 {
|
||||||
return nil, errors.New("only one of withdrawal account, public key or address is allowed")
|
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") == "" {
|
if viper.GetString("depositvalue") == "" {
|
||||||
return nil, errors.New("deposit value is required")
|
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")
|
return nil, errors.Wrap(err, "deposit value is invalid")
|
||||||
}
|
}
|
||||||
data.amount = spec.Gwei(amount)
|
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)
|
data.forkVersion, err = inputForkVersion(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,6 +122,12 @@ func input() (*dataIn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy(data.domain[:], e2types.Domain(e2types.DomainDeposit, data.forkVersion[:], e2types.ZeroGenesisValidatorsRoot))
|
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
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,17 +190,6 @@ func TestInput(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: "deposit value is required",
|
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",
|
name: "DepositValueInvalid",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
type dataOut struct {
|
type dataOut struct {
|
||||||
format string
|
format string
|
||||||
account string
|
account string
|
||||||
|
path string
|
||||||
validatorPubKey *spec.BLSPubKey
|
validatorPubKey *spec.BLSPubKey
|
||||||
withdrawalCredentials []byte
|
withdrawalCredentials []byte
|
||||||
amount spec.Gwei
|
amount spec.Gwei
|
||||||
@@ -113,6 +114,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
|||||||
[4]byte{0x80, 0x00, 0x00, 0x69}: "ropsten",
|
[4]byte{0x80, 0x00, 0x00, 0x69}: "ropsten",
|
||||||
[4]byte{0x90, 0x00, 0x00, 0x69}: "sepolia",
|
[4]byte{0x90, 0x00, 0x00, 0x69}: "sepolia",
|
||||||
[4]byte{0x01, 0x01, 0x70, 0x00}: "holesky",
|
[4]byte{0x01, 0x01, 0x70, 0x00}: "holesky",
|
||||||
|
[4]byte{0x10, 0x00, 0x09, 0x10}: "hoodi",
|
||||||
}
|
}
|
||||||
|
|
||||||
if datum.validatorPubKey == nil {
|
if datum.validatorPubKey == nil {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
@@ -40,6 +42,21 @@ func process(data *dataIn) ([]*dataOut, error) {
|
|||||||
return nil, err
|
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 {
|
for _, validatorAccount := range data.validatorAccounts {
|
||||||
validatorPubKey, err := ethdoutil.BestPublicKey(validatorAccount)
|
validatorPubKey, err := ethdoutil.BestPublicKey(validatorAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,7 +97,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
|||||||
copy(depositDataRoot[:], root[:])
|
copy(depositDataRoot[:], root[:])
|
||||||
|
|
||||||
validatorWallet := validatorAccount.(e2wtypes.AccountWalletProvider).Wallet()
|
validatorWallet := validatorAccount.(e2wtypes.AccountWalletProvider).Wallet()
|
||||||
results = append(results, &dataOut{
|
result := &dataOut{
|
||||||
format: data.format,
|
format: data.format,
|
||||||
account: fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()),
|
account: fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()),
|
||||||
validatorPubKey: &pubKey,
|
validatorPubKey: &pubKey,
|
||||||
@@ -90,8 +107,53 @@ func process(data *dataIn) ([]*dataOut, error) {
|
|||||||
forkVersion: data.forkVersion,
|
forkVersion: data.forkVersion,
|
||||||
depositMessageRoot: &depositMessageRoot,
|
depositMessageRoot: &depositMessageRoot,
|
||||||
depositDataRoot: &depositDataRoot,
|
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
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +207,11 @@ func createWithdrawalCredentials(data *dataIn) ([]byte, error) {
|
|||||||
withdrawalCredentials = make([]byte, 32)
|
withdrawalCredentials = make([]byte, 32)
|
||||||
copy(withdrawalCredentials[12:32], withdrawalAddressBytes)
|
copy(withdrawalCredentials[12:32], withdrawalAddressBytes)
|
||||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
// 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:
|
default:
|
||||||
return nil, errors.New("withdrawal account, public key or address is required")
|
return nil, errors.New("withdrawal account, public key or address is required")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func Run(cmd *cobra.Command) (string, error) {
|
|||||||
case errors.Is(err, context.DeadlineExceeded):
|
case errors.Is(err, context.DeadlineExceeded):
|
||||||
return "", errors.New("operation timed out; try increasing with --timeout option")
|
return "", errors.New("operation timed out; try increasing with --timeout option")
|
||||||
default:
|
default:
|
||||||
return "", errors.Join(errors.New("failed to process"), err)
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -243,16 +243,24 @@ func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, attestation := range attestations {
|
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.
|
// We do not have any attestations for this slot.
|
||||||
continue
|
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.
|
// We do not have any attestations for this committee.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, duty := range dutiesBySlot[attestation.Data.Slot][attestation.Data.Index] {
|
for _, duty := range dutiesBySlot[attestationData.Slot][attestationData.Index] {
|
||||||
if attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
aggregationBits, err := attestation.AggregationBits()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to obtain aggregation bits")
|
||||||
|
}
|
||||||
|
if aggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
|
||||||
// Found it.
|
// Found it.
|
||||||
if _, exists := votes[duty.ValidatorIndex]; exists {
|
if _, exists := votes[duty.ValidatorIndex]; exists {
|
||||||
// Duplicate; ignore.
|
// Duplicate; ignore.
|
||||||
@@ -261,13 +269,13 @@ func (c *command) processAttesterDutiesSlot(ctx context.Context,
|
|||||||
votes[duty.ValidatorIndex] = struct{}{}
|
votes[duty.ValidatorIndex] = struct{}{}
|
||||||
|
|
||||||
// Update the metrics for the attestation.
|
// 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++
|
c.summary.Slots[index].Attestations.Included++
|
||||||
inclusionDelay := slot - duty.Slot
|
inclusionDelay := slot - duty.Slot
|
||||||
|
|
||||||
fault := &validatorFault{
|
fault := &validatorFault{
|
||||||
Validator: duty.ValidatorIndex,
|
Validator: duty.ValidatorIndex,
|
||||||
AttestationData: attestation.Data,
|
AttestationData: attestationData,
|
||||||
InclusionDistance: int(inclusionDelay),
|
InclusionDistance: int(inclusionDelay),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,14 +28,16 @@ var validatorCredentialsSetCmd = &cobra.Command{
|
|||||||
|
|
||||||
ethdo validator credentials set --validator=primary/validator --withdrawal-address=0x00...13 --private-key=0x00...1f
|
ethdo validator credentials set --validator=primary/validator --withdrawal-address=0x00...13 --private-key=0x00...1f
|
||||||
|
|
||||||
|
Note that to change credentials the private key to the existing BLS withdrawal credentials must be available, either directly as an account or private key, or via the mnemonic that was used when generating the initial deposits.
|
||||||
|
|
||||||
The validator account can be specified in one of a number of ways:
|
The validator account can be specified in one of a number of ways:
|
||||||
|
|
||||||
- mnemonic using --mnemonic; this will scan the mnemonic and generate all applicable operations
|
- mnemonic using --mnemonic; this will scan the mnemonic and generate all applicable operations
|
||||||
- mnemonic and path to the validator key using --mnemonic and --path; this will generate a single operation
|
- mnemonic and path to the validator key using --mnemonic and --path; this will generate a single operation
|
||||||
- mnemonic and validator index or public key --mnemonic and --validator; this will generate a single operation
|
- mnemonic and validator index or public key --mnemonic and --validator; this will generate a single operation
|
||||||
- mnemonic and withdrawal private key using --mnemonic and --private-key; this will generate all applicable operations
|
- mnemonic and existing BLS withdrawal private key using --mnemonic and --private-key; this will generate all applicable operations
|
||||||
- validator and withdrawal private key using --validator and --private-key; this will generate a single operation
|
- validator and existing BLS withdrawal private key using --validator and --private-key; this will generate a single operation
|
||||||
- account and withdrawal account using --account and --withdrawal-account; this will generate a single operation
|
- account and existing BLS 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.`,
|
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, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2019 - 2022 Weald Technology Trading
|
// Copyright © 2019 - 2025 Weald Technology Trading
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -26,7 +26,7 @@ var validatorDepositDataCmd = &cobra.Command{
|
|||||||
Short: "Generate deposit data for one or more validators",
|
Short: "Generate deposit data for one or more validators",
|
||||||
Long: `Generate data for deposits to the Ethereum 1 validator contract. For example:
|
Long: `Generate data for deposits to the Ethereum 1 validator contract. For example:
|
||||||
|
|
||||||
ethdo validator depositdata --validatoraccount=primary/validator --withdrawalaccount=primary/current --value="32 Ether"
|
ethdo validator depositdata --validatoraccount=primary/validator --withdrawalaccount=primary/current --depositvalue="32 Ether"
|
||||||
|
|
||||||
If validatoraccount is provided with an account path it will generate deposit data for all matching accounts.
|
If validatoraccount is provided with an account path it will generate deposit data for all matching accounts.
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ func init() {
|
|||||||
validatorDepositDataCmd.Flags().Bool("raw", false, "Print raw deposit data transaction data")
|
validatorDepositDataCmd.Flags().Bool("raw", false, "Print raw deposit data transaction data")
|
||||||
validatorDepositDataCmd.Flags().String("forkversion", "", "Use a hard-coded fork version (default is to use mainnet value)")
|
validatorDepositDataCmd.Flags().String("forkversion", "", "Use a hard-coded fork version (default is to use mainnet value)")
|
||||||
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
|
validatorDepositDataCmd.Flags().Bool("launchpad", false, "Print launchpad-compatible JSON")
|
||||||
|
validatorDepositDataCmd.Flags().Bool("compounding", false, "Create a compounding (max 2048 ETH) validator")
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatorDepositdataBindings(cmd *cobra.Command) {
|
func validatorDepositdataBindings(cmd *cobra.Command) {
|
||||||
@@ -84,4 +85,7 @@ func validatorDepositdataBindings(cmd *cobra.Command) {
|
|||||||
if err := viper.BindPFlag("launchpad", cmd.Flags().Lookup("launchpad")); err != nil {
|
if err := viper.BindPFlag("launchpad", cmd.Flags().Lookup("launchpad")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if err := viper.BindPFlag("compounding", cmd.Flags().Lookup("compounding")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2020 - 2022 Weald Technology Trading
|
// Copyright © 2020 - 2025 Weald Technology Trading
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -67,9 +67,17 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
|||||||
os.Exit(_exitFailure)
|
os.Exit(_exitFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), "head")
|
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), viper.GetString("blockid"))
|
||||||
errCheck(err, "Failed to obtain validator")
|
errCheck(err, "Failed to obtain validator")
|
||||||
|
|
||||||
|
if viper.GetBool("json") {
|
||||||
|
data, err := json.Marshal(validator)
|
||||||
|
errCheck(err, "failed to marshal JSON")
|
||||||
|
fmt.Fprintf(os.Stdout, "%s", string(data))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if viper.GetBool("verbose") {
|
if viper.GetBool("verbose") {
|
||||||
network, err := util.Network(ctx, eth2Client)
|
network, err := util.Network(ctx, eth2Client)
|
||||||
errCheck(err, "Failed to obtain network")
|
errCheck(err, "Failed to obtain network")
|
||||||
@@ -185,7 +193,8 @@ func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
validatorCmd.AddCommand(validatorInfoCmd)
|
validatorCmd.AddCommand(validatorInfoCmd)
|
||||||
validatorInfoCmd.Flags().String("validator", "", "Public key for which to obtain status")
|
validatorInfoCmd.Flags().String("validator", "", "ID of the validator")
|
||||||
|
validatorInfoCmd.Flags().String("blockid", "head", "the block at which to fetch the information")
|
||||||
validatorFlags(validatorInfoCmd)
|
validatorFlags(validatorInfoCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,4 +202,7 @@ func validatorInfoBindings(cmd *cobra.Command) {
|
|||||||
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2019 - 2024 Weald Technology Trading.
|
// Copyright © 2019 - 2025 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
// ReleaseVersion is the release version of the codebase.
|
// ReleaseVersion is the release version of the codebase.
|
||||||
// Usually overridden by tag names when building binaries.
|
// Usually overridden by tag names when building binaries.
|
||||||
var ReleaseVersion = "local build (latest release 1.36.5)"
|
var ReleaseVersion = "local build (latest release 1.39.0)"
|
||||||
|
|
||||||
// versionCmd represents the version command.
|
// versionCmd represents the version command.
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -371,6 +371,18 @@ Deposits: 0
|
|||||||
Voluntary exits: 0
|
Voluntary exits: 0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `trail`
|
||||||
|
|
||||||
|
`ethdo block trail` tracks back from the provided block to see if it is in a chain descending from the a target block. Options include:
|
||||||
|
|
||||||
|
- `blockid`: the ID (slot, root, 'head') of the block to trail from; defaults to head
|
||||||
|
- `target`: the target block (slot, block hash, 'justified', 'finalized') to check; defaults to 'justified'
|
||||||
|
- `max-blocks`: the maximum number of blocks to look at to find the target
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ ethdo block trail
|
||||||
|
Target 'justified' found at a distance of 54 block(s)
|
||||||
|
```
|
||||||
### `chain` commands
|
### `chain` commands
|
||||||
|
|
||||||
Chain commands focus on providing information about Ethereum consensus chains.
|
Chain commands focus on providing information about Ethereum consensus chains.
|
||||||
@@ -659,6 +671,7 @@ $ ethdo validator credentials set --validator=Validators/1 --withdrawal-address=
|
|||||||
- `withdrawaladdress` specify the Ethereum execution address to be used for the withdrawal credentials (if withdrawalpubkey is not supplied)
|
- `withdrawaladdress` specify the Ethereum execution address to be used for the withdrawal credentials (if withdrawalpubkey is not supplied)
|
||||||
- `withdrawalpubkey` specify the public key to be used for the withdrawal credentials (if withdrawalaccount is not supplied)
|
- `withdrawalpubkey` specify the public key to be used for the withdrawal credentials (if withdrawalaccount is not supplied)
|
||||||
- `validatoraccount` specify the account to be used for the validator
|
- `validatoraccount` specify the account to be used for the validator
|
||||||
|
- `compounding` specify if this validator should be compounding, retaining Ether above 32 ETH by default to compound rewards
|
||||||
- `depositvalue` specify the amount of the deposit
|
- `depositvalue` specify the amount of the deposit
|
||||||
- `forkversion` specify the fork version for the deposit signature; this defaults to mainnet. Note that supplying an incorrect value could result in the loss of your deposit, so only supply this value if you are sure you know what you are doing. You can find the value for other chains by fetching the value supplied in "Genesis fork version" of the `ethdo chain info` command
|
- `forkversion` specify the fork version for the deposit signature; this defaults to mainnet. Note that supplying an incorrect value could result in the loss of your deposit, so only supply this value if you are sure you know what you are doing. You can find the value for other chains by fetching the value supplied in "Genesis fork version" of the `ethdo chain info` command
|
||||||
- `raw` generate raw hex output that can be supplied as the data to an Ethereum 1 deposit transaction
|
- `raw` generate raw hex output that can be supplied as the data to an Ethereum 1 deposit transaction
|
||||||
@@ -682,6 +695,7 @@ $ ethdo validator exit --private-key=0x01e748d098d3bcb477d636f19d510399ae18205fa
|
|||||||
`ethdo validator info` provides information for a given validator. Options include:
|
`ethdo validator info` provides information for a given validator. Options include:
|
||||||
|
|
||||||
- `validator`: the validator for which to obtain information, as a [validator specifier](https://github.com/wealdtech/ethdo#validator-specifier)
|
- `validator`: the validator for which to obtain information, as a [validator specifier](https://github.com/wealdtech/ethdo#validator-specifier)
|
||||||
|
- `blockid`: the ID (slot, root, 'head') of the block at which to obtain information
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo validator info --validator=Validators/1
|
$ ethdo validator info --validator=Validators/1
|
||||||
|
|||||||
74
go.mod
74
go.mod
@@ -1,16 +1,16 @@
|
|||||||
module github.com/wealdtech/ethdo
|
module github.com/wealdtech/ethdo
|
||||||
|
|
||||||
go 1.22.7
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/attestantio/go-eth2-client v0.22.0
|
github.com/attestantio/go-eth2-client v0.27.1
|
||||||
github.com/ferranbt/fastssz v0.1.4
|
github.com/ferranbt/fastssz v0.1.4
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||||
github.com/herumi/bls-eth-go-binary v1.36.1
|
github.com/herumi/bls-eth-go-binary v1.36.4
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
@@ -18,92 +18,88 @@ require (
|
|||||||
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
|
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
|
||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.6
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.20.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0
|
github.com/tyler-smith/go-bip39 v1.1.0
|
||||||
github.com/wealdtech/go-bytesutil v1.2.1
|
github.com/wealdtech/go-bytesutil v1.2.1
|
||||||
github.com/wealdtech/go-ecodec v1.1.4
|
github.com/wealdtech/go-ecodec v1.1.4
|
||||||
github.com/wealdtech/go-eth2-types/v2 v2.8.2
|
github.com/wealdtech/go-eth2-types/v2 v2.8.2
|
||||||
github.com/wealdtech/go-eth2-util v1.8.2
|
github.com/wealdtech/go-eth2-util v1.8.2
|
||||||
github.com/wealdtech/go-eth2-wallet v1.17.0
|
github.com/wealdtech/go-eth2-wallet v1.17.2
|
||||||
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1
|
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1
|
||||||
github.com/wealdtech/go-eth2-wallet-distributed v1.2.1
|
github.com/wealdtech/go-eth2-wallet-distributed v1.2.2
|
||||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1
|
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1
|
||||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.0
|
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.2
|
||||||
github.com/wealdtech/go-eth2-wallet-keystore v1.0.0
|
github.com/wealdtech/go-eth2-wallet-keystore v1.0.2
|
||||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.0
|
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.1
|
||||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1
|
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1
|
||||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.12.0
|
github.com/wealdtech/go-eth2-wallet-store-s3 v1.12.0
|
||||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.2
|
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.2
|
||||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.12.0
|
github.com/wealdtech/go-eth2-wallet-types/v2 v2.12.0
|
||||||
github.com/wealdtech/go-string2eth v1.2.1
|
github.com/wealdtech/go-string2eth v1.2.1
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.23.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go v1.55.5 // indirect
|
github.com/aws/aws-sdk-go v1.55.6 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emicklei/dot v1.6.4 // indirect
|
github.com/emicklei/dot v1.8.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/goccy/go-yaml v1.15.12 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/goccy/go-yaml v1.16.0 // indirect
|
||||||
github.com/holiman/uint256 v1.3.2 // indirect
|
github.com/holiman/uint256 v1.3.2 // indirect
|
||||||
github.com/huandu/go-clone v1.7.2 // indirect
|
github.com/huandu/go-clone v1.7.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/magiconair/properties v1.8.9 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/highwayhash v1.0.3 // indirect
|
github.com/minio/highwayhash v1.0.3 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pk910/dynamic-ssz v0.0.5 // indirect
|
github.com/pk910/dynamic-ssz v0.0.6 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
github.com/prometheus/client_golang v1.21.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.61.0 // indirect
|
github.com/prometheus/common v0.63.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/protolambda/zssz v0.1.5 // indirect
|
github.com/protolambda/zssz v0.1.5 // indirect
|
||||||
github.com/r3labs/sse/v2 v2.10.0 // indirect
|
github.com/r3labs/sse/v2 v2.10.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
github.com/sagikazarmark/locafero v0.8.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
|
||||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 // indirect
|
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.14.0 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.7.1 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/wealdtech/eth2-signer-api v1.7.2 // indirect
|
github.com/wealdtech/eth2-signer-api v1.7.2 // indirect
|
||||||
github.com/wealdtech/go-indexer v1.1.0 // indirect
|
github.com/wealdtech/go-indexer v1.1.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect
|
google.golang.org/grpc v1.71.0 // indirect
|
||||||
google.golang.org/grpc v1.69.2 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
google.golang.org/protobuf v1.36.0 // indirect
|
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
|
||||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
161
go.sum
161
go.sum
@@ -1,13 +1,13 @@
|
|||||||
github.com/attestantio/go-eth2-client v0.22.0 h1:KmF9kPNNWWGfE7l1BP7pXps4EOXgKnYeFGR0/WbyFhY=
|
github.com/attestantio/go-eth2-client v0.27.1 h1:g7bm+gG/p+gfzYdEuxuAepVWYb8EO+2KojV5/Lo2BxM=
|
||||||
github.com/attestantio/go-eth2-client v0.22.0/go.mod h1:d7ZPNrMX8jLfIgML5u7QZxFo2AukLM+5m08iMaLdqb8=
|
github.com/attestantio/go-eth2-client v0.27.1/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY=
|
||||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -18,8 +18,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
|
|||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/emicklei/dot v1.6.4 h1:cG9ycT67d9Yw22G+mAb4XiuUz6E6H1S0zePp/5Cwe/c=
|
github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI=
|
||||||
github.com/emicklei/dot v1.6.4/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||||
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
|
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
@@ -31,8 +31,10 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/goccy/go-yaml v1.15.12 h1:KLUSwfrUcTU6F8sAkf23OIPYC6aFVMNFSu4btROEm6w=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/goccy/go-yaml v1.15.12/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/goccy/go-yaml v1.16.0 h1:d7m1G7A0t+logajVtklHfDYJs2Et9g3gHwdBNNFou0w=
|
||||||
|
github.com/goccy/go-yaml v1.16.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
@@ -40,16 +42,14 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
|
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
|
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/herumi/bls-eth-go-binary v1.36.4 h1:yff41RSbfyZwfE1NF/qddP5nXhgdU0c3RGOpYOoM7YM=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/herumi/bls-eth-go-binary v1.36.4/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
||||||
github.com/herumi/bls-eth-go-binary v1.36.1 h1:SfLjxbO1fWkKtKS7J3Ezd1/5QXrcaTZgWynxdSe10hQ=
|
|
||||||
github.com/herumi/bls-eth-go-binary v1.36.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
|
|
||||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||||
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||||
github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
|
github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
|
||||||
@@ -66,18 +66,17 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
|
||||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -96,19 +95,19 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx
|
|||||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pk910/dynamic-ssz v0.0.5 h1:VP9heGYUwzlpyhk28P2nCAzhvGsePJOOOO5vQMDh2qQ=
|
github.com/pk910/dynamic-ssz v0.0.6 h1:Tu97LSc2TtCyqRfoSbhG9XuR/FbA7CkKeAnlkgUydFY=
|
||||||
github.com/pk910/dynamic-ssz v0.0.5/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c=
|
github.com/pk910/dynamic-ssz v0.0.6/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||||
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
|
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
|
||||||
@@ -127,26 +126,24 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ=
|
||||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
|
||||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
||||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
|
||||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@@ -167,22 +164,22 @@ github.com/wealdtech/go-eth2-types/v2 v2.8.2 h1:b5aXlNBLKgjAg/Fft9VvGlqAUCQMP5Lz
|
|||||||
github.com/wealdtech/go-eth2-types/v2 v2.8.2/go.mod h1:IAz9Lz1NVTaHabQa+4zjk2QDKMv8LVYo0n46M9o/TXw=
|
github.com/wealdtech/go-eth2-types/v2 v2.8.2/go.mod h1:IAz9Lz1NVTaHabQa+4zjk2QDKMv8LVYo0n46M9o/TXw=
|
||||||
github.com/wealdtech/go-eth2-util v1.8.2 h1:gq+JMrnadifyKadUr75wmfP7+usiqMu9t3VVoob5Dvo=
|
github.com/wealdtech/go-eth2-util v1.8.2 h1:gq+JMrnadifyKadUr75wmfP7+usiqMu9t3VVoob5Dvo=
|
||||||
github.com/wealdtech/go-eth2-util v1.8.2/go.mod h1:/80GAK0K/3+PqUBZHvaOPd3b1sjHeimxQh1nrJzgaPk=
|
github.com/wealdtech/go-eth2-util v1.8.2/go.mod h1:/80GAK0K/3+PqUBZHvaOPd3b1sjHeimxQh1nrJzgaPk=
|
||||||
github.com/wealdtech/go-eth2-wallet v1.17.0 h1:hMjGRjvpk95gguW6UXFDkRHWjYqE0cdrO7cOClF9Ubo=
|
github.com/wealdtech/go-eth2-wallet v1.17.2 h1:tFkWddJwH8Iq3H9K1Fnp4avxNn+4qbE3Go7k81a/c1U=
|
||||||
github.com/wealdtech/go-eth2-wallet v1.17.0/go.mod h1:qMmDrx//GrdZ3q+0Jf9SNwCaLsFOxOmXgr1yptpSMIE=
|
github.com/wealdtech/go-eth2-wallet v1.17.2/go.mod h1:CMtJ9IpvrkW2lD3B6ZAn3q/uALcxCBPBliFpIovV8+4=
|
||||||
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1 h1:h1wZK31yonLkwddajg+Prhhd2rrvIIxQ3HxwZ3udnaY=
|
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1 h1:h1wZK31yonLkwddajg+Prhhd2rrvIIxQ3HxwZ3udnaY=
|
||||||
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1/go.mod h1:Yz1Mc+HfbG1CODeBpAQ++/Us76OdXzI5kVs1qGvUiBM=
|
github.com/wealdtech/go-eth2-wallet-dirk v1.5.1/go.mod h1:Yz1Mc+HfbG1CODeBpAQ++/Us76OdXzI5kVs1qGvUiBM=
|
||||||
github.com/wealdtech/go-eth2-wallet-distributed v1.2.1 h1:+pbG9i9b5TrWd7GDRX8yq4FKA+D7k7aI6uySEvAZ+Kk=
|
github.com/wealdtech/go-eth2-wallet-distributed v1.2.2 h1:O6nfhMRTUpblOzj8KiCLFgQAyAqrv2dweorzss/V6PU=
|
||||||
github.com/wealdtech/go-eth2-wallet-distributed v1.2.1/go.mod h1:jYkDax2VhUNKIct6TVlgxAagvR56/eg7y7J+JFq+gDo=
|
github.com/wealdtech/go-eth2-wallet-distributed v1.2.2/go.mod h1:jYkDax2VhUNKIct6TVlgxAagvR56/eg7y7J+JFq+gDo=
|
||||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1 h1:9j7bpwjT9wmwBb54ZkBhTm1uNIlFFcCJXefd/YskZPw=
|
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1 h1:9j7bpwjT9wmwBb54ZkBhTm1uNIlFFcCJXefd/YskZPw=
|
||||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1/go.mod h1:+tI1VD76E1WINI+Nstg7RVGpUolL5ql10nu2YztMO/4=
|
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.4.1/go.mod h1:+tI1VD76E1WINI+Nstg7RVGpUolL5ql10nu2YztMO/4=
|
||||||
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.2 h1:IMIyl70hbJlxOkgTcCK//3vKe5ylhGIk6oUlIlK9xp0=
|
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.2 h1:IMIyl70hbJlxOkgTcCK//3vKe5ylhGIk6oUlIlK9xp0=
|
||||||
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.2/go.mod h1:T8nyAscWIWNcNa6EG/19PwH/OCt2Ly7Orn5okmiuSP4=
|
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.2/go.mod h1:T8nyAscWIWNcNa6EG/19PwH/OCt2Ly7Orn5okmiuSP4=
|
||||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.0 h1:5g4emFacTf+sX6zx6SbZIZGR7Jx5Xr/Xdb7sXnEXlWk=
|
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.2 h1:7wB8j12LVdUR/IFLmwTxdXfuTvpXSn4yj+ZD1OhDSJY=
|
||||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.0/go.mod h1:aWgnEi07w1L9wMBRB69sYvoEONppAUly6FDQRWQGqH8=
|
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.7.2/go.mod h1:aWgnEi07w1L9wMBRB69sYvoEONppAUly6FDQRWQGqH8=
|
||||||
github.com/wealdtech/go-eth2-wallet-keystore v1.0.0 h1:DYR6TAyi7RxXoAanLSPdiufGxCX617BQwWOdCxHqHX4=
|
github.com/wealdtech/go-eth2-wallet-keystore v1.0.2 h1:OseWEBvr13voALVCdg7ojsU3Kly/FPR9sCadnsx3/tM=
|
||||||
github.com/wealdtech/go-eth2-wallet-keystore v1.0.0/go.mod h1:6DGINunnasS9y9F7KH3ya2h74fHWgSCfP3dAJWe4A6U=
|
github.com/wealdtech/go-eth2-wallet-keystore v1.0.2/go.mod h1:SjHHqYS0IragcGcOkbFqjX0lIxRe4d0mE7tPiR+R7HI=
|
||||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.0 h1:vphAFklkYMRJVo9f5rVWly7PECHrLS4yarjemBa7fRM=
|
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.1 h1:bSdDCn+o4wq5MHogGkUtqbPp6Z7Tndt2qBb1zjof96Y=
|
||||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.0/go.mod h1:kBZUZogqwvvxulEvXi5l6OjZyd7EBmCKxce5Q+lW7fs=
|
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.5.1/go.mod h1:kBZUZogqwvvxulEvXi5l6OjZyd7EBmCKxce5Q+lW7fs=
|
||||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1 h1:Ceq74WL57jdBQnrZJFJyGRBKOOFI5wwq9VoxeAbjoEk=
|
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1 h1:Ceq74WL57jdBQnrZJFJyGRBKOOFI5wwq9VoxeAbjoEk=
|
||||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1/go.mod h1:woTpldN8qThnmya/0yeD+a3u/3Zj42u6/ijgF9CGaz8=
|
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.18.1/go.mod h1:woTpldN8qThnmya/0yeD+a3u/3Zj42u6/ijgF9CGaz8=
|
||||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.12.0 h1:noknYCbHw2soPhwke1LvC99Kk/2CLN787KcgxdZ7OGo=
|
github.com/wealdtech/go-eth2-wallet-store-s3 v1.12.0 h1:noknYCbHw2soPhwke1LvC99Kk/2CLN787KcgxdZ7OGo=
|
||||||
@@ -197,51 +194,49 @@ github.com/wealdtech/go-string2eth v1.2.1 h1:u9sofvGFkp+uvTg4Nvsvy5xBaiw8AibGLLn
|
|||||||
github.com/wealdtech/go-string2eth v1.2.1/go.mod h1:9uwxm18zKZfrReXrGIbdiRYJtbE91iGcj6TezKKEx80=
|
github.com/wealdtech/go-string2eth v1.2.1/go.mod h1:9uwxm18zKZfrReXrGIbdiRYJtbE91iGcj6TezKKEx80=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||||
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||||
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
|
||||||
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
|
||||||
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
|
||||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
|
||||||
@@ -249,8 +244,6 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UD
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
|||||||
@@ -62,4 +62,6 @@ type Service interface {
|
|||||||
CapellaInitialEpoch() phase0.Epoch
|
CapellaInitialEpoch() phase0.Epoch
|
||||||
// DenebInitialEpoch provides the epoch at which the Deneb hard fork takes place.
|
// DenebInitialEpoch provides the epoch at which the Deneb hard fork takes place.
|
||||||
DenebInitialEpoch() phase0.Epoch
|
DenebInitialEpoch() phase0.Epoch
|
||||||
|
// ElectraInitialEpoch provides the epoch at which the Electra hard fork takes place.
|
||||||
|
ElectraInitialEpoch() phase0.Epoch
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type Service struct {
|
|||||||
bellatrixForkEpoch phase0.Epoch
|
bellatrixForkEpoch phase0.Epoch
|
||||||
capellaForkEpoch phase0.Epoch
|
capellaForkEpoch phase0.Epoch
|
||||||
denebForkEpoch phase0.Epoch
|
denebForkEpoch phase0.Epoch
|
||||||
|
electraForkEpoch phase0.Epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
// module-wide log.
|
// module-wide log.
|
||||||
@@ -116,6 +117,13 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
|||||||
}
|
}
|
||||||
log.Trace().Uint64("epoch", uint64(denebForkEpoch)).Msg("Obtained Deneb fork epoch")
|
log.Trace().Uint64("epoch", uint64(denebForkEpoch)).Msg("Obtained Deneb fork epoch")
|
||||||
|
|
||||||
|
electraForkEpoch, err := fetchElectraForkEpoch(ctx, parameters.specProvider)
|
||||||
|
if err != nil {
|
||||||
|
// Set to far future epoch.
|
||||||
|
electraForkEpoch = 0xffffffffffffffff
|
||||||
|
}
|
||||||
|
log.Trace().Uint64("epoch", uint64(electraForkEpoch)).Msg("Obtained Electra fork epoch")
|
||||||
|
|
||||||
s := &Service{
|
s := &Service{
|
||||||
genesisTime: genesisResponse.Data.GenesisTime,
|
genesisTime: genesisResponse.Data.GenesisTime,
|
||||||
slotDuration: slotDuration,
|
slotDuration: slotDuration,
|
||||||
@@ -125,6 +133,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
|||||||
bellatrixForkEpoch: bellatrixForkEpoch,
|
bellatrixForkEpoch: bellatrixForkEpoch,
|
||||||
capellaForkEpoch: capellaForkEpoch,
|
capellaForkEpoch: capellaForkEpoch,
|
||||||
denebForkEpoch: denebForkEpoch,
|
denebForkEpoch: denebForkEpoch,
|
||||||
|
electraForkEpoch: electraForkEpoch,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -341,3 +350,32 @@ func fetchDenebForkEpoch(ctx context.Context,
|
|||||||
|
|
||||||
return phase0.Epoch(epoch), nil
|
return phase0.Epoch(epoch), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ElectraInitialEpoch provides the epoch at which the Electra hard fork takes place.
|
||||||
|
func (s *Service) ElectraInitialEpoch() phase0.Epoch {
|
||||||
|
return s.electraForkEpoch
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchElectraForkEpoch(ctx context.Context,
|
||||||
|
specProvider eth2client.SpecProvider,
|
||||||
|
) (
|
||||||
|
phase0.Epoch,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
// Fetch the fork version.
|
||||||
|
specResponse, err := specProvider.Spec(ctx, &api.SpecOpts{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to obtain spec")
|
||||||
|
}
|
||||||
|
tmp, exists := specResponse.Data["ELECTRA_FORK_EPOCH"]
|
||||||
|
if !exists {
|
||||||
|
return 0, errors.New("deneb fork version not known by chain")
|
||||||
|
}
|
||||||
|
epoch, isEpoch := tmp.(uint64)
|
||||||
|
if !isEpoch {
|
||||||
|
//nolint:revive
|
||||||
|
return 0, errors.New("ELECTRA_FORK_EPOCH is not a uint64!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return phase0.Epoch(epoch), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,22 +18,41 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
|
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
|
"github.com/attestantio/go-eth2-client/mock"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/wealdtech/ethdo/services/chaintime"
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
"github.com/wealdtech/ethdo/services/chaintime/standard"
|
"github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/testing/mock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestService(t *testing.T) {
|
func TestService(t *testing.T) {
|
||||||
genesisTime := time.Now()
|
ctx := context.Background()
|
||||||
slotDuration := 12 * time.Second
|
|
||||||
slotsPerEpoch := uint64(32)
|
|
||||||
epochsPerSyncCommitteePeriod := uint64(256)
|
|
||||||
|
|
||||||
mockGenesisProvider := mock.NewGenesisProvider(genesisTime)
|
mockClient, err := mock.New(ctx)
|
||||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
|
require.NoError(t, err)
|
||||||
|
// genesis is 1 day ago.
|
||||||
|
genesisTime := time.Now().AddDate(0, 0, -1)
|
||||||
|
mockClient.GenesisFunc = func(context.Context, *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error) {
|
||||||
|
return &api.Response[*apiv1.Genesis]{
|
||||||
|
Data: &apiv1.Genesis{
|
||||||
|
GenesisTime: genesisTime,
|
||||||
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
mockClient.SpecFunc = func(context.Context, *api.SpecOpts) (*api.Response[map[string]any], error) {
|
||||||
|
return &api.Response[map[string]any]{
|
||||||
|
Data: map[string]any{
|
||||||
|
"SECONDS_PER_SLOT": time.Second * 12,
|
||||||
|
"SLOTS_PER_EPOCH": uint64(32),
|
||||||
|
"EPOCHS_PER_SYNC_COMMITTEE_PERIOD": uint64(256),
|
||||||
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -44,7 +63,7 @@ func TestService(t *testing.T) {
|
|||||||
name: "GenesisProviderMissing",
|
name: "GenesisProviderMissing",
|
||||||
params: []standard.Parameter{
|
params: []standard.Parameter{
|
||||||
standard.WithLogLevel(zerolog.Disabled),
|
standard.WithLogLevel(zerolog.Disabled),
|
||||||
standard.WithSpecProvider(mockSpecProvider),
|
standard.WithSpecProvider(mockClient),
|
||||||
},
|
},
|
||||||
err: "problem with parameters: no genesis provider specified",
|
err: "problem with parameters: no genesis provider specified",
|
||||||
},
|
},
|
||||||
@@ -52,7 +71,7 @@ func TestService(t *testing.T) {
|
|||||||
name: "SpecProviderMissing",
|
name: "SpecProviderMissing",
|
||||||
params: []standard.Parameter{
|
params: []standard.Parameter{
|
||||||
standard.WithLogLevel(zerolog.Disabled),
|
standard.WithLogLevel(zerolog.Disabled),
|
||||||
standard.WithGenesisProvider(mockGenesisProvider),
|
standard.WithGenesisProvider(mockClient),
|
||||||
},
|
},
|
||||||
err: "problem with parameters: no spec provider specified",
|
err: "problem with parameters: no spec provider specified",
|
||||||
},
|
},
|
||||||
@@ -60,8 +79,8 @@ func TestService(t *testing.T) {
|
|||||||
name: "Good",
|
name: "Good",
|
||||||
params: []standard.Parameter{
|
params: []standard.Parameter{
|
||||||
standard.WithLogLevel(zerolog.Disabled),
|
standard.WithLogLevel(zerolog.Disabled),
|
||||||
standard.WithGenesisProvider(mockGenesisProvider),
|
standard.WithGenesisProvider(mockClient),
|
||||||
standard.WithSpecProvider(mockSpecProvider),
|
standard.WithSpecProvider(mockClient),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -80,7 +99,14 @@ func TestService(t *testing.T) {
|
|||||||
|
|
||||||
// createService is a helper that creates a mock chaintime service.
|
// createService is a helper that creates a mock chaintime service.
|
||||||
func createService(genesisTime time.Time) (chaintime.Service, time.Duration, uint64, uint64, []*phase0.Fork, error) {
|
func createService(genesisTime time.Time) (chaintime.Service, time.Duration, uint64, uint64, []*phase0.Fork, error) {
|
||||||
slotDuration := 12 * time.Second
|
ctx := context.Background()
|
||||||
|
|
||||||
|
mockClient, err := mock.New(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondsPerSlot := time.Second * 12
|
||||||
slotsPerEpoch := uint64(32)
|
slotsPerEpoch := uint64(32)
|
||||||
epochsPerSyncCommitteePeriod := uint64(256)
|
epochsPerSyncCommitteePeriod := uint64(256)
|
||||||
forkSchedule := []*phase0.Fork{
|
forkSchedule := []*phase0.Fork{
|
||||||
@@ -96,13 +122,36 @@ func createService(genesisTime time.Time) (chaintime.Service, time.Duration, uin
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mockGenesisProvider := mock.NewGenesisProvider(genesisTime)
|
mockClient.GenesisFunc = func(context.Context, *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error) {
|
||||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
|
return &api.Response[*apiv1.Genesis]{
|
||||||
s, err := standard.New(context.Background(),
|
Data: &apiv1.Genesis{
|
||||||
standard.WithGenesisProvider(mockGenesisProvider),
|
GenesisTime: genesisTime,
|
||||||
standard.WithSpecProvider(mockSpecProvider),
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
mockClient.SpecFunc = func(context.Context, *api.SpecOpts) (*api.Response[map[string]any], error) {
|
||||||
|
return &api.Response[map[string]any]{
|
||||||
|
Data: map[string]any{
|
||||||
|
"SECONDS_PER_SLOT": secondsPerSlot,
|
||||||
|
"SLOTS_PER_EPOCH": slotsPerEpoch,
|
||||||
|
"EPOCHS_PER_SYNC_COMMITTEE_PERIOD": epochsPerSyncCommitteePeriod,
|
||||||
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
mockClient.ForkScheduleFunc = func(context.Context, *api.ForkScheduleOpts) (*api.Response[[]*phase0.Fork], error) {
|
||||||
|
return &api.Response[[]*phase0.Fork]{
|
||||||
|
Data: forkSchedule,
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := standard.New(ctx,
|
||||||
|
standard.WithGenesisProvider(mockClient),
|
||||||
|
standard.WithSpecProvider(mockClient),
|
||||||
)
|
)
|
||||||
return s, slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod, forkSchedule, err
|
return s, secondsPerSlot, slotsPerEpoch, epochsPerSyncCommitteePeriod, forkSchedule, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenesisTime(t *testing.T) {
|
func TestGenesisTime(t *testing.T) {
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
// Copyright © 2021 Weald Technology Trading.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
|
||||||
"github.com/attestantio/go-eth2-client/api"
|
|
||||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
|
||||||
"github.com/attestantio/go-eth2-client/spec"
|
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenesisProvider is a mock for eth2client.GenesisProvider.
|
|
||||||
type GenesisProvider struct {
|
|
||||||
genesisTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGenesisProvider returns a mock genesis provider with the provided value.
|
|
||||||
func NewGenesisProvider(genesisTime time.Time) eth2client.GenesisProvider {
|
|
||||||
return &GenesisProvider{
|
|
||||||
genesisTime: genesisTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Genesisis a mock.
|
|
||||||
func (m *GenesisProvider) Genesis(_ context.Context, _ *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error) {
|
|
||||||
return &api.Response[*apiv1.Genesis]{
|
|
||||||
Data: &apiv1.Genesis{
|
|
||||||
GenesisTime: m.genesisTime,
|
|
||||||
},
|
|
||||||
Metadata: make(map[string]any),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpecProvider is a mock for eth2client.SpecProvider.
|
|
||||||
type SpecProvider struct {
|
|
||||||
spec map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSpecProvider returns a mock spec provider with the provided values.
|
|
||||||
func NewSpecProvider(slotDuration time.Duration,
|
|
||||||
slotsPerEpoch uint64,
|
|
||||||
epochsPerSyncCommitteePeriod uint64,
|
|
||||||
) eth2client.SpecProvider {
|
|
||||||
return &SpecProvider{
|
|
||||||
spec: map[string]any{
|
|
||||||
"SECONDS_PER_SLOT": slotDuration,
|
|
||||||
"SLOTS_PER_EPOCH": slotsPerEpoch,
|
|
||||||
"EPOCHS_PER_SYNC_COMMITTEE_PERIOD": epochsPerSyncCommitteePeriod,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spec is a mock.
|
|
||||||
func (m *SpecProvider) Spec(_ context.Context, _ *api.SpecOpts) (*api.Response[map[string]any], error) {
|
|
||||||
return &api.Response[map[string]any]{
|
|
||||||
Data: m.spec,
|
|
||||||
Metadata: make(map[string]any),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForkScheduleProvider is a mock for eth2client.ForkScheduleProvider.
|
|
||||||
type ForkScheduleProvider struct {
|
|
||||||
schedule []*phase0.Fork
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewForkScheduleProvider returns a mock spec provider with the provided values.
|
|
||||||
func NewForkScheduleProvider(schedule []*phase0.Fork) eth2client.ForkScheduleProvider {
|
|
||||||
return &ForkScheduleProvider{
|
|
||||||
schedule: schedule,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForkSchedule is a mock.
|
|
||||||
func (m *ForkScheduleProvider) ForkSchedule(_ context.Context, _ *api.ForkScheduleOpts) (*api.Response[[]*phase0.Fork], error) {
|
|
||||||
return &api.Response[[]*phase0.Fork]{
|
|
||||||
Data: m.schedule,
|
|
||||||
Metadata: make(map[string]any),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlotsPerEpochProvider is a mock for eth2client.SlotsPerEpochProvider.
|
|
||||||
type SlotsPerEpochProvider struct {
|
|
||||||
slotsPerEpoch uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSlotsPerEpochProvider returns a mock slots per epoch provider with the provided value.
|
|
||||||
func NewSlotsPerEpochProvider(slotsPerEpoch uint64) eth2client.SlotsPerEpochProvider {
|
|
||||||
return &SlotsPerEpochProvider{
|
|
||||||
slotsPerEpoch: slotsPerEpoch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SlotsPerEpoch is a mock.
|
|
||||||
func (m *SlotsPerEpochProvider) SlotsPerEpoch(_ context.Context) (uint64, error) {
|
|
||||||
return m.slotsPerEpoch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttestationsSubmitter is a mock for eth2client.AttestationsSubmitter.
|
|
||||||
type AttestationsSubmitter struct{}
|
|
||||||
|
|
||||||
// NewAttestationSubmitter returns a mock attestations submitter with the provided value.
|
|
||||||
func NewAttestationSubmitter() eth2client.AttestationsSubmitter {
|
|
||||||
return &AttestationsSubmitter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitAttestations is a mock.
|
|
||||||
func (m *AttestationsSubmitter) SubmitAttestations(_ context.Context, _ []*phase0.Attestation) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeaconBlockSubmitter is a mock for eth2client.BeaconBlockSubmitter.
|
|
||||||
type BeaconBlockSubmitter struct{}
|
|
||||||
|
|
||||||
// NewBeaconBlockSubmitter returns a mock beacon block submitter with the provided value.
|
|
||||||
func NewBeaconBlockSubmitter() eth2client.BeaconBlockSubmitter {
|
|
||||||
return &BeaconBlockSubmitter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitBeaconBlock is a mock.
|
|
||||||
func (m *BeaconBlockSubmitter) SubmitBeaconBlock(_ context.Context, _ *spec.VersionedSignedBeaconBlock) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AggregateAttestationsSubmitter is a mock for eth2client.AggregateAttestationsSubmitter.
|
|
||||||
type AggregateAttestationsSubmitter struct{}
|
|
||||||
|
|
||||||
// NewAggregateAttestationsSubmitter returns a mock aggregate attestation submitter with the provided value.
|
|
||||||
func NewAggregateAttestationsSubmitter() eth2client.AggregateAttestationsSubmitter {
|
|
||||||
return &AggregateAttestationsSubmitter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitAggregateAttestations is a mock.
|
|
||||||
func (m *AggregateAttestationsSubmitter) SubmitAggregateAttestations(_ context.Context, _ []*phase0.SignedAggregateAndProof) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeaconCommitteeSubscriptionsSubmitter is a mock for eth2client.BeaconCommitteeSubscriptionsSubmitter.
|
|
||||||
type BeaconCommitteeSubscriptionsSubmitter struct{}
|
|
||||||
|
|
||||||
// NewBeaconCommitteeSubscriptionsSubmitter returns a mock beacon committee subscription submitter with the provided value.
|
|
||||||
func NewBeaconCommitteeSubscriptionsSubmitter() eth2client.BeaconCommitteeSubscriptionsSubmitter {
|
|
||||||
return &BeaconCommitteeSubscriptionsSubmitter{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitBeaconCommitteeSubscriptions is a mock.
|
|
||||||
func (m *BeaconCommitteeSubscriptionsSubmitter) SubmitBeaconCommitteeSubscriptions(_ context.Context, _ []*apiv1.BeaconCommitteeSubscription) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -17,19 +17,26 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/attestantio/go-eth2-client/spec"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/wealdtech/ethdo/services/chaintime"
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttestationHead returns the head for which the attestation should have voted.
|
// AttestationHead returns the head for which the attestation should have voted.
|
||||||
func AttestationHead(ctx context.Context,
|
func AttestationHead(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
attestation *phase0.Attestation,
|
attestation *spec.VersionedAttestation,
|
||||||
) (
|
) (
|
||||||
phase0.Root,
|
phase0.Root,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
slot := attestation.Data.Slot
|
attestationData, err := attestation.Data()
|
||||||
|
if err != nil {
|
||||||
|
return phase0.Root{}, errors.Wrap(err, "failed to obtain attestation data")
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := attestationData.Slot
|
||||||
for {
|
for {
|
||||||
header, err := headersCache.Fetch(ctx, slot)
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,12 +60,17 @@ func AttestationHead(ctx context.Context,
|
|||||||
// AttestationHeadCorrect returns true if the given attestation had the correct head.
|
// AttestationHeadCorrect returns true if the given attestation had the correct head.
|
||||||
func AttestationHeadCorrect(ctx context.Context,
|
func AttestationHeadCorrect(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
attestation *phase0.Attestation,
|
attestation *spec.VersionedAttestation,
|
||||||
) (
|
) (
|
||||||
bool,
|
bool,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
slot := attestation.Data.Slot
|
attestationData, err := attestation.Data()
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "failed to obtain attestation data")
|
||||||
|
}
|
||||||
|
|
||||||
|
slot := attestationData.Slot
|
||||||
for {
|
for {
|
||||||
header, err := headersCache.Fetch(ctx, slot)
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -74,7 +86,8 @@ func AttestationHeadCorrect(ctx context.Context,
|
|||||||
slot--
|
slot--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return bytes.Equal(header.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
|
|
||||||
|
return bytes.Equal(header.Root[:], attestationData.BeaconBlockRoot[:]), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,13 +95,18 @@ func AttestationHeadCorrect(ctx context.Context,
|
|||||||
func AttestationTarget(ctx context.Context,
|
func AttestationTarget(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
chainTime chaintime.Service,
|
chainTime chaintime.Service,
|
||||||
attestation *phase0.Attestation,
|
attestation *spec.VersionedAttestation,
|
||||||
) (
|
) (
|
||||||
phase0.Root,
|
phase0.Root,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
|
attestationData, err := attestation.Data()
|
||||||
|
if err != nil {
|
||||||
|
return phase0.Root{}, errors.Wrap(err, "failed to obtain attestation data")
|
||||||
|
}
|
||||||
|
|
||||||
// Start with first slot of the target epoch.
|
// Start with first slot of the target epoch.
|
||||||
slot := chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
slot := chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||||
for {
|
for {
|
||||||
header, err := headersCache.Fetch(ctx, slot)
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,13 +131,18 @@ func AttestationTarget(ctx context.Context,
|
|||||||
func AttestationTargetCorrect(ctx context.Context,
|
func AttestationTargetCorrect(ctx context.Context,
|
||||||
headersCache *BeaconBlockHeaderCache,
|
headersCache *BeaconBlockHeaderCache,
|
||||||
chainTime chaintime.Service,
|
chainTime chaintime.Service,
|
||||||
attestation *phase0.Attestation,
|
attestation *spec.VersionedAttestation,
|
||||||
) (
|
) (
|
||||||
bool,
|
bool,
|
||||||
error,
|
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.
|
// Start with first slot of the target epoch.
|
||||||
slot := chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
|
slot := chainTime.FirstSlotOfEpoch(attestationData.Target.Epoch)
|
||||||
for {
|
for {
|
||||||
header, err := headersCache.Fetch(ctx, slot)
|
header, err := headersCache.Fetch(ctx, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,6 +158,7 @@ func AttestationTargetCorrect(ctx context.Context,
|
|||||||
slot--
|
slot--
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return bytes.Equal(header.Root[:], attestation.Data.Target.Root[:]), nil
|
|
||||||
|
return bytes.Equal(header.Root[:], attestationData.Target.Root[:]), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,28 +18,44 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
|
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
|
"github.com/attestantio/go-eth2-client/mock"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/testing/mock"
|
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseEpoch(t *testing.T) {
|
func TestParseEpoch(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
mockClient, err := mock.New(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
// genesis is 1 day ago.
|
// genesis is 1 day ago.
|
||||||
genesisTime := time.Now().AddDate(0, 0, -1)
|
genesisTime := time.Now().AddDate(0, 0, -1)
|
||||||
slotDuration := 12 * time.Second
|
mockClient.GenesisFunc = func(context.Context, *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error) {
|
||||||
slotsPerEpoch := uint64(32)
|
return &api.Response[*apiv1.Genesis]{
|
||||||
epochsPerSyncCommitteePeriod := uint64(256)
|
Data: &apiv1.Genesis{
|
||||||
mockGenesisProvider := mock.NewGenesisProvider(genesisTime)
|
GenesisTime: genesisTime,
|
||||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
mockClient.SpecFunc = func(context.Context, *api.SpecOpts) (*api.Response[map[string]any], error) {
|
||||||
|
return &api.Response[map[string]any]{
|
||||||
|
Data: map[string]any{
|
||||||
|
"SECONDS_PER_SLOT": time.Second * 12,
|
||||||
|
"SLOTS_PER_EPOCH": uint64(32),
|
||||||
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
chainTime, err := standardchaintime.New(context.Background(),
|
chainTime, err := standardchaintime.New(context.Background(),
|
||||||
standardchaintime.WithLogLevel(zerolog.Disabled),
|
standardchaintime.WithLogLevel(zerolog.Disabled),
|
||||||
standardchaintime.WithGenesisProvider(mockGenesisProvider),
|
standardchaintime.WithGenesisProvider(mockClient),
|
||||||
standardchaintime.WithSpecProvider(mockSpecProvider),
|
standardchaintime.WithSpecProvider(mockClient),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
@@ -18,28 +18,44 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/attestantio/go-eth2-client/api"
|
||||||
|
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
|
"github.com/attestantio/go-eth2-client/mock"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/testing/mock"
|
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSlot(t *testing.T) {
|
func TestParseSlot(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
mockClient, err := mock.New(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
// genesis is 1 day ago.
|
// genesis is 1 day ago.
|
||||||
genesisTime := time.Now().AddDate(0, 0, -1)
|
genesisTime := time.Now().AddDate(0, 0, -1)
|
||||||
slotDuration := 12 * time.Second
|
mockClient.GenesisFunc = func(context.Context, *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error) {
|
||||||
slotsPerSlot := uint64(32)
|
return &api.Response[*apiv1.Genesis]{
|
||||||
epochsPerSyncCommitteePeriod := uint64(256)
|
Data: &apiv1.Genesis{
|
||||||
mockGenesisProvider := mock.NewGenesisProvider(genesisTime)
|
GenesisTime: genesisTime,
|
||||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerSlot, epochsPerSyncCommitteePeriod)
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
mockClient.SpecFunc = func(context.Context, *api.SpecOpts) (*api.Response[map[string]any], error) {
|
||||||
|
return &api.Response[map[string]any]{
|
||||||
|
Data: map[string]any{
|
||||||
|
"SECONDS_PER_SLOT": time.Second * 12,
|
||||||
|
"SLOTS_PER_EPOCH": uint64(32),
|
||||||
|
},
|
||||||
|
Metadata: make(map[string]any),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
chainTime, err := standardchaintime.New(context.Background(),
|
chainTime, err := standardchaintime.New(context.Background(),
|
||||||
standardchaintime.WithLogLevel(zerolog.Disabled),
|
standardchaintime.WithLogLevel(zerolog.Disabled),
|
||||||
standardchaintime.WithGenesisProvider(mockGenesisProvider),
|
standardchaintime.WithGenesisProvider(mockClient),
|
||||||
standardchaintime.WithSpecProvider(mockSpecProvider),
|
standardchaintime.WithSpecProvider(mockClient),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ func ParseValidators(ctx context.Context, validatorsProvider eth2client.Validato
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(validators) == 0 && len(indices) == 0 {
|
||||||
|
// Nothing to obtain.
|
||||||
|
return validators, nil
|
||||||
|
}
|
||||||
|
|
||||||
response, err := validatorsProvider.Validators(ctx, &api.ValidatorsOpts{State: stateID, Indices: indices})
|
response, err := validatorsProvider.Validators(ctx, &api.ValidatorsOpts{State: stateID, Indices: indices})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to obtain validators %v", indices))
|
return nil, errors.Wrap(err, fmt.Sprintf("failed to obtain validators %v", indices))
|
||||||
|
|||||||
Reference in New Issue
Block a user