Compare commits

...

35 Commits

Author SHA1 Message Date
Jim McDonald
d274ab3db0 Release 1.26.0 2022-11-24 15:15:22 +00:00
Jim McDonald
ad3d8606fd Update workflow. 2022-10-31 17:56:35 +00:00
Jim McDonald
30455e7c43 Merge pull request #47 from wealdtech/credentials-set
Set withdrawal credentials.
2022-10-31 17:41:54 +00:00
Jim McDonald
7eb2c68a19 Update operation and docs. 2022-10-29 12:15:14 +01:00
Jim McDonald
2d96f7cb13 Update operation and docs. 2022-10-29 12:10:31 +01:00
Jim McDonald
fd1e4a97bb Initial work on validator summary. 2022-10-16 23:35:44 +01:00
Jim McDonald
be2270c543 Increase security of shamir number generation. 2022-10-16 22:50:30 +01:00
Jim McDonald
0c36239b8b Update output and associated documentation. 2022-10-06 18:19:38 +00:00
Jim McDonald
f78b2922ec Tidy-ups. 2022-10-06 16:15:45 +00:00
Jim McDonald
bcf6ffdaf0 Linting. 2022-10-06 15:33:01 +00:00
Jim McDonald
9fc184f6a1 Update documentation. 2022-10-06 15:24:15 +00:00
Jim McDonald
c9a30a6e4b Update documentation. 2022-10-06 15:20:08 +00:00
Jim McDonald
1ec6ddc914 Set withdrawal credentials. 2022-10-06 15:14:23 +00:00
Jim McDonald
2c96ef958e Use standard function to obtain best public key. 2022-10-05 13:54:41 +00:00
Jim McDonald
3c10131c45 Use util func to obtain validator from input. 2022-10-02 20:53:10 +01:00
Jim McDonald
fe0bfd4f87 Add more information to "epoch summary". 2022-10-01 22:52:20 +01:00
Jim McDonald
290413f115 Update install instructions. 2022-09-27 19:53:39 +01:00
Jim McDonald
4aa6bef6a3 Update release. 2022-08-28 21:28:54 +01:00
Jim McDonald
1b0f4e2803 Bump version. 2022-08-18 14:42:57 +01:00
Jim McDonald
301224748c Update workflow. 2022-08-18 14:28:23 +01:00
Jim McDonald
1e15b836c2 Bump version. 2022-08-11 08:16:55 +01:00
Jim McDonald
1e709b7592 Remove mandatory connection parameter.
The connection parameter is no longer mandatory, in that ethdo will
attempt to obtain a connection using well-known ports if no override is
supplied.  As such the `--connection` parameter can be omitted and so is
not force-required as part of the command initialisation.
2022-08-11 08:11:55 +01:00
Jim McDonald
8744a85cb7 Merge pull request #45 from tcrossland/master
feat: support block analyze on bellatrix
2022-08-06 08:16:58 +01:00
Tom Crossland
92ad77d8f5 feat: support block analyze on bellatrix 2022-08-04 17:25:20 +02:00
Jim McDonald
2298640e4c Merge pull request #44 from aaron-alderman/fix/deposit-message-root-verification
Add deposit message root match verification
2022-07-16 16:27:24 +01:00
Jim McDonald
5baef59672 Tidy up streaming output. 2022-07-13 11:21:09 +01:00
Aaron Alderman
e54e8affa7 Add deposit message root match verification 2022-07-12 10:47:27 +08:00
Jim McDonald
97fa04a7b2 Bump version. 2022-07-10 18:28:54 +01:00
Jim McDonald
4977ee82e5 Add dpeosit signature verirication to "deposit verify". 2022-07-10 12:33:19 +01:00
Jim McDonald
090680366c Do not print 0-value deposit validator information. 2022-06-23 09:52:45 +01:00
Jim McDonald
531c86847f Tidy up tests. 2022-06-22 07:51:22 +01:00
Jim McDonald
446e437531 Update docs. 2022-06-22 07:51:14 +01:00
Jim McDonald
63d8ccf1a0 Add "proposer duties". 2022-06-22 07:50:53 +01:00
Jim McDonald
77abe0e158 Add sepolia support. 2022-06-21 10:05:29 +01:00
Jim McDonald
547f8d9e71 Fix potential crash when new validator is activated. 2022-06-20 16:18:28 +01:00
103 changed files with 5150 additions and 921 deletions

View File

@@ -1,23 +1,22 @@
name: golangci-lint
on: [ push, pull_request ]
on:
push:
branches:
- master
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
- uses: actions/setup-go@v3
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.45
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
args: --timeout=10m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
go-version: 1.17
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout 5m

View File

@@ -4,6 +4,7 @@ on:
push:
tags:
- 'v*'
- 't*'
jobs:
build:
@@ -12,10 +13,9 @@ jobs:
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ^1.17
id: go
go-version: 1.17
- name: Check out code into the Go module directory
uses: actions/checkout@v2
@@ -36,9 +36,9 @@ jobs:
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
echo "::set-output name=RELEASE_TAG::${RELEASE_TAG}"
# Ensure the release tag has expected format.
echo ${RELEASE_TAG} | grep -q '^v' || exit 1
echo ${RELEASE_TAG} | grep -q '^[vt]' || exit 1
# Release version is same as release tag without leading 'v'.
RELEASE_VERSION=$(echo ${GITHUB_REF} | sed -e 's!.*/v!!')
RELEASE_VERSION=$(echo ${GITHUB_REF} | sed -e 's!.*/[vt]!!')
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV
echo "::set-output name=RELEASE_VERSION::${RELEASE_VERSION}"
@@ -50,7 +50,7 @@ jobs:
- name: Fetch xgo
run: |
go install github.com/crazy-max/xgo@v0.14.0
go install github.com/wealdtech/xgo@latest
- name: Cross-compile linux
run: |

3
.gitignore vendored
View File

@@ -18,5 +18,8 @@ coverage.html
# Vim
*.sw?
# Local JSON files
*.json
# Local TODO
TODO.md

View File

@@ -1,3 +1,23 @@
1.26.0
- add commands and documentation to set user validator credentials (not usable until capella)
1.25.3
- add more information to "epoch summary"
- add "validator summary"
1.25.2:
- no longer require connection parameter
- support "block analyze" on bellatrix (thanks @tcrossland)
- check deposit message root match for verifying deposits (thanks @aaron-alderman)
1.25.0:
- add "proposer duties"
- add deposit signature verification to "deposit verify"
1.24.1:
- fix potential crash when new validators are activated
- add "sepolia" to the list of supported networks
1.24.0:
- add "validator yield"

View File

@@ -35,7 +35,7 @@ docker pull wealdtech/ethdo
`ethdo` is a standard Go program which can be installed with:
```sh
GO111MODULE=on go get github.com/wealdtech/ethdo
go install github.com/wealdtech/ethdo@latest
```
Note that `ethdo` requires at least version 1.13 of go to operate. The version of go can be found with `go version`.

View File

@@ -1,315 +0,0 @@
// Copyright © 2019, 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 accountcreate
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"math/rand"
"os"
"testing"
"time"
"github.com/attestantio/dirk/testing/daemon"
"github.com/attestantio/dirk/testing/resources"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
e2types "github.com/wealdtech/go-eth2-types/v2"
dirk "github.com/wealdtech/go-eth2-wallet-dirk"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
hd "github.com/wealdtech/go-eth2-wallet-hd/v2"
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
"google.golang.org/grpc/credentials"
)
func TestProcess(t *testing.T) {
require.NoError(t, e2types.InitBLS())
testNDWallet, err := nd.CreateWallet(context.Background(),
"Test",
scratch.New(),
keystorev4.New(),
)
require.NoError(t, err)
testHDWallet, err := hd.CreateWallet(context.Background(),
"Test",
[]byte("pass"),
scratch.New(),
keystorev4.New(),
[]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
)
require.NoError(t, err)
// #nosec G404
port1 := uint32(12000 + rand.Intn(4000))
// #nosec G404
port2 := uint32(12000 + rand.Intn(4000))
// #nosec G404
port3 := uint32(12000 + rand.Intn(4000))
peers := map[uint64]string{
1: fmt.Sprintf("signer-test01:%d", port1),
2: fmt.Sprintf("signer-test02:%d", port2),
3: fmt.Sprintf("signer-test03:%d", port3),
}
_, path, err := daemon.New(context.Background(), "", 1, port1, peers)
require.NoError(t, err)
defer os.RemoveAll(path)
_, path, err = daemon.New(context.Background(), "", 2, port2, peers)
require.NoError(t, err)
defer os.RemoveAll(path)
_, path, err = daemon.New(context.Background(), "", 3, port3, peers)
require.NoError(t, err)
defer os.RemoveAll(path)
endpoints := []*dirk.Endpoint{
dirk.NewEndpoint("signer-test01", port1),
dirk.NewEndpoint("signer-test02", port2),
dirk.NewEndpoint("signer-test03", port3),
}
credentials, err := credentialsFromCerts(context.Background(), resources.ClientTest01Crt, resources.ClientTest01Key, resources.CACrt)
require.NoError(t, err)
testDistributedWallet, err := dirk.OpenWallet(context.Background(), "Wallet 3", credentials, endpoints)
require.NoError(t, err)
tests := []struct {
name string
dataIn *dataIn
err string
}{
{
name: "Nil",
err: "no data",
},
{
name: "WalletPassphraseIncorrect",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Good",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "bad",
participants: 1,
signingThreshold: 1,
},
err: "failed to unlock wallet: incorrect passphrase",
},
{
name: "PassphraseMissing",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Good",
passphrase: "",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
},
err: "passphrase is required",
},
{
name: "PassphraseWeak",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Good",
passphrase: "poor",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
},
err: "supplied passphrase is weak; use a stronger one or run with the --allow-weak-passphrases flag",
},
{
name: "Good",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Good",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
},
},
{
name: "PathMalformed",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Pathed",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
path: "n/12381/3600/1/2/3",
},
err: "path does not match expected format m/…",
},
{
name: "PathPassphraseMissing",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Pathed",
passphrase: "",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
path: "m/12381/3600/1/2/3",
},
err: "passphrase is required",
},
{
name: "PathNotSupported",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testNDWallet,
accountName: "Pathed",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
path: "m/12381/3600/1/2/3",
},
err: "wallet does not support account creation with an explicit path",
},
{
name: "GoodWithPath",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testHDWallet,
accountName: "Pathed",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 1,
signingThreshold: 1,
path: "m/12381/3600/1/2/3",
},
},
{
name: "DistributedSigningThresholdZero",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testDistributedWallet,
accountName: "Remote",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 3,
signingThreshold: 0,
},
err: "signing threshold required",
},
{
name: "DistributedSigningThresholdNotHalf",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testDistributedWallet,
accountName: "Remote",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 3,
signingThreshold: 1,
},
err: "signing threshold must be more than half the number of participants",
},
{
name: "DistributedSigningThresholdTooHigh",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testDistributedWallet,
accountName: "Remote",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 3,
signingThreshold: 4,
},
err: "signing threshold cannot be higher than the number of participants",
},
{
name: "DistributedNotSupported",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testNDWallet,
accountName: "Remote",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 3,
signingThreshold: 2,
},
err: "wallet does not support distributed account creation",
},
{
name: "DistributedGood",
dataIn: &dataIn{
timeout: 5 * time.Second,
wallet: testDistributedWallet,
accountName: "Remote",
passphrase: "ce%NohGhah4ye5ra",
walletPassphrase: "pass",
participants: 3,
signingThreshold: 2,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res, err := process(context.Background(), test.dataIn)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.dataIn.accountName, res.account.Name())
}
})
}
}
func TestNilData(t *testing.T) {
_, err := processStandard(context.Background(), nil)
require.EqualError(t, err, "no data")
_, err = processPathed(context.Background(), nil)
require.EqualError(t, err, "no data")
_, err = processDistributed(context.Background(), nil)
require.EqualError(t, err, "no data")
}
func credentialsFromCerts(ctx context.Context, clientCert []byte, clientKey []byte, caCert []byte) (credentials.TransportCredentials, error) {
clientPair, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return nil, errors.Wrap(err, "failed to load client keypair")
}
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{clientPair},
MinVersion: tls.VersionTLS13,
}
if caCert != nil {
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(caCert) {
return nil, errors.New("failed to add CA certificate")
}
tlsCfg.RootCAs = cp
}
return credentials.NewTLS(tlsCfg), nil
}

View File

@@ -42,11 +42,13 @@ func output(ctx context.Context, data *dataOut) (string, error) {
if data.showPrivateKey {
builder.WriteString(fmt.Sprintf("Private key: %#x\n", data.key.Marshal()))
}
builder.WriteString(fmt.Sprintf("Public key: %#x", data.key.PublicKey().Marshal()))
if data.showWithdrawalCredentials {
withdrawalCredentials := util.SHA256(data.key.PublicKey().Marshal())
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
builder.WriteString(fmt.Sprintf("\nWithdrawal credentials: %#x", withdrawalCredentials))
builder.WriteString(fmt.Sprintf("Withdrawal credentials: %#x\n", withdrawalCredentials))
}
if !(data.showPrivateKey || data.showWithdrawalCredentials) {
builder.WriteString(fmt.Sprintf("Public key: %#x\n", data.key.PublicKey().Marshal()))
}
return builder.String(), nil

View File

@@ -64,7 +64,7 @@ func TestOutput(t *testing.T) {
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
showPrivateKey: true,
},
needs: []string{"Public key", "Private key"},
needs: []string{"Private key"},
},
{
name: "WithdrawalCredentials",
@@ -72,7 +72,7 @@ func TestOutput(t *testing.T) {
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
showWithdrawalCredentials: true,
},
needs: []string{"Public key", "Withdrawal credentials"},
needs: []string{"Withdrawal credentials"},
},
{
name: "All",
@@ -81,7 +81,7 @@ func TestOutput(t *testing.T) {
showPrivateKey: true,
showWithdrawalCredentials: true,
},
needs: []string{"Public key", "Private key", "Withdrawal credentials"},
needs: []string{"Private key", "Withdrawal credentials"},
},
}

View File

@@ -15,57 +15,32 @@ package accountderive
import (
"context"
"regexp"
"strings"
"github.com/pkg/errors"
"github.com/tyler-smith/go-bip39"
util "github.com/wealdtech/go-eth2-util"
"golang.org/x/text/unicode/norm"
"github.com/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// pathRegex is the regular expression that matches an HD path.
var pathRegex = regexp.MustCompile("^m/[0-9]+/[0-9]+(/[0-9+])+")
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
if data == nil {
return nil, errors.New("no data")
}
// If there are more than 24 words we treat the additional characters as the passphrase.
mnemonicParts := strings.Split(data.mnemonic, " ")
mnemonicPassphrase := ""
if len(mnemonicParts) > 24 {
data.mnemonic = strings.Join(mnemonicParts[:24], " ")
mnemonicPassphrase = strings.Join(mnemonicParts[24:], " ")
}
// Normalise the input.
data.mnemonic = string(norm.NFKD.Bytes([]byte(data.mnemonic)))
mnemonicPassphrase = string(norm.NFKD.Bytes([]byte(mnemonicPassphrase)))
if !bip39.IsMnemonicValid(data.mnemonic) {
return nil, errors.New("mnemonic is invalid")
}
// Create seed from mnemonic and passphrase.
seed := bip39.NewSeed(data.mnemonic, mnemonicPassphrase)
// Ensure the path is valid.
match := pathRegex.Match([]byte(data.path))
if !match {
return nil, errors.New("path does not match expected format m/…")
}
// Derive private key from seed and path.
key, err := util.PrivateKeyFromSeedAndPath(seed, data.path)
account, err := util.ParseAccount(ctx, data.mnemonic, []string{data.path}, true)
if err != nil {
return nil, errors.Wrap(err, "failed to generate key")
return nil, errors.Wrap(err, "failed to derive account")
}
key, err := account.(e2wtypes.AccountPrivateKeyProvider).PrivateKey(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain account private key")
}
results := &dataOut{
showPrivateKey: data.showPrivateKey,
showPrivateKey: data.showPrivateKey,
showWithdrawalCredentials: data.showWithdrawalCredentials,
key: key,
key: key.(*e2types.BLSPrivateKey),
}
return results, nil

View File

@@ -40,7 +40,7 @@ func TestProcess(t *testing.T) {
dataIn: &dataIn{
path: "m/12381/3600/0/0",
},
err: "mnemonic is invalid",
err: "failed to derive account: no account specified",
},
{
name: "MnemonicInvalid",
@@ -48,14 +48,14 @@ func TestProcess(t *testing.T) {
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
path: "m/12381/3600/0/0",
},
err: "mnemonic is invalid",
err: "failed to derive account: mnemonic is invalid",
},
{
name: "PathMissing",
dataIn: &dataIn{
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
},
err: "path does not match expected format m/…",
err: "failed to derive account: path does not match expected format m/…",
},
{
name: "PathInvalid",
@@ -63,7 +63,7 @@ func TestProcess(t *testing.T) {
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
path: "n/12381/3600/0/0",
},
err: "path does not match expected format m/…",
err: "failed to derive account: path does not match expected format m/…",
},
{
name: "Good",

View File

@@ -15,6 +15,7 @@ package accountderive
import (
"context"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -45,5 +46,5 @@ func Run(cmd *cobra.Command) (string, error) {
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil
return strings.TrimSuffix(results, "\n"), nil
}

View File

@@ -49,7 +49,6 @@ func init() {
accountFlags(accountCreateCmd)
accountCreateCmd.Flags().Uint32("participants", 1, "Number of participants (1 for non-distributed accounts, >1 for distributed accounts)")
accountCreateCmd.Flags().Uint32("signing-threshold", 1, "Signing threshold (1 for non-distributed accounts)")
accountCreateCmd.Flags().String("path", "", "path of account (for hierarchical deterministic accounts)")
}
func accountCreateBindings() {
@@ -59,7 +58,4 @@ func accountCreateBindings() {
if err := viper.BindPFlag("signing-threshold", accountCreateCmd.Flags().Lookup("signing-threshold")); err != nil {
panic(err)
}
if err := viper.BindPFlag("path", accountCreateCmd.Flags().Lookup("path")); err != nil {
panic(err)
}
}

View File

@@ -47,19 +47,11 @@ In quiet mode this will return 0 if the inputs can derive an account account, ot
func init() {
accountCmd.AddCommand(accountDeriveCmd)
accountFlags(accountDeriveCmd)
accountDeriveCmd.Flags().String("mnemonic", "", "mnemonic from which to derive the HD seed")
accountDeriveCmd.Flags().String("path", "", "path from which to derive the account")
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")
}
func accountDeriveBindings() {
if err := viper.BindPFlag("mnemonic", accountDeriveCmd.Flags().Lookup("mnemonic")); err != nil {
panic(err)
}
if err := viper.BindPFlag("path", accountDeriveCmd.Flags().Lookup("path")); err != nil {
panic(err)
}
if err := viper.BindPFlag("show-private-key", accountDeriveCmd.Flags().Lookup("show-private-key")); err != nil {
panic(err)
}

View File

@@ -73,7 +73,7 @@ func TestInput(t *testing.T) {
"timeout": "5s",
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
}

View File

@@ -73,7 +73,7 @@ func TestInput(t *testing.T) {
"timeout": "5s",
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
}

View File

@@ -122,9 +122,6 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,27 +37,10 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"validators": "1",
"timeout": "5s",
},
err: "connection is required",
},
{
name: "ValidatorsZero",
vars: map[string]interface{}{
"timeout": "5s",
"validators": "0",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
err: "validators must be at least 1",
},
{
name: "Good",
vars: map[string]interface{}{
"validators": "1",
"blockid": "1",
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},

View File

@@ -425,6 +425,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.DataVersionBellatrix:
c.analysis.SyncCommitee.Contributions = int(block.Bellatrix.Message.Body.SyncAggregate.SyncCommitteeBits.Count())
c.analysis.SyncCommitee.PossibleContributions = int(block.Bellatrix.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)
}

View File

@@ -33,13 +33,13 @@ func TestProcess(t *testing.T) {
err string
}{
{
name: "InvalidData",
name: "NoBlock",
vars: map[string]interface{}{
"timeout": "60s",
"validators": "1",
"data": "[[",
"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\"}",
},
}

View File

@@ -66,7 +66,7 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{
"timeout": "5s",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
{
name: "ConnectionBad",
@@ -79,7 +79,7 @@ func TestInput(t *testing.T) {
timeout: 5 * time.Second,
blockID: "justified",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
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://localhost:1/eth/v1/beacon/genesis\": dial tcp 127.0.0.1:1: connect: connection refused",
},
{
name: "BlockIDNil",

View File

@@ -296,7 +296,7 @@ func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *b
bodyRoot,
signedBlock.Message.ParentRoot,
signedBlock.Message.StateRoot,
signedBlock.Message.Body.Graffiti,
signedBlock.Message.Body.Graffiti[:],
data.genesisTime,
data.slotDuration,
data.slotsPerEpoch)
@@ -386,7 +386,7 @@ func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *alta
bodyRoot,
signedBlock.Message.ParentRoot,
signedBlock.Message.StateRoot,
signedBlock.Message.Body.Graffiti,
signedBlock.Message.Body.Graffiti[:],
data.genesisTime,
data.slotDuration,
data.slotsPerEpoch)
@@ -469,7 +469,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
bodyRoot,
signedBlock.Message.ParentRoot,
signedBlock.Message.StateRoot,
signedBlock.Message.Body.Graffiti,
signedBlock.Message.Body.Graffiti[:],
data.genesisTime,
data.slotDuration,
data.slotsPerEpoch)
@@ -540,6 +540,8 @@ func outputBlockExecutionPayload(ctx context.Context,
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: ")
@@ -573,6 +575,8 @@ func outputBlockExecutionPayload(ctx context.Context,
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)))
}
return res.String(), nil

View File

@@ -82,6 +82,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
if data.stream {
jsonOutput = data.jsonOutput
sszOutput = data.sszOutput
if !jsonOutput && !sszOutput {
fmt.Println("")
}
err := data.eth2Client.(eth2client.EventsProvider).Events(ctx, []string{"head"}, headEventHandler)
if err != nil {
return nil, errors.Wrap(err, "failed to start block stream")
@@ -101,13 +104,13 @@ func headEventHandler(event *api.Event) {
blockID := fmt.Sprintf("%#x", event.Data.(*api.HeadEvent).Block[:])
signedBlock, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(context.Background(), blockID)
if err != nil {
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Printf("Failed to obtain block: %v\n", err)
}
return
}
if signedBlock == nil {
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Println("Empty beacon block")
}
return
@@ -115,31 +118,34 @@ func headEventHandler(event *api.Event) {
switch signedBlock.Version {
case spec.DataVersionPhase0:
if err := outputPhase0Block(context.Background(), jsonOutput, signedBlock.Phase0); err != nil {
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Printf("Failed to output block: %v\n", err)
}
return
}
case spec.DataVersionAltair:
if err := outputAltairBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Altair); err != nil {
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Printf("Failed to output block: %v\n", err)
}
return
}
case spec.DataVersionBellatrix:
if err := outputBellatrixBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Bellatrix); err != nil {
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Printf("Failed to output block: %v\n", err)
}
return
}
default:
if !jsonOutput {
if !jsonOutput && !sszOutput {
fmt.Printf("Unknown block version: %v\n", signedBlock.Version)
}
return
}
if !jsonOutput && !sszOutput {
fmt.Println("")
}
}
func outputPhase0Block(ctx context.Context, jsonOutput bool, signedBlock *phase0.SignedBeaconBlock) error {
@@ -155,7 +161,7 @@ func outputPhase0Block(ctx context.Context, jsonOutput bool, signedBlock *phase0
if err != nil {
return errors.Wrap(err, "failed to generate text")
}
fmt.Printf("%s\n", data)
fmt.Print(data)
}
return nil
}
@@ -179,7 +185,7 @@ func outputAltairBlock(ctx context.Context, jsonOutput bool, sszOutput bool, sig
if err != nil {
return errors.Wrap(err, "failed to generate text")
}
fmt.Printf("%s\n", data)
fmt.Print(data)
}
return nil
}
@@ -203,7 +209,7 @@ func outputBellatrixBlock(ctx context.Context, jsonOutput bool, sszOutput bool,
if err != nil {
return errors.Wrap(err, "failed to generate text")
}
fmt.Printf("%s\n", data)
fmt.Print(data)
}
return nil
}

View File

@@ -77,9 +77,6 @@ func newCommand(ctx context.Context) (*command, error) {
c.xepoch = viper.GetString("epoch")
c.xperiod = viper.GetString("period")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"data": "{}",
},
err: "connection is required",
},
{
name: "Good",
vars: map[string]interface{}{

View File

@@ -65,9 +65,6 @@ func newCommand(ctx context.Context) (*command, error) {
c.epoch = viper.GetString("epoch")
}
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"data": "{}",
},
err: "connection is required",
},
{
name: "Good",
vars: map[string]interface{}{

View File

@@ -71,9 +71,6 @@ func input(ctx context.Context) (*dataIn, error) {
return nil, errors.New("one of timestamp, slot or epoch required")
}
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
data.connection = viper.GetString("connection")
data.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -60,14 +60,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"slot": "1",
},
err: "connection is required",
},
{
name: "IDMissing",
vars: map[string]interface{}{

View File

@@ -76,9 +76,6 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.data = viper.GetString("data")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -44,14 +44,6 @@ func TestInput(t *testing.T) {
},
err: "data is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"data": "{}",
},
err: "connection is required",
},
{
name: "Good",
vars: map[string]interface{}{

View File

@@ -1,4 +1,4 @@
// Copyright © 2020 Weald Technology Trading
// Copyright © 2020, 2022 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -53,6 +53,11 @@ In quiet mode this will return 0 if the chain information can be obtained, other
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)
os.Exit(_exitSuccess)
}
if genesis.GenesisTime.Unix() == 0 {
fmt.Println("Genesis time: undefined")
} else {
@@ -84,4 +89,11 @@ 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)
}
}

View File

@@ -21,7 +21,7 @@ import (
"os"
"strings"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/wealdtech/ethdo/util"
@@ -219,15 +219,15 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
outputIf(!quiet, "Validator public key verified")
}
var pubKey spec.BLSPubKey
var pubKey phase0.BLSPubKey
copy(pubKey[:], deposit.PublicKey)
var signature spec.BLSSignature
var signature phase0.BLSSignature
copy(signature[:], deposit.Signature)
depositData := &spec.DepositData{
depositData := &phase0.DepositData{
PublicKey: pubKey,
WithdrawalCredentials: deposit.WithdrawalCredentials,
Amount: spec.Gwei(deposit.Amount),
Amount: phase0.Gwei(deposit.Amount),
Signature: signature,
}
depositDataRoot, err := depositData.HashTreeRoot()
@@ -248,7 +248,7 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
}
} else {
if depositVerifyForkVersion == "" {
outputIf(!quiet, "fork version not supplied; NOT checked")
outputIf(!quiet, "fork version not supplied; not checked")
} else {
forkVersion, err := hex.DecodeString(strings.TrimPrefix(depositVerifyForkVersion, "0x"))
if err != nil {
@@ -260,6 +260,56 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
outputIf(!quiet, "Fork version incorrect")
return false, nil
}
if len(deposit.DepositMessageRoot) != 32 {
outputIf(!quiet, "Deposit message root not supplied; not checked")
} else {
// We can also verify the deposit message signature.
depositMessage := &phase0.DepositMessage{
PublicKey: pubKey,
WithdrawalCredentials: withdrawalCredentials,
Amount: phase0.Gwei(deposit.Amount),
}
depositMessageRoot, err := depositMessage.HashTreeRoot()
if err != nil {
return false, errors.Wrap(err, "failed to generate deposit message root")
}
if bytes.Equal(deposit.DepositMessageRoot, depositMessageRoot[:]) {
outputIf(!quiet, "Deposit message root verified")
} else {
outputIf(!quiet, "Deposit message root incorrect")
return false, nil
}
domainBytes := e2types.Domain(e2types.DomainDeposit, forkVersion, e2types.ZeroGenesisValidatorsRoot)
var domain phase0.Domain
copy(domain[:], domainBytes)
container := &phase0.SigningData{
ObjectRoot: depositMessageRoot,
Domain: domain,
}
containerRoot, err := container.HashTreeRoot()
if err != nil {
return false, errors.New("failed to generate root for container")
}
validatorPubKey, err := e2types.BLSPublicKeyFromBytes(pubKey[:])
if err != nil {
return false, errors.Wrap(err, "failed to generate validator public key")
}
blsSig, err := e2types.BLSSignatureFromBytes(signature[:])
if err != nil {
return false, errors.New("failed to verify BLS signature")
}
signatureVerified := blsSig.Verify(containerRoot[:], validatorPubKey)
if signatureVerified {
outputIf(!quiet, "Deposit message signature verified")
} else {
outputIf(!quiet, "Deposit message signature NOT verified")
return false, nil
}
}
}
}

View File

@@ -40,13 +40,14 @@ type command struct {
jsonOutput bool
// Data access.
eth2Client eth2client.Service
chainTime chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
blocksProvider eth2client.SignedBeaconBlockProvider
syncCommitteesProvider eth2client.SyncCommitteesProvider
validatorsProvider eth2client.ValidatorsProvider
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
eth2Client eth2client.Service
chainTime chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
blocksProvider eth2client.SignedBeaconBlockProvider
syncCommitteesProvider eth2client.SyncCommitteesProvider
validatorsProvider eth2client.ValidatorsProvider
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
// Results.
summary *epochSummary
@@ -60,6 +61,11 @@ type epochSummary struct {
SyncCommittee []*epochSyncCommittee `json:"sync_committees"`
ActiveValidators int `json:"active_validators"`
ParticipatingValidators int `json:"participating_validators"`
HeadCorrectValidators int `json:"head_correct_validators"`
HeadTimelyValidators int `json:"head_timely_validators"`
SourceTimelyValidators int `json:"source_timely_validators"`
TargetCorrectValidators int `json:"target_correct_validators"`
TargetTimelyValidators int `json:"target_timely_validators"`
NonParticipatingValidators []*nonParticipatingValidator `json:"nonparticipating_validators"`
}
@@ -94,9 +100,6 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,13 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
},
err: "connection is required",
},
{
name: "Good",
vars: map[string]interface{}{

View File

@@ -70,6 +70,11 @@ func (c *command) outputTxt(_ context.Context) (string, error) {
}
builder.WriteString(fmt.Sprintf("\n Attestations: %d/%d (%0.2f%%)", c.summary.ParticipatingValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.ParticipatingValidators)/float64(c.summary.ActiveValidators)))
builder.WriteString(fmt.Sprintf("\n Source timely: %d/%d (%0.2f%%)", c.summary.SourceTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.SourceTimelyValidators)/float64(c.summary.ActiveValidators)))
builder.WriteString(fmt.Sprintf("\n Target correct: %d/%d (%0.2f%%)", c.summary.TargetCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetCorrectValidators)/float64(c.summary.ActiveValidators)))
builder.WriteString(fmt.Sprintf("\n Target timely: %d/%d (%0.2f%%)", c.summary.TargetTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.TargetTimelyValidators)/float64(c.summary.ActiveValidators)))
builder.WriteString(fmt.Sprintf("\n Head correct: %d/%d (%0.2f%%)", c.summary.HeadCorrectValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadCorrectValidators)/float64(c.summary.ActiveValidators)))
builder.WriteString(fmt.Sprintf("\n Head timely: %d/%d (%0.2f%%)", c.summary.HeadTimelyValidators, c.summary.ActiveValidators, 100.0*float64(c.summary.HeadTimelyValidators)/float64(c.summary.ActiveValidators)))
if c.verbose {
// Sort list by validator index.
for _, validator := range c.summary.NonParticipatingValidators {

View File

@@ -79,11 +79,10 @@ func (c *command) processProposerDuties(ctx context.Context) error {
return nil
}
func (c *command) processAttesterDuties(ctx context.Context) error {
// Obtain all active validators for the given epoch.
func (c *command) activeValidators(ctx context.Context) (map[phase0.ValidatorIndex]*apiv1.Validator, error) {
validators, err := c.validatorsProvider.Validators(ctx, fmt.Sprintf("%d", c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)), nil)
if err != nil {
return errors.Wrap(err, "failed to obtain validators for epoch")
return nil, errors.Wrap(err, "failed to obtain validators for epoch")
}
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
for _, validator := range validators {
@@ -92,6 +91,15 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
}
}
return activeValidators, nil
}
func (c *command) processAttesterDuties(ctx context.Context) error {
activeValidators, err := c.activeValidators(ctx)
if err != nil {
return err
}
c.summary.ActiveValidators = len(activeValidators)
// Obtain number of validators that voted for blocks in the epoch.
// These votes can be included anywhere from the second slot of
// the epoch to the first slot of the next-but-one epoch.
@@ -101,22 +109,76 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
lastSlot = c.chainTime.CurrentSlot()
}
var votes map[phase0.ValidatorIndex]struct{}
var participations map[phase0.ValidatorIndex]*nonParticipatingValidator
c.summary.ParticipatingValidators, c.summary.HeadCorrectValidators, c.summary.HeadTimelyValidators, c.summary.SourceTimelyValidators, c.summary.TargetCorrectValidators, c.summary.TargetTimelyValidators, votes, participations, err = c.processSlots(ctx, firstSlot, lastSlot)
if err != nil {
return err
}
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0, len(activeValidators)-len(votes))
for activeValidatorIndex := range activeValidators {
if _, exists := votes[activeValidatorIndex]; !exists {
if _, exists := participations[activeValidatorIndex]; exists {
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
}
}
}
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
return c.summary.NonParticipatingValidators[i].Slot < c.summary.NonParticipatingValidators[j].Slot
}
if c.summary.NonParticipatingValidators[i].Committee != c.summary.NonParticipatingValidators[j].Committee {
return c.summary.NonParticipatingValidators[i].Committee < c.summary.NonParticipatingValidators[j].Committee
}
return c.summary.NonParticipatingValidators[i].Validator < c.summary.NonParticipatingValidators[j].Validator
})
return nil
}
func (c *command) processSlots(ctx context.Context,
firstSlot phase0.Slot,
lastSlot phase0.Slot,
) (
int,
int,
int,
int,
int,
int,
map[phase0.ValidatorIndex]struct{},
map[phase0.ValidatorIndex]*nonParticipatingValidator,
error,
) {
votes := make(map[phase0.ValidatorIndex]struct{})
headCorrects := make(map[phase0.ValidatorIndex]struct{})
headTimelys := make(map[phase0.ValidatorIndex]struct{})
sourceTimelys := make(map[phase0.ValidatorIndex]struct{})
targetCorrects := make(map[phase0.ValidatorIndex]struct{})
targetTimelys := make(map[phase0.ValidatorIndex]struct{})
allCommittees := make(map[phase0.Slot]map[phase0.CommitteeIndex][]phase0.ValidatorIndex)
participations := make(map[phase0.ValidatorIndex]*nonParticipatingValidator)
// Need a cache of beacon block headers to reduce lookup times.
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
for slot := firstSlot; slot <= lastSlot; slot++ {
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
}
if block == nil {
// No block at this slot; that's fine.
continue
}
slot, err := block.Slot()
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
attestations, err := block.Attestations()
if err != nil {
return err
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
for _, attestation := range attestations {
if attestation.Data.Slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) || attestation.Data.Slot >= c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) {
@@ -127,7 +189,7 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
if !exists {
beaconCommittees, err := c.beaconCommitteesProvider.BeaconCommittees(ctx, fmt.Sprintf("%d", attestation.Data.Slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
return 0, 0, 0, 0, 0, 0, nil, nil, errors.Wrap(err, fmt.Sprintf("failed to obtain committees for slot %d", attestation.Data.Slot))
}
for _, beaconCommittee := range beaconCommittees {
if _, exists := allCommittees[beaconCommittee.Slot]; !exists {
@@ -146,33 +208,48 @@ func (c *command) processAttesterDuties(ctx context.Context) error {
slotCommittees = allCommittees[attestation.Data.Slot]
}
committee := slotCommittees[attestation.Data.Index]
inclusionDistance := slot - attestation.Data.Slot
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
if err != nil {
return 0, 0, 0, 0, 0, 0, nil, nil, err
}
for i := uint64(0); i < attestation.AggregationBits.Len(); i++ {
if attestation.AggregationBits.BitAt(i) {
votes[committee[int(i)]] = struct{}{}
if _, exists := headCorrects[committee[int(i)]]; !exists && headCorrect {
headCorrects[committee[int(i)]] = struct{}{}
}
if _, exists := headTimelys[committee[int(i)]]; !exists && headCorrect && inclusionDistance == 1 {
headTimelys[committee[int(i)]] = struct{}{}
}
if _, exists := sourceTimelys[committee[int(i)]]; !exists && inclusionDistance <= 5 {
sourceTimelys[committee[int(i)]] = struct{}{}
}
if _, exists := targetCorrects[committee[int(i)]]; !exists && targetCorrect {
targetCorrects[committee[int(i)]] = struct{}{}
}
if _, exists := targetTimelys[committee[int(i)]]; !exists && targetCorrect && inclusionDistance <= 32 {
targetTimelys[committee[int(i)]] = struct{}{}
}
}
}
}
}
c.summary.ActiveValidators = len(activeValidators)
c.summary.ParticipatingValidators = len(votes)
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0, len(activeValidators)-len(votes))
for activeValidatorIndex := range activeValidators {
if _, exists := votes[activeValidatorIndex]; !exists {
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, participations[activeValidatorIndex])
}
}
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
return c.summary.NonParticipatingValidators[i].Slot < c.summary.NonParticipatingValidators[j].Slot
}
if c.summary.NonParticipatingValidators[i].Committee != c.summary.NonParticipatingValidators[j].Committee {
return c.summary.NonParticipatingValidators[i].Committee < c.summary.NonParticipatingValidators[j].Committee
}
return c.summary.NonParticipatingValidators[i].Validator < c.summary.NonParticipatingValidators[j].Validator
})
return nil
return len(votes),
len(headCorrects),
len(headTimelys),
len(sourceTimelys),
len(targetCorrects),
len(targetTimelys),
votes,
participations,
nil
}
func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
@@ -284,6 +361,10 @@ func (c *command) setup(ctx context.Context) error {
if !isProvider {
return errors.New("connection does not provide beacon committees")
}
c.beaconBlockHeadersProvider, isProvider = c.eth2Client.(eth2client.BeaconBlockHeadersProvider)
if !isProvider {
return errors.New("connection does not provide beacon block headers")
}
return nil
}

View File

@@ -66,7 +66,7 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{
"timeout": "5s",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
{
name: "ConnectionBad",
@@ -75,7 +75,7 @@ func TestInput(t *testing.T) {
"connection": "localhost:1",
"topics": []string{"one", "two"},
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
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://localhost:1/eth/v1/beacon/genesis\": dial tcp 127.0.0.1:1: connect: connection refused",
},
{
name: "TopicsNil",

View File

@@ -27,3 +27,6 @@ var proposerCmd = &cobra.Command{
func init() {
RootCmd.AddCommand(proposerCmd)
}
func proposerFlags(cmd *cobra.Command) {
}

View File

@@ -0,0 +1,77 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/services/chaintime"
)
type command struct {
quiet bool
verbose bool
debug bool
// Beacon node connection.
timeout time.Duration
connection string
allowInsecureConnections bool
// Operation.
epoch string
jsonOutput bool
// Data access.
eth2Client eth2client.Service
chainTime chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
// Results.
results *results
}
type results struct {
Epoch phase0.Epoch `json:"epoch"`
Duties []*apiv1.ProposerDuty `json:"duties"`
}
func newCommand(ctx context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
results: &results{},
}
// Timeout.
if viper.GetDuration("timeout") == 0 {
return nil, errors.New("timeout is required")
}
c.timeout = viper.GetDuration("timeout")
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.epoch = viper.GetString("epoch")
c.jsonOutput = viper.GetBool("json")
return c, nil
}

View File

@@ -0,0 +1,72 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestInput(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
tests := []struct {
name string
vars map[string]interface{}
err string
}{
{
name: "TimeoutMissing",
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "Good",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
},
{
name: "GoodWithEpoch",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"epoch": "-1",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
_, err := newCommand(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,62 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
"encoding/json"
"fmt"
"strings"
)
func (c *command) output(ctx context.Context) (string, error) {
if c.quiet {
return "", nil
}
if c.jsonOutput {
return c.outputJSON(ctx)
}
return c.outputTxt(ctx)
}
func (c *command) outputJSON(_ context.Context) (string, error) {
data, err := json.Marshal(c.results)
if err != nil {
return "", err
}
return string(data), nil
}
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 ")
builder.WriteString(fmt.Sprintf("%d", duty.ValidatorIndex))
if c.verbose {
builder.WriteString(" (pubkey ")
builder.WriteString(fmt.Sprintf("%#x)", duty.PubKey))
}
builder.WriteString("\n")
}
return strings.TrimSuffix(builder.String(), "\n"), nil
}

View File

@@ -0,0 +1,70 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
)
func (c *command) process(ctx context.Context) error {
// Obtain information we need to process.
err := c.setup(ctx)
if err != nil {
return err
}
c.results.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
if err != nil {
return errors.Wrap(err, "failed to parse epoch")
}
c.results.Duties, err = c.proposerDutiesProvider.ProposerDuties(ctx, c.results.Epoch, nil)
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}
return nil
}
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)
if err != nil {
return errors.Wrap(err, "failed to connect to beacon node")
}
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithForkScheduleProvider(c.eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")
}
var isProvider bool
c.proposerDutiesProvider, isProvider = c.eth2Client.(eth2client.ProposerDutiesProvider)
if !isProvider {
return errors.New("connection does not provide proposer duties")
}
return nil
}

View File

@@ -0,0 +1,62 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestProcess(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
tests := []struct {
name string
vars map[string]interface{}
err string
}{
{
name: "InvalidData",
vars: map[string]interface{}{
"timeout": "60s",
"data": "[[",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
cmd, err := newCommand(context.Background())
require.NoError(t, err)
err = cmd.process(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,50 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 proposerduties
import (
"context"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// Run runs the command.
func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
c, err := newCommand(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to set up command")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
if err := c.process(ctx); err != nil {
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
return "", nil
}
results, err := c.output(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil
}

61
cmd/proposerduties.go Normal file
View File

@@ -0,0 +1,61 @@
// Copyright © 2022 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
proposerduties "github.com/wealdtech/ethdo/cmd/proposer/duties"
)
var proposerDutiesCmd = &cobra.Command{
Use: "duties",
Short: "Obtain information about duties of an proposer",
Long: `Obtain information about dutes of an proposer. For example:
ethdo proposer duties --epoch=12345
In quiet mode this will return 0 if duties can be obtained, otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
res, err := proposerduties.Run(cmd)
if err != nil {
return err
}
if viper.GetBool("quiet") {
return nil
}
if res != "" {
fmt.Println(res)
}
return nil
},
}
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")
}
func proposerDutiesBindings() {
if err := viper.BindPFlag("epoch", proposerDutiesCmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("json", proposerDutiesCmd.Flags().Lookup("json")); err != nil {
panic(err)
}
}

View File

@@ -77,6 +77,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
return util.SetupStore()
}
// nolint:gocyclo
func includeCommandBindings(cmd *cobra.Command) {
switch commandPath(cmd) {
case "account/create":
@@ -95,6 +96,8 @@ func includeCommandBindings(cmd *cobra.Command) {
blockInfoBindings()
case "chain/eth1votes":
chainEth1VotesBindings()
case "chain/info":
chainInfoBindings()
case "chain/queues":
chainQueuesBindings()
case "chain/time":
@@ -107,6 +110,8 @@ func includeCommandBindings(cmd *cobra.Command) {
exitVerifyBindings()
case "node/events":
nodeEventsBindings()
case "proposer/duties":
proposerDutiesBindings()
case "slot/time":
slotTimeBindings()
case "synccommittee/inclusion":
@@ -115,6 +120,8 @@ func includeCommandBindings(cmd *cobra.Command) {
synccommitteeMembersBindings()
case "validator/credentials/get":
validatorCredentialsGetBindings()
case "validator/credentials/set":
validatorCredentialsSetBindings()
case "validator/depositdata":
validatorDepositdataBindings()
case "validator/duties":
@@ -125,6 +132,8 @@ func includeCommandBindings(cmd *cobra.Command) {
validatorInfoBindings()
case "validator/keycheck":
validatorKeycheckBindings()
case "validator/summary":
validatorSummaryBindings()
case "validator/yield":
validatorYieldBindings()
case "validator/expectation":
@@ -166,10 +175,26 @@ func init() {
if err := viper.BindPFlag("store", RootCmd.PersistentFlags().Lookup("store")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("account", "", "Account name (in format \"wallet/account\")")
RootCmd.PersistentFlags().String("account", "", `Account name (in format "<wallet>/<account>")`)
if err := viper.BindPFlag("account", RootCmd.PersistentFlags().Lookup("account")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("mnemonic", "", "Mnemonic to provide access to an account")
if err := viper.BindPFlag("mnemonic", RootCmd.PersistentFlags().Lookup("mnemonic")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("path", "", "Hierarchical derivation path used with mnemonic to provide access to an account")
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")
if err := viper.BindPFlag("private-key", RootCmd.PersistentFlags().Lookup("private-key")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("public-key", "", "public key to provide access to an account")
if err := viper.BindPFlag("public-key", RootCmd.PersistentFlags().Lookup("public-key")); err != nil {
panic(err)
}
RootCmd.PersistentFlags().String("basedir", "", "Base directory for filesystem wallets")
if err := viper.BindPFlag("basedir", RootCmd.PersistentFlags().Lookup("basedir")); err != nil {
panic(err)
@@ -372,24 +397,6 @@ func walletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet
return wallet, account, nil
}
// bestPublicKey returns the best public key for operations.
// It prefers the composite public key if present, otherwise the public key.
func bestPublicKey(account e2wtypes.Account) (e2types.PublicKey, error) {
var pubKey e2types.PublicKey
publicKeyProvider, isCompositePublicKeyProvider := account.(e2wtypes.AccountCompositePublicKeyProvider)
if isCompositePublicKeyProvider {
pubKey = publicKeyProvider.CompositePublicKey()
} else {
publicKeyProvider, isPublicKeyProvider := account.(e2wtypes.AccountPublicKeyProvider)
if isPublicKeyProvider {
pubKey = publicKeyProvider.PublicKey()
} else {
return nil, errors.New("account does not provide a public key")
}
}
return pubKey, nil
}
// remotesToEndpoints generates endpoints from remote addresses.
func remotesToEndpoints(remotes []string) ([]*dirk.Endpoint, error) {
endpoints := make([]*dirk.Endpoint, 0)

View File

@@ -73,7 +73,7 @@ func TestInput(t *testing.T) {
"timeout": "5s",
"slot": "1",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
}

View File

@@ -37,7 +37,7 @@ func TestOutput(t *testing.T) {
dataOut: &dataOut{
startTime: time.Unix(1606824023, 0),
},
res: "2020-12-01 12:00:23 +0000 GMT",
res: "2020-12-01 12:00:23 +0000 UTC",
},
{
name: "Verbose",
@@ -46,7 +46,7 @@ func TestOutput(t *testing.T) {
endTime: time.Unix(1606824035, 0),
verbose: true,
},
res: "2020-12-01 12:00:23 +0000 GMT - 2020-12-01 12:00:35 +0000 GMT",
res: "2020-12-01 12:00:23 +0000 UTC - 2020-12-01 12:00:35 +0000 UTC",
},
}

View File

@@ -62,9 +62,7 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
// Connection.
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"validators": "1",
"timeout": "5s",
},
err: "connection is required",
},
{
name: "NoValidator",
vars: map[string]interface{}{

View File

@@ -32,6 +32,14 @@ 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{}{
@@ -39,7 +47,7 @@ func TestProcess(t *testing.T) {
"index": "1",
"connection": "invalid",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
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",
},
{
name: "Good",

View File

@@ -65,7 +65,15 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{
"timeout": "5s",
},
err: "failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to any beacon node",
},
{
name: "ConnectionInvalid",
vars: map[string]interface{}{
"timeout": "5s",
"connection": "localhost:1",
},
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://localhost:1/eth/v1/beacon/genesis\": dial tcp 127.0.0.1:1: connect: connection refused",
},
}

View File

@@ -30,3 +30,6 @@ func init() {
func validatorFlags(cmd *cobra.Command) {
}
func validatorBindings() {
}

View File

@@ -29,9 +29,7 @@ type command struct {
debug bool
// Input.
account string
index string
pubKey string
validator string
// Beacon node connection.
timeout time.Duration
@@ -43,7 +41,7 @@ type command struct {
validatorsProvider eth2client.ValidatorsProvider
// Output.
validator *apiv1.Validator
validatorInfo *apiv1.Validator
}
func newCommand(ctx context.Context) (*command, error) {
@@ -59,31 +57,13 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.account = viper.GetString("account")
c.index = viper.GetString("index")
c.pubKey = viper.GetString("pubkey")
nonNil := 0
if c.account != "" {
nonNil++
}
if c.index != "" {
nonNil++
}
if c.pubKey != "" {
nonNil++
}
if nonNil == 0 {
return nil, errors.New("one of account, index or pubkey required")
}
if nonNil > 1 {
return nil, errors.New("only one of account, index and pubkey allowed")
if viper.GetString("validator") == "" {
return nil, errors.New("validator is required")
}
c.validator = viper.GetString("validator")
return c, nil
}

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"index": "1",
},
err: "connection is required",
},
{
name: "NoValidatorInfo",
vars: map[string]interface{}{

View File

@@ -17,6 +17,8 @@ import (
"context"
"fmt"
"strings"
ethutil "github.com/wealdtech/go-eth2-util"
)
func (c *command) output(ctx context.Context) (string, error) {
@@ -26,19 +28,38 @@ func (c *command) output(ctx context.Context) (string, error) {
builder := strings.Builder{}
switch c.validator.Validator.WithdrawalCredentials[0] {
switch c.validatorInfo.Validator.WithdrawalCredentials[0] {
case 0:
builder.WriteString("BLS credentials: ")
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials))
builder.WriteString(fmt.Sprintf("%#x", c.validatorInfo.Validator.WithdrawalCredentials))
case 1:
builder.WriteString("Ethereum execution address: ")
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials[12:]))
builder.WriteString(addressBytesToEIP55(c.validatorInfo.Validator.WithdrawalCredentials[12:]))
if c.verbose {
builder.WriteString("\n")
builder.WriteString("Withdrawal credentials: ")
builder.WriteString(fmt.Sprintf("%#x", c.validator.Validator.WithdrawalCredentials))
builder.WriteString(fmt.Sprintf("%#x", c.validatorInfo.Validator.WithdrawalCredentials))
}
}
return builder.String(), nil
}
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
func addressBytesToEIP55(address []byte) string {
bytes := []byte(fmt.Sprintf("%x", address))
hash := ethutil.Keccak256(bytes)
for i := 0; i < len(bytes); i++ {
hashByte := hash[i/2]
if i%2 == 0 {
hashByte >>= 4
} else {
hashByte &= 0xf
}
if bytes[i] > '9' && hashByte > 7 {
bytes[i] -= 32
}
}
return fmt.Sprintf("0x%s", string(bytes))
}

View File

@@ -15,14 +15,10 @@ package validatorcredentialsget
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/util"
)
@@ -68,71 +64,10 @@ func (c *command) setup(ctx context.Context) error {
}
func (c *command) fetchValidator(ctx context.Context) error {
if c.account != "" {
_, account, err := util.WalletAndAccountFromInput(ctx)
if err != nil {
return errors.Wrap(err, "unable to obtain account")
}
accPubKey, err := util.BestPublicKey(account)
if err != nil {
return errors.Wrap(err, "unable to obtain public key for account")
}
pubKey := phase0.BLSPubKey{}
copy(pubKey[:], accPubKey.Marshal())
validators, err := c.validatorsProvider.ValidatorsByPubKey(ctx,
"head",
[]phase0.BLSPubKey{pubKey},
)
if err != nil {
return errors.Wrap(err, "failed to obtain validator information")
}
if len(validators) == 0 {
return errors.New("unknown validator")
}
for _, validator := range validators {
c.validator = validator
}
}
if c.index != "" {
tmp, err := strconv.ParseUint(c.index, 10, 64)
if err != nil {
return errors.Wrap(err, "invalid validator index")
}
index := phase0.ValidatorIndex(tmp)
validators, err := c.validatorsProvider.Validators(ctx,
"head",
[]phase0.ValidatorIndex{index},
)
if err != nil {
return errors.Wrap(err, "failed to obtain validator information")
}
if _, exists := validators[index]; !exists {
return errors.New("unknown validator")
}
c.validator = validators[index]
}
if c.pubKey != "" {
bytes, err := hex.DecodeString(strings.TrimPrefix(c.pubKey, "0x"))
if err != nil {
return errors.Wrap(err, "invalid validator public key")
}
pubKey := phase0.BLSPubKey{}
copy(pubKey[:], bytes)
validators, err := c.validatorsProvider.ValidatorsByPubKey(ctx,
"head",
[]phase0.BLSPubKey{pubKey},
)
if err != nil {
return errors.Wrap(err, "failed to obtain validator information")
}
if len(validators) == 0 {
return errors.New("unknown validator")
}
for _, validator := range validators {
c.validator = validator
}
var err error
c.validatorInfo, err = util.ParseValidator(ctx, c.validatorsProvider, c.validator, "head")
if err != nil {
return errors.Wrap(err, "failed to obtain validator information")
}
return nil

View File

@@ -0,0 +1,127 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
type chainInfo struct {
Version uint64
Validators []*validatorInfo
GenesisValidatorsRoot phase0.Root
Epoch phase0.Epoch
ForkVersion phase0.Version
Domain phase0.Domain
}
type chainInfoJSON struct {
Version string `json:"version"`
Validators []*validatorInfo `json:"validators"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
Epoch string `json:"epoch"`
ForkVersion string `json:"fork_version"`
Domain string `json:"domain"`
}
// MarshalJSON implements json.Marshaler.
func (v *chainInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&chainInfoJSON{
Version: fmt.Sprintf("%d", v.Version),
Validators: v.Validators,
GenesisValidatorsRoot: fmt.Sprintf("%#x", v.GenesisValidatorsRoot),
Epoch: fmt.Sprintf("%d", v.Epoch),
ForkVersion: fmt.Sprintf("%#x", v.ForkVersion),
Domain: fmt.Sprintf("%#x", v.Domain),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *chainInfo) UnmarshalJSON(input []byte) error {
var data chainInfoJSON
if err := json.Unmarshal(input, &data); err != nil {
return errors.Wrap(err, "invalid JSON")
}
if data.Version == "" {
// Default to 1.
v.Version = 1
} else {
version, err := strconv.ParseUint(data.Version, 10, 64)
if err != nil {
return errors.Wrap(err, "version invalid")
}
v.Version = version
}
if len(data.Validators) == 0 {
return errors.New("validators missing")
}
v.Validators = data.Validators
if data.GenesisValidatorsRoot == "" {
return errors.New("genesis validators root missing")
}
genesisValidatorsRootBytes, err := hex.DecodeString(strings.TrimPrefix(data.GenesisValidatorsRoot, "0x"))
if err != nil {
return errors.Wrap(err, "genesis validators root invalid")
}
if len(genesisValidatorsRootBytes) != phase0.RootLength {
return errors.New("genesis validators root incorrect length")
}
copy(v.GenesisValidatorsRoot[:], genesisValidatorsRootBytes)
if data.Epoch == "" {
return errors.New("epoch missing")
}
epoch, err := strconv.ParseUint(data.Epoch, 10, 64)
if err != nil {
return errors.Wrap(err, "epoch invalid")
}
v.Epoch = phase0.Epoch(epoch)
if data.ForkVersion == "" {
return errors.New("fork version missing")
}
forkVersionBytes, err := hex.DecodeString(strings.TrimPrefix(data.ForkVersion, "0x"))
if err != nil {
return errors.Wrap(err, "fork version invalid")
}
if len(forkVersionBytes) != phase0.ForkVersionLength {
return errors.New("fork version incorrect length")
}
copy(v.ForkVersion[:], forkVersionBytes)
if data.Domain == "" {
return errors.New("domain missing")
}
domainBytes, err := hex.DecodeString(strings.TrimPrefix(data.Domain, "0x"))
if err != nil {
return errors.Wrap(err, "domain invalid")
}
if len(domainBytes) != phase0.DomainLength {
return errors.New("domain incorrect length")
}
copy(v.Domain[:], domainBytes)
return nil
}

View File

@@ -0,0 +1,104 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"context"
"time"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
capella "github.com/attestantio/go-eth2-client/spec/capella"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/services/chaintime"
"github.com/wealdtech/ethdo/util"
)
type command struct {
quiet bool
verbose bool
debug bool
offline bool
json bool
// Input.
account string
passphrases []string
mnemonic string
path string
privateKey string
validator string
withdrawalAddressStr string
forkVersion string
genesisValidatorsRoot string
prepareOffline bool
// Beacon node connection.
timeout time.Duration
connection string
allowInsecureConnections bool
// Information required to generate the operations.
withdrawalAddress bellatrix.ExecutionAddress
chainInfo *chainInfo
// Processing.
consensusClient consensusclient.Service
chainTime chaintime.Service
// Output.
signedOperations []*capella.SignedBLSToExecutionChange
}
func newCommand(ctx context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
offline: viper.GetBool("offline"),
json: viper.GetBool("json"),
timeout: viper.GetDuration("timeout"),
connection: viper.GetString("connection"),
allowInsecureConnections: viper.GetBool("allow-insecure-connections"),
prepareOffline: viper.GetBool("prepare-offline"),
account: viper.GetString("account"),
passphrases: util.GetPassphrases(),
mnemonic: viper.GetString("mnemonic"),
path: viper.GetString("path"),
privateKey: viper.GetString("private-key"),
validator: viper.GetString("validator"),
withdrawalAddressStr: viper.GetString("withdrawal-address"),
forkVersion: viper.GetString("fork-version"),
genesisValidatorsRoot: viper.GetString("genesis-validators-root"),
}
// Timeout is required.
if c.timeout == 0 {
return nil, errors.New("timeout is required")
}
// We are generating information for offline use, we don't need any information
// related to the accounts or signing.
if c.prepareOffline {
return c, nil
}
if c.account != "" && len(c.passphrases) == 0 {
return nil, errors.New("passphrase required with account")
}
return c, nil
}

View File

@@ -0,0 +1,83 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"context"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestInput(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
tests := []struct {
name string
vars map[string]interface{}
err string
}{
{
name: "TimeoutMissing",
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "NoValidatorInfo",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
err: "one of account, index or pubkey required",
},
{
name: "MultipleValidatorInfo",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"index": "1",
"pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
err: "only one of account, index and pubkey allowed",
},
{
name: "Good",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"index": "1",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
_, err := newCommand(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,41 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"context"
"encoding/json"
"os"
"github.com/pkg/errors"
)
func (c *command) output(ctx context.Context) (string, error) {
if c.quiet {
return "", nil
}
if c.json || c.offline {
data, err := json.Marshal(c.signedOperations)
if err != nil {
return "", errors.Wrap(err, "failed to marshal signed operations")
}
if err := os.WriteFile("credentials-operations.json", data, 0600); err != nil {
return "", errors.Wrap(err, "failed to write credentials-operations.json")
}
return "", nil
}
return "", nil
}

View File

@@ -0,0 +1,626 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"
"strings"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
capella "github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/signing"
"github.com/wealdtech/ethdo/util"
ethutil "github.com/wealdtech/go-eth2-util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// validatorPath is the regular expression that matches a validator path.
var validatorPath = regexp.MustCompile("^m/12381/3600/[0-9]+/0/0$")
var offlinePreparationFilename = "offline-preparation.json"
var changeOperationsFilename = "change-operations.json"
func (c *command) process(ctx context.Context) error {
if err := c.setup(ctx); err != nil {
return err
}
if err := c.obtainRequiredInformation(ctx); err != nil {
return err
}
if c.prepareOffline {
return c.dumpRequiredInformation(ctx)
}
if err := c.generateOperations(ctx); err != nil {
return err
}
if validated, reason := c.validateOperations(ctx); !validated {
return fmt.Errorf("operation failed validation: %s", reason)
}
if c.json || c.offline {
// Want JSON output, or cannot broadcast.
return nil
}
return c.broadcastOperations(ctx)
}
// obtainRequiredInformation obtains the information required to create a
// withdrawal credentials change operation.
func (c *command) obtainRequiredInformation(ctx context.Context) error {
c.chainInfo = &chainInfo{
Validators: make([]*validatorInfo, 0),
}
// Use the offline preparation file if present (and we haven't been asked to recreate it).
if !c.prepareOffline {
err := c.loadChainInfo(ctx)
if err == nil {
return nil
}
}
if c.offline {
return fmt.Errorf("could not find the %s file; this is required to have been previously generated using --offline-preparation on an online mcahine and be readable in the directory in which this command is being run", offlinePreparationFilename)
}
if err := c.populateChainInfo(ctx); err != nil {
return err
}
return nil
}
// populateChainInfo populates chain info structure from a beacon node.
func (c *command) populateChainInfo(ctx context.Context) error {
// Obtain validators.
validators, err := c.consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, "head", nil)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
for _, validator := range validators {
c.chainInfo.Validators = append(c.chainInfo.Validators, &validatorInfo{
Index: validator.Index,
Pubkey: validator.Validator.PublicKey,
WithdrawalCredentials: validator.Validator.WithdrawalCredentials,
})
}
// Obtain genesis validators root.
genesis, err := c.consensusClient.(consensusclient.GenesisProvider).Genesis(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain genesis information")
}
c.chainInfo.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot
// Obtain epoch.
c.chainInfo.Epoch = c.chainTime.CurrentEpoch()
// Obtain fork version.
forkSchedule, err := c.consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain fork schedule")
}
for i := range forkSchedule {
if forkSchedule[i].Epoch <= c.chainInfo.Epoch {
c.chainInfo.ForkVersion = forkSchedule[i].CurrentVersion
}
}
// Calculate domain.
spec, err := c.consensusClient.(consensusclient.SpecProvider).Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
domainType, exists := spec["DOMAIN_BLS_TO_EXECUTION_CHANGE"].(phase0.DomainType)
if !exists {
return errors.New("failed to obtain DOMAIN_BLS_TO_EXECUTION_CHANGE")
}
domainProvider, isProvider := c.consensusClient.(consensusclient.DomainProvider)
if !isProvider {
return errors.New("consensus node does not provide domain information")
}
c.chainInfo.Domain, err = domainProvider.Domain(ctx, domainType, c.chainInfo.Epoch)
if err != nil {
return errors.Wrap(err, "failed to obtain domain")
}
return nil
}
// dumpRequiredInformation prepares for an offline run of this command by dumping
// the chain information to a file.
func (c *command) dumpRequiredInformation(ctx context.Context) error {
data, err := json.Marshal(c.chainInfo)
if err != nil {
return err
}
if err := os.WriteFile(offlinePreparationFilename, data, 0600); err != nil {
return err
}
return nil
}
func (c *command) generateOperations(ctx context.Context) error {
// Ensure that we are beyond the capella hard fork epoch.
if c.chainTime.CurrentEpoch() < c.chainTime.CapellaInitialEpoch() {
return errors.New("chain not yet activated capella hard fork")
}
if c.account == "" && c.mnemonic == "" && c.privateKey == "" {
// No input information; fetch the operations from a file.
if err := c.loadOperations(ctx); err == nil {
return nil
}
return fmt.Errorf("no account, mnemonic or private key specified and no %s file found; cannot proceed", changeOperationsFilename)
}
if c.mnemonic != "" && c.path == "" {
// Have a mnemonic and no path; scan mnemonic.
return c.generateOperationsFromMnemonic(ctx)
}
if c.mnemonic != "" && c.path != "" {
// Have a mnemonic and path.
return c.generateOperationsFromMnemonicAndPath(ctx)
}
// Have a validator index or public key ; fetch the validator info.
validatorInfo, err := c.fetchValidatorInfo(ctx)
if err != nil {
return err
}
// Fetch the individual account.
withdrawalAccount, err := c.fetchAccount(ctx)
if err != nil {
return err
}
// Generate the operation.
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}
func (c *command) loadChainInfo(ctx context.Context) error {
_, err := os.Stat(offlinePreparationFilename)
if err != nil {
if c.debug {
fmt.Printf("Failed to read offline preparation file: %v\n", err)
}
}
if c.debug {
fmt.Printf("%s found; loading chain state\n", offlinePreparationFilename)
}
data, err := os.ReadFile(offlinePreparationFilename)
if err != nil {
return errors.Wrap(err, "failed to read offline preparation file")
}
if err := json.Unmarshal(data, c.chainInfo); err != nil {
return errors.Wrap(err, "failed to parse offline preparation file")
}
return nil
}
func (c *command) loadOperations(ctx context.Context) error {
_, err := os.Stat(changeOperationsFilename)
if err != nil {
if c.debug {
fmt.Printf("Failed to read change operations file: %v\n", err)
}
return err
}
if c.debug {
fmt.Printf("%s found; loading operations\n", changeOperationsFilename)
}
data, err := os.ReadFile(changeOperationsFilename)
if err != nil {
return errors.Wrap(err, "failed to read change operations file")
}
if err := json.Unmarshal(data, &c.signedOperations); err != nil {
return errors.Wrap(err, "failed to parse change operations file")
}
return nil
}
func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
return err
}
// Turn the validators in to a map for easy lookup.
validators := make(map[string]*validatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
}
maxDistance := 1024
// Start scanning the validator keys.
lastFoundIndex := 0
for i := 0; ; i++ {
if i-lastFoundIndex > maxDistance {
if c.debug {
fmt.Printf("Gone %d indices without finding a validator, not scanning any further\n", maxDistance)
}
break
}
validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", i)
found, err := c.generateOperationFromSeedAndPath(ctx, validators, seed, validatorKeyPath)
if err != nil {
return errors.Wrap(err, "failed to generate operation from seed and path")
}
if found {
lastFoundIndex = i
}
}
return nil
}
func (c *command) generateOperationFromSeedAndPath(ctx context.Context,
validators map[string]*validatorInfo,
seed []byte,
path string,
) (
bool,
error,
) {
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, path)
if err != nil {
return false, errors.Wrap(err, "failed to generate validator private key")
}
validatorPubkey := fmt.Sprintf("%#x", validatorPrivkey.PublicKey().Marshal())
validator, exists := validators[validatorPubkey]
if !exists {
if c.debug {
fmt.Printf("No validator found with public key %s at path %s\n", validatorPubkey, path)
}
return false, nil
}
if c.verbose {
fmt.Printf("Validator %d found with public key %s at path %s\n", validator.Index, validatorPubkey, path)
}
if validator.WithdrawalCredentials[0] != byte(0) {
if c.debug {
fmt.Printf("Validator %s has non-BLS withdrawal credentials %#x\n", validatorPubkey, validator.WithdrawalCredentials)
}
return false, nil
}
// Recreate the withdrawal credentials to ensure a match.
withdrawalKeyPath := strings.TrimSuffix(path, "/0")
withdrawalPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, withdrawalKeyPath)
if err != nil {
return false, errors.Wrap(err, "failed to generate withdrawal private key")
}
withdrawalPubkey := withdrawalPrivkey.PublicKey()
withdrawalCredentials := ethutil.SHA256(withdrawalPubkey.Marshal())
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
if !bytes.Equal(withdrawalCredentials, validator.WithdrawalCredentials) {
if c.verbose {
fmt.Printf("Validator %s withdrawal credentials %#x do not match expected credentials, cannot update\n", validatorPubkey, validator.WithdrawalCredentials)
}
return false, nil
}
if c.debug {
fmt.Printf("Validator %s eligible for setting credentials\n", validatorPubkey)
}
withdrawalAccount, err := util.ParseAccount(ctx, c.mnemonic, []string{withdrawalKeyPath}, true)
if err != nil {
return false, errors.Wrap(err, "failed to create withdrawal account")
}
err = c.generateOperationFromAccount(ctx, validator, withdrawalAccount)
if err != nil {
return false, err
}
return true, nil
}
func (c *command) generateOperationFromAccount(ctx context.Context,
validator *validatorInfo,
withdrawalAccount e2wtypes.Account,
) error {
signedOperation, err := c.createSignedOperation(ctx, validator, withdrawalAccount)
if err != nil {
return err
}
c.signedOperations = append(c.signedOperations, signedOperation)
return nil
}
func (c *command) createSignedOperation(ctx context.Context,
validator *validatorInfo,
withdrawalAccount e2wtypes.Account,
) (
*capella.SignedBLSToExecutionChange,
error,
) {
pubkey, err := util.BestPublicKey(withdrawalAccount)
if err != nil {
return nil, err
}
blsPubkey := phase0.BLSPubKey{}
copy(blsPubkey[:], pubkey.Marshal())
if err := c.parseWithdrawalAddress(ctx); err != nil {
return nil, errors.Wrap(err, "invalid withdrawal address")
}
operation := &capella.BLSToExecutionChange{
ValidatorIndex: validator.Index,
FromBLSPubkey: blsPubkey,
ToExecutionAddress: c.withdrawalAddress,
}
root, err := operation.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to generate root for credentials change operation")
}
// Sign the operation.
signature, err := signing.SignRoot(ctx, withdrawalAccount, nil, root, c.chainInfo.Domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign credentials change operation")
}
return &capella.SignedBLSToExecutionChange{
Message: operation,
Signature: signature,
}, nil
}
func (c *command) parseWithdrawalAddress(ctx context.Context) error {
withdrawalAddressBytes, err := hex.DecodeString(strings.TrimPrefix(c.withdrawalAddressStr, "0x"))
if err != nil {
return errors.Wrap(err, "failed to obtain execution address")
}
if len(withdrawalAddressBytes) != bellatrix.ExecutionAddressLength {
return errors.New("withdrawal address must be exactly 20 bytes in length")
}
// Ensure the address is properly checksummed.
checksummedAddress := addressBytesToEIP55(withdrawalAddressBytes)
if checksummedAddress != c.withdrawalAddressStr {
return fmt.Errorf("withdrawal address checksum does not match (expected %s)", checksummedAddress)
}
copy(c.withdrawalAddress[:], withdrawalAddressBytes)
return nil
}
func (c *command) validateOperations(ctx context.Context) (bool, string) {
// Turn the validators in to a map for easy lookup.
validators := make(map[phase0.ValidatorIndex]*validatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[validator.Index] = validator
}
for _, signedOperation := range c.signedOperations {
if validated, reason := c.validateOperation(ctx, validators, signedOperation); !validated {
return validated, reason
}
}
return true, ""
}
func (c *command) validateOperation(ctx context.Context,
validators map[phase0.ValidatorIndex]*validatorInfo,
signedOperation *capella.SignedBLSToExecutionChange,
) (
bool,
string,
) {
validator, exists := validators[signedOperation.Message.ValidatorIndex]
if !exists {
return false, "validator not known on chain"
}
if c.debug {
fmt.Printf("Credentials change operation: %v", signedOperation)
fmt.Printf("On-chain validator info: %v\n", validator)
}
if validator.WithdrawalCredentials[0] != byte(0) {
return false, "validator is not using BLS withdrawal credentials"
}
withdrawalCredentials := ethutil.SHA256(signedOperation.Message.FromBLSPubkey[:])
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
if !bytes.Equal(withdrawalCredentials, validator.WithdrawalCredentials) {
if c.debug {
fmt.Printf("validator withdrawal credentials %#x do not match calculated operation withdrawal credentials %#x\n", validator.WithdrawalCredentials, withdrawalCredentials)
}
return false, "validator withdrawal credentials do not match those in the operation"
}
return true, ""
}
func (c *command) broadcastOperations(ctx context.Context) error {
// Broadcast the operations.
for _, signedOperation := range c.signedOperations {
if err := c.consensusClient.(consensusclient.BLSToExecutionChangeSubmitter).SubmitBLSToExecutionChange(ctx, signedOperation); err != nil {
return err
}
}
return nil
}
func (c *command) setup(ctx context.Context) error {
if c.offline {
return nil
}
// Connect to the consensus node.
var err error
c.consensusClient, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
if err != nil {
return errors.Wrap(err, "failed to connect to consensus node")
}
// Set up chaintime.
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(c.consensusClient.(consensusclient.GenesisTimeProvider)),
standardchaintime.WithForkScheduleProvider(c.consensusClient.(consensusclient.ForkScheduleProvider)),
standardchaintime.WithSpecProvider(c.consensusClient.(consensusclient.SpecProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to create chaintime service")
}
return nil
}
func (c *command) fetchValidatorInfo(ctx context.Context) (*validatorInfo, error) {
var validatorInfo *validatorInfo
switch {
case c.validator == "":
return nil, errors.New("no validator specified")
case strings.HasPrefix(c.validator, "0x"):
// A public key
for _, validator := range c.chainInfo.Validators {
if strings.EqualFold(c.validator, fmt.Sprintf("%#x", validator.Pubkey)) {
validatorInfo = validator
break
}
}
case strings.Contains(c.validator, "/"):
// An account.
_, account, err := util.WalletAndAccountFromPath(ctx, c.validator)
if err != nil {
return nil, errors.Wrap(err, "unable to obtain account")
}
accPubKey, err := util.BestPublicKey(account)
if err != nil {
return nil, errors.Wrap(err, "unable to obtain public key for account")
}
pubkey := fmt.Sprintf("%#x", accPubKey.Marshal())
for _, validator := range c.chainInfo.Validators {
if strings.EqualFold(pubkey, fmt.Sprintf("%#x", validator.Pubkey)) {
validatorInfo = validator
break
}
}
default:
// An index.
index, err := strconv.ParseUint(c.validator, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse validator index")
}
validatorIndex := phase0.ValidatorIndex(index)
for _, validator := range c.chainInfo.Validators {
if validator.Index == validatorIndex {
validatorInfo = validator
break
}
}
}
if validatorInfo == nil {
return nil, errors.New("unknown validator")
}
return validatorInfo, nil
}
func (c *command) fetchAccount(ctx context.Context) (e2wtypes.Account, error) {
var account e2wtypes.Account
var err error
switch {
case c.account != "":
account, err = util.ParseAccount(ctx, c.account, c.passphrases, true)
case c.mnemonic != "":
account, err = util.ParseAccount(ctx, c.mnemonic, []string{c.path}, true)
case c.privateKey != "":
account, err = util.ParseAccount(ctx, c.privateKey, nil, true)
default:
err = errors.New("account, mnemonic or private key must be supplied")
}
return account, err
}
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
func addressBytesToEIP55(address []byte) string {
bytes := []byte(fmt.Sprintf("%x", address))
hash := ethutil.Keccak256(bytes)
for i := 0; i < len(bytes); i++ {
hashByte := hash[i/2]
if i%2 == 0 {
hashByte >>= 4
} else {
hashByte &= 0xf
}
if bytes[i] > '9' && hashByte > 7 {
bytes[i] -= 32
}
}
return fmt.Sprintf("0x%s", string(bytes))
}
func (c *command) generateOperationsFromMnemonicAndPath(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
return err
}
// Turn the validators in to a map for easy lookup.
validators := make(map[string]*validatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
}
validatorKeyPath := c.path
match := validatorPath.Match([]byte(c.path))
if !match {
return fmt.Errorf("path %s does not match EIP-2334 format", c.path)
}
if _, err := c.generateOperationFromSeedAndPath(ctx, validators, seed, validatorKeyPath); err != nil {
return errors.Wrap(err, "failed to generate operation from seed and path")
}
return nil
}

View File

@@ -0,0 +1,50 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"context"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// Run runs the command.
func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
c, err := newCommand(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to set up command")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
if err := c.process(ctx); err != nil {
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
return "", nil
}
results, err := c.output(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil
}

View File

@@ -0,0 +1,97 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorcredentialsset
import (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
type validatorInfo struct {
Index phase0.ValidatorIndex
Pubkey phase0.BLSPubKey
WithdrawalCredentials []byte
}
type validatorInfoJSON struct {
Index string `json:"index"`
Pubkey string `json:"pubkey"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
}
// MarshalJSON implements json.Marshaler.
func (v *validatorInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&validatorInfoJSON{
Index: fmt.Sprintf("%d", v.Index),
Pubkey: fmt.Sprintf("%#x", v.Pubkey),
WithdrawalCredentials: fmt.Sprintf("%#x", v.WithdrawalCredentials),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *validatorInfo) UnmarshalJSON(input []byte) error {
var data validatorInfoJSON
if err := json.Unmarshal(input, &data); err != nil {
return errors.Wrap(err, "invalid JSON")
}
if data.Index == "" {
return errors.New("index missing")
}
index, err := strconv.ParseUint(data.Index, 10, 64)
if err != nil {
return errors.Wrap(err, "invalid value for index")
}
v.Index = phase0.ValidatorIndex(index)
if data.Pubkey == "" {
return errors.New("public key missing")
}
pubkey, err := hex.DecodeString(strings.TrimPrefix(data.Pubkey, "0x"))
if err != nil {
return errors.Wrap(err, "invalid value for public key")
}
if len(pubkey) != phase0.PublicKeyLength {
return fmt.Errorf("incorrect length %d for public key", len(pubkey))
}
copy(v.Pubkey[:], pubkey)
if data.WithdrawalCredentials == "" {
return errors.New("withdrawal credentials missing")
}
v.WithdrawalCredentials, err = hex.DecodeString(strings.TrimPrefix(data.WithdrawalCredentials, "0x"))
if err != nil {
return errors.Wrap(err, "invalid value for withdrawal credentials")
}
if len(v.WithdrawalCredentials) != phase0.HashLength {
return fmt.Errorf("incorrect length %d for withdrawal credentials", len(v.WithdrawalCredentials))
}
return nil
}
// String implements the Stringer interface.
func (v *validatorInfo) String() string {
data, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("Err: %v\n", err)
}
return string(data)
}

View File

@@ -111,6 +111,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
[4]byte{0x00, 0x00, 0x20, 0x09}: "pyrmont",
[4]byte{0x00, 0x00, 0x10, 0x20}: "prater",
[4]byte{0x80, 0x00, 0x00, 0x69}: "ropsten",
[4]byte{0x90, 0x00, 0x00, 0x69}: "sepolia",
}
if datum.validatorPubKey == nil {

View File

@@ -49,9 +49,6 @@ func input(ctx context.Context) (*dataIn, error) {
// Ethereum 2 connection.
data.eth2Client = viper.GetString("connection")
if data.eth2Client == "" {
return nil, errors.New("connection is required")
}
data.allowInsecure = viper.GetBool("allow-insecure-connections")
// Account.

View File

@@ -71,14 +71,6 @@ func TestInput(t *testing.T) {
},
err: "account, pubkey or index required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"timeout": "5s",
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
},
err: "connection is required",
},
}
for _, test := range tests {

View File

@@ -158,7 +158,7 @@ func TestInput(t *testing.T) {
"timeout": "5s",
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
},
err: "failed to connect to Ethereum 2 beacon node: failed to connect to beacon node: failed to connect to Ethereum 2 client with any known method",
err: "failed to connect to Ethereum 2 beacon node: 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://localhost:1/eth/v1/beacon/genesis\": dial tcp 127.0.0.1:1: connect: connection refused",
},
{
name: "EpochProvided",

View File

@@ -58,9 +58,6 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"validators": "1",
"timeout": "5s",
},
err: "connection is required",
},
{
name: "ValidatorsZero",
vars: map[string]interface{}{

View File

@@ -0,0 +1,140 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/services/chaintime"
)
type command struct {
quiet bool
verbose bool
debug bool
// Beacon node connection.
timeout time.Duration
connection string
allowInsecureConnections bool
// Operation.
epoch string
validators []string
jsonOutput bool
// Data access.
eth2Client eth2client.Service
chainTime chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
attesterDutiesProvider eth2client.AttesterDutiesProvider
blocksProvider eth2client.SignedBeaconBlockProvider
syncCommitteesProvider eth2client.SyncCommitteesProvider
validatorsProvider eth2client.ValidatorsProvider
beaconCommitteesProvider eth2client.BeaconCommitteesProvider
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
// Processing.
validatorsByIndex map[phase0.ValidatorIndex]*apiv1.Validator
// Results.
summary *validatorSummary
}
type validatorSummary struct {
Epoch phase0.Epoch `json:"epoch"`
Validators []*apiv1.Validator `json:"validators"`
FirstSlot phase0.Slot `json:"first_slot"`
LastSlot phase0.Slot `json:"last_slot"`
ActiveValidators int `json:"active_validators"`
ParticipatingValidators int `json:"participating_validators"`
NonParticipatingValidators []*nonParticipatingValidator `json:"non_participating_validators"`
IncorrectHeadValidators []*validatorFault `json:"incorrect_head_validators"`
UntimelyHeadValidators []*validatorFault `json:"untimely_head_validators"`
UntimelySourceValidators []*validatorFault `json:"untimely_source_validators"`
IncorrectTargetValidators []*validatorFault `json:"incorrect_target_validators"`
UntimelyTargetValidators []*validatorFault `json:"untimely_target_validators"`
Slots []*slot `json:"slots"`
Proposals []*epochProposal `json:"-"`
SyncCommittee []*epochSyncCommittee `json:"-"`
}
type slot struct {
Slot phase0.Slot `json:"slot"`
Attestations *slotAttestations `json:"attestations"`
}
type slotAttestations struct {
Expected int `json:"expected"`
Included int `json:"included"`
CorrectHead int `json:"correct_head"`
TimelyHead int `json:"timely_head"`
CorrectTarget int `json:"correct_target"`
TimelyTarget int `json:"timely_target"`
TimelySource int `json:"timely_source"`
}
type epochProposal struct {
Slot phase0.Slot `json:"slot"`
Proposer phase0.ValidatorIndex `json:"proposer"`
Block bool `json:"block"`
}
type epochSyncCommittee struct {
Index phase0.ValidatorIndex `json:"index"`
Missed int `json:"missed"`
}
type validatorFault struct {
Validator phase0.ValidatorIndex `json:"validator_index"`
AttestationData *phase0.AttestationData `json:"attestation_data,omitempty"`
InclusionDistance int `json:"inclusion_delay"`
}
type nonParticipatingValidator struct {
Validator phase0.ValidatorIndex `json:"validator_index"`
Slot phase0.Slot `json:"slot"`
Committee phase0.CommitteeIndex `json:"committee_index"`
}
func newCommand(ctx context.Context) (*command, error) {
c := &command{
quiet: viper.GetBool("quiet"),
verbose: viper.GetBool("verbose"),
debug: viper.GetBool("debug"),
validatorsByIndex: make(map[phase0.ValidatorIndex]*apiv1.Validator),
summary: &validatorSummary{},
}
// Timeout.
if viper.GetDuration("timeout") == 0 {
return nil, errors.New("timeout is required")
}
c.timeout = viper.GetDuration("timeout")
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
c.epoch = viper.GetString("epoch")
c.validators = viper.GetStringSlice("validators")
c.jsonOutput = viper.GetBool("json")
return c, nil
}

View File

@@ -0,0 +1,64 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestInput(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
tests := []struct {
name string
vars map[string]interface{}
err string
}{
{
name: "TimeoutMissing",
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "Good",
vars: map[string]interface{}{
"timeout": "5s",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
_, err := newCommand(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,86 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"encoding/json"
"fmt"
"strings"
)
func (c *command) output(ctx context.Context) (string, error) {
if c.quiet {
return "", nil
}
if c.jsonOutput {
return c.outputJSON(ctx)
}
return c.outputTxt(ctx)
}
func (c *command) outputJSON(_ context.Context) (string, error) {
data, err := json.Marshal(c.summary)
if err != nil {
return "", err
}
return string(data), nil
}
func (c *command) outputTxt(_ context.Context) (string, error) {
builder := strings.Builder{}
builder.WriteString("Epoch ")
builder.WriteString(fmt.Sprintf("%d:\n", c.summary.Epoch))
if len(c.summary.NonParticipatingValidators) > 0 {
builder.WriteString(" Non-participating validators:\n")
for _, validator := range c.summary.NonParticipatingValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.Slot, validator.Committee))
}
}
if len(c.summary.IncorrectHeadValidators) > 0 {
builder.WriteString(" Incorrect head validators:\n")
for _, validator := range c.summary.IncorrectHeadValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index))
}
}
if len(c.summary.UntimelyHeadValidators) > 0 {
builder.WriteString(" Untimely head validators:\n")
for _, validator := range c.summary.UntimelyHeadValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
}
}
if len(c.summary.UntimelySourceValidators) > 0 {
builder.WriteString(" Untimely source validators:\n")
for _, validator := range c.summary.UntimelySourceValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
}
}
if len(c.summary.IncorrectTargetValidators) > 0 {
builder.WriteString(" Incorrect target validators:\n")
for _, validator := range c.summary.IncorrectTargetValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index))
}
}
if len(c.summary.UntimelyTargetValidators) > 0 {
builder.WriteString(" Untimely target validators:\n")
for _, validator := range c.summary.UntimelyTargetValidators {
builder.WriteString(fmt.Sprintf(" %d (slot %d, committee %d, inclusion distance %d)\n", validator.Validator, validator.AttestationData.Slot, validator.AttestationData.Index, validator.InclusionDistance))
}
}
return builder.String(), nil
}

View File

@@ -0,0 +1,425 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"fmt"
"sort"
eth2client "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/util"
)
func (c *command) process(ctx context.Context) error {
// Obtain information we need to process.
err := c.setup(ctx)
if err != nil {
return err
}
c.summary.Epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epoch)
if err != nil {
return errors.Wrap(err, "failed to parse epoch")
}
c.summary.FirstSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch)
c.summary.LastSlot = c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1) - 1
c.summary.Slots = make([]*slot, 1+int(c.summary.LastSlot)-int(c.summary.FirstSlot))
for i := range c.summary.Slots {
c.summary.Slots[i] = &slot{
Slot: c.summary.FirstSlot + phase0.Slot(i),
}
}
c.summary.Validators, err = util.ParseValidators(ctx, c.validatorsProvider, c.validators, fmt.Sprintf("%d", c.summary.FirstSlot))
if err != nil {
return errors.Wrap(err, "failed to parse validators")
}
// Reorder validators by index.
sort.Slice(c.summary.Validators, func(i int, j int) bool {
return c.summary.Validators[i].Index < c.summary.Validators[j].Index
})
// Create a map for validator indices for easy lookup.
c.validatorsByIndex = make(map[phase0.ValidatorIndex]*apiv1.Validator)
for _, validator := range c.summary.Validators {
c.validatorsByIndex[validator.Index] = validator
}
if err := c.processProposerDuties(ctx); err != nil {
return err
}
if err := c.processAttesterDuties(ctx); err != nil {
return err
}
// if err := c.processSyncCommitteeDuties(ctx); err != nil {
// return err
// }
return nil
}
func (c *command) processProposerDuties(ctx context.Context) error {
duties, err := c.proposerDutiesProvider.ProposerDuties(ctx, c.summary.Epoch, nil)
if err != nil {
return errors.Wrap(err, "failed to obtain proposer duties")
}
if duties == nil {
return errors.New("empty proposer duties")
}
for _, duty := range duties {
if _, exists := c.validatorsByIndex[duty.ValidatorIndex]; !exists {
continue
}
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", duty.Slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", duty.Slot))
}
present := block != nil
c.summary.Proposals = append(c.summary.Proposals, &epochProposal{
Slot: duty.Slot,
Proposer: duty.ValidatorIndex,
Block: present,
})
}
return nil
}
func (c *command) activeValidators() (map[phase0.ValidatorIndex]*apiv1.Validator, []phase0.ValidatorIndex) {
activeValidators := make(map[phase0.ValidatorIndex]*apiv1.Validator)
activeValidatorIndices := make([]phase0.ValidatorIndex, 0, len(c.validatorsByIndex))
for _, validator := range c.summary.Validators {
if validator.Validator.ActivationEpoch <= c.summary.Epoch && validator.Validator.ExitEpoch > c.summary.Epoch {
activeValidators[validator.Index] = validator
activeValidatorIndices = append(activeValidatorIndices, validator.Index)
}
}
return activeValidators, activeValidatorIndices
}
func (c *command) processAttesterDuties(ctx context.Context) error {
activeValidators, activeValidatorIndices := c.activeValidators()
// Obtain number of validators that voted for blocks in the epoch.
// These votes can be included anywhere from the second slot of
// the epoch to the first slot of the next-but-one epoch.
firstSlot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch) + 1
lastSlot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch + 2)
if lastSlot > c.chainTime.CurrentSlot() {
lastSlot = c.chainTime.CurrentSlot()
}
// Obtain the duties for the validators to know where they should be attesting.
duties, err := c.attesterDutiesProvider.AttesterDuties(ctx, c.summary.Epoch, activeValidatorIndices)
if err != nil {
return errors.Wrap(err, "failed to obtain attester duties")
}
for slot := c.chainTime.FirstSlotOfEpoch(c.summary.Epoch); slot < c.chainTime.FirstSlotOfEpoch(c.summary.Epoch+1); slot++ {
index := int(slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
c.summary.Slots[index].Attestations = &slotAttestations{}
}
// Need a cache of beacon block headers to reduce lookup times.
headersCache := util.NewBeaconBlockHeaderCache(c.beaconBlockHeadersProvider)
// Need a map of duties to easily find the attestations we care about.
dutiesBySlot := make(map[phase0.Slot]map[phase0.CommitteeIndex][]*apiv1.AttesterDuty)
dutiesByValidatorIndex := make(map[phase0.ValidatorIndex]*apiv1.AttesterDuty)
for _, duty := range duties {
index := int(duty.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
dutiesByValidatorIndex[duty.ValidatorIndex] = duty
c.summary.Slots[index].Attestations.Expected++
if _, exists := dutiesBySlot[duty.Slot]; !exists {
dutiesBySlot[duty.Slot] = make(map[phase0.CommitteeIndex][]*apiv1.AttesterDuty)
}
if _, exists := dutiesBySlot[duty.Slot][duty.CommitteeIndex]; !exists {
dutiesBySlot[duty.Slot][duty.CommitteeIndex] = make([]*apiv1.AttesterDuty, 0)
}
dutiesBySlot[duty.Slot][duty.CommitteeIndex] = append(dutiesBySlot[duty.Slot][duty.CommitteeIndex], duty)
}
c.summary.IncorrectHeadValidators = make([]*validatorFault, 0)
c.summary.UntimelyHeadValidators = make([]*validatorFault, 0)
c.summary.UntimelySourceValidators = make([]*validatorFault, 0)
c.summary.IncorrectTargetValidators = make([]*validatorFault, 0)
c.summary.UntimelyTargetValidators = make([]*validatorFault, 0)
// Hunt through the blocks looking for attestations from the validators.
votes := make(map[phase0.ValidatorIndex]struct{})
for slot := firstSlot; slot <= lastSlot; slot++ {
if err := c.processAttesterDutiesSlot(ctx, slot, dutiesBySlot, votes, headersCache, activeValidatorIndices); err != nil {
return err
}
}
// Use dutiesMap and votes to work out which validators didn't participate.
c.summary.NonParticipatingValidators = make([]*nonParticipatingValidator, 0)
for _, index := range activeValidatorIndices {
if _, exists := votes[index]; !exists {
// Didn't vote.
duty := dutiesByValidatorIndex[index]
c.summary.NonParticipatingValidators = append(c.summary.NonParticipatingValidators, &nonParticipatingValidator{
Validator: index,
Slot: duty.Slot,
Committee: duty.CommitteeIndex,
})
}
}
// Sort the non-participating validators list.
sort.Slice(c.summary.NonParticipatingValidators, func(i int, j int) bool {
if c.summary.NonParticipatingValidators[i].Slot != c.summary.NonParticipatingValidators[j].Slot {
return c.summary.NonParticipatingValidators[i].Slot < c.summary.NonParticipatingValidators[j].Slot
}
if c.summary.NonParticipatingValidators[i].Committee != c.summary.NonParticipatingValidators[j].Committee {
return c.summary.NonParticipatingValidators[i].Committee < c.summary.NonParticipatingValidators[j].Committee
}
return c.summary.NonParticipatingValidators[i].Validator < c.summary.NonParticipatingValidators[j].Validator
})
c.summary.ActiveValidators = len(activeValidators)
c.summary.ParticipatingValidators = len(votes)
return nil
}
func (c *command) processAttesterDutiesSlot(ctx context.Context,
slot phase0.Slot,
dutiesBySlot map[phase0.Slot]map[phase0.CommitteeIndex][]*apiv1.AttesterDuty,
votes map[phase0.ValidatorIndex]struct{},
headersCache *util.BeaconBlockHeaderCache,
activeValidatorIndices []phase0.ValidatorIndex,
) error {
block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
}
if block == nil {
// No block at this slot; that's fine.
return nil
}
attestations, err := block.Attestations()
if err != nil {
return err
}
for _, attestation := range attestations {
if _, exists := dutiesBySlot[attestation.Data.Slot]; !exists {
// We do not have any attestations for this slot.
continue
}
if _, exists := dutiesBySlot[attestation.Data.Slot][attestation.Data.Index]; !exists {
// We do not have any attestations for this committee.
continue
}
for _, duty := range dutiesBySlot[attestation.Data.Slot][attestation.Data.Index] {
if attestation.AggregationBits.BitAt(duty.ValidatorCommitteeIndex) {
// Found it.
if _, exists := votes[duty.ValidatorIndex]; exists {
// Duplicate; ignore.
continue
}
votes[duty.ValidatorIndex] = struct{}{}
// Update the metrics for the attestation.
index := int(attestation.Data.Slot - c.chainTime.FirstSlotOfEpoch(c.summary.Epoch))
c.summary.Slots[index].Attestations.Included++
inclusionDelay := slot - duty.Slot
fault := &validatorFault{
Validator: duty.ValidatorIndex,
AttestationData: attestation.Data,
InclusionDistance: int(inclusionDelay),
}
headCorrect, err := util.AttestationHeadCorrect(ctx, headersCache, attestation)
if err != nil {
return errors.Wrap(err, "failed to calculate if attestation had correct head vote")
}
if headCorrect {
c.summary.Slots[index].Attestations.CorrectHead++
if inclusionDelay == 1 {
c.summary.Slots[index].Attestations.TimelyHead++
} else {
c.summary.UntimelyHeadValidators = append(c.summary.UntimelyHeadValidators, fault)
}
} else {
c.summary.IncorrectHeadValidators = append(c.summary.IncorrectHeadValidators, fault)
if inclusionDelay > 1 {
c.summary.UntimelyHeadValidators = append(c.summary.UntimelyHeadValidators, fault)
}
}
if inclusionDelay <= 5 {
c.summary.Slots[index].Attestations.TimelySource++
} else {
c.summary.UntimelySourceValidators = append(c.summary.UntimelySourceValidators, fault)
}
targetCorrect, err := util.AttestationTargetCorrect(ctx, headersCache, c.chainTime, attestation)
if err != nil {
return errors.Wrap(err, "failed to calculate if attestation had correct target vote")
}
if targetCorrect {
c.summary.Slots[index].Attestations.CorrectTarget++
if inclusionDelay <= 32 {
c.summary.Slots[index].Attestations.TimelyTarget++
} else {
c.summary.UntimelyTargetValidators = append(c.summary.UntimelyTargetValidators, fault)
}
} else {
c.summary.IncorrectTargetValidators = append(c.summary.IncorrectTargetValidators, fault)
if inclusionDelay > 32 {
c.summary.UntimelyTargetValidators = append(c.summary.UntimelyTargetValidators, fault)
}
}
}
}
if len(votes) == len(activeValidatorIndices) {
// Found them all.
break
}
}
return nil
}
// func (c *command) processSyncCommitteeDuties(ctx context.Context) error {
// if c.summary.Epoch < c.chainTime.AltairInitialEpoch() {
// // The epoch is pre-Altair. No info but no error.
// return nil
// }
//
// committee, err := c.syncCommitteesProvider.SyncCommittee(ctx, fmt.Sprintf("%d", c.summary.FirstSlot))
// if err != nil {
// return errors.Wrap(err, "failed to obtain sync committee")
// }
// if len(committee.Validators) == 0 {
// return errors.Wrap(err, "empty sync committee")
// }
//
// missed := make(map[phase0.ValidatorIndex]int)
// for _, index := range committee.Validators {
// missed[index] = 0
// }
//
// for slot := c.summary.FirstSlot; slot <= c.summary.LastSlot; slot++ {
// block, err := c.blocksProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%d", slot))
// if err != nil {
// return errors.Wrap(err, fmt.Sprintf("failed to obtain block for slot %d", slot))
// }
// if block == nil {
// // If the block is missed we don't count the sync aggregate miss.
// continue
// }
// var aggregate *altair.SyncAggregate
// switch block.Version {
// case spec.DataVersionPhase0:
// // No sync committees in this fork.
// return nil
// case spec.DataVersionAltair:
// aggregate = block.Altair.Message.Body.SyncAggregate
// case spec.DataVersionBellatrix:
// aggregate = block.Bellatrix.Message.Body.SyncAggregate
// default:
// return fmt.Errorf("unhandled block version %v", block.Version)
// }
// for i := uint64(0); i < aggregate.SyncCommitteeBits.Len(); i++ {
// if !aggregate.SyncCommitteeBits.BitAt(i) {
// missed[committee.Validators[int(i)]]++
// }
// }
// }
//
// c.summary.SyncCommittee = make([]*epochSyncCommittee, 0, len(missed))
// for index, count := range missed {
// if count > 0 {
// c.summary.SyncCommittee = append(c.summary.SyncCommittee, &epochSyncCommittee{
// Index: index,
// Missed: count,
// })
// }
// }
//
// sort.Slice(c.summary.SyncCommittee, func(i int, j int) bool {
// missedDiff := c.summary.SyncCommittee[i].Missed - c.summary.SyncCommittee[j].Missed
// if missedDiff != 0 {
// // Actually want to order by missed descending, so invert the expected condition.
// return missedDiff > 0
// }
// // Then order by validator index.
// return c.summary.SyncCommittee[i].Index < c.summary.SyncCommittee[j].Index
// })
//
// return nil
// }
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)
if err != nil {
return errors.Wrap(err, "failed to connect to beacon node")
}
c.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(c.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithForkScheduleProvider(c.eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithGenesisTimeProvider(c.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to set up chaintime service")
}
var isProvider bool
c.proposerDutiesProvider, isProvider = c.eth2Client.(eth2client.ProposerDutiesProvider)
if !isProvider {
return errors.New("connection does not provide proposer duties")
}
c.attesterDutiesProvider, isProvider = c.eth2Client.(eth2client.AttesterDutiesProvider)
if !isProvider {
return errors.New("connection does not provide attester duties")
}
c.blocksProvider, isProvider = c.eth2Client.(eth2client.SignedBeaconBlockProvider)
if !isProvider {
return errors.New("connection does not provide signed beacon blocks")
}
c.syncCommitteesProvider, isProvider = c.eth2Client.(eth2client.SyncCommitteesProvider)
if !isProvider {
return errors.New("connection does not provide sync committee duties")
}
c.validatorsProvider, isProvider = c.eth2Client.(eth2client.ValidatorsProvider)
if !isProvider {
return errors.New("connection does not provide validators")
}
c.beaconCommitteesProvider, isProvider = c.eth2Client.(eth2client.BeaconCommitteesProvider)
if !isProvider {
return errors.New("connection does not provide beacon committees")
}
c.beaconBlockHeadersProvider, isProvider = c.eth2Client.(eth2client.BeaconBlockHeadersProvider)
if !isProvider {
return errors.New("connection does not provide beacon block headers")
}
return nil
}

View File

@@ -0,0 +1,62 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
)
func TestProcess(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
tests := []struct {
name string
vars map[string]interface{}
err string
}{
{
name: "InvalidData",
vars: map[string]interface{}{
"timeout": "60s",
"data": "[[",
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
cmd, err := newCommand(context.Background())
require.NoError(t, err)
err = cmd.process(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,50 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 validatorsummary
import (
"context"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// Run runs the command.
func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
c, err := newCommand(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to set up command")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
if err := c.process(ctx); err != nil {
return "", errors.Wrap(err, "failed to process")
}
if viper.GetBool("quiet") {
return "", nil
}
results, err := c.output(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain output")
}
return results, nil
}

View File

@@ -72,9 +72,6 @@ func newCommand(ctx context.Context) (*command, error) {
}
c.timeout = viper.GetDuration("timeout")
if viper.GetString("connection") == "" {
return nil, errors.New("connection is required")
}
c.connection = viper.GetString("connection")
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")

View File

@@ -37,14 +37,6 @@ func TestInput(t *testing.T) {
vars: map[string]interface{}{},
err: "timeout is required",
},
{
name: "ConnectionMissing",
vars: map[string]interface{}{
"validators": "1",
"timeout": "5s",
},
err: "connection is required",
},
{
name: "Good",
vars: map[string]interface{}{

View File

@@ -26,7 +26,7 @@ var validatorCredentialsGetCmd = &cobra.Command{
Short: "Obtain withdrawal credentials for an Ethereum consensus validator",
Long: `Obtain withdrawal credentials for an Ethereum consensus validator. For example:
ethdo validator credentials get --account=primary/validator
ethdo validator credentials get --validator=primary/validator
In quiet mode this will return 0 if the validator exists, otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -47,19 +47,11 @@ In quiet mode this will return 0 if the validator exists, otherwise 1.`,
func init() {
validatorCredentialsCmd.AddCommand(validatorCredentialsGetCmd)
validatorCredentialsFlags(validatorCredentialsGetCmd)
validatorCredentialsGetCmd.Flags().String("account", "", "Account for which to fetch validator credentials")
validatorCredentialsGetCmd.Flags().String("index", "", "Validator index for which to fetch validator credentials")
validatorCredentialsGetCmd.Flags().String("pubkey", "", "Validator public key for which to fetch validator credentials")
validatorCredentialsGetCmd.Flags().String("validator", "", "Validator for which to get validator credentials")
}
func validatorCredentialsGetBindings() {
if err := viper.BindPFlag("account", validatorCredentialsGetCmd.Flags().Lookup("account")); err != nil {
panic(err)
}
if err := viper.BindPFlag("index", validatorCredentialsGetCmd.Flags().Lookup("index")); err != nil {
panic(err)
}
if err := viper.BindPFlag("pubkey", validatorCredentialsGetCmd.Flags().Lookup("pubkey")); err != nil {
if err := viper.BindPFlag("validator", validatorCredentialsGetCmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,92 @@
// Copyright © 2022 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
validatorcredentialsset "github.com/wealdtech/ethdo/cmd/validator/credentials/set"
)
var validatorCredentialsSetCmd = &cobra.Command{
Use: "set",
Short: "Set withdrawal credentials for an Ethereum consensus validator",
Long: `Set withdrawal credentials for an Ethereum consensus validator via a "change credentials" operation. For example:
ethdo validator credentials set --validator=primary/validator --withdrawal-address=0x00...13 --private-key=0x00...1f
The existing account can be specified in one of a number of ways:
- mnemonic using --mnemonic; this will scan the mnemonic and generate all required operations
- mnemonic and path to the validator key using --mnemonic and --path; this will generate a single operation
- private key using --private-key; this will generate a single operation
- account and passphrase using --account and --passphrase; this will generate a single operation
In quiet mode this will return 0 if the credentials operation has been generated (and successfully broadcast if online), otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
res, err := validatorcredentialsset.Run(cmd)
if err != nil {
return err
}
if viper.GetBool("quiet") {
return nil
}
if res != "" {
fmt.Println(res)
}
return nil
},
}
func init() {
validatorCredentialsCmd.AddCommand(validatorCredentialsSetCmd)
validatorCredentialsFlags(validatorCredentialsSetCmd)
validatorCredentialsSetCmd.Flags().Bool("prepare-offline", false, "Create files for offline use")
validatorCredentialsSetCmd.Flags().String("validator", "", "Validator for which to set validator credentials")
validatorCredentialsSetCmd.Flags().String("withdrawal-address", "", "Execution address to which to direct withdrawals")
validatorCredentialsSetCmd.Flags().String("signed-operation", "", "Use pre-defined JSON signed operation as created by --json to transmit the credentials change operation")
validatorCredentialsSetCmd.Flags().Bool("json", false, "Generate JSON data containing a signed operation rather than broadcast it to the network (implied when offline)")
validatorCredentialsSetCmd.Flags().Bool("offline", false, "Do not attempt to connect to a beacon node to obtain information for the operation")
validatorCredentialsSetCmd.Flags().String("fork-version", "", "Fork version to use for signing (offline only)")
validatorCredentialsSetCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (offline only)")
}
func validatorCredentialsSetBindings() {
if err := viper.BindPFlag("prepare-offline", validatorCredentialsSetCmd.Flags().Lookup("prepare-offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validator", validatorCredentialsSetCmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
if err := viper.BindPFlag("signed-operation", validatorCredentialsSetCmd.Flags().Lookup("signed-operation")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawal-address", validatorCredentialsSetCmd.Flags().Lookup("withdrawal-address")); err != nil {
panic(err)
}
if err := viper.BindPFlag("json", validatorCredentialsSetCmd.Flags().Lookup("json")); err != nil {
panic(err)
}
if err := viper.BindPFlag("offline", validatorCredentialsSetCmd.Flags().Lookup("offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("fork-version", validatorCredentialsSetCmd.Flags().Lookup("fork-version")); err != nil {
panic(err)
}
if err := viper.BindPFlag("genesis-validators-root", validatorCredentialsSetCmd.Flags().Lookup("genesis-validators-root")); err != nil {
panic(err)
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2020, 2021 Weald Technology Trading
// Copyright © 2020 - 2022 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -16,7 +16,6 @@ package cmd
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
@@ -32,7 +31,6 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
string2eth "github.com/wealdtech/go-string2eth"
)
@@ -41,7 +39,7 @@ var validatorInfoCmd = &cobra.Command{
Short: "Obtain information about a validator",
Long: `Obtain information about validator. For example:
ethdo validator info --account=primary/validator
ethdo validator info --validator=primary/validator
In quiet mode this will return 0 if the validator information can be obtained, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
@@ -54,33 +52,22 @@ In quiet mode this will return 0 if the validator information can be obtained, o
)
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
account, err := validatorInfoAccount(ctx, eth2Client)
errCheck(err, "Failed to obtain validator account")
pubKeys := make([]spec.BLSPubKey, 1)
pubKey, err := util.BestPublicKey(account)
errCheck(err, "Failed to obtain validator public key")
copy(pubKeys[0][:], pubKey.Marshal())
validators, err := eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", pubKeys)
errCheck(err, "Failed to obtain validator information")
if len(validators) == 0 {
fmt.Println("Validator not known by beacon node")
os.Exit(_exitSuccess)
if viper.GetString("validator") == "" {
fmt.Println("validator is required")
os.Exit(_exitFailure)
}
var validator *api.Validator
for _, v := range validators {
validator = v
}
validator, err := util.ParseValidator(ctx, eth2Client.(eth2client.ValidatorsProvider), viper.GetString("validator"), "head")
errCheck(err, "Failed to obtain validator")
if verbose {
network, err := util.Network(ctx, eth2Client)
errCheck(err, "Failed to obtain network")
outputIf(debug, fmt.Sprintf("Network is %s", network))
pubKey, err := bestPublicKey(account)
pubKey, err := validator.PubKey(ctx)
if err == nil {
deposits, totalDeposited, err := graphData(network, pubKey.Marshal())
if err == nil {
deposits, totalDeposited, err := graphData(network, pubKey[:])
if err == nil && deposits > 0 {
fmt.Printf("Number of deposits: %d\n", deposits)
fmt.Printf("Total deposited: %s\n", string2eth.GWeiToString(uint64(totalDeposited), true))
}
@@ -122,54 +109,6 @@ In quiet mode this will return 0 if the validator information can be obtained, o
},
}
// validatorInfoAccount obtains the account for the validator info command.
func validatorInfoAccount(ctx context.Context, eth2Client eth2client.Service) (e2wtypes.Account, error) {
var account e2wtypes.Account
var err error
switch {
case viper.GetString("account") != "":
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
if err != nil {
return nil, errors.Wrap(err, "failed to obtain account")
}
case viper.GetString("pubkey") != "":
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(viper.GetString("pubkey"), "0x"))
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", viper.GetString("pubkey")))
}
account, err = util.NewScratchAccount(nil, pubKeyBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
}
case viper.GetInt64("index") != -1:
validatorsProvider, isValidatorsProvider := eth2Client.(eth2client.ValidatorsProvider)
if !isValidatorsProvider {
return nil, errors.New("client does not provide validator information")
}
index := spec.ValidatorIndex(viper.GetInt64("index"))
validators, err := validatorsProvider.Validators(ctx, "head", []spec.ValidatorIndex{
index,
})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validator information")
}
if len(validators) == 0 {
return nil, errors.New("unknown validator index")
}
pubKeyBytes := make([]byte, 48)
copy(pubKeyBytes, validators[index].Validator.PublicKey[:])
account, err = util.NewScratchAccount(nil, pubKeyBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
}
default:
return nil, errors.New("neither account nor public key supplied")
}
return account, nil
}
// graphData returns data from the graph about number and amount of deposits
func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error) {
subgraph := ""
@@ -224,16 +163,12 @@ func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error
func init() {
validatorCmd.AddCommand(validatorInfoCmd)
validatorInfoCmd.Flags().String("pubkey", "", "Public key for which to obtain status")
validatorInfoCmd.Flags().Int64("index", -1, "Index for which to obtain status")
validatorInfoCmd.Flags().String("validator", "", "Public key for which to obtain status")
validatorFlags(validatorInfoCmd)
}
func validatorInfoBindings() {
if err := viper.BindPFlag("pubkey", validatorInfoCmd.Flags().Lookup("pubkey")); err != nil {
panic(err)
}
if err := viper.BindPFlag("index", validatorInfoCmd.Flags().Lookup("index")); err != nil {
if err := viper.BindPFlag("validator", validatorInfoCmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
}

View File

@@ -50,7 +50,6 @@ func init() {
validatorCmd.AddCommand(validatorKeycheckCmd)
validatorFlags(validatorKeycheckCmd)
validatorKeycheckCmd.Flags().String("withdrawal-credentials", "", "Withdrawal credentials to check (can run offline)")
validatorKeycheckCmd.Flags().String("mnemonic", "", "Mnemonic from which to generate withdrawal credentials")
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
}
@@ -58,9 +57,6 @@ func validatorKeycheckBindings() {
if err := viper.BindPFlag("withdrawal-credentials", validatorKeycheckCmd.Flags().Lookup("withdrawal-credentials")); err != nil {
panic(err)
}
if err := viper.BindPFlag("mnemonic", validatorKeycheckCmd.Flags().Lookup("mnemonic")); err != nil {
panic(err)
}
if err := viper.BindPFlag("privkey", validatorKeycheckCmd.Flags().Lookup("privkey")); err != nil {
panic(err)
}

66
cmd/validatorsummary.go Normal file
View File

@@ -0,0 +1,66 @@
// Copyright © 2022 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
validatorsummary "github.com/wealdtech/ethdo/cmd/validator/summary"
)
var validatorSummaryCmd = &cobra.Command{
Use: "summary",
Short: "Obtain summary information about validator(s) in an epoch",
Long: `Obtain summary information about one or more validators in an epoch. For example:
ethdo validator summary --validators=1,2,3 --epoch=12345
In quiet mode this will return 0 if information for the epoch is found, otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
res, err := validatorsummary.Run(cmd)
if err != nil {
return err
}
if viper.GetBool("quiet") {
return nil
}
if res != "" {
fmt.Println(res)
}
return nil
},
}
func init() {
validatorCmd.AddCommand(validatorSummaryCmd)
validatorFlags(validatorSummaryCmd)
validatorSummaryCmd.Flags().String("epoch", "", "the epoch for which to obtain information ()")
validatorSummaryCmd.Flags().StringSlice("validators", nil, "the list of validators for which to obtain information")
validatorSummaryCmd.Flags().Bool("json", false, "output data in JSON format")
}
func validatorSummaryBindings() {
validatorBindings()
if err := viper.BindPFlag("epoch", validatorSummaryCmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("validators", validatorSummaryCmd.Flags().Lookup("validators")); err != nil {
panic(err)
}
if err := viper.BindPFlag("json", validatorSummaryCmd.Flags().Lookup("json")); err != nil {
panic(err)
}
}

View File

@@ -24,7 +24,7 @@ import (
// ReleaseVersion is the release version of the codebase.
// Usually overridden by tag names when building binaries.
var ReleaseVersion = "local build (latest release 1.24.0)"
var ReleaseVersion = "local build (latest release 1.26.0)"
// versionCmd represents the version command
var versionCmd = &cobra.Command{

View File

@@ -45,14 +45,10 @@ func init() {
walletCmd.AddCommand(walletCreateCmd)
walletFlags(walletCreateCmd)
walletCreateCmd.Flags().String("type", "non-deterministic", "Type of wallet to create (non-deterministic or hierarchical deterministic)")
walletCreateCmd.Flags().String("mnemonic", "", "The 24-word mnemonic for a hierarchical deterministic wallet")
}
func walletCreateBindings() {
if err := viper.BindPFlag("type", walletCreateCmd.Flags().Lookup("type")); err != nil {
panic(err)
}
if err := viper.BindPFlag("mnemonic", walletCreateCmd.Flags().Lookup("mnemonic")); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,218 @@
# Changing withdrawal credentials
When creating a validator it is possible to set its withdrawal credentials to those based upon a BLS private key (known as BLS withdrawal credentials, or "type 0" withdrawal credentials) or based upon an Ethereum execution address (known as execution withdrawal credentials, or "type 1" withdrawal credentials). With the advent of the Capella hard fork, it is possible for rewards accrued on the consensus chain (also known as the beacon chain) to be sent to the execution chain. However, for this to occur the validator's withdrawal credentials must be type 1. Capella also brings a mechanism to change existing type 0 withdrawal credentials to type 1 withdrawal credentials, and this document outlines the process to change withdrawal credentials from type 0 to type 1 so that consensus rewards can be accessed.
**Once a validator has Ethereum execution credentials set they cannot be changed. Please be careful when following this or any similar process to ensure that you have access to the private key (either as a software file, a hardware key or a mnemonic) of the withdrawal address you use so that you have the ability to access your rewards.**
## Concepts
The following concepts are useful when understanding the rest of this guide.
### Validator
A validator is a logical entity that secures the Ethereum beacon chain (and hence the execution chain) by proposing blocks and attesting to blocks proposed by other validators.
### Withdrawal credentials
Withdrawal credentials, held as part of a validator's on-chain definition, define where consensus rewards will be sent.
### Private key
A private key is a hexadecimal string (_e.g._ 0x010203…a1a2a3) that can be used to generate a public key and (in the case of the execution chain) Ethereum address.
### Mnemonic
A mnemonic is a 24-word phrase that can be used to generate multiple private keys with the use of _paths_.
### Path
A path is a string starting with "m" and containing a number of components separated by "/", for example "m/12381/3600/0/0". The process to obtain a key from a mnemonic and path is known as "hierarchical derivation".
### Withdrawal address
A withdrawal address is an Ethereum execution address that will receive consensus rewards periodically during the operation of the validator and, ultimately, to which the initial deposit will be returned when the validator is exited. It is important to understand that at time of writing this value cannot be changed, so it is critical that one of the following criteria are met:
- the private keys for the Ethereum address are known
- the Ethereum address is secured by a hardware wallet
- the Ethereum address is that of a smart contract with the ability to withdraw funds
The execution address must be supplied in [EIP-55](https://eips.ethereum.org/EIPS/eip-55) format, _i.e._ using mixed case for checksum. An example of a mixed-case Ethereum address is `0x8f0844Fd51E31ff6Bf5baBe21DCcf7328E19Fd9F`
### Online and Offline
An _online_ computer is one that is is connected to the internet. It should be running a consensus node connected to the larger Ethereum network. An online computer is required to carry out the process, to obtain information from the consensus node and to broadcast your actions to the rest of the Ethereum network.
An _offline_ computer is one that is not connected to the internet. As such, it will not be running a consensus node. It can optionally be used in conjunction with an online computer to provide higher levels of security for your mnemonic or private key, but is less convenient because it requires manual transfer of files from the online computer to the offline computer, and back.
With only an online computer the flow of information is roughly as follows:
![Online process](images/credentials-change-online.png)
Here it can be seen that a copy of `ethdo` with access to private keys connects to a consensus node with access to the internet. Due to its connection to the internet it is possible that the computer on which `ethdo` and the consensus node runs has been compromised, and as such would expose the private keys to an attacker.
With both an offline and an online computer the flow of information is roughly as follows:
![Offline process](images/credentials-change-offline.png)
Here the copy of `ethdo` with access to private keys is on an offline computer, which protects it from being compromised via the internet. Data is physically moved from the offline to the online computer via a USB storage key or similar, and none of the information on the online computer is sensitive.
## Preparation
Regardless of the method selected, preparation must take place on the online computer to ensure that `ethdo` can access your consensus node. `ethdo` will attempt to find a local consensus node automatically, but if not then an explicit connection value will be required. To find out if `ethdo` has access to the consensus node run:
```
ethdo node info --verbose
```
The result should be something similar to the following:
```
Version: teku/v22.9.1/linux-x86_64/-privatebuild-openjdk64bitservervm-java-14
Syncing: false
```
It is important to confirm that the "Syncing" value is "false". If this is "true" it means that the node is currently syncing, and you will need to wait for the process to finish before proceeding.
If this command instead returns an error you will need to add an explicit connection string. For example, if your consensus node is serving its REST API on port 12345 then you should add `--connection=http://localhost:12345` to all `ethdo` commands in this process, for example:
```sh
ethdo --connection=http://localhost:12345 node info --verbose
```
Note that some consensus nodes may require configuration to serve their REST API. Please refer to the documentation of your specific consensus node to enable this.
Once the preparation is complete you should select either basic or advanced operation, depending on your requirements.
## Basic operation
Given the above concepts, the purpose of this guide is to allow a change of validators' withdrawal credentials to be changed to a withdrawal address, allowing validator rewards to be accessed on the Ethereum execution chain.
Basic operation is suitable in the majority of cases. If you:
- generated your validators using a mnemonic (_e.g._ using the deposit CLI or launchpad)
- want to change all of your validators to have the same withdrawal address
- want to change all of your validators' withdrawal credentials at the same time
then this method is for you. If any of the above does not apply then please go to the "Advanced operation" section.
### Online process
The online process generates and broadcasts the operations to change withdrawal credentials for all of your validators tied to a mnemonic in a single action.
Two pieces of information are required for carrying out this process online: the mnemonic and withdrawal address.
On your _online_ computer run the following:
```
ethdo validator credentials set --mnemonic="abandon abandon abandon … art" --withdrawal-address=0x0123…cdef
```
Replacing the `mnemonic` and `withdrawal-address` values with your own values. This command will:
1. obtain information from your consensus node about all currently-running validators and various additional information required to generate the operations
2. scan your mnemonic to find any validators that were generated by it, and create the operations to change their credentials
3. broadcast the credentials change operations to the Ethereum network
### Online and Offline process
The online and offline process contains three steps. In the first, data is gathered on the online computer. In the second, the credentials change operations are generated on the offline computer. In the third, the operations are broadcast on the online computer.
Two pieces of information are required for carrying out this process online: the mnemonic and withdrawal address.
On your _online_ computer run the following:
```
ethdo validator credentials set --prepare-offline
```
This command will:
1. obtain information from your consensus node about all currently-running validators and various additional information required to generate the operations
2. write this information to a file called `offline-preparation.json`
The `offline-preparation.json` file must be copied to your _offline_ computer. Once this has been done, on your _offline_ computer run the following:
```
ethdo validator credentials set --offline --mnemonic="abandon abandon abandon … art" --withdrawal-address=0x0123…cdef
```
Replacing the `mnemonic` and `withdrawal-address` values with your own values. This command will:
1. read the `offline-preparation.json` file to obtain information about all currently-running validators and various additional information required to generate the operations
2. scan your mnemonic to find any validators that were generated by it, and create the operations to change their credentials
3. write this information to a file called `change-operations.json`
The `change-operations.json` file must be copied to your _online_ computer. Once this has been done, on your _online_ computer run the following:
```
ethdo validator credentials set
```
This command will:
1. read the `change-operations.json` file to obtain the operations to change the validators' credentials
2. broadcast the credentials change operations to the Ethereum network
## Advanced operation
Advanced operation is required when any of the following conditions are met:
- your validators were created using something other than the deposit CLI or launchpad (_e.g._ `ethdo`)
- you want to set your validators to have different withdrawal addresses
- you want to change your validators' withdrawal credentials individually
### Validator reference
There are three options to reference a validator:
- the `ethdo` account of the validator (in format wallet/account)
- the validator's public key (in format 0x…)
- the validator's on-chain index (in format 123…)
Any of these can be passed to the following commands with the `--validator` parameter. You need to ensure that you have this information before starting the process.
**In the following examples we will use the validator with index 123. Please replace this with the reference to your validator in all commands.**
### Withdrawal address
The withdrawal address is defined above in the concepts section.
**In the following examples we will use a withdrawal address of 0x8f…9F. Please replace this with the your withdrawal address in all commands.**
### Generating credentials change operations
Note that if you are carrying out this process offline then you still need to carry out the first and third steps outlined in the "Basic operation" section above. This is to ensure that the offline computer has the correct information to generate the operations, and that the operations are made available to the online computer for broadcasting to the network.
If using the online and offline process run the commands below on the offline computer, and add the `--offline` flag to the commands below. You will need to copy the resultant `change-operations.json` file to the online computer to broadcast to the network.
If using the online process run the commands below on the online computer. The operation will be broadcast to the network automatically.
#### Using a mnemonic and path.
A mnemonic is a 24-word phrase from which withdrawal and validator keys are derived using a _path_. Commonly, keys will have been generated using two paths:
- m/12381/3600/_i_/0 is the path to a withdrawal key, where _i_ starts at 0 for the first validator, 1 for the second validator, _etc._
- m/12381/3600/_i_/0/0 is the path to a validator key, where _i_ starts at 0 for the first validator, 1 for the second validator, _etc._
however this is only a standard and not a restriction, and it is possible for users to have created validators using paths of their own choice.
```
ethdo validator credentials set --validator=123 --mnemonic="abandon abandon abandon … art" --path='m/12381/3600/0/0/0' --withdrawal-address=0x0123…cdef
```
replacing the path with the path to your _withdrawal_ key, and all other parameters with your own values.
#### Using a private key
If you have the private key from which the current withdrawal credentials were derived this can be used to generate and broadcast the credentials change operation with the following command:
```
ethdo validator credentials set --validator=123 --withdrawal-address=0x8f…9F --private-key=0x3b…9c
```
replacing the parameters with your own values.
#### Using an account
If you used `ethdo` to generate your validator deposit data you will likely have used a separate account to generate the withdrawal credentials. You can specify the account to generate and broadcast the credentials change operation with the following command:
```
ethdo validator credentials set --validator=123 --withdrawal-address=0x8f…9F --account=Wallet/Account --passphrase=secret
```
replacing the parameters with your own values.
## Confirming the process has succeeded
The final step is confirming the operation has taken place. To do so, run the following command on an online server:
```sh
ethdo validator credentials get --validator=123
```
The result should start with the phrase "Ethereum execution address" and display the execution address you chose at the beginning of the process, for example:
```
Ethereum execution address: 0x8f0844Fd51E31ff6Bf5baBe21DCcf7328E19Fd9F
```
If the result starts with the phrase "BLS credentials" then it may be that the operation has yet to be incorporated on the chain, please wait a few minutes and check again. If this continues to be the case please obtain help to understand why the change operation failed to work.

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

923
docs/images/diagrams.svg Normal file
View File

@@ -0,0 +1,923 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: MerkleTree Pages: 1 -->
<svg
width="1200"
height="500"
version="1.1"
id="svg4363"
sodipodi:docname="diagrams.svg"
inkscape:export-filename="/home/jgm/src/go/wealdtech/ethdo/docs/images/credentials-change-online.png"
inkscape:export-xdpi="180"
inkscape:export-ydpi="180"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata4369">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4367">
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker4440"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4438" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker4436"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4434" />
</marker>
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4412"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path4410"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4408"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4406"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4318"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path4316"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="marker4314"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4312"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker4120"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4118" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker4016"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4014" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker4012"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4010" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3717"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3715" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3713"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3711" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3637"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3635" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3633"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3631" />
</marker>
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker3617"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path3615"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="marker3613"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path3611"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker3607"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path3605"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="marker3603"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path3601"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3453"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3451" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3353"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3351" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3349"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3347" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3035"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3033" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker3027"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3025" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3001"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path2999" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible"
id="marker2997"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path2995" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2565"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path2563"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(0,0)" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker2555"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path2553"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(0,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker2532"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
id="path2530" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker15035"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
id="path15033" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker14901"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
id="path14899" />
</marker>
<marker
inkscape:stockid="Arrow2Mend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker12697"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path12695"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
transform="scale(0.6) rotate(180) translate(0,0)" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker5569"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow2Mend">
<path
transform="scale(0.6) rotate(180) translate(0,0)"
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#d0d0d0;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
id="path5567" />
</marker>
<marker
inkscape:isstock="true"
style="overflow:visible;"
id="marker3116"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lend">
<path
transform="scale(0.8) rotate(180) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path3114" />
</marker>
<marker
inkscape:stockid="Arrow1Lend"
orient="auto"
refY="0.0"
refX="0.0"
id="marker3102"
style="overflow:visible;"
inkscape:isstock="true">
<path
id="path2842"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#d0d0d0;stroke-width:1pt;stroke-opacity:1;fill:#d0d0d0;fill-opacity:1"
transform="scale(0.8) rotate(180) translate(12.5,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Lstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path1073"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1140"
id="namedview4365"
showgrid="false"
inkscape:zoom="0.82"
inkscape:cx="600"
inkscape:cy="249.39024"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer9"
units="px"
inkscape:pagecheckerboard="0" />
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="Base withdrawal credentials change"
style="display:inline">
<g
id="g3352"
transform="translate(330.851,-33.97924)"
style="display:inline">
<path
id="path3344"
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g3350"
transform="translate(117.34562,2.27034)">
<path
id="path3346"
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
id="path3348"
inkscape:connector-curvature="0"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
</g>
</g>
<g
id="g10761"
transform="matrix(0.19161542,0,0,0.19161542,-2.1459431,201.48294)"
style="display:inline">
<path
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke-width:0.95"
d="m 224,2 c -67.165,0 -121.59961,54.434608 -121.59961,121.59961 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,0.33436 0.009,0.66628 0.0117,1 -0.003,0.33372 -0.0117,0.66564 -0.0117,1 0,67.165 54.43461,121.59961 121.59961,121.59961 67.165,0 121.59961,-54.43461 121.59961,-121.59961 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 0,-0.33436 -0.009,-0.66628 -0.0117,-1 0.003,-0.33372 0.0117,-0.66564 0.0117,-1 C 345.59961,56.434608 291.165,2 224,2 Z M 132.99023,276.16992 C 65.255238,279.39992 11.199219,334.7843 11.199219,403.2793 v 18 21.52148 2 2 2 2 2 2 2 2 2 c 0,25.175 20.426562,45.59961 45.601562,45.59961 H 391.19922 c 25.175,0 45.60156,-20.42461 45.60156,-45.59961 v -2 -2 -2 -2 -2 -2 -2 -2 -2 -21.52148 -18 c 0,-68.495 -54.05601,-123.87938 -121.79101,-127.10938 L 269.59961,458 243.5,347.06641 h -39 L 178.40039,458 Z"
id="path7306" />
<path
d="m 224,243.2 c 67.165,0 121.6,-54.435 121.6,-121.6 C 345.6,54.435 291.165,0 224,0 156.835,0 102.4,54.435 102.4,121.6 c 0,67.165 54.435,121.6 121.6,121.6 z M 315.01,274.17 269.6,456 239.2,326.8 269.6,273.6 H 178.4 L 208.8,326.8 178.4,456 132.99,274.17 C 65.255,277.4 11.2,332.785 11.2,401.28 v 39.52 c 0,25.175 20.425,45.6 45.6,45.6 h 334.4 c 25.175,0 45.6,-20.425 45.6,-45.6 v -39.52 c 0,-68.495 -54.055,-123.88 -121.79,-127.11 z"
id="path1534"
inkscape:connector-curvature="0"
style="fill:#d0d0d0;fill-opacity:1;stroke-width:0.95" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 224.11719,272.89258 c -30.84856,-1e-5 -61.38197,0.44231 -91.30664,1.25 v 0.0391 c 0.0601,-0.003 0.11956,-0.009 0.17968,-0.0117 L 178.40039,456 208.80078,326.80078 178.40039,273.59961 h 91.19922 L 239.19922,326.80078 269.59961,456 315.00977,274.16992 c 0.16788,0.008 0.33423,0.0206 0.50195,0.0293 v -0.0547 c -29.94323,-0.80978 -60.50517,-1.25196 -91.39453,-1.25195 z"
id="path10733"
inkscape:connector-curvature="0" />
</g>
<g
id="g4284"
transform="translate(608.49706,-233.4392)"
style="display:inline">
<path
id="path4276"
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g4282"
transform="translate(117.34562,2.27034)">
<path
id="path4278"
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
id="path4280"
inkscape:connector-curvature="0"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
</g>
</g>
<g
id="g4294"
transform="translate(608.49706,-33.97924)"
style="display:inline">
<path
id="path4286"
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g4292"
transform="translate(117.34562,2.27034)">
<path
id="path4288"
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
id="path4290"
inkscape:connector-curvature="0"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
</g>
</g>
<g
id="g4304"
transform="translate(608.49706,165.48072)"
style="display:inline">
<path
id="path4296"
d="m 542.8631,233.6392 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g4302"
transform="translate(117.34562,2.27034)">
<path
id="path4298"
d="m 425.51748,231.16886 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -48.63984,-48.63984 z m -26.54023,24.08008 h 3.35625 c 1.14095,0 1.86236,1.20829 1.32656,2.21719 -1.57129,2.9583 -4.71309,11.01741 0.016,19.88281 0.53769,1.00985 -0.1835,2.21992 -1.3254,2.21992 h -3.30976 c -0.58805,0 -1.13899,-0.33312 -1.38789,-0.86797 -2.23915,-4.80035 -2.53555,-8.94055 -2.53555,-11.2918 0,-4.02325 0.89283,-7.83393 2.45938,-11.27578 0.24699,-0.5434 0.80474,-0.88437 1.40039,-0.88437 z m 49.71171,0 h 3.36485 c 0.59565,0 1.15222,0.34036 1.39922,0.88281 1.5694,3.44185 2.46445,7.25314 2.46445,11.27734 0,4.0242 -0.89567,7.8355 -2.46602,11.27735 -0.247,0.54245 -0.80395,0.88242 -1.3996,0.88242 h -3.34688 c -1.15045,0 -1.86488,-1.21977 -1.32148,-2.23438 4.5733,-8.53194 1.70908,-16.61901 -0.0266,-19.87656 -0.5358,-1.00605 0.19393,-2.20898 1.33203,-2.20898 z m -39.23203,6.08008 h 3.2043 c 1.04595,0 1.76892,1.02886 1.41172,2.01211 -0.46835,1.2901 -0.71719,2.65816 -0.71719,4.06796 0,1.4098 0.24884,2.77787 0.71719,4.06797 0.35625,0.98325 -0.36577,2.01211 -1.41172,2.01211 h -3.2043 c -0.66595,0 -1.27898,-0.42767 -1.46328,-1.06797 -0.4655,-1.61215 -0.71601,-3.29071 -0.71601,-5.01211 0,-1.7214 0.25068,-3.40018 0.71523,-5.01328 0.18525,-0.6403 0.79811,-1.06679 1.46406,-1.06679 z m 16.06055,0 c 3.35635,0 6.07617,2.72182 6.07617,6.08007 0,1.27395 -0.39431,2.4537 -1.06406,3.43125 l 12.39766,29.77383 c 0.32205,0.77425 -0.0437,1.6645 -0.81797,1.9875 l -2.80352,1.16953 c -0.77425,0.323 -1.66333,-0.0428 -1.98633,-0.81796 l -4.67422,-11.22422 h -14.25664 l -4.67304,11.22422 c -0.32205,0.77424 -1.21208,1.14096 -1.98633,0.81796 l -2.80352,-1.16953 c -0.77425,-0.323 -1.14097,-1.21325 -0.81797,-1.9875 l 12.39766,-29.77383 c -0.66975,-0.97755 -1.06406,-2.1573 -1.06406,-3.43125 0,-3.35825 2.72077,-6.08007 6.07617,-6.08007 z m 12.85625,0 h 3.2043 c 0.66595,0 1.27898,0.42671 1.46328,1.06796 0.4655,1.61215 0.71601,3.29071 0.71601,5.01211 0,1.7214 -0.24973,3.39996 -0.71523,5.01211 -0.18525,0.64125 -0.79716,1.06797 -1.46406,1.06797 h -3.2043 c -1.04595,0 -1.76892,-1.02886 -1.41172,-2.01211 0.46835,-1.2901 0.71719,-2.65817 0.71719,-4.06797 0,-1.4098 -0.24884,-2.77786 -0.71719,-4.06796 -0.35625,-0.98325 0.36577,-2.01211 1.41172,-2.01211 z m -12.85625,13.28203 -4.59609,11.03789 h 9.19218 z"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
d="m 409.45678,275.48901 h 3.20435 c 1.04595,0 1.76795,-1.02885 1.4117,-2.0121 -0.46835,-1.2901 -0.71725,-2.6581 -0.71725,-4.0679 0,-1.4098 0.2489,-2.7778 0.71725,-4.0679 0.3572,-0.98325 -0.36575,-2.0121 -1.4117,-2.0121 h -3.20435 c -0.66595,0 -1.2787,0.42655 -1.46395,1.06685 -0.46455,1.6131 -0.71535,3.29175 -0.71535,5.01315 0,1.7214 0.2508,3.40005 0.7163,5.0122 0.1843,0.6403 0.79705,1.0678 1.463,1.0678 z m -5.7969,-16.0227 c 0.5358,-1.0089 -0.18525,-2.2173 -1.3262,-2.2173 h -3.35635 c -0.59565,0 -1.1533,0.34105 -1.4003,0.88445 -1.56655,3.44185 -2.45955,7.2523 -2.45955,11.27555 0,2.35125 0.2964,6.49135 2.53555,11.2917 0.2489,0.53485 0.7999,0.8683 1.38795,0.8683 h 3.3098 c 1.1419,0 1.86295,-1.2103 1.32525,-2.22015 -4.7291,-8.8654 -1.58745,-16.92425 -0.0162,-19.88255 z m 49.7933,-1.33475 c -0.247,-0.54245 -0.8037,-0.88255 -1.39935,-0.88255 h -3.3649 c -1.1381,0 -1.8677,1.2027 -1.3319,2.20875 1.73565,3.25755 4.5999,11.3449 0.0266,19.87685 -0.5434,1.0146 0.171,2.2344 1.32145,2.2344 h 3.34685 c 0.59565,0 1.15235,-0.3401 1.39935,-0.88255 1.57035,-3.44185 2.4662,-7.25325 2.4662,-11.27745 0,-4.0242 -0.8949,-7.8356 -2.4643,-11.27745 z m -11.875,5.19745 h -3.20435 c -1.04595,0 -1.76795,1.02885 -1.4117,2.0121 0.46835,1.2901 0.71725,2.6581 0.71725,4.0679 0,1.4098 -0.2489,2.7778 -0.71725,4.0679 -0.3572,0.98325 0.36575,2.0121 1.4117,2.0121 h 3.20435 c 0.6669,0 1.2787,-0.42655 1.46395,-1.0678 0.4655,-1.61215 0.71535,-3.2908 0.71535,-5.0122 0,-1.7214 -0.2508,-3.40005 -0.7163,-5.0122 -0.1843,-0.64125 -0.79705,-1.0678 -1.463,-1.0678 z m -11.0485,9.5114 c 0.66975,-0.97755 1.064,-2.15745 1.064,-3.4314 0,-3.35825 -2.71985,-6.08 -6.0762,-6.08 -3.3554,0 -6.0762,2.72175 -6.0762,6.08 0,1.27395 0.39425,2.45385 1.064,3.4314 l -12.3975,29.77395 c -0.323,0.77425 0.0437,1.6644 0.81795,1.9874 l 2.80345,1.16945 c 0.77425,0.323 1.6644,-0.0437 1.98645,-0.81795 l 4.67305,-11.22425 h 14.25665 l 4.674,11.22425 c 0.323,0.7752 1.2122,1.14095 1.98645,0.81795 l 2.80345,-1.16945 c 0.77425,-0.323 1.14,-1.21315 0.81795,-1.9874 z m -9.6083,14.8086 4.5961,-11.03805 4.5961,11.03805 z"
id="path4300"
inkscape:connector-curvature="0"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:0.095" />
</g>
</g>
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 657.63833,252.24403 H 804.3783"
id="path6146"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 935.28443,252.24403 H 1082.0244"
id="path6247"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 936.44494,192.24403 1083.1849,97.693151"
id="path6249"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 936.65042,312.24403 1083.3904,421.52638"
id="path6251"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="873.41541"
y="317.23297"
id="text8140"><tspan
sodipodi:role="line"
id="tspan8138"
x="873.41541"
y="317.23297"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">Local</tspan><tspan
sodipodi:role="line"
x="873.41541"
y="340.56635"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan11236">consensus node</tspan></text>
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="1151.2808"
y="145.29068"
id="text11322"><tspan
sodipodi:role="line"
x="1151.2808"
y="145.29068"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan11320">Consensus</tspan><tspan
sodipodi:role="line"
x="1151.2808"
y="168.62405"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan12530">network</tspan></text>
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="1151.2808"
y="344.75064"
id="text12616"><tspan
sodipodi:role="line"
x="1151.2808"
y="344.75064"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan12612">Consensus</tspan><tspan
sodipodi:role="line"
x="1151.2808"
y="368.08401"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan12614">network</tspan></text>
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="40.075897"
y="321.42487"
id="text12622"><tspan
sodipodi:role="line"
x="40.075897"
y="321.42487"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan14902">User</tspan></text>
<g
id="g27545"
transform="translate(2.7996094e-5,147.10741)"
style="display:inline">
<path
id="path27529"
d="m 596.06796,54.89213 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
r="48.639999"
cy="103.13213"
cx="596.06799"
id="circle27531"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g27537"
transform="translate(-165.65975,5.478358)">
<path
d="m 780.23799,97.443192 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.056218 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.027298 a 2.8922269,2.8922269 0 0 0 -4.88787,2.082398 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.660838 z"
fill="#000000"
id="path27533"
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
<path
d="m 780.23799,66.409593 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370005 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370005 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
fill="#000000"
id="path27535"
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
</g>
<g
id="g27543">
<path
d="m 614.57824,100.92155 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.05622 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.0824 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.66084 z"
fill="#000000"
id="path27539"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
<path
d="m 614.57824,69.887949 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370009 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370009 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
fill="#000000"
id="path27541"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
</g>
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer9"
inkscape:label="Online withdrawal credentials change"
style="display:inline">
<path
style="fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 104.72769,252.24403 H 524.35081"
id="path5578" />
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="596.06799"
y="321.42487"
id="text11108"><tspan
sodipodi:role="line"
id="tspan11106"
x="596.06799"
y="321.42487"
style="text-align:center;text-anchor:middle;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
sodipodi:role="line"
x="596.06799"
y="344.75824"
style="text-align:center;text-anchor:middle;fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan11132">(with keys)</tspan></text>
<text
xml:space="preserve"
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="599.86401"
y="26.809605"
id="text27911"><tspan
sodipodi:role="line"
x="599.86401"
y="26.809605"
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan27909">Online</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer8"
inkscape:label="Offline withdrawal credentials change"
style="display:none">
<g
id="g27447"
transform="translate(-277.64603,147.10741)">
<path
id="path27431"
d="m 596.06796,54.89213 a 48.64,48.64 0 0 0 -48.63984,48.63984 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 0.008,0.23438 48.64,48.64 0 0 0 -0.008,0.16562 48.64,48.64 0 0 0 48.63984,48.64024 48.64,48.64 0 0 0 48.63984,-48.64024 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -0.008,-0.23437 48.64,48.64 0 0 0 0.008,-0.16563 48.64,48.64 0 0 0 -48.63984,-48.63984 z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
r="48.639999"
cy="103.13213"
cx="596.06799"
id="circle27433"
style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:0.19;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g27439"
transform="translate(-165.65975,5.478358)">
<path
d="m 780.23799,97.443192 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.056218 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.027298 a 2.8922269,2.8922269 0 0 0 -4.88787,2.082398 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.660838 z"
fill="#000000"
id="path27435"
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
<path
d="m 780.23799,66.409593 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370005 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370005 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
fill="#000000"
id="path27437"
style="fill:#000000;fill-opacity:1;stroke-width:2.89223" />
</g>
<g
id="g27445">
<path
d="m 614.57824,100.92155 a 2.8922269,2.8922269 0 0 0 -3.15252,0.54952 l -14.92389,14.05622 a 0.69413446,0.69413446 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.0824 v 13.767 a 2.8922269,2.8922269 0 0 0 0.89659,2.11133 l 17.35337,16.37 a 2.8922269,2.8922269 0 0 0 3.99127,0 l 17.35336,-16.37 a 2.8922269,2.8922269 0 0 0 0.89659,-2.11133 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.66084 z"
fill="#000000"
id="path27441"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
<path
d="m 614.57824,69.887949 a 2.8922269,2.8922269 0 0 0 -3.15252,0.520601 l -14.92389,14.056222 a 0.72305672,0.72305672 0 0 1 -0.98336,0 l -14.8082,-14.0273 a 2.8922269,2.8922269 0 0 0 -4.88787,2.111325 v 13.767001 a 2.8922269,2.8922269 0 0 0 0.89659,2.082403 l 17.35337,16.370009 a 2.8922269,2.8922269 0 0 0 1.99563,0.80982 2.8922269,2.8922269 0 0 0 1.99564,-0.80982 l 17.35336,-16.370009 a 2.8922269,2.8922269 0 0 0 0.89659,-2.111326 v -13.767 a 2.8922269,2.8922269 0 0 0 -1.73534,-2.631926 z"
fill="#000000"
id="path27443"
style="fill:#b0b0b0;fill-opacity:1;stroke-width:2.89223" />
</g>
</g>
<path
style="fill:none;stroke:#b0b0b0;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:8, 16;stroke-dashoffset:0;stroke-opacity:1"
d="M 452.22833,1.446294 V 498.3684"
id="path4358" />
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 102.34622,252.24403 H 249.08619"
id="path6402"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#d0d0d0;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 4;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker4440)"
d="M 379.99228,252.24403 H 526.73225"
id="path6404"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="596.06799"
y="321.42487"
id="text15698"><tspan
sodipodi:role="line"
id="tspan15694"
x="596.06799"
y="321.42487"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
sodipodi:role="line"
x="596.06799"
y="344.75824"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan15696">(without keys)</tspan></text>
<text
xml:space="preserve"
style="font-size:18.6667px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="318.42194"
y="321.42487"
id="text15707"><tspan
sodipodi:role="line"
id="tspan15703"
x="318.42194"
y="321.42487"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1">ethdo</tspan><tspan
sodipodi:role="line"
x="318.42194"
y="344.75824"
style="text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan15705">(with keys)</tspan></text>
<text
xml:space="preserve"
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="246.36612"
y="26.809605"
id="text16335"><tspan
sodipodi:role="line"
x="246.36612"
y="26.809605"
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan16333">Offline</tspan></text>
<text
xml:space="preserve"
style="font-size:32px;line-height:1.25;font-family:Lato;-inkscape-font-specification:Lato;display:inline;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
x="801.6582"
y="26.809605"
id="text18181"><tspan
sodipodi:role="line"
x="801.6582"
y="26.809605"
style="font-size:32px;text-align:center;text-anchor:middle;fill:#b0b0b0;fill-opacity:1;stroke:none;stroke-opacity:1"
id="tspan18179">Online</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -453,23 +453,25 @@ Epoch commands focus on information about a beacon chain epoch.
```sh
$ ethdo epoch summary
Epoch 1406:
Slot 44992 (0/32):
Proposer: 31501
Proposed: ✓
Slot 44993 (1/32):
Proposer: 9302
Proposed: ✓
...
Sync committee validator 71248:
Chances: 29
Included: 7
Inclusion %: 24.14
Sync committee validator 87371:
Chances: 29
Included: 0
Inclusion %: 0.00
...
Epoch 380:
Proposals: 31/32 (96.88%)
Attestations: 1530/1572 (97.33%)
Sync committees: 13086/15872 (82.45%)
```
More detailed information can be obtained with the `--verbose` flag:
```sh
$ ethdo epoch summary --verbose
Epoch 380:
Proposals: 31/32 (96.88%)
Slot 12188 (28/32) validator 1518 not proposed or not included
Attestations: 1530/1572 (97.33%)
Slot 12160 committee 0 validator 292 failed to participate
Slot 12162 committee 0 validator 204 failed to participate
Slot 12163 committee 0 validator 297 failed to participate
Slot 12164 committee 0 validator 209 failed to participate
...
```
### `exit` comands
@@ -579,12 +581,18 @@ Validator commands focus on interaction with Ethereum 2 validators.
#### `credentials get`
`ethdo validator credentials get` provides information about the withdrawal credentials for the provided validator. Options include:
- `account` the account for which to obtain the withdrawal credentials (in format "wallet/account")
- `pubkey` the public key of the validator for which to obtain the withdrawal credentials
- `index` the index of the validator for which to obtain the withdrawal credentials
- `validator` the account, public key or index for which to obtain the withdrawal credentials
```sh
$ ethdo validator credentials get --account=Validators/1
$ ethdo validator credentials get --validator=Validators/1
```
#### `credentials set`
`ethdo validator credentials set` updates withdrawal credentials from BLS "type 0" credentials to execution "type 1" credentials. Full information about using this command can be found in the [specific documentation](./changingwithdrawalcredentials.md).
```sh
$ ethdo validator credentials set --validator=Validators/1 --execution-address=0x8f…9F --private-key=0x3b…9c
```
#### `depositdata`
@@ -620,7 +628,7 @@ $ ethdo validator exit --key=0x01e748d098d3bcb477d636f19d510399ae18205fadf9814ee
`ethdo validator info` provides information for a given validator.
```sh
$ ethdo validator info --account=Validators/1
$ ethdo validator info --validator=Validators/1
Status: Active
Balance: 3.203823585 Ether
Effective balance: 3.1 Ether
@@ -639,7 +647,7 @@ Effective balance: 3.1 Ether
Withdrawal credentials: 0x0033ef3cb10b36d0771ffe8a02bc5bfc7e64ea2f398ce77e25bb78989edbee36
```
If the validator is not an account it can be queried directly with `--pubkey`.
If the validator is not an account then `--validator` option can be supplied with a validator index or public key.
```sh
$ ethdo validator info --pubkey=0x842dd66cfeaeff4397fc7c94f7350d2131ca0c4ad14ff727963be9a1edb4526604970df6010c3da6474a9820fa81642b
@@ -709,6 +717,32 @@ $ ethdo validator yield
Yield: 4.64%
```
#### `summary`
`ethdo validator summary` provides a summary of the given epoch for the given validators. Options include:
- `epoch`: the epoch for which to provide a summary; defaults to last complete epoch
- `validators`: the list of validators for which to provide a summary
- `json`: provide JSON output
### `proposer` commands
Proposer commands focus on Ethereum 2 validators' actions as proposers.
#### `duties`
`ethdo proposer duties` provides information on the proposal duties for a given epoch. Options include:
- `epoch` the epoch in which to obtain the duties (defaults to current epoch)
- `json` obtain detailed information in JSON format
```sh
$ ethdo proposer duties --epoch=5
Epoch 5:
Slot 160: validator 8221
Slot 161: validator 11193
Slot 162: validator 4116
Slot 163: validator 631
...
```
## Maintainers
Jim McDonald: [@mcdee](https://github.com/mcdee).

39
go.mod
View File

@@ -3,17 +3,18 @@ module github.com/wealdtech/ethdo
go 1.16
require (
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/attestantio/dirk v1.1.0
github.com/attestantio/go-eth2-client v0.11.0
github.com/aws/aws-sdk-go v1.42.44 // indirect
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b
github.com/attestantio/go-eth2-client v0.14.0
github.com/aws/aws-sdk-go v1.44.111 // indirect
github.com/ferranbt/fastssz v0.1.2
github.com/gofrs/uuid v4.2.0+incompatible
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
github.com/hashicorp/hcl v1.0.1-vault-3 // indirect
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7
github.com/jackc/puddle v1.2.1 // indirect
github.com/herumi/bls-eth-go-binary v1.28.1
github.com/jackc/puddle v1.3.0 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
@@ -21,22 +22,22 @@ require (
github.com/protolambda/zssz v0.1.5 // indirect
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
github.com/rs/zerolog v1.26.1
github.com/rs/zerolog v1.28.0
github.com/shopspring/decimal v1.3.1
github.com/spf13/afero v1.8.0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.8.0
github.com/tyler-smith/go-bip39 v1.1.0
github.com/wealdtech/go-bytesutil v1.1.1
github.com/wealdtech/go-ecodec v1.1.2
github.com/wealdtech/go-eth2-types/v2 v2.6.0
github.com/wealdtech/go-eth2-types/v2 v2.7.0
github.com/wealdtech/go-eth2-util v1.7.0
github.com/wealdtech/go-eth2-wallet v1.15.0
github.com/wealdtech/go-eth2-wallet-dirk v1.2.0
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.6.0
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.4.0
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.17.0
@@ -44,9 +45,11 @@ require (
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.0
github.com/wealdtech/go-eth2-wallet-types/v2 v2.9.0
github.com/wealdtech/go-string2eth v1.2.0
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed // indirect
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.7
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
google.golang.org/grpc v1.44.0
gopkg.in/ini.v1 v1.66.3 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect
)

383
go.sum
View File

@@ -29,35 +29,156 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
cloud.google.com/go/secretmanager v1.0.0/go.mod h1:+Qkm5qxIJ5mk74xxIXA+87fseaY1JLYBcFPQoc/GQxg=
cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/HdrHistogram/hdrhistogram-go v1.1.1/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -75,16 +196,15 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/attestantio/dirk v1.1.0 h1:hwMTYZkwj/Y0um3OD0LQxg2xSl4/5xqVWV2MRePE4ec=
github.com/attestantio/dirk v1.1.0/go.mod h1:2jkOw/XHjvIDdhDcmj+Z3kuVPpxMcQ6zxzzjSSv71PY=
github.com/attestantio/go-eth2-client v0.8.1/go.mod h1:kEK9iAAOBoADO5wEkd84FEOzjT1zXgVWveQsqn+uBGg=
github.com/attestantio/go-eth2-client v0.11.0 h1:8/Jn5AAfd+4tOggLi+FvOv9/ORaObECv42ab7vK2FJc=
github.com/attestantio/go-eth2-client v0.11.0/go.mod h1:zXL/BxC0cBBhxj+tP7QG7t9Ufoa8GwQLdlbvZRd9+dM=
github.com/attestantio/go-eth2-client v0.14.0 h1:usWgI0KIfGWQ8G7XUsMM2bTe+yuJEh4d3HEO1vPD5Go=
github.com/attestantio/go-eth2-client v0.14.0/go.mod h1:bcg5gfjVcm+MtcaZfzv/uSWNHU4i8hGamVG+9JCZnC0=
github.com/aws/aws-sdk-go v1.33.17/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.40.41/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.41.19/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.42.44 h1:vPlF4cUsdN5ETfvb7ewZFbFZyB6Rsfndt3kS2XqLXKo=
github.com/aws/aws-sdk-go v1.42.44/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
github.com/aws/aws-sdk-go v1.44.111 h1:AcWfOgeedSQ4gQVwcIe6aLxpQNJMloZQyqnr7Dzki+s=
github.com/aws/aws-sdk-go v1.44.111/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -116,6 +236,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -143,6 +264,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.0/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -154,12 +276,16 @@ github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iu
github.com/ferranbt/fastssz v0.0.0-20210526181520-7df50c8568f8/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
github.com/ferranbt/fastssz v0.0.0-20211031100431-9823ca9021f1/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b h1:Jea4sHxe4sTegJgpfhWvxSjFF2nyq4/R/qWm6AziPiI=
github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b/go.mod h1:S8yiDeAXy8f88W4Ul+0dBMPx49S05byYbmZD6Uv94K4=
github.com/ferranbt/fastssz v0.1.1/go.mod h1:U2ZsxlYyvGeQGmadhz8PlEqwkBzDIhHwd3xuKrg2JIs=
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -188,7 +314,6 @@ github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZg
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -241,8 +366,11 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -269,13 +397,21 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
@@ -290,6 +426,7 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -304,9 +441,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl v1.0.1-vault-3 h1:V95v5KSTu6DB5huDSKiq4uAfILEuNigK/+qPET6H/Mg=
github.com/hashicorp/hcl v1.0.1-vault-3/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
@@ -314,20 +450,21 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/herumi/bls-eth-go-binary v0.0.0-20210520070601-31246bfa8ac4/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/herumi/bls-eth-go-binary v0.0.0-20210902234237-7763804ee078/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/herumi/bls-eth-go-binary v0.0.0-20210917013441-d37c07cfda4e/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/herumi/bls-eth-go-binary v0.0.0-20211117070716-2bdbdadbf8bb/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7 h1:gMN4oOdFLVR7ye4SCacHD4SIB64NPUNE+V9w8o7bR6A=
github.com/herumi/bls-eth-go-binary v0.0.0-20220103074059-01b0ca9e9ef7/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/herumi/bls-eth-go-binary v1.28.1 h1:fcIZ48y5EE9973k05XjE8+P3YiQgjZz4JI/YabAm8KA=
github.com/herumi/bls-eth-go-binary v1.28.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/puddle v1.1.4/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -348,20 +485,23 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A=
github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -370,24 +510,26 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@@ -406,8 +548,9 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -425,8 +568,11 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -442,25 +588,22 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
@@ -471,24 +614,30 @@ github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4=
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388 h1:4bD+ujqGfY4zoDUF3q9MhdmpPXzdp03DYUIlXeQ72kk=
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388/go.mod h1:VecIJZrewdAuhVckySLFt2wAAHRME934bSDurP8ftkc=
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk=
github.com/r3labs/sse/v2 v2.3.0/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
github.com/r3labs/sse/v2 v2.7.4 h1:pvCMswPDlXd/ZUFx1dry0LbXJNHXwWPulLcUGYwClc0=
github.com/r3labs/sse/v2 v2.7.4/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
@@ -496,19 +645,19 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
@@ -521,21 +670,26 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
@@ -552,8 +706,9 @@ github.com/wealdtech/go-ecodec v1.1.2 h1:CcNBnFV6b8IoDtu8wj6cfrmuBRBw00VYqsvpT4+
github.com/wealdtech/go-ecodec v1.1.2/go.mod h1:0Rgn8mEr0GX4fDyuU3aUL+xp4n17Wviscasp6rWCh10=
github.com/wealdtech/go-eth2-types/v2 v2.5.5/go.mod h1:QY9t7nwxc26C9/iZHh1wzc+hZbGGn7ipgjMYPhKnN/0=
github.com/wealdtech/go-eth2-types/v2 v2.5.6/go.mod h1:SX7+QxnhXGxeW8tESzJ/BSIXvoflBHUi21chqRsZ9wE=
github.com/wealdtech/go-eth2-types/v2 v2.6.0 h1:djgMv1A40bstgkg6L1ZA7eowR/Gbmj1ZWnBdrK39lhY=
github.com/wealdtech/go-eth2-types/v2 v2.6.0/go.mod h1:psOez/ZRBzZSDl5hiNDwRf5ZqQujNE6h5FxAz09Koxg=
github.com/wealdtech/go-eth2-types/v2 v2.7.0 h1:TV2jrNkane2nxTiVhyG3npVtg825WvRdCZZ4j+jTENA=
github.com/wealdtech/go-eth2-types/v2 v2.7.0/go.mod h1:aQ5dk+KNPE/A2r3lLhKpW+f5B24aJO68tRz6wO4Zo/g=
github.com/wealdtech/go-eth2-util v1.7.0 h1:bgCnZ4H5K5VE+CiAwFTKhrICItq5rI+5VYaODFWC6is=
github.com/wealdtech/go-eth2-util v1.7.0/go.mod h1:L0q3G7S1Dm1WJPye5m2wjuNweXC98o1CmLTKPmBTlpg=
github.com/wealdtech/go-eth2-wallet v1.15.0 h1:yff2rWCvHyr5bOU41wwilsNemFRYOFinZoIPjhzG10M=
@@ -563,8 +718,9 @@ github.com/wealdtech/go-eth2-wallet-dirk v1.2.0/go.mod h1:1lnHDSdcRxj9CFJh+jPtdg
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4 h1:EBk/FofiQWtbRYmwdCp0qM9TPDvlh1LN2yjlLr4W3ao=
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4/go.mod h1:Y31pDxcdyADwfQl65t8V6KhcmCes31EXkXZPDcT2SIk=
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.6/go.mod h1:hIeHsLJWVxFUUqQqaBm9nmUW3Y/eHneQclYyNayUeQg=
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0 h1:HgIK30YsXxgR6Ra6pvxW1KFUPbx7BpIIK1VGhOlmCm4=
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.2.0/go.mod h1:qqIU42c9sXcNYsiEjUQoOOWYZfZDL1zmyLtz3t+wN2s=
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0 h1:3Kx2QvKU/4PP0d7+e3+q9G+QiXQprNQPeAXsWOcfKRI=
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0/go.mod h1:qqIU42c9sXcNYsiEjUQoOOWYZfZDL1zmyLtz3t+wN2s=
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.1 h1:x6bq8cVgRgfhwtSQSYo/9AqJ8qEeaS6af28cW0cVj5U=
github.com/wealdtech/go-eth2-wallet-encryptor-unencrypted v1.0.1/go.mod h1:49K88T/4LNQpB8ghVcjTKeRRi/bZHeYjN8Ef5S23yps=
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.6.0 h1:Oy5TsD6HqZzb3MwGOe+5WVCj4pCKKUYYc2OKyN42QCM=
@@ -586,8 +742,6 @@ github.com/wealdtech/go-eth2-wallet-types/v2 v2.9.0/go.mod h1:7Ad2xp27vOQRQWQsIe
github.com/wealdtech/go-indexer v1.0.0 h1:/S4rfWQbSOnnYmwnvuTVatDibZ8o1s9bmTCHO16XINg=
github.com/wealdtech/go-indexer v1.0.0/go.mod h1:u1cjsbsOXsm5jzJDyLmZY7GsrdX8KYXKBXkZcAmk3Zg=
github.com/wealdtech/go-majordomo v1.0.1/go.mod h1:QoT4S1nUQwdQK19+CfepDwV+Yr7cc3dbF+6JFdQnIqY=
github.com/wealdtech/go-string2eth v1.1.0 h1:USJQmysUrBYYmZs7d45pMb90hRSyEwizP7lZaOZLDAw=
github.com/wealdtech/go-string2eth v1.1.0/go.mod h1:RUzsLjJtbZaJ/3UKn9kY19a/vCCUHtEWoUW3uiK6yGU=
github.com/wealdtech/go-string2eth v1.2.0 h1:C0E5p78tecZTsGccJc9r/kreFah4EfDs5uUPnS6XXMs=
github.com/wealdtech/go-string2eth v1.2.0/go.mod h1:RUzsLjJtbZaJ/3UKn9kY19a/vCCUHtEWoUW3uiK6yGU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -599,10 +753,14 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -634,8 +792,9 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA=
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -728,9 +887,18 @@ golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -748,6 +916,13 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -758,8 +933,11 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -844,8 +1022,23 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -924,8 +1117,12 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
@@ -964,6 +1161,22 @@ google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnn
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1016,6 +1229,7 @@ google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210406143921-e86de6bf7a46/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
@@ -1046,8 +1260,46 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 h1:YxHp5zqIcAShDEvRr5/0rVESVS+njYF68PSdazrNLJo=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 h1:Ezh2cpcnP5Rq60sLensUsFnxh7P6513NLvNtCm9iyJ4=
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1077,9 +1329,14 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -1093,8 +1350,10 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
@@ -1106,8 +1365,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w=
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1119,8 +1378,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1134,3 +1394,4 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -54,4 +54,6 @@ type Service interface {
AltairInitialEpoch() phase0.Epoch
// AltairInitialSyncCommitteePeriod provides the sync committee period in which the Altair hard fork takes place.
AltairInitialSyncCommitteePeriod() uint64
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
CapellaInitialEpoch() phase0.Epoch
}

View File

@@ -33,6 +33,7 @@ type Service struct {
epochsPerSyncCommitteePeriod uint64
altairForkEpoch phase0.Epoch
bellatrixForkEpoch phase0.Epoch
capellaForkEpoch phase0.Epoch
}
// module-wide log.
@@ -100,6 +101,13 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
}
log.Trace().Uint64("epoch", uint64(bellatrixForkEpoch)).Msg("Obtained Bellatrix fork epoch")
capellaForkEpoch, err := fetchCapellaForkEpoch(ctx, parameters.forkScheduleProvider)
if err != nil {
// Set to far future epoch.
capellaForkEpoch = 0xffffffffffffffff
}
log.Trace().Uint64("epoch", uint64(capellaForkEpoch)).Msg("Obtained Capella fork epoch")
s := &Service{
genesisTime: genesisTime,
slotDuration: slotDuration,
@@ -107,6 +115,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod,
altairForkEpoch: altairForkEpoch,
bellatrixForkEpoch: bellatrixForkEpoch,
capellaForkEpoch: capellaForkEpoch,
}
return s, nil
@@ -247,3 +256,28 @@ func fetchBellatrixForkEpoch(ctx context.Context, provider eth2client.ForkSchedu
}
return 0, errors.New("no bellatrix fork obtained")
}
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
func (s *Service) CapellaInitialEpoch() phase0.Epoch {
return s.capellaForkEpoch
}
func fetchCapellaForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
forkSchedule, err := provider.ForkSchedule(ctx)
if err != nil {
return 0, err
}
count := 0
for i := range forkSchedule {
count++
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
// This is the genesis fork; ignore it.
continue
}
if count == 2 {
return forkSchedule[i].Epoch, nil
}
count++
}
return 0, errors.New("no capella fork obtained")
}

View File

@@ -1,11 +1,11 @@
package shamir
import (
"crypto/rand"
cryptorand "crypto/rand"
"crypto/subtle"
"encoding/binary"
"fmt"
mathrand "math/rand"
"time"
)
const (
@@ -20,6 +20,20 @@ type polynomial struct {
coefficients []uint8
}
type cryptoSource struct{}
func (s cryptoSource) Int63() int64 {
bytes := make([]byte, 8)
if _, err := cryptorand.Read(bytes); err != nil {
panic(err)
}
return int64(binary.BigEndian.Uint64(bytes) >> 1)
}
func (s cryptoSource) Seed(seed int64) {
panic("seed")
}
// makePolynomial constructs a random polynomial of the given
// degree but with the provided intercept value.
func makePolynomial(intercept, degree uint8) (polynomial, error) {
@@ -32,7 +46,7 @@ func makePolynomial(intercept, degree uint8) (polynomial, error) {
p.coefficients[0] = intercept
// Assign random co-efficients to the polynomial
if _, err := rand.Read(p.coefficients[1:]); err != nil {
if _, err := cryptorand.Read(p.coefficients[1:]); err != nil {
return p, err
}
@@ -143,8 +157,8 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
}
// Generate random list of x coordinates
mathrand.Seed(time.Now().UnixNano())
xCoordinates := mathrand.Perm(255)
rnd := mathrand.New(&cryptoSource{})
xCoordinates := rnd.Perm(255)
// Allocate the output array, initialize the final byte
// of the output with the offset. The representation of each

View File

@@ -1,4 +1,6 @@
// Code generated by fastssz. DO NOT EDIT.
// Hash: c953c4b72fdc3f250f4227b181e286fe53fd663c5aa4f5cc3de6cc9998c7fdb7
// Version: 0.1.2
package signing
import (
@@ -15,15 +17,15 @@ func (c *Container) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
// Field (0) 'Root'
if len(c.Root) != 32 {
err = ssz.ErrBytesLength
if size := len(c.Root); size != 32 {
err = ssz.ErrBytesLengthFn("Container.Root", size, 32)
return
}
dst = append(dst, c.Root...)
// Field (1) 'Domain'
if len(c.Domain) != 32 {
err = ssz.ErrBytesLength
if size := len(c.Domain); size != 32 {
err = ssz.ErrBytesLengthFn("Container.Domain", size, 32)
return
}
dst = append(dst, c.Domain...)
@@ -66,19 +68,19 @@ func (c *Container) HashTreeRoot() ([32]byte, error) {
}
// HashTreeRootWith ssz hashes the Container object with a hasher
func (c *Container) HashTreeRootWith(hh *ssz.Hasher) (err error) {
func (c *Container) HashTreeRootWith(hh ssz.HashWalker) (err error) {
indx := hh.Index()
// Field (0) 'Root'
if len(c.Root) != 32 {
err = ssz.ErrBytesLength
if size := len(c.Root); size != 32 {
err = ssz.ErrBytesLengthFn("Container.Root", size, 32)
return
}
hh.PutBytes(c.Root)
// Field (1) 'Domain'
if len(c.Domain) != 32 {
err = ssz.ErrBytesLength
if size := len(c.Domain); size != 32 {
err = ssz.ErrBytesLengthFn("Container.Domain", size, 32)
return
}
hh.PutBytes(c.Domain)
@@ -86,3 +88,8 @@ func (c *Container) HashTreeRootWith(hh *ssz.Hasher) (err error) {
hh.Merkleize(indx)
return
}
// GetTree ssz hashes the Container object
func (c *Container) GetTree() (*ssz.Node, error) {
return ssz.ProofTree(c)
}

View File

@@ -15,11 +15,126 @@ package util
import (
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
util "github.com/wealdtech/go-eth2-util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// ParseAccount parses input to obtain an account.
func ParseAccount(ctx context.Context,
accountStr string,
supplementary []string,
unlock bool,
) (
e2wtypes.Account,
error,
) {
if accountStr == "" {
return nil, errors.New("no account specified")
}
var account e2wtypes.Account
var err error
switch {
case strings.HasPrefix(accountStr, "0x"):
// A key. Could be public or private.
data, err := hex.DecodeString(strings.TrimPrefix(accountStr, "0x"))
if err != nil {
return nil, errors.Wrap(err, "failed to parse account key")
}
switch len(data) {
case 48:
// Public key.
account, err = newScratchAccountFromPubKey(data)
if err != nil {
return nil, errors.Wrap(err, "failed to create account from public key")
}
if unlock {
return nil, errors.New("cannot unlock an account specified by its public key")
}
case 32:
// Private key.
account, err = newScratchAccountFromPrivKey(data)
if err != nil {
return nil, errors.Wrap(err, "failed to create account from public key")
}
if unlock {
_, err = UnlockAccount(ctx, account, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to unlock account")
}
}
default:
return nil, fmt.Errorf("key of length %d neither public nor private key", len(data))
}
case strings.Contains(accountStr, "/"):
// An account.
_, account, err = WalletAndAccountFromPath(ctx, accountStr)
if err != nil {
return nil, errors.Wrap(err, "unable to obtain account")
}
if unlock {
// Supplementary will be the unlock passphrase(s).
_, err = UnlockAccount(ctx, account, supplementary)
if err != nil {
return nil, errors.Wrap(err, "failed to unlock account")
}
}
case strings.Contains(accountStr, " "):
// A mnemonic.
// Supplementary will be the path.
if len(supplementary) == 0 {
return nil, errors.New("missing derivation path")
}
account, err = accountFromMnemonicAndPath(accountStr, supplementary[0])
if err != nil {
return nil, err
}
if unlock {
err = account.(e2wtypes.AccountLocker).Unlock(ctx, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to unlock account")
}
}
default:
return nil, fmt.Errorf("unknown account specifier %s", accountStr)
}
return account, nil
}
func accountFromMnemonicAndPath(mnemonic string, path string) (e2wtypes.Account, error) {
seed, err := SeedFromMnemonic(mnemonic)
if err != nil {
return nil, err
}
// Ensure the path is valid.
match := hdPathRegex.Match([]byte(path))
if !match {
return nil, errors.New("path does not match expected format m/…")
}
// Derive private key from seed and path.
key, err := util.PrivateKeyFromSeedAndPath(seed, path)
if err != nil {
return nil, errors.Wrap(err, "failed to generate key")
}
// Create a scratch account given the private key.
account, err := newScratchAccountFromPrivKey(key.Marshal())
if err != nil {
return nil, errors.Wrap(err, "failed to generate scratch account")
}
return account, nil
}
// UnlockAccount attempts to unlock an account. It returns true if the account was already unlocked.
func UnlockAccount(ctx context.Context, account e2wtypes.Account, passphrases []string) (bool, error) {
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
@@ -46,6 +161,13 @@ func UnlockAccount(ctx context.Context, account e2wtypes.Account, passphrases []
}
}
// Also attempt to unlock without any passphrase.
err = locker.Unlock(ctx, nil)
if err == nil {
// Unlocked.
return false, nil
}
// Failed to unlock it.
return false, errors.New("failed to unlock account")
}

103
util/account_test.go Normal file
View File

@@ -0,0 +1,103 @@
// Copyright © 2020 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 util_test
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
func TestParseAccount(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
tests := []struct {
name string
accountStr string
supplementary []string
unlock bool
err string
expectedPubkey string
expectedUnlocked bool
}{
{
name: "Zero",
err: "no account specified",
},
{
name: "Bad",
accountStr: "bad",
err: "unknown account specifier bad",
},
{
name: "PublicKey",
accountStr: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
},
{
name: "PublicKeyUnlocked",
accountStr: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
unlock: true,
err: "cannot unlock an account specified by its public key",
},
{
name: "PrivateKey",
accountStr: "0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55",
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
},
{
name: "PrivateKeyUnlocked",
accountStr: "0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55",
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
unlock: true,
expectedUnlocked: true,
},
{
name: "Mnemonic",
accountStr: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
supplementary: []string{"m/12381/3600/0/0"},
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
},
{
name: "MnemonicUnlocked",
accountStr: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
supplementary: []string{"m/12381/3600/0/0"},
expectedPubkey: "0x99b1f1d84d76185466d86c34bde1101316afddae76217aa86cd066979b19858c2c9d9e56eebc1e067ac54277a61790db",
unlock: true,
expectedUnlocked: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
account, err := util.ParseAccount(ctx, test.accountStr, test.supplementary, test.unlock)
if test.err == "" {
require.NoError(t, err)
require.NotNil(t, account)
unlocked, err := account.(e2wtypes.AccountLocker).IsUnlocked(ctx)
require.NoError(t, err)
require.Equal(t, test.expectedPubkey, fmt.Sprintf("%#x", account.PublicKey().Marshal()))
require.Equal(t, test.expectedUnlocked, unlocked)
} else {
require.EqualError(t, err, test.err)
}
})
}
}

80
util/attestations.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 util
import (
"bytes"
"context"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/wealdtech/ethdo/services/chaintime"
)
// AttestationHeadCorrect returns true if the given attestation had the correct head.
func AttestationHeadCorrect(ctx context.Context,
headersCache *BeaconBlockHeaderCache,
attestation *phase0.Attestation,
) (
bool,
error,
) {
slot := attestation.Data.Slot
for {
header, err := headersCache.Fetch(ctx, slot)
if err != nil {
return false, nil
}
if header == nil {
// No block.
slot--
continue
}
if !header.Canonical {
// Not canonical.
slot--
continue
}
return bytes.Equal(header.Root[:], attestation.Data.BeaconBlockRoot[:]), nil
}
}
// AttestationTargetCorrect returns true if the given attestation had the correct target.
func AttestationTargetCorrect(ctx context.Context,
headersCache *BeaconBlockHeaderCache,
chainTime chaintime.Service,
attestation *phase0.Attestation,
) (
bool,
error,
) {
// Start with first slot of the target epoch.
slot := chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch)
for {
header, err := headersCache.Fetch(ctx, slot)
if err != nil {
return false, nil
}
if header == nil {
// No block.
slot--
continue
}
if !header.Canonical {
// Not canonical.
slot--
continue
}
return bytes.Equal(header.Root[:], attestation.Data.Target.Root[:]), nil
}
}

70
util/beaconheadercache.go Normal file
View File

@@ -0,0 +1,70 @@
// Copyright © 2022 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 util
import (
"context"
"fmt"
eth2client "github.com/attestantio/go-eth2-client"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
)
// BeaconBlockHeaderCache is a cache of beacon block headers.
type BeaconBlockHeaderCache struct {
beaconBlockHeadersProvider eth2client.BeaconBlockHeadersProvider
entries map[phase0.Slot]*beaconBlockHeaderEntry
}
// NewBeaconBlockHeaderCache makes a new beacon block header cache.
func NewBeaconBlockHeaderCache(provider eth2client.BeaconBlockHeadersProvider) *BeaconBlockHeaderCache {
return &BeaconBlockHeaderCache{
beaconBlockHeadersProvider: provider,
entries: make(map[phase0.Slot]*beaconBlockHeaderEntry),
}
}
type beaconBlockHeaderEntry struct {
present bool
value *apiv1.BeaconBlockHeader
}
// Fetch the beacon block header for the given slot.
func (b *BeaconBlockHeaderCache) Fetch(ctx context.Context,
slot phase0.Slot,
) (
*apiv1.BeaconBlockHeader,
error,
) {
entry, exists := b.entries[slot]
if !exists {
header, err := b.beaconBlockHeadersProvider.BeaconBlockHeader(ctx, fmt.Sprintf("%d", slot))
if err != nil {
return nil, err
}
if header == nil {
entry = &beaconBlockHeaderEntry{
present: false,
}
} else {
entry = &beaconBlockHeaderEntry{
present: true,
value: header,
}
}
b.entries[slot] = entry
}
return entry.value, nil
}

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