mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f5200296 | ||
|
|
36ddb1a2fe | ||
|
|
d4791ac8bd | ||
|
|
fff6469748 | ||
|
|
b11f9cf3a3 | ||
|
|
9d50a72270 | ||
|
|
9191cac389 | ||
|
|
f1a1d4f64c | ||
|
|
1f1ddc1719 | ||
|
|
92d964f092 | ||
|
|
972e35f7d7 | ||
|
|
5f4d7a389d | ||
|
|
696ba64238 | ||
|
|
aeb8142b27 | ||
|
|
a1ea298983 | ||
|
|
1c23e7cc54 | ||
|
|
b53e42aeb7 | ||
|
|
fa325e20f8 | ||
|
|
0ce563470f | ||
|
|
abd3567d05 | ||
|
|
399d0eaa64 | ||
|
|
655b9cbbc8 | ||
|
|
50c22818cb | ||
|
|
4991787b9d | ||
|
|
b652d2e083 | ||
|
|
ef58db3307 | ||
|
|
a95851dc9e | ||
|
|
ac7e0985fb | ||
|
|
7837165d46 | ||
|
|
46020396e4 | ||
|
|
105de0a139 | ||
|
|
a7da10360e | ||
|
|
d5351cbe7f | ||
|
|
874839754c | ||
|
|
4db0cdae3a | ||
|
|
6f0f3e4c91 | ||
|
|
713bbdd60c | ||
|
|
46ca70a615 | ||
|
|
2f24bb7884 | ||
|
|
9136c053b1 | ||
|
|
8efab62f8b | ||
|
|
7d723148ab | ||
|
|
2880ec9bdd | ||
|
|
871d1694ef | ||
|
|
f26c9e9c4a | ||
|
|
b28d5b2693 | ||
|
|
695f62bbd5 | ||
|
|
0b8de1f615 | ||
|
|
c1e5f1dd23 | ||
|
|
2e4337fe6d | ||
|
|
dbe45d5c27 | ||
|
|
c963ea8ba5 | ||
|
|
484361c034 | ||
|
|
d65f51d5af | ||
|
|
7857e97057 | ||
|
|
545665a79f | ||
|
|
394fd2bb04 | ||
|
|
a7631a6a7f | ||
|
|
d174219ddc | ||
|
|
854f3061b9 | ||
|
|
6c34d25ebb | ||
|
|
df34cef2bd | ||
|
|
e8513e60b2 | ||
|
|
4aadee3fad | ||
|
|
c7adf03aeb | ||
|
|
35944ca8c5 | ||
|
|
083d625813 | ||
|
|
263db812b3 | ||
|
|
2c01d18195 | ||
|
|
df99d43415 | ||
|
|
d81546f2de | ||
|
|
3d87a917af | ||
|
|
9240ed4857 | ||
|
|
18f9e8dca2 | ||
|
|
fbc24b81d2 | ||
|
|
f898466395 | ||
|
|
e496fa1977 | ||
|
|
d016326779 | ||
|
|
ee14b5ee8e | ||
|
|
9ae927feab | ||
|
|
7087a0a55c | ||
|
|
793a8d6d79 | ||
|
|
e15b22dc3c | ||
|
|
6ddd453900 | ||
|
|
24755099c0 |
58
.github/workflows/docker.yml
vendored
Normal file
58
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
# Set variables that will be available to all builds.
|
||||
env_vars:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
release_version: ${{ steps.release_version.outputs.release_version }}
|
||||
binary: ${{ steps.binary.outputs.binary }}
|
||||
steps:
|
||||
- id: release_version
|
||||
run: |
|
||||
RELEASE_VERSION=$(echo ${{ github.ref_name }} | sed -e 's/^[vt]//')
|
||||
echo "release_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
|
||||
- id: binary
|
||||
run: |
|
||||
BINARY=$(basename ${{ github.repository }})
|
||||
echo "binary=${BINARY}" >> $GITHUB_OUTPUT
|
||||
|
||||
# Build.
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [env_vars]
|
||||
steps:
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
push: true
|
||||
tags: wealdtech/ethdo:latest
|
||||
|
||||
- name: build and push on release
|
||||
uses: docker/build-push-action@v4
|
||||
if: ${{ github.event.release.tag_name != '' }}
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
push: true
|
||||
tags: wealdtech/ethdo:${{ github.event.release.tag_name }}
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
go-version: '^1.20'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
@@ -59,6 +59,10 @@ jobs:
|
||||
|
||||
- name: Compile
|
||||
run: |
|
||||
# Do not attempt to upgrade grub, as it errors on github (24th Feb 2023)
|
||||
sudo apt-mark hold grub-efi-amd64-signed grub-efi-amd64-bin
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
go build -tags osusergo,netgo -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }} -extldflags -static"
|
||||
tar zcf ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz ${{ needs.env_vars.outputs.binary }}
|
||||
sha256sum ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz | sed -e 's/ .*//' >${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-amd64.tar.gz.sha256
|
||||
@@ -84,8 +88,6 @@ jobs:
|
||||
|
||||
- name: Cross compile (ARM64)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
sudo apt install -y gcc-aarch64-linux-gnu libstdc++-11-pic-arm64-cross
|
||||
CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc GOOS=linux GOARCH=arm64 go build -tags osusergo,netgo -v -ldflags="-X github.com/${{ github.repository }}/cmd.ReleaseVersion=${{ needs.env_vars.outputs.release_version }} -extldflags -static"
|
||||
tar zcf ${{ needs.env_vars.outputs.binary }}-${{ needs.env_vars.outputs.release_version }}-linux-arm64.tar.gz ${{ needs.env_vars.outputs.binary }}
|
||||
@@ -119,7 +121,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
go-version: '^1.20'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
@@ -164,7 +166,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.19
|
||||
go-version: '^1.20'
|
||||
|
||||
- name: Check out repository into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -128,6 +128,7 @@ linters:
|
||||
- contextcheck
|
||||
- cyclop
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- errorlint
|
||||
- exhaustive
|
||||
|
||||
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,3 +1,49 @@
|
||||
1.33.2:
|
||||
- fix windows build
|
||||
|
||||
1.33.1:
|
||||
- add "slot" to "proposer duties" command
|
||||
- add activation epoch and time to "validator info" command where applicable
|
||||
- add "holesky" to the list of supported networks
|
||||
- avoid crash when requesting validators from beacon node without debug enabled
|
||||
|
||||
1.33.0:
|
||||
- show all slots with 'synccommittee inclusion'
|
||||
- add "wallet batch" command
|
||||
|
||||
1.32.0:
|
||||
- fix incorrect error when "deposit verify" is not given a withdrawal address
|
||||
- allow truncated mnemonics (first four characters of each word)
|
||||
- add deneb information to "block info"
|
||||
- add epoch parameter to "validator yield"
|
||||
- add proposer index to "block info"
|
||||
- "block info" honours "--quiet" flag
|
||||
- "block info" accepts "--block-time" option
|
||||
- increase default operation timeout from 10s to 30s
|
||||
- "epoch summary" JSON lists number of blobs
|
||||
|
||||
1.31.0:
|
||||
- initial support for deneb
|
||||
- add "--generate-keystore" option for "account derive"
|
||||
- update "validator exit" command to be able to generate multiple exits
|
||||
- support for 12-word and 18-word mnemonics with single-word (no whitespace) passphrases
|
||||
- add JSON output for "validator expectation"
|
||||
|
||||
1.30.0:
|
||||
- add "chain spec" command
|
||||
- add "validator withdrawal" command
|
||||
|
||||
1.29.2:
|
||||
- fix regression where validator index could not be used as an account specifier
|
||||
|
||||
1.29.0:
|
||||
- allow use of keystores with validator credentials set
|
||||
- tidy up various command options to provide more standard usage
|
||||
- add mainnet fallback beacon node
|
||||
|
||||
1.28.4:
|
||||
- allow validator exit to use a keystore as its validator parameter
|
||||
|
||||
1.28.2:
|
||||
- fix bix stopping validator exit creation by direct validator specification
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.18-bullseye as builder
|
||||
FROM golang:1.20-bullseye as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -86,6 +86,11 @@ Teku disables the REST API by default. To enable it, the beacon node must be st
|
||||
|
||||
The default port for the REST API is 5051, which can be changed with the `--rest-api-port` parameter.
|
||||
|
||||
### Lodestar
|
||||
Lodestar enables the REST API by default and should just work locally. If you want to access the REST API from a remote server then you should also look to change the `--rest.address` to `0.0.0.0` as per the Lodestar documentation.
|
||||
|
||||
The default port for the REST API is 9596, which can be changed with the `--rest.port` parameter.
|
||||
|
||||
## Usage
|
||||
|
||||
`ethdo` contains a large number of features that are useful for day-to-day interactions with the different consensus clients.
|
||||
@@ -166,6 +171,15 @@ If set, the `--debug` argument will output additional information about the oper
|
||||
|
||||
Commands will have an exit status of 0 on success and 1 on failure. The specific definition of success is specified in the help for each command.
|
||||
|
||||
### Validator specifier
|
||||
|
||||
Ethereum validators can be specified in a number of different ways. The options are:
|
||||
|
||||
- an `ethdo` account, in the format _wallet_/_account_. It is possible to use the validator specified in this way to sign validator-related operations, if the passphrase is also supplied, with a passphrase (for local accounts) or authority (for remote accounts)
|
||||
- the validator's 48-byte public key. It is not possible to use the a validator specified in this way to sign validator-related operations
|
||||
- a keystore, supplied either as direct JSON or as a path to a keystore on the local filesystem. It is possible to use the validator specified in this way to sign validator-related operations, if the passphrase is also supplied
|
||||
- the validator's numeric index. It is not possible to use a validator specified in this way to sign validator-related operations. Note that this only works with on-chain operations, as the validator's index must be resolved to its public key
|
||||
|
||||
## Passphrase strength
|
||||
|
||||
`ethdo` will by default not allow creation or export of accounts or wallets with weak passphrases. If a weak pasphrase is used then `ethdo` will refuse to continue.
|
||||
|
||||
@@ -269,7 +269,7 @@ func ObtainChainInfoFromNode(ctx context.Context,
|
||||
}
|
||||
tmp, exists := spec["GENESIS_FORK_VERSION"]
|
||||
if !exists {
|
||||
return nil, errors.New("capella fork version not known by chain")
|
||||
return nil, errors.New("genesis fork version not known by chain")
|
||||
}
|
||||
var isForkVersion bool
|
||||
res.GenesisForkVersion, isForkVersion = tmp.(phase0.Version)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(accountCmd)
|
||||
}
|
||||
|
||||
func accountFlags(cmd *cobra.Command) {
|
||||
func accountFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type dataOut struct {
|
||||
account e2wtypes.Account
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func processPathed(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data.passphrase == "" {
|
||||
return nil, errors.New("passphrase is required")
|
||||
}
|
||||
match, err := regexp.Match("^m/[0-9]+/[0-9]+(/[0-9+])+", []byte(data.path))
|
||||
match, err := regexp.MatchString("^m/[0-9]+/[0-9]+(/[0-9+])+", data.path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to match path to regular expression")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2023 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
|
||||
@@ -28,9 +28,10 @@ type dataIn struct {
|
||||
// Output options.
|
||||
showPrivateKey bool
|
||||
showWithdrawalCredentials bool
|
||||
generateKeystore bool
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
func input(_ context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
// Quiet.
|
||||
@@ -54,5 +55,8 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
// Show withdrawal credentials.
|
||||
data.showWithdrawalCredentials = viper.GetBool("show-withdrawal-credentials")
|
||||
|
||||
// Generate keystore.
|
||||
data.generateKeystore = viper.GetBool("generate-keystore")
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2023 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -15,18 +15,26 @@ package accountderive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
ethutil "github.com/wealdtech/go-eth2-util"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
showPrivateKey bool
|
||||
showWithdrawalCredentials bool
|
||||
generateKeystore bool
|
||||
key *e2types.BLSPrivateKey
|
||||
path string
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
@@ -37,13 +45,17 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
return "", errors.New("no key")
|
||||
}
|
||||
|
||||
if data.generateKeystore {
|
||||
return outputKeystore(ctx, data)
|
||||
}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
if data.showPrivateKey {
|
||||
builder.WriteString(fmt.Sprintf("Private key: %#x\n", data.key.Marshal()))
|
||||
}
|
||||
if data.showWithdrawalCredentials {
|
||||
withdrawalCredentials := util.SHA256(data.key.PublicKey().Marshal())
|
||||
withdrawalCredentials := ethutil.SHA256(data.key.PublicKey().Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
builder.WriteString(fmt.Sprintf("Withdrawal credentials: %#x\n", withdrawalCredentials))
|
||||
}
|
||||
@@ -53,3 +65,38 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func outputKeystore(_ context.Context, data *dataOut) (string, error) {
|
||||
passphrase, err := util.GetPassphrase()
|
||||
if err != nil {
|
||||
return "", errors.New("no passphrase supplied")
|
||||
}
|
||||
|
||||
encryptor := keystorev4.New()
|
||||
crypto, err := encryptor.Encrypt(data.key.Marshal(), passphrase)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to encrypt private key")
|
||||
}
|
||||
|
||||
uuid, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", errors.New("failed to generate UUID")
|
||||
}
|
||||
ks := make(map[string]interface{})
|
||||
ks["uuid"] = uuid.String()
|
||||
ks["pubkey"] = fmt.Sprintf("%x", data.key.PublicKey().Marshal())
|
||||
ks["version"] = 4
|
||||
ks["path"] = data.path
|
||||
ks["crypto"] = crypto
|
||||
out, err := json.Marshal(ks)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to marshal keystore JSON")
|
||||
}
|
||||
|
||||
keystoreFilename := fmt.Sprintf("keystore-%s-%d.json", strings.ReplaceAll(data.path, "/", "_"), time.Now().Unix())
|
||||
|
||||
if err := os.WriteFile(keystoreFilename, out, 0o600); err != nil {
|
||||
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", keystoreFilename))
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2023 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
|
||||
@@ -40,7 +40,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
results := &dataOut{
|
||||
showPrivateKey: data.showPrivateKey,
|
||||
showWithdrawalCredentials: data.showWithdrawalCredentials,
|
||||
generateKeystore: data.generateKeystore,
|
||||
key: key.(*e2types.BLSPrivateKey),
|
||||
path: data.path,
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -25,7 +25,7 @@ type dataOut struct {
|
||||
account e2wtypes.Account
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type dataOut struct {
|
||||
key []byte
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -51,11 +51,11 @@ func init() {
|
||||
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
|
||||
}
|
||||
|
||||
func accountCreateBindings() {
|
||||
if err := viper.BindPFlag("participants", accountCreateCmd.Flags().Lookup("participants")); err != nil {
|
||||
func accountCreateBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("participants", cmd.Flags().Lookup("participants")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("signing-threshold", accountCreateCmd.Flags().Lookup("signing-threshold")); err != nil {
|
||||
if err := viper.BindPFlag("signing-threshold", cmd.Flags().Lookup("signing-threshold")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +49,17 @@ func init() {
|
||||
accountFlags(accountDeriveCmd)
|
||||
accountDeriveCmd.Flags().Bool("show-private-key", false, "show private key for derived account")
|
||||
accountDeriveCmd.Flags().Bool("show-withdrawal-credentials", false, "show withdrawal credentials for derived account")
|
||||
accountDeriveCmd.Flags().Bool("generate-keystore", false, "generate a keystore for the derived account")
|
||||
}
|
||||
|
||||
func accountDeriveBindings() {
|
||||
if err := viper.BindPFlag("show-private-key", accountDeriveCmd.Flags().Lookup("show-private-key")); err != nil {
|
||||
func accountDeriveBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("show-private-key", cmd.Flags().Lookup("show-private-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("show-withdrawal-credentials", accountDeriveCmd.Flags().Lookup("show-withdrawal-credentials")); err != nil {
|
||||
if err := viper.BindPFlag("show-withdrawal-credentials", cmd.Flags().Lookup("show-withdrawal-credentials")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("generate-keystore", cmd.Flags().Lookup("generate-keystore")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ func init() {
|
||||
accountImportCmd.Flags().String("keystore-passphrase", "", "Passphrase of keystore")
|
||||
}
|
||||
|
||||
func accountImportBindings() {
|
||||
if err := viper.BindPFlag("key", accountImportCmd.Flags().Lookup("key")); err != nil {
|
||||
func accountImportBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("key", cmd.Flags().Lookup("key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("keystore", accountImportCmd.Flags().Lookup("keystore")); err != nil {
|
||||
if err := viper.BindPFlag("keystore", cmd.Flags().Lookup("keystore")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("keystore-passphrase", accountImportCmd.Flags().Lookup("keystore-passphrase")); err != nil {
|
||||
if err := viper.BindPFlag("keystore-passphrase", cmd.Flags().Lookup("keystore-passphrase")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,11 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
|
||||
// Disallow wildcards (for now)
|
||||
assert(fmt.Sprintf("%s/%s", wallet.Name(), account.Name()) == viper.GetString("account"), "Mismatched account name")
|
||||
|
||||
if quiet {
|
||||
if viper.GetBool("quiet") {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
outputIf(verbose, fmt.Sprintf("UUID: %v", account.ID()))
|
||||
outputIf(viper.GetBool("verbose"), fmt.Sprintf("UUID: %v", account.ID()))
|
||||
var withdrawalPubKey e2types.PublicKey
|
||||
if pubKeyProvider, ok := account.(e2wtypes.AccountPublicKeyProvider); ok {
|
||||
fmt.Printf("Public key: %#x\n", pubKeyProvider.PublicKey().Marshal())
|
||||
@@ -58,7 +58,7 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
|
||||
if distributedAccount, ok := account.(e2wtypes.DistributedAccount); ok {
|
||||
fmt.Printf("Composite public key: %#x\n", distributedAccount.CompositePublicKey().Marshal())
|
||||
fmt.Printf("Signing threshold: %d/%d\n", distributedAccount.SigningThreshold(), len(distributedAccount.Participants()))
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
fmt.Printf("Participants:\n")
|
||||
for k, v := range distributedAccount.Participants() {
|
||||
fmt.Printf(" %d: %s\n", k, v)
|
||||
@@ -67,7 +67,7 @@ In quiet mode this will return 0 if the account exists, otherwise 1.`,
|
||||
|
||||
withdrawalPubKey = distributedAccount.CompositePublicKey()
|
||||
}
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
withdrawalCredentials := util.SHA256(withdrawalPubKey.Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
fmt.Printf("Withdrawal credentials: %#x\n", withdrawalCredentials)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(attesterCmd)
|
||||
}
|
||||
|
||||
func attesterFlags(cmd *cobra.Command) {
|
||||
func attesterFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import (
|
||||
"time"
|
||||
|
||||
eth2client "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/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -31,13 +33,11 @@ type dataIn struct {
|
||||
verbose bool
|
||||
debug bool
|
||||
json bool
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
account string
|
||||
pubKey string
|
||||
validator string
|
||||
eth2Client eth2client.Service
|
||||
epoch spec.Epoch
|
||||
chainTime chaintime.Service
|
||||
epoch phase0.Epoch
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -52,38 +52,37 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
data.json = viper.GetBool("json")
|
||||
|
||||
// Account or pubkey.
|
||||
if viper.GetString("account") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account or pubkey is required")
|
||||
// Validator.
|
||||
data.validator = viper.GetString("validator")
|
||||
if data.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
data.account = viper.GetString("account")
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Required data.
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
// Epoch
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
||||
// Epoch.
|
||||
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "AccountMissing",
|
||||
name: "ValidatorMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "account or pubkey is required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"timeout": "5s",
|
||||
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type dataOut struct {
|
||||
duty *api.AttesterDuty
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@ package attesterduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
@@ -32,43 +28,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if data.account != "" {
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
_, account, err = util.WalletAndAccountFromPath(ctx, data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", data.pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", data.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch validator
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
@@ -77,7 +39,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
@@ -87,7 +49,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch spec.Epoch, slotsPerEpoch uint64) (*api.AttesterDuty, error) {
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch spec.Epoch) (*api.AttesterDuty, error) {
|
||||
// Find the attesting slot for the given epoch.
|
||||
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []spec.ValidatorIndex{validator.Index})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,9 +18,11 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
@@ -33,6 +35,12 @@ func TestProcess(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
chainTime, err := standardchaintime.New(context.Background(),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
||||
{
|
||||
name: "Client",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ package attesterinclusion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
@@ -23,6 +22,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -32,15 +32,11 @@ type dataIn struct {
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
epoch spec.Epoch
|
||||
account string
|
||||
pubKey string
|
||||
index string
|
||||
validator string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -54,48 +50,35 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.verbose = viper.GetBool("verbose")
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Account.
|
||||
data.account = viper.GetString("account")
|
||||
|
||||
// PubKey.
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// ID.
|
||||
data.index = viper.GetString("index")
|
||||
|
||||
if viper.GetString("account") == "" && viper.GetString("index") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account, index or pubkey is required")
|
||||
data.validator = viper.GetString("validator")
|
||||
if data.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
// Epoch.
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
||||
if epoch > 0 {
|
||||
epoch--
|
||||
}
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
if data.debug {
|
||||
fmt.Printf("Epoch is %d\n", data.epoch)
|
||||
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "IndexMissing",
|
||||
name: "ValidatorMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "account, index or pubkey is required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"timeout": "5s",
|
||||
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ type dataOut struct {
|
||||
targetTimely bool
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
buf := strings.Builder{}
|
||||
if data == nil {
|
||||
return buf.String(), errors.New("no data")
|
||||
|
||||
@@ -31,7 +31,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var err error
|
||||
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
@@ -41,30 +44,13 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
validatorIndex, err := util.ValidatorIndex(ctx, data.eth2Client, data.account, data.pubKey, data.index)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator index")
|
||||
}
|
||||
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), []phase0.ValidatorIndex{validatorIndex})
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
@@ -175,7 +161,7 @@ func calcTargetCorrect(ctx context.Context, data *dataIn, attestation *phase0.At
|
||||
}
|
||||
}
|
||||
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch phase0.Epoch, slotsPerEpoch uint64) (*api.AttesterDuty, error) {
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch phase0.Epoch) (*api.AttesterDuty, error) {
|
||||
// Find the attesting slot for the given epoch.
|
||||
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []phase0.ValidatorIndex{validator.Index})
|
||||
if err != nil {
|
||||
|
||||
@@ -18,18 +18,26 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/http"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
eth2Client, err := auto.New(context.Background(),
|
||||
auto.WithLogLevel(zerolog.Disabled),
|
||||
auto.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||
eth2Client, err := http.New(context.Background(),
|
||||
http.WithLogLevel(zerolog.Disabled),
|
||||
http.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
chainTime, err := standardchaintime.New(context.Background(),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
||||
{
|
||||
name: "Client",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var attesterDutiesCmd = &cobra.Command{
|
||||
Short: "Obtain information about duties of an attester",
|
||||
Long: `Obtain information about dutes of an attester. For example:
|
||||
|
||||
ethdo attester duties --account=Validators/00001 --epoch=12345
|
||||
ethdo attester duties --validator=Validators/00001 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if a duty from the attester is found, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,19 +47,15 @@ In quiet mode this will return 0 if a duty from the attester is found, otherwise
|
||||
func init() {
|
||||
attesterCmd.AddCommand(attesterDutiesCmd)
|
||||
attesterFlags(attesterDutiesCmd)
|
||||
attesterDutiesCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
||||
attesterDutiesCmd.Flags().String("pubkey", "", "the public key of the attester")
|
||||
attesterDutiesCmd.Flags().Bool("json", false, "Generate JSON data for an exit; do not broadcast to network")
|
||||
attesterDutiesCmd.Flags().String("epoch", "head", "the epoch for which to obtain the duties")
|
||||
attesterDutiesCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
||||
}
|
||||
|
||||
func attesterDutiesBindings() {
|
||||
if err := viper.BindPFlag("epoch", attesterDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
func attesterDutiesBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("pubkey", attesterDutiesCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", attesterDutiesCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var attesterInclusionCmd = &cobra.Command{
|
||||
Short: "Obtain information about attester inclusion",
|
||||
Long: `Obtain information about attester inclusion. For example:
|
||||
|
||||
ethdo attester inclusion --account=Validators/00001 --epoch=12345
|
||||
ethdo attester inclusion --validator=Validators/00001 --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if an attestation from the attester is found on the block of the given epoch, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -47,19 +47,19 @@ In quiet mode this will return 0 if an attestation from the attester is found on
|
||||
func init() {
|
||||
attesterCmd.AddCommand(attesterInclusionCmd)
|
||||
attesterFlags(attesterInclusionCmd)
|
||||
attesterInclusionCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
||||
attesterInclusionCmd.Flags().String("pubkey", "", "the public key of the attester")
|
||||
attesterInclusionCmd.Flags().String("epoch", "-1", "the epoch for which to obtain the inclusion")
|
||||
attesterInclusionCmd.Flags().String("validator", "", "the index, public key, or account of the validator")
|
||||
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
|
||||
}
|
||||
|
||||
func attesterInclusionBindings() {
|
||||
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
||||
func attesterInclusionBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("pubkey", attesterInclusionCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("index", attesterInclusionCmd.Flags().Lookup("index")); err != nil {
|
||||
if err := viper.BindPFlag("index", cmd.Flags().Lookup("index")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(blockCmd)
|
||||
}
|
||||
|
||||
func blockFlags(cmd *cobra.Command) {
|
||||
func blockFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ type attestationData struct {
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -106,9 +106,8 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
if attestation.NewVotes == 0 {
|
||||
builder.WriteString("\n")
|
||||
continue
|
||||
} else {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
switch {
|
||||
case !attestation.HeadCorrect:
|
||||
builder.WriteString("head vote incorrect, ")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 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
|
||||
@@ -77,11 +77,7 @@ func (c *command) analyze(ctx context.Context, block *spec.VersionedSignedBeacon
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.analyzeSyncCommittees(ctx, block); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.analyzeSyncCommittees(ctx, block)
|
||||
}
|
||||
|
||||
func (c *command) analyzeAttestations(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
@@ -213,7 +209,7 @@ func (c *command) fetchParents(ctx context.Context, block *spec.VersionedSignedB
|
||||
return c.fetchParents(ctx, parentBlock, minSlot)
|
||||
}
|
||||
|
||||
func (c *command) processParentBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
func (c *command) processParentBlock(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
attestations, err := block.Attestations()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -259,7 +255,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
@@ -412,7 +413,7 @@ func (c *command) calcTargetCorrect(ctx context.Context, attestation *phase0.Att
|
||||
return bytes.Equal(root[:], attestation.Data.Target.Root[:]), nil
|
||||
}
|
||||
|
||||
func (c *command) analyzeSyncCommittees(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.VersionedSignedBeaconBlock) error {
|
||||
c.analysis.SyncCommitee = &syncCommitteeAnalysis{}
|
||||
switch block.Version {
|
||||
case spec.DataVersionPhase0:
|
||||
@@ -438,6 +439,13 @@ func (c *command) analyzeSyncCommittees(ctx context.Context, block *spec.Version
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
case spec.DataVersionDeneb:
|
||||
c.analysis.SyncCommitee.Contributions = int(block.Deneb.Message.Body.SyncAggregate.SyncCommitteeBits.Count())
|
||||
c.analysis.SyncCommitee.PossibleContributions = int(block.Deneb.Message.Body.SyncAggregate.SyncCommitteeBits.Len())
|
||||
c.analysis.SyncCommitee.Score = float64(c.syncRewardWeight) / float64(c.weightDenominator)
|
||||
c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions)
|
||||
c.analysis.Value += c.analysis.SyncCommitee.Value
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported block version %d", block.Version)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestProcess(t *testing.T) {
|
||||
"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\":\"Invalid block: 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\":[]}",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -34,8 +34,9 @@ type dataIn struct {
|
||||
jsonOutput bool
|
||||
sszOutput bool
|
||||
// Chain information.
|
||||
blockID string
|
||||
stream bool
|
||||
blockID string
|
||||
blockTime string
|
||||
stream bool
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -50,21 +51,20 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
data.jsonOutput = viper.GetBool("json")
|
||||
data.sszOutput = viper.GetBool("ssz")
|
||||
|
||||
data.blockID = viper.GetString("blockid")
|
||||
data.blockTime = viper.GetString("block-time")
|
||||
data.stream = viper.GetBool("stream")
|
||||
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if viper.GetString("blockid") == "" {
|
||||
data.blockID = "head"
|
||||
} else {
|
||||
// Specific slot.
|
||||
data.blockID = viper.GetString("blockid")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionBad",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020, 2021 Weald Technology Trading
|
||||
// Copyright © 2019 - 2023 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
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
@@ -43,7 +44,7 @@ type dataOut struct {
|
||||
slotsPerEpoch uint64
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
@@ -51,9 +52,10 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func outputBlockGeneral(ctx context.Context,
|
||||
func outputBlockGeneral(_ context.Context,
|
||||
verbose bool,
|
||||
slot phase0.Slot,
|
||||
proposerIndex phase0.ValidatorIndex,
|
||||
blockRoot phase0.Root,
|
||||
bodyRoot phase0.Root,
|
||||
parentRoot phase0.Root,
|
||||
@@ -69,6 +71,7 @@ func outputBlockGeneral(ctx context.Context,
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString(fmt.Sprintf("Slot: %d\n", slot))
|
||||
res.WriteString(fmt.Sprintf("Proposing validator index: %d\n", proposerIndex))
|
||||
res.WriteString(fmt.Sprintf("Epoch: %d\n", phase0.Epoch(uint64(slot)/slotsPerEpoch)))
|
||||
res.WriteString(fmt.Sprintf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(slot)*int64(slotDuration.Seconds()), 0)))
|
||||
res.WriteString(fmt.Sprintf("Block root: %#x\n", blockRoot))
|
||||
@@ -89,7 +92,7 @@ func outputBlockGeneral(ctx context.Context,
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockETH1Data(ctx context.Context, eth1Data *phase0.ETH1Data) (string, error) {
|
||||
func outputBlockETH1Data(_ context.Context, eth1Data *phase0.ETH1Data) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString(fmt.Sprintf("Ethereum 1 deposit count: %d\n", eth1Data.DepositCount))
|
||||
@@ -194,7 +197,7 @@ func outputBlockAttesterSlashings(ctx context.Context, eth2Client eth2client.Ser
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBlockDeposits(ctx context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||
func outputBlockDeposits(_ context.Context, verbose bool, deposits []*phase0.Deposit) (string, error) {
|
||||
res := strings.Builder{}
|
||||
|
||||
// Deposits.
|
||||
@@ -246,7 +249,7 @@ func outputBlockBLSToExecutionChanges(ctx context.Context, eth2Client eth2client
|
||||
} else {
|
||||
res.WriteString(fmt.Sprintf(" Validator: %#x (%d)\n", validators[op.Message.ValidatorIndex].Validator.PublicKey, op.Message.ValidatorIndex))
|
||||
res.WriteString(fmt.Sprintf(" BLS public key: %#x\n", op.Message.FromBLSPubkey))
|
||||
res.WriteString(fmt.Sprintf(" Execution address: %#x\n", op.Message.ToExecutionAddress))
|
||||
res.WriteString(fmt.Sprintf(" Execution address: %s\n", op.Message.ToExecutionAddress.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,6 +317,7 @@ func outputCapellaBlockText(ctx context.Context, data *dataOut, signedBlock *cap
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
signedBlock.Message.ProposerIndex,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
@@ -388,6 +392,116 @@ func outputCapellaBlockText(ctx context.Context, data *dataOut, signedBlock *cap
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputDenebBlockText(ctx context.Context,
|
||||
data *dataOut,
|
||||
signedBlock *deneb.SignedBeaconBlock,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
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 = outputBlockAttestations(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.Attestations)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Attester slashings.
|
||||
tmp, err = outputBlockAttesterSlashings(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.AttesterSlashings)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
res.WriteString(fmt.Sprintf("Proposer slashings: %d\n", len(body.ProposerSlashings)))
|
||||
// Add verbose proposer slashings.
|
||||
|
||||
tmp, err = outputBlockDeposits(ctx, data.verbose, signedBlock.Message.Body.Deposits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
// Voluntary exits.
|
||||
tmp, err = outputBlockVoluntaryExits(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.VoluntaryExits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputBlockBLSToExecutionChanges(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.BLSToExecutionChanges)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputDenebBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
tmp, err = outputDenebBlobInfo(ctx, data.verbose, signedBlock.Message.Body, blobs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res.WriteString(tmp)
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *bellatrix.SignedBeaconBlock) (string, error) {
|
||||
if signedBlock == nil {
|
||||
return "", errors.New("no block supplied")
|
||||
@@ -410,6 +524,7 @@ func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *b
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
signedBlock.Message.ProposerIndex,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
@@ -500,6 +615,7 @@ func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *alta
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
signedBlock.Message.ProposerIndex,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
@@ -583,6 +699,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
signedBlock.Message.ProposerIndex,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
@@ -638,7 +755,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
func outputCapellaBlockExecutionPayload(_ context.Context,
|
||||
verbose bool,
|
||||
payload *capella.ExecutionPayload,
|
||||
) (
|
||||
@@ -676,7 +793,7 @@ func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
res.WriteString(" Parent hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash))
|
||||
res.WriteString(" Fee recipient: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.FeeRecipient))
|
||||
res.WriteString(payload.FeeRecipient.String())
|
||||
res.WriteString(" Gas limit: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit))
|
||||
res.WriteString(" Gas used: ")
|
||||
@@ -706,7 +823,102 @@ func outputCapellaBlockExecutionPayload(ctx context.Context,
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockExecutionPayload(ctx context.Context,
|
||||
func outputDenebBlockExecutionPayload(_ context.Context,
|
||||
verbose bool,
|
||||
payload *deneb.ExecutionPayload,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if payload == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// If the block number is 0 then we're before the merge.
|
||||
if payload.BlockNumber == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
if !verbose {
|
||||
res.WriteString("Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
res.WriteString("Transactions: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
|
||||
} else {
|
||||
res.WriteString("Execution payload:\n")
|
||||
res.WriteString(" Execution block number: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber))
|
||||
res.WriteString(" Base fee per gas: ")
|
||||
res.WriteString(string2eth.WeiToString(payload.BaseFeePerGas.ToBig(), true))
|
||||
res.WriteString("\n Block hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.BlockHash))
|
||||
res.WriteString(" Parent hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash))
|
||||
res.WriteString(" Fee recipient: ")
|
||||
res.WriteString(payload.FeeRecipient.String())
|
||||
res.WriteString(" Gas limit: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit))
|
||||
res.WriteString(" Gas used: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasUsed))
|
||||
res.WriteString(" Timestamp: ")
|
||||
res.WriteString(fmt.Sprintf("%s (%d)\n", time.Unix(int64(payload.Timestamp), 0).String(), payload.Timestamp))
|
||||
res.WriteString(" Prev RANDAO: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.PrevRandao))
|
||||
res.WriteString(" Receipts root: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ReceiptsRoot))
|
||||
res.WriteString(" State root: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.StateRoot))
|
||||
res.WriteString(" Extra data: ")
|
||||
if utf8.Valid(payload.ExtraData) {
|
||||
res.WriteString(fmt.Sprintf("%s\n", string(payload.ExtraData)))
|
||||
} else {
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ExtraData))
|
||||
}
|
||||
res.WriteString(" Logs bloom: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.LogsBloom))
|
||||
res.WriteString(" Transactions: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Transactions)))
|
||||
res.WriteString(" Withdrawals: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", len(payload.Withdrawals)))
|
||||
res.WriteString(" Excess blob gas: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.ExcessBlobGas))
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputDenebBlobInfo(_ context.Context,
|
||||
verbose bool,
|
||||
body *deneb.BeaconBlockBody,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) (
|
||||
string,
|
||||
error,
|
||||
) {
|
||||
if body == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !verbose {
|
||||
return fmt.Sprintf("Blobs: %d\n", len(body.BlobKzgCommitments)), nil
|
||||
}
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
for i, blob := range blobs {
|
||||
if i == 0 {
|
||||
res.WriteString("Blobs\n")
|
||||
}
|
||||
res.WriteString(fmt.Sprintf(" Index: %d\n", blob.Index))
|
||||
res.WriteString(fmt.Sprintf(" KZG commitment: %s\n", blob.KzgCommitment.String()))
|
||||
res.WriteString(fmt.Sprintf(" KZG proof: %s\n", blob.KzgProof.String()))
|
||||
}
|
||||
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func outputBellatrixBlockExecutionPayload(_ context.Context,
|
||||
verbose bool,
|
||||
payload *bellatrix.ExecutionPayload,
|
||||
) (
|
||||
@@ -744,7 +956,7 @@ func outputBellatrixBlockExecutionPayload(ctx context.Context,
|
||||
res.WriteString(" Parent hash: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash))
|
||||
res.WriteString(" Fee recipient: ")
|
||||
res.WriteString(fmt.Sprintf("%#x\n", payload.FeeRecipient))
|
||||
res.WriteString(payload.FeeRecipient.String())
|
||||
res.WriteString(" Gas limit: ")
|
||||
res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit))
|
||||
res.WriteString(" Gas used: ")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -17,6 +17,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
@@ -25,8 +28,10 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/bellatrix"
|
||||
"github.com/attestantio/go-eth2-client/spec/capella"
|
||||
"github.com/attestantio/go-eth2-client/spec/deneb"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -39,6 +44,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
if data.blockID == "" && data.blockTime == "" {
|
||||
return nil, errors.New("no block ID or block time")
|
||||
}
|
||||
|
||||
results = &dataOut{
|
||||
debug: data.debug,
|
||||
@@ -58,13 +66,27 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
results.slotDuration = config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
results.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
if data.blockTime != "" {
|
||||
data.blockID, err = timeToBlockID(ctx, data.eth2Client, data.blockTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, data.blockID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon block")
|
||||
}
|
||||
if signedBlock == nil {
|
||||
if data.quiet {
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil, errors.New("empty beacon block")
|
||||
}
|
||||
if data.quiet {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
switch signedBlock.Version {
|
||||
case spec.DataVersionPhase0:
|
||||
if err := outputPhase0Block(ctx, data.jsonOutput, signedBlock.Phase0); err != nil {
|
||||
@@ -82,6 +104,14 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if err := outputCapellaBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Capella); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
case spec.DataVersionDeneb:
|
||||
blobs, err := results.eth2Client.(eth2client.BeaconBlockBlobsProvider).BeaconBlockBlobs(ctx, data.blockID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain blobs")
|
||||
}
|
||||
if err := outputDenebBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Deneb, blobs); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to output block")
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown block version")
|
||||
}
|
||||
@@ -103,13 +133,15 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
|
||||
func headEventHandler(event *api.Event) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Only interested in head events.
|
||||
if event.Topic != "head" {
|
||||
return
|
||||
}
|
||||
|
||||
blockID := fmt.Sprintf("%#x", event.Data.(*api.HeadEvent).Block[:])
|
||||
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(context.Background(), blockID)
|
||||
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, blockID)
|
||||
if err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to obtain block: %v\n", err)
|
||||
@@ -122,41 +154,30 @@ func headEventHandler(event *api.Event) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch signedBlock.Version {
|
||||
case spec.DataVersionPhase0:
|
||||
if err := outputPhase0Block(context.Background(), jsonOutput, signedBlock.Phase0); err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = outputPhase0Block(ctx, jsonOutput, signedBlock.Phase0)
|
||||
case spec.DataVersionAltair:
|
||||
if err := outputAltairBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Altair); err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = outputAltairBlock(ctx, jsonOutput, sszOutput, signedBlock.Altair)
|
||||
case spec.DataVersionBellatrix:
|
||||
if err := outputBellatrixBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Bellatrix); err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = outputBellatrixBlock(ctx, jsonOutput, sszOutput, signedBlock.Bellatrix)
|
||||
case spec.DataVersionCapella:
|
||||
if err := outputCapellaBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Capella); err != nil {
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
}
|
||||
return
|
||||
err = outputCapellaBlock(ctx, jsonOutput, sszOutput, signedBlock.Capella)
|
||||
case spec.DataVersionDeneb:
|
||||
var blobs []*deneb.BlobSidecar
|
||||
blobs, err = results.eth2Client.(eth2client.BeaconBlockBlobsProvider).BeaconBlockBlobs(ctx, blockID)
|
||||
if err == nil {
|
||||
err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Deneb, blobs)
|
||||
}
|
||||
default:
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Unknown block version: %v\n", signedBlock.Version)
|
||||
}
|
||||
err = errors.New("unknown block version")
|
||||
}
|
||||
if err != nil && !jsonOutput && !sszOutput {
|
||||
fmt.Printf("Failed to output block: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !jsonOutput && !sszOutput {
|
||||
fmt.Println("")
|
||||
}
|
||||
@@ -251,3 +272,70 @@ func outputCapellaBlock(ctx context.Context, jsonOutput bool, sszOutput bool, si
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputDenebBlock(ctx context.Context,
|
||||
jsonOutput bool,
|
||||
sszOutput bool,
|
||||
signedBlock *deneb.SignedBeaconBlock,
|
||||
blobs []*deneb.BlobSidecar,
|
||||
) error {
|
||||
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 := outputDenebBlockText(ctx, results, signedBlock, blobs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to generate text")
|
||||
}
|
||||
fmt.Print(data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func timeToBlockID(ctx context.Context, eth2Client eth2client.Service, input string) (string, error) {
|
||||
var timestamp time.Time
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(input, "0x"):
|
||||
// Hex string.
|
||||
hexTime, err := strconv.ParseInt(strings.TrimPrefix(input, "0x"), 16, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse block time as hex string")
|
||||
}
|
||||
timestamp = time.Unix(hexTime, 0)
|
||||
case !strings.Contains(input, ":"):
|
||||
// No colon, assume decimal string.
|
||||
decTime, err := strconv.ParseInt(input, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse block time as decimal string")
|
||||
}
|
||||
timestamp = time.Unix(decTime, 0)
|
||||
default:
|
||||
dateTime, err := time.Parse("2006-01-02T15:04:05", input)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse block time as datetime")
|
||||
}
|
||||
timestamp = dateTime
|
||||
}
|
||||
|
||||
// Assume timestamp.
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d", chainTime.TimestampToSlot(timestamp)), nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019 - 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -43,11 +43,11 @@ func TestProcess(t *testing.T) {
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Client",
|
||||
name: "NoBlockID",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
},
|
||||
err: "empty beacon block",
|
||||
err: "no block ID",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -49,17 +49,13 @@ func init() {
|
||||
blockFlags(blockAnalyzeCmd)
|
||||
blockAnalyzeCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
|
||||
blockAnalyzeCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
||||
blockAnalyzeCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func blockAnalyzeBindings() {
|
||||
if err := viper.BindPFlag("blockid", blockAnalyzeCmd.Flags().Lookup("blockid")); err != nil {
|
||||
func blockAnalyzeBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("stream", blockAnalyzeCmd.Flags().Lookup("stream")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", blockAnalyzeCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,22 +48,22 @@ func init() {
|
||||
blockCmd.AddCommand(blockInfoCmd)
|
||||
blockFlags(blockInfoCmd)
|
||||
blockInfoCmd.Flags().String("blockid", "head", "the ID of the block to fetch")
|
||||
blockInfoCmd.Flags().String("block-time", "", "the time of the block to fetch (format YYYY-MM-DDTHH:MM:SS, or a hex or decimal timestamp")
|
||||
blockInfoCmd.Flags().Bool("stream", false, "continually stream blocks as they arrive")
|
||||
blockInfoCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
blockInfoCmd.Flags().Bool("ssz", false, "output data in SSZ format")
|
||||
}
|
||||
|
||||
func blockInfoBindings() {
|
||||
if err := viper.BindPFlag("blockid", blockInfoCmd.Flags().Lookup("blockid")); err != nil {
|
||||
func blockInfoBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("blockid", cmd.Flags().Lookup("blockid")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("stream", blockInfoCmd.Flags().Lookup("stream")); err != nil {
|
||||
if err := viper.BindPFlag("block-time", cmd.Flags().Lookup("block-time")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", blockInfoCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("stream", cmd.Flags().Lookup("stream")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("ssz", blockInfoCmd.Flags().Lookup("ssz")); err != nil {
|
||||
if err := viper.BindPFlag("ssz", cmd.Flags().Lookup("ssz")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(chainCmd)
|
||||
}
|
||||
|
||||
func chainFlags(cmd *cobra.Command) {
|
||||
func chainFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ type vote struct {
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -42,7 +42,7 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
return c.outputText(ctx)
|
||||
}
|
||||
|
||||
func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
votes := make([]*vote, 0, len(c.votes))
|
||||
totalVotes := 0
|
||||
for _, vote := range c.votes {
|
||||
@@ -71,7 +71,7 @@ func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *command) outputText(ctx context.Context) (string, error) {
|
||||
func (c *command) outputText(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
builder.WriteString("Voting period: ")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 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
|
||||
@@ -90,6 +90,10 @@ func (c *command) process(ctx context.Context) error {
|
||||
c.slot = state.Capella.Slot
|
||||
c.incumbent = state.Capella.ETH1Data
|
||||
c.eth1DataVotes = state.Capella.ETH1DataVotes
|
||||
case spec.DataVersionDeneb:
|
||||
c.slot = state.Deneb.Slot
|
||||
c.incumbent = state.Deneb.ETH1Data
|
||||
c.eth1DataVotes = state.Deneb.ETH1DataVotes
|
||||
default:
|
||||
return fmt.Errorf("unhandled beacon state version %v", state.Version)
|
||||
}
|
||||
@@ -114,7 +118,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ type command struct {
|
||||
exitQueue int
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -36,7 +36,7 @@ func (c *command) output(ctx context.Context) (string, error) {
|
||||
return c.outputText(ctx)
|
||||
}
|
||||
|
||||
func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
output := &jsonOutput{
|
||||
ActivationQueue: c.activationQueue,
|
||||
ExitQueue: c.exitQueue,
|
||||
@@ -49,7 +49,7 @@ func (c *command) outputJSON(ctx context.Context) (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *command) outputText(ctx context.Context) (string, error) {
|
||||
func (c *command) outputText(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
if c.activationQueue > 0 {
|
||||
|
||||
@@ -58,7 +58,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type dataIn struct {
|
||||
epoch string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
func input(_ context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
|
||||
@@ -34,6 +34,7 @@ type dataOut struct {
|
||||
slot spec.Slot
|
||||
slotStart time.Time
|
||||
slotEnd time.Time
|
||||
hasSyncCommittees bool
|
||||
syncCommitteePeriod uint64
|
||||
syncCommitteePeriodStart time.Time
|
||||
syncCommitteePeriodEpochStart spec.Epoch
|
||||
@@ -41,7 +42,7 @@ type dataOut struct {
|
||||
syncCommitteePeriodEpochEnd spec.Epoch
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
@@ -56,27 +57,59 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epoch))
|
||||
builder.WriteString("\n Epoch start ")
|
||||
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epochStart.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n Epoch end ")
|
||||
builder.WriteString(data.epochEnd.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epochEnd.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\nSlot ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slot))
|
||||
builder.WriteString("\n Slot start ")
|
||||
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slotStart.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n Slot end ")
|
||||
builder.WriteString(data.slotEnd.Format("2006-01-02 15:04:05"))
|
||||
if data.verbose {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slotEnd.Unix()))
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\nSync committee period ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
||||
builder.WriteString("\n Sync committee period start ")
|
||||
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
||||
builder.WriteString(")\n Sync committee period end ")
|
||||
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
||||
builder.WriteString(")\n")
|
||||
if data.hasSyncCommittees {
|
||||
builder.WriteString("\nSync committee period ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
||||
builder.WriteString("\n Sync committee period start ")
|
||||
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
||||
if data.verbose {
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodStart.Unix()))
|
||||
}
|
||||
builder.WriteString(")\n Sync committee period end ")
|
||||
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString(" (epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
||||
if data.verbose {
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEnd.Unix()))
|
||||
}
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -29,22 +30,22 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.connection, data.timeout, data.allowInsecureConnections)
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: data.connection,
|
||||
Timeout: data.timeout,
|
||||
AllowInsecure: data.allowInsecureConnections,
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
}
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
|
||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
epochsPerSyncCommitteePeriod := config["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"].(uint64)
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
@@ -62,34 +63,33 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
results.slot = phase0.Slot(slot)
|
||||
case data.epoch != "":
|
||||
epoch, err := strconv.ParseUint(data.epoch, 10, 64)
|
||||
epoch, err := util.ParseEpoch(ctx, chainTime, data.epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse epoch")
|
||||
}
|
||||
results.slot = phase0.Slot(epoch * slotsPerEpoch)
|
||||
results.slot = chainTime.FirstSlotOfEpoch(epoch)
|
||||
case data.timestamp != "":
|
||||
timestamp, err := time.Parse("2006-01-02T15:04:05-0700", data.timestamp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse timestamp")
|
||||
}
|
||||
secs := timestamp.Sub(genesis.GenesisTime)
|
||||
if secs < 0 {
|
||||
return nil, errors.New("timestamp prior to genesis")
|
||||
}
|
||||
results.slot = phase0.Slot(secs / slotDuration)
|
||||
results.slot = chainTime.TimestampToSlot(timestamp)
|
||||
}
|
||||
|
||||
// Fill in the info given the slot.
|
||||
results.slotStart = genesis.GenesisTime.Add(time.Duration(results.slot) * slotDuration)
|
||||
results.slotEnd = genesis.GenesisTime.Add(time.Duration(results.slot+1) * slotDuration)
|
||||
results.epoch = phase0.Epoch(uint64(results.slot) / slotsPerEpoch)
|
||||
results.epochStart = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch)*slotsPerEpoch) * slotDuration)
|
||||
results.epochEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch+1)*slotsPerEpoch) * slotDuration)
|
||||
results.syncCommitteePeriod = uint64(results.epoch) / epochsPerSyncCommitteePeriod
|
||||
results.syncCommitteePeriodEpochStart = phase0.Epoch(results.syncCommitteePeriod * epochsPerSyncCommitteePeriod)
|
||||
results.syncCommitteePeriodEpochEnd = phase0.Epoch((results.syncCommitteePeriod+1)*epochsPerSyncCommitteePeriod) - 1
|
||||
results.syncCommitteePeriodStart = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochStart)*slotsPerEpoch) * slotDuration)
|
||||
results.syncCommitteePeriodEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochEnd)*slotsPerEpoch) * slotDuration)
|
||||
results.slotStart = chainTime.StartOfSlot(results.slot)
|
||||
results.slotEnd = chainTime.StartOfSlot(results.slot + 1)
|
||||
results.epoch = chainTime.SlotToEpoch(results.slot)
|
||||
results.epochStart = chainTime.StartOfEpoch(results.epoch)
|
||||
results.epochEnd = chainTime.StartOfEpoch(results.epoch + 1)
|
||||
if results.epoch >= chainTime.FirstEpochOfSyncPeriod(chainTime.AltairInitialSyncCommitteePeriod()) {
|
||||
results.hasSyncCommittees = true
|
||||
results.syncCommitteePeriod = chainTime.SlotToSyncCommitteePeriod(results.slot)
|
||||
results.syncCommitteePeriodEpochStart = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod)
|
||||
results.syncCommitteePeriodEpochEnd = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod + 1)
|
||||
results.syncCommitteePeriodStart = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochStart)
|
||||
results.syncCommitteePeriodEnd = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochEnd)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Copyright © 2021 - 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -15,7 +15,6 @@ package chaintime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -47,16 +46,11 @@ func TestProcess(t *testing.T) {
|
||||
slot: "1",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epochStart: time.Unix(1606824023, 0),
|
||||
epochEnd: time.Unix(1606824407, 0),
|
||||
slot: 1,
|
||||
slotStart: time.Unix(1606824035, 0),
|
||||
slotEnd: time.Unix(1606824047, 0),
|
||||
syncCommitteePeriod: 0,
|
||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
||||
syncCommitteePeriodEpochStart: 0,
|
||||
syncCommitteePeriodEpochEnd: 255,
|
||||
epochStart: time.Unix(1606824023, 0),
|
||||
epochEnd: time.Unix(1606824407, 0),
|
||||
slot: 1,
|
||||
slotStart: time.Unix(1606824035, 0),
|
||||
slotEnd: time.Unix(1606824047, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,17 +62,12 @@ func TestProcess(t *testing.T) {
|
||||
epoch: "2",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 2,
|
||||
epochStart: time.Unix(1606824791, 0),
|
||||
epochEnd: time.Unix(1606825175, 0),
|
||||
slot: 64,
|
||||
slotStart: time.Unix(1606824791, 0),
|
||||
slotEnd: time.Unix(1606824803, 0),
|
||||
syncCommitteePeriod: 0,
|
||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
||||
syncCommitteePeriodEpochStart: 0,
|
||||
syncCommitteePeriodEpochEnd: 255,
|
||||
epoch: 2,
|
||||
epochStart: time.Unix(1606824791, 0),
|
||||
epochEnd: time.Unix(1606825175, 0),
|
||||
slot: 64,
|
||||
slotStart: time.Unix(1606824791, 0),
|
||||
slotEnd: time.Unix(1606824803, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,20 +76,21 @@ func TestProcess(t *testing.T) {
|
||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
timeout: 10 * time.Second,
|
||||
allowInsecureConnections: true,
|
||||
timestamp: "2021-01-01T00:00:00+0000",
|
||||
timestamp: "2023-01-01T00:00:00+0000",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 6862,
|
||||
epochStart: time.Unix(1609459031, 0),
|
||||
epochEnd: time.Unix(1609459415, 0),
|
||||
slot: 219598,
|
||||
slotStart: time.Unix(1609459199, 0),
|
||||
slotEnd: time.Unix(1609459211, 0),
|
||||
syncCommitteePeriod: 26,
|
||||
syncCommitteePeriodStart: time.Unix(1609379927, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1609477847, 0),
|
||||
syncCommitteePeriodEpochStart: 6656,
|
||||
syncCommitteePeriodEpochEnd: 6911,
|
||||
epoch: 171112,
|
||||
epochStart: time.Unix(1672531031, 0),
|
||||
epochEnd: time.Unix(1672531415, 0),
|
||||
slot: 5475598,
|
||||
slotStart: time.Unix(1672531199, 0),
|
||||
slotEnd: time.Unix(1672531211, 0),
|
||||
hasSyncCommittees: true,
|
||||
syncCommitteePeriod: 668,
|
||||
syncCommitteePeriodStart: time.Unix(1672491095, 0),
|
||||
syncCommitteePeriodEnd: time.Unix(1672589399, 0),
|
||||
syncCommitteePeriodEpochStart: 171008,
|
||||
syncCommitteePeriodEpochEnd: 171264,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -112,7 +102,6 @@ func TestProcess(t *testing.T) {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
fmt.Printf("****** %d %d\n", res.syncCommitteePeriodStart.Unix(), res.syncCommitteePeriodEnd.Unix())
|
||||
require.Equal(t, test.expected, res)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ type command struct {
|
||||
additionalInfo string
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
func (c *command) output(_ context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -85,7 +85,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
|
||||
@@ -51,17 +51,13 @@ func init() {
|
||||
chainFlags(chainEth1VotesCmd)
|
||||
chainEth1VotesCmd.Flags().String("epoch", "", "epoch for which to fetch the votes")
|
||||
chainEth1VotesCmd.Flags().String("period", "", "period for which to fetch the votes")
|
||||
chainEth1VotesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func chainEth1VotesBindings() {
|
||||
if err := viper.BindPFlag("epoch", chainEth1VotesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
func chainEth1VotesBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("period", chainEth1VotesCmd.Flags().Lookup("period")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", chainEth1VotesCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("period", cmd.Flags().Lookup("period")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,12 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !viper.GetBool("quiet"),
|
||||
})
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
@@ -49,12 +54,7 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
||||
errCheck(err, "Failed to obtain current fork")
|
||||
|
||||
if quiet {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if viper.GetBool("prepare-offline") {
|
||||
fmt.Printf("Add the following to your command to run it offline:\n --offline --genesis-validators=root=%#x --fork-version=%#x\n", genesis.GenesisValidatorsRoot, fork.CurrentVersion)
|
||||
if viper.GetBool("quiet") {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
@@ -62,12 +62,12 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
fmt.Println("Genesis time: undefined")
|
||||
} else {
|
||||
fmt.Printf("Genesis time: %s\n", genesis.GenesisTime.Format(time.UnixDate))
|
||||
outputIf(verbose, fmt.Sprintf("Genesis timestamp: %v", genesis.GenesisTime.Unix()))
|
||||
outputIf(viper.GetBool("verbose"), fmt.Sprintf("Genesis timestamp: %v", genesis.GenesisTime.Unix()))
|
||||
}
|
||||
fmt.Printf("Genesis validators root: %#x\n", genesis.GenesisValidatorsRoot)
|
||||
fmt.Printf("Genesis fork version: %#x\n", config["GENESIS_FORK_VERSION"].(spec.Version))
|
||||
fmt.Printf("Current fork version: %#x\n", fork.CurrentVersion)
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
forkData := &spec.ForkData{
|
||||
CurrentVersion: fork.CurrentVersion,
|
||||
GenesisValidatorsRoot: genesis.GenesisValidatorsRoot,
|
||||
@@ -89,11 +89,7 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainInfoCmd)
|
||||
chainFlags(chainInfoCmd)
|
||||
chainInfoCmd.Flags().Bool("prepare-offline", false, "Provide information useful for offline commands")
|
||||
}
|
||||
|
||||
func chainInfoBindings() {
|
||||
if err := viper.BindPFlag("prepare-offline", chainInfoCmd.Flags().Lookup("prepare-offline")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func chainInfoBindings(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -48,14 +48,10 @@ func init() {
|
||||
chainCmd.AddCommand(chainQueuesCmd)
|
||||
chainFlags(chainQueuesCmd)
|
||||
chainQueuesCmd.Flags().String("epoch", "", "epoch for which to fetch the queues")
|
||||
chainQueuesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func chainQueuesBindings() {
|
||||
if err := viper.BindPFlag("epoch", chainQueuesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", chainQueuesCmd.Flags().Lookup("json")); err != nil {
|
||||
func chainQueuesBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
97
cmd/chainspec.go
Normal file
97
cmd/chainspec.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright © 2023 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
var chainSpecCmd = &cobra.Command{
|
||||
Use: "spec",
|
||||
Short: "Obtain specification for a chain",
|
||||
Long: `Obtain specification for a chain. For example:
|
||||
|
||||
ethdo chain spec
|
||||
|
||||
In quiet mode this will return 0 if the chain specification can be obtained, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !viper.GetBool("quiet"),
|
||||
})
|
||||
errCheck(err, "Failed to connect to Ethereum consensus node")
|
||||
|
||||
spec, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
errCheck(err, "Failed to obtain chain specification")
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return
|
||||
}
|
||||
|
||||
// Tweak the spec for output.
|
||||
for k, v := range spec {
|
||||
switch t := v.(type) {
|
||||
case phase0.Version:
|
||||
spec[k] = fmt.Sprintf("%#x", t)
|
||||
case phase0.DomainType:
|
||||
spec[k] = fmt.Sprintf("%#x", t)
|
||||
case time.Time:
|
||||
spec[k] = fmt.Sprintf("%d", t.Unix())
|
||||
case time.Duration:
|
||||
spec[k] = fmt.Sprintf("%d", uint64(t.Seconds()))
|
||||
case []byte:
|
||||
spec[k] = fmt.Sprintf("%#x", t)
|
||||
case uint64:
|
||||
spec[k] = fmt.Sprintf("%d", t)
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("json") {
|
||||
data, err := json.Marshal(spec)
|
||||
errCheck(err, "Failed to marshal JSON")
|
||||
fmt.Printf("%s\n", string(data))
|
||||
} else {
|
||||
keys := make([]string, 0, len(spec))
|
||||
for k := range spec {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
fmt.Printf("%s: %v\n", key, spec[key])
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainSpecCmd)
|
||||
chainFlags(chainSpecCmd)
|
||||
}
|
||||
|
||||
func chainSpecBindings(_ *cobra.Command) {
|
||||
}
|
||||
@@ -41,7 +41,12 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !viper.GetBool("quiet"),
|
||||
})
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
@@ -78,7 +83,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
res.WriteString(fmt.Sprintf("%d", epoch))
|
||||
res.WriteString("\n")
|
||||
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
res.WriteString("Epoch slots: ")
|
||||
res.WriteString(fmt.Sprintf("%d", epochStartSlot))
|
||||
res.WriteString("-")
|
||||
@@ -101,7 +106,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
res.WriteString("Justified epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", finality.Justified.Epoch))
|
||||
res.WriteString("\n")
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
distance := epoch - finality.Justified.Epoch
|
||||
res.WriteString("Justified epoch distance: ")
|
||||
res.WriteString(fmt.Sprintf("%d", distance))
|
||||
@@ -111,14 +116,14 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
res.WriteString("Finalized epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", finality.Finalized.Epoch))
|
||||
res.WriteString("\n")
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
distance := epoch - finality.Finalized.Epoch
|
||||
res.WriteString("Finalized epoch distance: ")
|
||||
res.WriteString(fmt.Sprintf("%d", distance))
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
validatorsProvider, isProvider := eth2Client.(eth2client.ValidatorsProvider)
|
||||
if isProvider {
|
||||
validators, err := validatorsProvider.Validators(ctx, "head", nil)
|
||||
@@ -160,7 +165,7 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
res.WriteString(fmt.Sprintf("%d", period))
|
||||
res.WriteString("\n")
|
||||
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
res.WriteString("Sync committee epochs: ")
|
||||
res.WriteString(fmt.Sprintf("%d", periodStartEpoch))
|
||||
res.WriteString("-")
|
||||
|
||||
@@ -47,14 +47,14 @@ func init() {
|
||||
chainTimeCmd.Flags().String("timestamp", "", "The timestamp for which to obtain information (format YYYY-MM-DDTHH:MM:SS+ZZZZ)")
|
||||
}
|
||||
|
||||
func chainTimeBindings() {
|
||||
if err := viper.BindPFlag("slot", chainTimeCmd.Flags().Lookup("slot")); err != nil {
|
||||
func chainTimeBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("epoch", chainTimeCmd.Flags().Lookup("epoch")); err != nil {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("timestamp", chainTimeCmd.Flags().Lookup("timestamp")); err != nil {
|
||||
if err := viper.BindPFlag("timestamp", cmd.Flags().Lookup("timestamp")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(depositCmd)
|
||||
}
|
||||
|
||||
func depositFlags(cmd *cobra.Command) {
|
||||
func depositFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
eth2util "github.com/wealdtech/go-eth2-util"
|
||||
@@ -74,6 +76,12 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
|
||||
|
||||
deposits, err := util.DepositInfoFromJSON(data)
|
||||
errCheck(err, "Failed to fetch deposit data")
|
||||
if viper.GetBool("debug") {
|
||||
data, err := json.Marshal(deposits)
|
||||
if err == nil {
|
||||
fmt.Fprintf(os.Stderr, "Deposit data is %s\n", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
var withdrawalCredentials []byte
|
||||
if depositVerifyWithdrawalPubKey != "" {
|
||||
@@ -92,7 +100,7 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
|
||||
withdrawalCredentials[0] = 0x01 // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
copy(withdrawalCredentials[12:], withdrawalAddressBytes)
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
outputIf(viper.GetBool("debug"), fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
|
||||
depositAmount := uint64(0)
|
||||
if depositVerifyDepositAmount != "" {
|
||||
@@ -120,9 +128,9 @@ In quiet mode this will return 0 if the data is verified correctly, otherwise 1.
|
||||
}
|
||||
if !verified {
|
||||
failures = true
|
||||
outputIf(!quiet, fmt.Sprintf("%s failed verification", depositName))
|
||||
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%s failed verification", depositName))
|
||||
} else {
|
||||
outputIf(!quiet, fmt.Sprintf("%s verified", depositName))
|
||||
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%s verified", depositName))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,34 +198,34 @@ func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) {
|
||||
|
||||
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
|
||||
if withdrawalCredentials == nil {
|
||||
outputIf(!quiet, "Withdrawal public key or address not supplied; withdrawal credentials NOT checked")
|
||||
outputIf(!viper.GetBool("quiet"), "Withdrawal public key or address not supplied; withdrawal credentials NOT checked")
|
||||
} else {
|
||||
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
|
||||
outputIf(!quiet, "Withdrawal credentials incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials incorrect")
|
||||
return false, nil
|
||||
}
|
||||
outputIf(!quiet, "Withdrawal credentials verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials verified")
|
||||
}
|
||||
if amount == 0 {
|
||||
outputIf(!quiet, "Amount not supplied; NOT checked")
|
||||
outputIf(!viper.GetBool("quiet"), "Amount not supplied; NOT checked")
|
||||
} else {
|
||||
if deposit.Amount != amount {
|
||||
outputIf(!quiet, "Amount incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Amount incorrect")
|
||||
return false, nil
|
||||
}
|
||||
outputIf(!quiet, "Amount verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Amount verified")
|
||||
}
|
||||
|
||||
if len(validatorPubKeys) == 0 {
|
||||
outputIf(!quiet, "Validator public key not suppled; NOT checked")
|
||||
outputIf(!viper.GetBool("quiet"), "Validator public key not suppled; NOT checked")
|
||||
} else {
|
||||
var key [48]byte
|
||||
copy(key[:], deposit.PublicKey)
|
||||
if _, exists := validatorPubKeys[key]; !exists {
|
||||
outputIf(!quiet, "Validator public key incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Validator public key incorrect")
|
||||
return false, nil
|
||||
}
|
||||
outputIf(!quiet, "Validator public key verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Validator public key verified")
|
||||
}
|
||||
|
||||
var pubKey phase0.BLSPubKey
|
||||
@@ -237,34 +245,37 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
||||
}
|
||||
|
||||
if bytes.Equal(deposit.DepositDataRoot, depositDataRoot[:]) {
|
||||
outputIf(!quiet, "Deposit data root verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit data root verified")
|
||||
} else {
|
||||
outputIf(!quiet, "Deposit data root incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit data root incorrect")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(deposit.ForkVersion) == 0 {
|
||||
if depositVerifyForkVersion != "" {
|
||||
outputIf(!quiet, "Data format does not contain fork version for verification; NOT verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Data format does not contain fork version for verification; NOT verified")
|
||||
}
|
||||
} else {
|
||||
if depositVerifyForkVersion == "" {
|
||||
outputIf(!quiet, "fork version not supplied; not checked")
|
||||
outputIf(!viper.GetBool("quiet"), "fork version not supplied; not checked")
|
||||
} else {
|
||||
forkVersion, err := hex.DecodeString(strings.TrimPrefix(depositVerifyForkVersion, "0x"))
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to decode fork version")
|
||||
}
|
||||
if bytes.Equal(deposit.ForkVersion, forkVersion) {
|
||||
outputIf(!quiet, "Fork version verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Fork version verified")
|
||||
} else {
|
||||
outputIf(!quiet, "Fork version incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Fork version incorrect")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(deposit.DepositMessageRoot) != 32 {
|
||||
outputIf(!quiet, "Deposit message root not supplied; not checked")
|
||||
} else {
|
||||
switch {
|
||||
case len(deposit.DepositMessageRoot) != 32:
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit message root not supplied; not checked")
|
||||
case len(withdrawalCredentials) != 32:
|
||||
outputIf(!viper.GetBool("quiet"), "Withdrawal credentials not available; cannot recreate deposit message")
|
||||
default:
|
||||
// We can also verify the deposit message signature.
|
||||
depositMessage := &phase0.DepositMessage{
|
||||
PublicKey: pubKey,
|
||||
@@ -277,9 +288,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
||||
}
|
||||
|
||||
if bytes.Equal(deposit.DepositMessageRoot, depositMessageRoot[:]) {
|
||||
outputIf(!quiet, "Deposit message root verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit message root verified")
|
||||
} else {
|
||||
outputIf(!quiet, "Deposit message root incorrect")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit message root incorrect")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -305,9 +316,9 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
||||
}
|
||||
signatureVerified := blsSig.Verify(containerRoot[:], validatorPubKey)
|
||||
if signatureVerified {
|
||||
outputIf(!quiet, "Deposit message signature verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit message signature verified")
|
||||
} else {
|
||||
outputIf(!quiet, "Deposit message signature NOT verified")
|
||||
outputIf(!viper.GetBool("quiet"), "Deposit message signature NOT verified")
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ func init() {
|
||||
RootCmd.AddCommand(epochCmd)
|
||||
}
|
||||
|
||||
func epochFlags(cmd *cobra.Command) {
|
||||
func epochFlags(_ *cobra.Command) {
|
||||
epochSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information (default current, can be 'current', 'last' or a number)")
|
||||
}
|
||||
|
||||
func epochBindings() {
|
||||
if err := viper.BindPFlag("epoch", epochSummaryCmd.Flags().Lookup("epoch")); err != nil {
|
||||
func epochBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
@@ -49,6 +50,9 @@ type command struct {
|
||||
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
|
||||
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
|
||||
|
||||
// Caches.
|
||||
blocksCache map[string]*spec.VersionedSignedBeaconBlock
|
||||
|
||||
// Results.
|
||||
summary *epochSummary
|
||||
}
|
||||
@@ -67,6 +71,7 @@ type epochSummary struct {
|
||||
TargetCorrectValidators int `json:"target_correct_validators"`
|
||||
TargetTimelyValidators int `json:"target_timely_validators"`
|
||||
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
|
||||
Blobs int `json:"blobs"`
|
||||
}
|
||||
|
||||
type epochProposal struct {
|
||||
@@ -86,12 +91,13 @@ type nonParticipatingValidator struct {
|
||||
Committee phase0.CommitteeIndex `json:"committee_index"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
summary: &epochSummary{},
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
summary: &epochSummary{},
|
||||
blocksCache: make(map[string]*spec.VersionedSignedBeaconBlock),
|
||||
}
|
||||
|
||||
// Timeout.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 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
|
||||
@@ -51,8 +51,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
if err := c.processSyncCommitteeDuties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return c.processBlobs(ctx)
|
||||
}
|
||||
|
||||
func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
@@ -64,7 +63,7 @@ func (c *command) processProposerDuties(ctx context.Context) error {
|
||||
return errors.New("empty proposer duties")
|
||||
}
|
||||
for _, duty := range duties {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", duty.Slot))
|
||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", duty.Slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
|
||||
}
|
||||
@@ -165,7 +164,7 @@ func (c *command) processSlots(ctx context.Context,
|
||||
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
|
||||
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
@@ -272,7 +271,7 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
|
||||
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
@@ -291,6 +290,8 @@ func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
|
||||
aggregate = block.Bellatrix.Message.Body.SyncAggregate
|
||||
case spec.DataVersionCapella:
|
||||
aggregate = block.Capella.Message.Body.SyncAggregate
|
||||
case spec.DataVersionDeneb:
|
||||
aggregate = block.Deneb.Message.Body.SyncAggregate
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
@@ -328,7 +329,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
@@ -369,3 +375,43 @@ func (c *command) setup(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processBlobs(ctx context.Context) error {
|
||||
for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
|
||||
block, err := c.fetchBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
|
||||
}
|
||||
if block == nil {
|
||||
continue
|
||||
}
|
||||
switch block.Version {
|
||||
case spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix, spec.DataVersionCapella:
|
||||
// No blobs in these forks.
|
||||
case spec.DataVersionDeneb:
|
||||
c.summary.Blobs += len(block.Deneb.Message.Body.BlobKzgCommitments)
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) fetchBlock(ctx context.Context,
|
||||
blockID string,
|
||||
) (
|
||||
*spec.VersionedSignedBeaconBlock,
|
||||
error,
|
||||
) {
|
||||
block, exists := c.blocksCache[blockID]
|
||||
if !exists {
|
||||
var err error
|
||||
block, err = c.blocksProvider.SignedBeaconBlock(ctx, blockID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch block")
|
||||
}
|
||||
c.blocksCache[blockID] = block
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
@@ -47,12 +47,8 @@ In quiet mode this will return 0 if information for the epoch is found, otherwis
|
||||
func init() {
|
||||
epochCmd.AddCommand(epochSummaryCmd)
|
||||
epochFlags(epochSummaryCmd)
|
||||
epochSummaryCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
}
|
||||
|
||||
func epochSummaryBindings() {
|
||||
epochBindings()
|
||||
if err := viper.BindPFlag("json", epochSummaryCmd.Flags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
func epochSummaryBindings(cmd *cobra.Command) {
|
||||
epochBindings(cmd)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// errCheck checks for an error and quits if it is present.
|
||||
func errCheck(err error, msg string) {
|
||||
if err != nil {
|
||||
if !quiet {
|
||||
if !viper.GetBool("quiet") {
|
||||
if msg == "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
} else {
|
||||
@@ -57,7 +59,7 @@ func assert(condition bool, msg string) {
|
||||
|
||||
// die prints an error and quits.
|
||||
func die(msg string) {
|
||||
if msg != "" && !quiet {
|
||||
if msg != "" && !viper.GetBool("quiet") {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
}
|
||||
os.Exit(_exitFailure)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(exitCmd)
|
||||
}
|
||||
|
||||
func exitFlags(cmd *cobra.Command) {
|
||||
func exitFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -14,80 +14,97 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
consensusclient "github.com/attestantio/go-eth2-client"
|
||||
v1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var exitVerifyPubKey string
|
||||
|
||||
var exitVerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify exit data is valid",
|
||||
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
||||
|
||||
ethdo exit verify --data=exitdata.json --account=primary/current
|
||||
ethdo exit verify --signed-operation=exitdata.json --validator=primary/current
|
||||
|
||||
In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
assert(viper.GetString("account") != "" || exitVerifyPubKey != "", "account or public key is required")
|
||||
account, err := exitVerifyAccount(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
assert(viper.GetString("signed-operation") != "", "signed-operation is required")
|
||||
signedOp, err := obtainSignedOperation(viper.GetString("signed-operation"))
|
||||
errCheck(err, "Failed to obtain signed operation")
|
||||
|
||||
assert(viper.GetString("exit") != "", "exit is required")
|
||||
data, err := obtainExitData(viper.GetString("exit"))
|
||||
errCheck(err, "Failed to obtain exit data")
|
||||
|
||||
// Confirm signature is good.
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !viper.GetBool("quiet"),
|
||||
})
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
validator, err := util.ParseValidator(ctx, eth2Client.(consensusclient.ValidatorsProvider), fmt.Sprintf("%d", signedOp.Message.ValidatorIndex), "head")
|
||||
errCheck(err, "failed to obtain validator")
|
||||
pubkey, err := validator.PubKey(ctx)
|
||||
errCheck(err, "failed to obtain validator public key")
|
||||
account, err := util.ParseAccount(ctx, pubkey.String(), nil, false)
|
||||
errCheck(err, "failed to obtain account")
|
||||
|
||||
// Ensure the validator is in a suitable state.
|
||||
assert(validator.Status == v1.ValidatorStateActiveOngoing, "validator not in a suitable state to exit")
|
||||
|
||||
// Obtain the hash tree root of the message to check the signature.
|
||||
opRoot, err := signedOp.Message.HashTreeRoot()
|
||||
errCheck(err, "Failed to obtain exit hash tree root")
|
||||
|
||||
genesis, err := eth2Client.(consensusclient.GenesisProvider).Genesis(ctx)
|
||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||
|
||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
var exitDomain spec.Domain
|
||||
copy(exitDomain[:], domain)
|
||||
exit := &spec.VoluntaryExit{
|
||||
Epoch: data.Exit.Message.Epoch,
|
||||
ValidatorIndex: data.Exit.Message.ValidatorIndex,
|
||||
}
|
||||
exitRoot, err := exit.HashTreeRoot()
|
||||
errCheck(err, "Failed to obtain exit hash tree root")
|
||||
fork, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, "head")
|
||||
errCheck(err, "Failed to obtain fork information")
|
||||
|
||||
// Check against current and prior fork versions.
|
||||
signatureBytes := make([]byte, 96)
|
||||
copy(signatureBytes, data.Exit.Signature[:])
|
||||
copy(signatureBytes, signedOp.Signature[:])
|
||||
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
||||
errCheck(err, "Invalid signature")
|
||||
verified, err := util.VerifyRoot(account, exitRoot, exitDomain, sig)
|
||||
|
||||
verified := false
|
||||
|
||||
// Try with the current fork.
|
||||
domain := phase0.Domain{}
|
||||
currentExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.CurrentVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
errCheck(err, "Failed to compute domain")
|
||||
copy(domain[:], currentExitDomain)
|
||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
assert(verified, "Voluntary exit failed to verify")
|
||||
if !verified {
|
||||
// Try again with the previous fork.
|
||||
previousExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.PreviousVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
copy(domain[:], previousExitDomain)
|
||||
errCheck(err, "Failed to compute domain")
|
||||
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
}
|
||||
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
||||
|
||||
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
||||
errCheck(err, "Failed to obtain current fork")
|
||||
assert(bytes.Equal(data.ForkVersion[:], fork.CurrentVersion[:]) || bytes.Equal(data.ForkVersion[:], fork.PreviousVersion[:]), "Exit is for an old fork version and is no longer valid")
|
||||
|
||||
outputIf(verbose, "Verified")
|
||||
outputIf(viper.GetBool("verbose"), "Verified")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// obtainExitData obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||
func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
||||
// obtainSignedOperation obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||
func obtainSignedOperation(input string) (*phase0.SignedVoluntaryExit, error) {
|
||||
var err error
|
||||
var data []byte
|
||||
// Input could be JSON or a path to JSON
|
||||
@@ -101,46 +118,23 @@ func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||
}
|
||||
}
|
||||
exitData := &util.ValidatorExitData{}
|
||||
err = json.Unmarshal(data, exitData)
|
||||
signedOp := &phase0.SignedVoluntaryExit{}
|
||||
err = json.Unmarshal(data, signedOp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "data is not valid JSON")
|
||||
}
|
||||
|
||||
return exitData, nil
|
||||
}
|
||||
|
||||
// exitVerifyAccount obtains the account for the exitVerify command.
|
||||
func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if viper.GetString("account") != "" {
|
||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(exitVerifyPubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", exitVerifyPubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", exitVerifyPubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
return signedOp, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
exitCmd.AddCommand(exitVerifyCmd)
|
||||
exitFlags(exitVerifyCmd)
|
||||
exitVerifyCmd.Flags().String("exit", "", "JSON data, or path to JSON data")
|
||||
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
||||
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
|
||||
}
|
||||
|
||||
func exitVerifyBindings() {
|
||||
if err := viper.BindPFlag("exit", exitVerifyCmd.Flags().Lookup("exit")); err != nil {
|
||||
func exitVerifyBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("signed-operation", cmd.Flags().Lookup("signed-operation")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(nodeCmd)
|
||||
}
|
||||
|
||||
func nodeFlags(cmd *cobra.Command) {
|
||||
func nodeFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -50,7 +50,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.topics = viper.GetStringSlice("topics")
|
||||
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionBad",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
@@ -45,8 +45,8 @@ func init() {
|
||||
nodeEventsCmd.Flags().StringSlice("topics", nil, "The topics of events for which to listen (attestation,block,chain_reorg,finalized_checkpoint,head,voluntary_exit)")
|
||||
}
|
||||
|
||||
func nodeEventsBindings() {
|
||||
if err := viper.BindPFlag("topics", nodeEventsCmd.Flags().Lookup("topics")); err != nil {
|
||||
func nodeEventsBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("topics", cmd.Flags().Lookup("topics")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,6 @@ import (
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
// defaultBeaconNode is used if no other connection is supplied.
|
||||
var defaultBeaconNode = "http://mainnet-consensus.attestant.io/"
|
||||
|
||||
var nodeInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Obtain information about a node",
|
||||
@@ -38,33 +35,19 @@ In quiet mode this will return 0 if the node information can be obtained, otherw
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
if viper.GetString("connection") != "" {
|
||||
// The user provided a connection, so don't second-guess them by using a different node.
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return
|
||||
}
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !viper.GetBool("quiet"),
|
||||
})
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
// The user did not provide a connection, so attempt to use the default node.
|
||||
if viper.GetBool("debug") {
|
||||
fmt.Fprintf(os.Stderr, "No node connection, attempting to use %s\n", defaultBeaconNode)
|
||||
}
|
||||
eth2Client, err = util.ConnectToBeaconNode(ctx, defaultBeaconNode, viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return
|
||||
}
|
||||
if !viper.GetBool("quiet") {
|
||||
fmt.Fprintf(os.Stderr, "No connection supplied; using mainnet public access endpoint\n")
|
||||
}
|
||||
}
|
||||
|
||||
if quiet {
|
||||
if viper.GetBool("quiet") {
|
||||
os.Exit(_exitSuccess)
|
||||
}
|
||||
|
||||
if verbose {
|
||||
if viper.GetBool("verbose") {
|
||||
version, err := eth2Client.(eth2client.NodeVersionProvider).NodeVersion(ctx)
|
||||
errCheck(err, "Failed to obtain node version")
|
||||
fmt.Printf("Version: %s\n", version)
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(proposerCmd)
|
||||
}
|
||||
|
||||
func proposerFlags(cmd *cobra.Command) {
|
||||
func proposerFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -37,6 +37,7 @@ type command struct {
|
||||
|
||||
// Operation.
|
||||
epoch string
|
||||
slot string
|
||||
jsonOutput bool
|
||||
|
||||
// Data access.
|
||||
@@ -53,25 +54,24 @@ type results struct {
|
||||
Duties []*apiv1.ProposerDuty `json:"duties"`
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
results: &results{},
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
timeout: viper.GetDuration("timeout"),
|
||||
connection: viper.GetString("connection"),
|
||||
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
|
||||
epoch: viper.GetString("epoch"),
|
||||
slot: viper.GetString("slot"),
|
||||
jsonOutput: viper.GetBool("json"),
|
||||
results: &results{},
|
||||
}
|
||||
|
||||
// Timeout.
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
if c.timeout == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
c.timeout = viper.GetDuration("timeout")
|
||||
|
||||
c.connection = viper.GetString("connection")
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
c.epoch = viper.GetString("epoch")
|
||||
c.jsonOutput = viper.GetBool("json")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -43,19 +43,32 @@ func (c *command) outputJSON(_ context.Context) (string, error) {
|
||||
func (c *command) outputTxt(_ context.Context) (string, error) {
|
||||
builder := strings.Builder{}
|
||||
|
||||
builder.WriteString("Epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))
|
||||
|
||||
for _, duty := range c.results.Duties {
|
||||
builder.WriteString(" Slot ")
|
||||
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
|
||||
builder.WriteString("validator ")
|
||||
if len(c.results.Duties) == 1 {
|
||||
// Only have a single slot, just print the validator.
|
||||
duty := c.results.Duties[0]
|
||||
builder.WriteString("Validator ")
|
||||
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
|
||||
if c.verbose {
|
||||
builder.WriteString(" (pubkey ")
|
||||
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
} else {
|
||||
// Have multiple slots, print per-slot information.
|
||||
builder.WriteString("Epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d:\n", c.results.Epoch))
|
||||
|
||||
for _, duty := range c.results.Duties {
|
||||
builder.WriteString(" Slot ")
|
||||
builder.WriteString(fmt.Sprintf("%d: ", duty.Slot))
|
||||
builder.WriteString("validator ")
|
||||
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
|
||||
if c.verbose {
|
||||
builder.WriteString(" (pubkey ")
|
||||
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(builder.String(), "\n"), nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
@@ -24,11 +25,45 @@ import (
|
||||
|
||||
func (c *command) process(ctx context.Context) error {
|
||||
// Obtain information we need to process.
|
||||
err := c.setup(ctx)
|
||||
if err != nil {
|
||||
if err := c.setup(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.slot != "" {
|
||||
return c.processSlot(ctx)
|
||||
}
|
||||
|
||||
return c.processEpoch(ctx)
|
||||
}
|
||||
|
||||
func (c *command) processSlot(ctx context.Context) error {
|
||||
var err error
|
||||
slot, err := util.ParseSlot(ctx, c.chainTime, c.slot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse slot")
|
||||
}
|
||||
|
||||
c.results.Epoch = c.chainTime.SlotToEpoch(slot)
|
||||
|
||||
duties, err := c.proposerDutiesProvider.ProposerDuties(ctx, c.results.Epoch, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain proposer duties")
|
||||
}
|
||||
|
||||
c.results.Duties = make([]*apiv1.ProposerDuty, 0, 1)
|
||||
|
||||
for _, duty := range duties {
|
||||
if duty.Slot == slot {
|
||||
c.results.Duties = append(c.results.Duties, duty)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) processEpoch(ctx context.Context) error {
|
||||
var err error
|
||||
c.results.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse epoch")
|
||||
@@ -46,7 +81,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, 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")
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ func init() {
|
||||
proposerCmd.AddCommand(proposerDutiesCmd)
|
||||
proposerFlags(proposerDutiesCmd)
|
||||
proposerDutiesCmd.Flags().String("epoch", "", "the epoch for which to fetch duties")
|
||||
proposerDutiesCmd.Flags().Bool("json", false, "output data in JSON format")
|
||||
proposerDutiesCmd.Flags().String("slot", "", "the slot for which to fetch duties")
|
||||
}
|
||||
|
||||
func proposerDutiesBindings() {
|
||||
if err := viper.BindPFlag("epoch", proposerDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
||||
func proposerDutiesBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("epoch", cmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("json", proposerDutiesCmd.Flags().Lookup("json")); err != nil {
|
||||
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
150
cmd/root.go
150
cmd/root.go
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 - 2021 Weald Technology Trading.
|
||||
// Copyright © 2019 - 2023 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
|
||||
@@ -33,22 +33,57 @@ import (
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
)
|
||||
var cfgFile string
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands.
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "ethdo",
|
||||
Short: "Ethereum 2 CLI",
|
||||
Long: `Manage common Ethereum 2 tasks from the command line.`,
|
||||
Short: "Ethereum consensus layer CLI",
|
||||
Long: `Manage common Ethereum consensus layer tasks from the command line.`,
|
||||
PersistentPreRunE: persistentPreRunE,
|
||||
}
|
||||
|
||||
func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
// bindings are the command-specific bindings.
|
||||
var bindings = map[string]func(cmd *cobra.Command){
|
||||
"account/create": accountCreateBindings,
|
||||
"account/derive": accountDeriveBindings,
|
||||
"account/import": accountImportBindings,
|
||||
"attester/duties": attesterDutiesBindings,
|
||||
"attester/inclusion": attesterInclusionBindings,
|
||||
"block/analyze": blockAnalyzeBindings,
|
||||
"block/info": blockInfoBindings,
|
||||
"chain/eth1votes": chainEth1VotesBindings,
|
||||
"chain/info": chainInfoBindings,
|
||||
"chain/queues": chainQueuesBindings,
|
||||
"chain/spec": chainSpecBindings,
|
||||
"chain/time": chainTimeBindings,
|
||||
"chain/verify/signedcontributionandproof": chainVerifySignedContributionAndProofBindings,
|
||||
"epoch/summary": epochSummaryBindings,
|
||||
"exit/verify": exitVerifyBindings,
|
||||
"node/events": nodeEventsBindings,
|
||||
"proposer/duties": proposerDutiesBindings,
|
||||
"slot/time": slotTimeBindings,
|
||||
"synccommittee/inclusion": synccommitteeInclusionBindings,
|
||||
"synccommittee/members": synccommitteeMembersBindings,
|
||||
"validator/credentials/get": validatorCredentialsGetBindings,
|
||||
"validator/credentials/set": validatorCredentialsSetBindings,
|
||||
"validator/depositdata": validatorDepositdataBindings,
|
||||
"validator/duties": validatorDutiesBindings,
|
||||
"validator/exit": validatorExitBindings,
|
||||
"validator/info": validatorInfoBindings,
|
||||
"validator/keycheck": validatorKeycheckBindings,
|
||||
"validator/summary": validatorSummaryBindings,
|
||||
"validator/yield": validatorYieldBindings,
|
||||
"validator/expectation": validatorExpectationBindings,
|
||||
"validator/withdrawal": validatorWithdrawalBindings,
|
||||
"wallet/batch": walletBatchBindings,
|
||||
"wallet/create": walletCreateBindings,
|
||||
"wallet/import": walletImportBindings,
|
||||
"wallet/sharedexport": walletSharedExportBindings,
|
||||
"wallet/sharedimport": walletSharedImportBindings,
|
||||
}
|
||||
|
||||
func persistentPreRunE(cmd *cobra.Command, _ []string) error {
|
||||
if cmd.Name() == "help" {
|
||||
// User just wants help
|
||||
return nil
|
||||
@@ -63,11 +98,14 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
|
||||
// We bind viper here so that we bind to the correct command.
|
||||
quiet = viper.GetBool("quiet")
|
||||
verbose = viper.GetBool("verbose")
|
||||
debug = viper.GetBool("debug")
|
||||
quiet := viper.GetBool("quiet")
|
||||
verbose := viper.GetBool("verbose")
|
||||
debug := viper.GetBool("debug")
|
||||
|
||||
includeCommandBindings(cmd)
|
||||
// Command-specific bindings.
|
||||
if bindingsFunc, exists := bindings[commandPath(cmd)]; exists {
|
||||
bindingsFunc(cmd)
|
||||
}
|
||||
|
||||
if quiet && verbose {
|
||||
fmt.Println("Cannot supply both quiet and verbose flags")
|
||||
@@ -79,78 +117,6 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
return util.SetupStore()
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func includeCommandBindings(cmd *cobra.Command) {
|
||||
switch commandPath(cmd) {
|
||||
case "account/create":
|
||||
accountCreateBindings()
|
||||
case "account/derive":
|
||||
accountDeriveBindings()
|
||||
case "account/import":
|
||||
accountImportBindings()
|
||||
case "attester/duties":
|
||||
attesterDutiesBindings()
|
||||
case "attester/inclusion":
|
||||
attesterInclusionBindings()
|
||||
case "block/analyze":
|
||||
blockAnalyzeBindings()
|
||||
case "block/info":
|
||||
blockInfoBindings()
|
||||
case "chain/eth1votes":
|
||||
chainEth1VotesBindings()
|
||||
case "chain/info":
|
||||
chainInfoBindings()
|
||||
case "chain/queues":
|
||||
chainQueuesBindings()
|
||||
case "chain/time":
|
||||
chainTimeBindings()
|
||||
case "chain/verify/signedcontributionandproof":
|
||||
chainVerifySignedContributionAndProofBindings(cmd)
|
||||
case "epoch/summary":
|
||||
epochSummaryBindings()
|
||||
case "exit/verify":
|
||||
exitVerifyBindings()
|
||||
case "node/events":
|
||||
nodeEventsBindings()
|
||||
case "proposer/duties":
|
||||
proposerDutiesBindings()
|
||||
case "slot/time":
|
||||
slotTimeBindings()
|
||||
case "synccommittee/inclusion":
|
||||
synccommitteeInclusionBindings()
|
||||
case "synccommittee/members":
|
||||
synccommitteeMembersBindings()
|
||||
case "validator/credentials/get":
|
||||
validatorCredentialsGetBindings()
|
||||
case "validator/credentials/set":
|
||||
validatorCredentialsSetBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "validator/duties":
|
||||
validatorDutiesBindings()
|
||||
case "validator/exit":
|
||||
validatorExitBindings()
|
||||
case "validator/info":
|
||||
validatorInfoBindings()
|
||||
case "validator/keycheck":
|
||||
validatorKeycheckBindings()
|
||||
case "validator/summary":
|
||||
validatorSummaryBindings()
|
||||
case "validator/yield":
|
||||
validatorYieldBindings()
|
||||
case "validator/expectation":
|
||||
validatorExpectationBindings()
|
||||
case "wallet/create":
|
||||
walletCreateBindings()
|
||||
case "wallet/import":
|
||||
walletImportBindings()
|
||||
case "wallet/sharedexport":
|
||||
walletSharedExportBindings()
|
||||
case "wallet/sharedimport":
|
||||
walletSharedImportBindings()
|
||||
}
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
@@ -167,8 +133,12 @@ func init() {
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
addPersistentFlags()
|
||||
}
|
||||
|
||||
func addPersistentFlags() {
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ethdo.yaml)")
|
||||
|
||||
RootCmd.PersistentFlags().String("log", "", "log activity to the named file (default $HOME/ethdo.log). Logs are written for every action that generates a transaction")
|
||||
if err := viper.BindPFlag("log", RootCmd.PersistentFlags().Lookup("log")); err != nil {
|
||||
panic(err)
|
||||
@@ -189,7 +159,7 @@ func init() {
|
||||
if err := viper.BindPFlag("path", RootCmd.PersistentFlags().Lookup("path")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().String("private-key", "", "Private key to provide access to an account")
|
||||
RootCmd.PersistentFlags().String("private-key", "", "Private key to provide access to an account or validator")
|
||||
if err := viper.BindPFlag("private-key", RootCmd.PersistentFlags().Lookup("private-key")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -242,6 +212,10 @@ func init() {
|
||||
if err := viper.BindPFlag("verbose", RootCmd.PersistentFlags().Lookup("verbose")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().Bool("json", false, "generate JSON output where available")
|
||||
if err := viper.BindPFlag("json", RootCmd.PersistentFlags().Lookup("json")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().Bool("debug", false, "generate debug output")
|
||||
if err := viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug")); err != nil {
|
||||
panic(err)
|
||||
@@ -250,7 +224,7 @@ func init() {
|
||||
if err := viper.BindPFlag("connection", RootCmd.PersistentFlags().Lookup("connection")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RootCmd.PersistentFlags().Duration("timeout", 10*time.Second, "the time after which a network request will be considered failed. Increase this if you are running on an error-prone, high-latency or low-bandwidth connection")
|
||||
RootCmd.PersistentFlags().Duration("timeout", 30*time.Second, "the time after which a network request will be considered failed. Increase this if you are running on an error-prone, high-latency or low-bandwidth connection")
|
||||
if err := viper.BindPFlag("timeout", RootCmd.PersistentFlags().Lookup("timeout")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/herumi/bls-eth-go-binary/bls"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -50,7 +51,7 @@ In quiet mode this will return 0 if the signatures can be aggregated, otherwise
|
||||
}
|
||||
errCheck(err, "Failed to aggregate signature")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Serialize()))
|
||||
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%#x", signature.Serialize()))
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
errCheck(err, "Failed to parse domain")
|
||||
assert(len(domain) == 32, "Domain data invalid")
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Domain is %#x", domain))
|
||||
outputIf(viper.GetBool("debug"), fmt.Sprintf("Domain is %#x", domain))
|
||||
|
||||
var account e2wtypes.Account
|
||||
switch {
|
||||
@@ -66,11 +66,11 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
copy(specDomain[:], domain)
|
||||
var fixedSizeData [32]byte
|
||||
copy(fixedSizeData[:], data)
|
||||
fmt.Printf("Signing %#x with domain %#x by public key %#x\n", fixedSizeData, specDomain, account.PublicKey().Marshal())
|
||||
outputIf(viper.GetBool("debug"), fmt.Sprintf("Signing %#x with domain %#x by public key %#x", fixedSizeData, specDomain, account.PublicKey().Marshal()))
|
||||
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
|
||||
errCheck(err, "Failed to sign")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Marshal()))
|
||||
outputIf(!viper.GetBool("quiet"), fmt.Sprintf("%#x", signature.Marshal()))
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
account, err = util.ParseAccount(ctx, viper.GetString("public-key"), nil, false)
|
||||
}
|
||||
errCheck(err, "Failed to obtain account")
|
||||
outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
|
||||
outputIf(viper.GetBool("debug"), fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
|
||||
|
||||
var specDomain spec.Domain
|
||||
copy(specDomain[:], domain)
|
||||
@@ -83,7 +83,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
errCheck(err, "Failed to verify data")
|
||||
assert(verified, "Failed to verify")
|
||||
|
||||
outputIf(verbose, "Verified")
|
||||
outputIf(viper.GetBool("verbose"), "Verified")
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(slotCmd)
|
||||
}
|
||||
|
||||
func slotFlags(cmd *cobra.Command) {
|
||||
func slotFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -52,7 +52,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -67,14 +67,6 @@ func TestInput(t *testing.T) {
|
||||
},
|
||||
err: "slot is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"slot": "1",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -29,7 +29,7 @@ type dataOut struct {
|
||||
endTime time.Time
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func output(_ context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ func init() {
|
||||
slotTimeCmd.Flags().String("slot", "", "the ID of the slot to fetch")
|
||||
}
|
||||
|
||||
func slotTimeBindings() {
|
||||
if err := viper.BindPFlag("slot", slotTimeCmd.Flags().Lookup("slot")); err != nil {
|
||||
func slotTimeBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("slot", cmd.Flags().Lookup("slot")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ func init() {
|
||||
RootCmd.AddCommand(synccommitteeCmd)
|
||||
}
|
||||
|
||||
func synccommitteeFlags(cmd *cobra.Command) {
|
||||
func synccommitteeFlags(_ *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"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"
|
||||
@@ -34,22 +35,21 @@ type command struct {
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Input.
|
||||
account string
|
||||
pubKey string
|
||||
index string
|
||||
epoch int64
|
||||
validator string
|
||||
epochStr string
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
|
||||
// Output.
|
||||
epoch phase0.Epoch
|
||||
inCommittee bool
|
||||
committeeIndex uint64
|
||||
inclusions []int
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
func newCommand(_ context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
@@ -67,15 +67,13 @@ func newCommand(ctx context.Context) (*command, error) {
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
// Validator.
|
||||
c.account = viper.GetString("account")
|
||||
c.pubKey = viper.GetString("pubkey")
|
||||
c.index = viper.GetString("index")
|
||||
if c.account == "" && c.pubKey == "" && c.index == "" {
|
||||
return nil, errors.New("account, pubkey or index required")
|
||||
c.validator = viper.GetString("validator")
|
||||
if c.validator == "" {
|
||||
return nil, errors.New("validator is required")
|
||||
}
|
||||
|
||||
// Epoch.
|
||||
c.epoch = viper.GetInt64("epoch")
|
||||
c.epochStr = viper.GetString("epoch")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ func TestInput(t *testing.T) {
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
err: "account, pubkey or index required",
|
||||
err: "validator is required",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"validators": "1",
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
func (c *command) output(_ context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2022 Weald Technology Trading.
|
||||
// Copyright © 2022, 2023 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
@@ -32,14 +31,17 @@ func (c *command) process(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
firstSlot, lastSlot := c.calculateSlots(ctx)
|
||||
|
||||
validatorIndex, err := util.ValidatorIndex(ctx, c.eth2Client, c.account, c.pubKey, c.index)
|
||||
validator, err := util.ParseValidator(ctx, c.eth2Client.(eth2client.ValidatorsProvider), c.validator, "head")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", phase0.Epoch(c.epoch))
|
||||
c.epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epochStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", c.epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain sync committee information")
|
||||
}
|
||||
@@ -49,7 +51,7 @@ func (c *command) process(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for i := range syncCommittee.Validators {
|
||||
if syncCommittee.Validators[i] == validatorIndex {
|
||||
if syncCommittee.Validators[i] == validator.Index {
|
||||
c.inCommittee = true
|
||||
c.committeeIndex = uint64(i)
|
||||
break
|
||||
@@ -57,12 +59,14 @@ func (c *command) process(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if c.inCommittee {
|
||||
firstSlot := c.chainTime.FirstSlotOfEpoch(c.epoch)
|
||||
lastSlot := c.chainTime.LastSlotOfEpoch(c.epoch)
|
||||
// This validator is in the sync committee. Check blocks to see where it has been included.
|
||||
c.inclusions = make([]int, 0)
|
||||
if lastSlot > c.chainTime.CurrentSlot() {
|
||||
lastSlot = c.chainTime.CurrentSlot()
|
||||
}
|
||||
for slot := firstSlot; slot < lastSlot; slot++ {
|
||||
for slot := firstSlot; slot <= lastSlot; slot++ {
|
||||
block, err := c.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -94,6 +98,13 @@ func (c *command) process(ctx context.Context) error {
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
case spec.DataVersionDeneb:
|
||||
aggregate = block.Deneb.Message.Body.SyncAggregate
|
||||
if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) {
|
||||
c.inclusions = append(c.inclusions, 1)
|
||||
} else {
|
||||
c.inclusions = append(c.inclusions, 2)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unhandled block version %v", block.Version)
|
||||
}
|
||||
@@ -107,7 +118,12 @@ func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: c.connection,
|
||||
Timeout: c.timeout,
|
||||
AllowInsecure: c.allowInsecureConnections,
|
||||
LogFallback: !c.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -122,15 +138,3 @@ func (c *command) setup(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) calculateSlots(ctx context.Context) (phase0.Slot, phase0.Slot) {
|
||||
var firstSlot phase0.Slot
|
||||
var lastSlot phase0.Slot
|
||||
if c.epoch == -1 {
|
||||
c.epoch = int64(c.chainTime.CurrentEpoch()) - 1
|
||||
}
|
||||
firstSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch))
|
||||
lastSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch) + 1)
|
||||
|
||||
return firstSlot, lastSlot
|
||||
}
|
||||
|
||||
@@ -32,28 +32,20 @@ func TestProcess(t *testing.T) {
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "MissingConnection",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "InvalidConnection",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"connection": "invalid",
|
||||
},
|
||||
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid: no such host",
|
||||
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid on 127.0.0.53:53: no such host",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"index": "1",
|
||||
"validator": "1",
|
||||
"epoch": "-1",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ type dataIn struct {
|
||||
// Operation.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
epoch int64
|
||||
epoch string
|
||||
period string
|
||||
}
|
||||
|
||||
@@ -51,7 +51,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||
Address: viper.GetString("connection"),
|
||||
Timeout: viper.GetDuration("timeout"),
|
||||
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||
LogFallback: !data.quiet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,7 +71,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
}
|
||||
|
||||
// Epoch
|
||||
data.epoch = viper.GetInt64("epoch")
|
||||
data.epoch = viper.GetString("epoch")
|
||||
data.period = viper.GetString("period")
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -60,13 +60,6 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to any beacon node",
|
||||
},
|
||||
{
|
||||
name: "ConnectionInvalid",
|
||||
vars: map[string]interface{}{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user