Compare commits

...

5 Commits

Author SHA1 Message Date
Jim McDonald
4e2fa63f30 Bump version. 2023-01-17 13:49:01 +00:00
Jim McDonald
ca5eec9c8c Update code for spec change. 2023-01-13 22:34:04 +00:00
Jim McDonald
582930d982 Verify operations on load. 2023-01-02 22:04:07 +00:00
Jim McDonald
5a572abc6f Tidy-ups. 2023-01-02 21:32:14 +00:00
Jim McDonald
a2d018c321 Update dependencies. 2022-12-28 19:39:16 +00:00
45 changed files with 2592 additions and 1590 deletions

View File

@@ -1,6 +1,7 @@
dev:
1.27.0:
- use new build system
- support S3 credentials
- update operation of validator exit to match validator credentials set
1.26.5:
- provide validator information in "chain status" verbose output

300
beacon/chaininfo.go Normal file
View File

@@ -0,0 +1,300 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beacon
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/services/chaintime"
"github.com/wealdtech/ethdo/util"
)
type ChainInfo struct {
Version uint64
Validators []*ValidatorInfo
GenesisValidatorsRoot phase0.Root
Epoch phase0.Epoch
GenesisForkVersion phase0.Version
CurrentForkVersion phase0.Version
BLSToExecutionChangeDomainType phase0.DomainType
VoluntaryExitDomainType phase0.DomainType
}
type chainInfoJSON struct {
Version string `json:"version"`
Validators []*ValidatorInfo `json:"validators"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
Epoch string `json:"epoch"`
GenesisForkVersion string `json:"genesis_fork_version"`
CurrentForkVersion string `json:"current_fork_version"`
BLSToExecutionChangeDomainType string `json:"bls_to_execution_change_domain_type"`
VoluntaryExitDomainType string `json:"voluntary_exit_domain_type"`
}
type chainInfoVersionJSON struct {
Version string `json:"version"`
}
// MarshalJSON implements json.Marshaler.
func (c *ChainInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&chainInfoJSON{
Version: fmt.Sprintf("%d", c.Version),
Validators: c.Validators,
GenesisValidatorsRoot: fmt.Sprintf("%#x", c.GenesisValidatorsRoot),
Epoch: fmt.Sprintf("%d", c.Epoch),
GenesisForkVersion: fmt.Sprintf("%#x", c.GenesisForkVersion),
CurrentForkVersion: fmt.Sprintf("%#x", c.CurrentForkVersion),
BLSToExecutionChangeDomainType: fmt.Sprintf("%#x", c.BLSToExecutionChangeDomainType),
VoluntaryExitDomainType: fmt.Sprintf("%#x", c.VoluntaryExitDomainType),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (c *ChainInfo) UnmarshalJSON(input []byte) error {
// See which version we are dealing with.
var metadata chainInfoVersionJSON
if err := json.Unmarshal(input, &metadata); err != nil {
return errors.Wrap(err, "invalid JSON")
}
if metadata.Version == "" {
return errors.New("version missing")
}
version, err := strconv.ParseUint(metadata.Version, 10, 64)
if err != nil {
return errors.Wrap(err, "version invalid")
}
if version < 2 {
return errors.New("outdated version; please regenerate your offline data")
}
c.Version = version
var data chainInfoJSON
if err := json.Unmarshal(input, &data); err != nil {
return errors.Wrap(err, "invalid JSON")
}
if len(data.Validators) == 0 {
return errors.New("validators missing")
}
c.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(c.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")
}
c.Epoch = phase0.Epoch(epoch)
if data.GenesisForkVersion == "" {
return errors.New("genesis fork version missing")
}
genesisForkVersionBytes, err := hex.DecodeString(strings.TrimPrefix(data.GenesisForkVersion, "0x"))
if err != nil {
return errors.Wrap(err, "genesis fork version invalid")
}
if len(genesisForkVersionBytes) != phase0.ForkVersionLength {
return errors.New("genesis fork version incorrect length")
}
copy(c.GenesisForkVersion[:], genesisForkVersionBytes)
if data.CurrentForkVersion == "" {
return errors.New("current fork version missing")
}
currentForkVersionBytes, err := hex.DecodeString(strings.TrimPrefix(data.CurrentForkVersion, "0x"))
if err != nil {
return errors.Wrap(err, "current fork version invalid")
}
if len(currentForkVersionBytes) != phase0.ForkVersionLength {
return errors.New("current fork version incorrect length")
}
copy(c.CurrentForkVersion[:], currentForkVersionBytes)
if data.BLSToExecutionChangeDomainType == "" {
return errors.New("bls to execution domain type missing")
}
blsToExecutionChangeDomainType, err := hex.DecodeString(strings.TrimPrefix(data.BLSToExecutionChangeDomainType, "0x"))
if err != nil {
return errors.Wrap(err, "bls to execution domain type invalid")
}
if len(blsToExecutionChangeDomainType) != phase0.DomainTypeLength {
return errors.New("bls to execution domain type incorrect length")
}
copy(c.BLSToExecutionChangeDomainType[:], blsToExecutionChangeDomainType)
if data.VoluntaryExitDomainType == "" {
return errors.New("voluntary exit domain type missing")
}
voluntaryExitDomainType, err := hex.DecodeString(strings.TrimPrefix(data.VoluntaryExitDomainType, "0x"))
if err != nil {
return errors.Wrap(err, "voluntary exit domain type invalid")
}
if len(voluntaryExitDomainType) != phase0.DomainTypeLength {
return errors.New("voluntary exit domain type incorrect length")
}
copy(c.VoluntaryExitDomainType[:], voluntaryExitDomainType)
return nil
}
// FetchValidatorInfo fetches validator info given a validator identifier.
func (c *ChainInfo) FetchValidatorInfo(ctx context.Context, id string) (*ValidatorInfo, error) {
var validatorInfo *ValidatorInfo
switch {
case id == "":
return nil, errors.New("no validator specified")
case strings.HasPrefix(id, "0x"):
// A public key.
for _, validator := range c.Validators {
if strings.EqualFold(id, fmt.Sprintf("%#x", validator.Pubkey)) {
validatorInfo = validator
break
}
}
case strings.Contains(id, "/"):
// An account.
_, account, err := util.WalletAndAccountFromPath(ctx, id)
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.Validators {
if strings.EqualFold(pubkey, fmt.Sprintf("%#x", validator.Pubkey)) {
validatorInfo = validator
break
}
}
default:
// An index.
index, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse validator index")
}
validatorIndex := phase0.ValidatorIndex(index)
for _, validator := range c.Validators {
if validator.Index == validatorIndex {
validatorInfo = validator
break
}
}
}
if validatorInfo == nil {
return nil, errors.New("unknown validator")
}
return validatorInfo, nil
}
// ObtainChainInfoFromNode obtains the chain information from a node.
func ObtainChainInfoFromNode(ctx context.Context,
consensusClient consensusclient.Service,
chainTime chaintime.Service,
) (
*ChainInfo,
error,
) {
res := &ChainInfo{
Version: 2,
Validators: make([]*ValidatorInfo, 0),
Epoch: chainTime.CurrentEpoch(),
}
// Obtain validators.
validators, err := consensusClient.(consensusclient.ValidatorsProvider).Validators(ctx, "head", nil)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validators")
}
for _, validator := range validators {
res.Validators = append(res.Validators, &ValidatorInfo{
Index: validator.Index,
Pubkey: validator.Validator.PublicKey,
WithdrawalCredentials: validator.Validator.WithdrawalCredentials,
State: validator.Status,
})
}
// Genesis validators root obtained from beacon node.
genesis, err := consensusClient.(consensusclient.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain genesis information")
}
res.GenesisValidatorsRoot = genesis.GenesisValidatorsRoot
// Fetch the genesis fork version from the specification.
spec, err := consensusClient.(consensusclient.SpecProvider).Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := spec["GENESIS_FORK_VERSION"]
if !exists {
return nil, errors.New("capella fork version not known by chain")
}
var isForkVersion bool
res.GenesisForkVersion, isForkVersion = tmp.(phase0.Version)
if !isForkVersion {
return nil, errors.New("could not obtain GENESIS_FORK_VERSION")
}
// Fetch the current fork version from the fork schedule.
forkSchedule, err := consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain fork schedule")
}
for i := range forkSchedule {
if forkSchedule[i].Epoch <= res.Epoch {
res.CurrentForkVersion = forkSchedule[i].CurrentVersion
}
}
blsToExecutionChangeDomainType, exists := spec["DOMAIN_BLS_TO_EXECUTION_CHANGE"].(phase0.DomainType)
if !exists {
return nil, errors.New("failed to obtain DOMAIN_BLS_TO_EXECUTION_CHANGE")
}
copy(res.BLSToExecutionChangeDomainType[:], blsToExecutionChangeDomainType[:])
voluntaryExitDomainType, exists := spec["DOMAIN_VOLUNTARY_EXIT"].(phase0.DomainType)
if !exists {
return nil, errors.New("failed to obtain DOMAIN_VOLUNTARY_EXIT")
}
copy(res.VoluntaryExitDomainType[:], voluntaryExitDomainType[:])
return res, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorcredentialsset
package beacon
import (
"encoding/hex"
@@ -20,33 +20,37 @@ import (
"strconv"
"strings"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
type validatorInfo struct {
type ValidatorInfo struct {
Index phase0.ValidatorIndex
Pubkey phase0.BLSPubKey
State apiv1.ValidatorState
WithdrawalCredentials []byte
}
type validatorInfoJSON struct {
Index string `json:"index"`
Pubkey string `json:"pubkey"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
Index string `json:"index"`
Pubkey string `json:"pubkey"`
State apiv1.ValidatorState `json:"state"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
}
// MarshalJSON implements json.Marshaler.
func (v *validatorInfo) MarshalJSON() ([]byte, error) {
func (v *ValidatorInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(&validatorInfoJSON{
Index: fmt.Sprintf("%d", v.Index),
Pubkey: fmt.Sprintf("%#x", v.Pubkey),
State: v.State,
WithdrawalCredentials: fmt.Sprintf("%#x", v.WithdrawalCredentials),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *validatorInfo) UnmarshalJSON(input []byte) error {
func (v *ValidatorInfo) UnmarshalJSON(input []byte) error {
var data validatorInfoJSON
if err := json.Unmarshal(input, &data); err != nil {
return errors.Wrap(err, "invalid JSON")
@@ -73,6 +77,11 @@ func (v *validatorInfo) UnmarshalJSON(input []byte) error {
}
copy(v.Pubkey[:], pubkey)
if data.State == apiv1.ValidatorStateUnknown {
return errors.New("state unknown")
}
v.State = data.State
if data.WithdrawalCredentials == "" {
return errors.New("withdrawal credentials missing")
}
@@ -88,7 +97,7 @@ func (v *validatorInfo) UnmarshalJSON(input []byte) error {
}
// String implements the Stringer interface.
func (v *validatorInfo) String() string {
func (v *ValidatorInfo) String() string {
data, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("Err: %v\n", err)

View File

@@ -35,7 +35,6 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
standardchaintime.WithForkScheduleProvider(data.eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
)
if err != nil {

View File

@@ -266,7 +266,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -121,7 +121,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -65,7 +65,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -46,7 +46,6 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
chainTime, err := standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithForkScheduleProvider(eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
)
errCheck(err, "Failed to configure chaintime service")

View File

@@ -335,7 +335,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -53,7 +53,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -342,7 +342,12 @@ func walletFromPath(ctx context.Context, path string) (e2wtypes.Wallet, error) {
return nil, errors.Wrap(err, "failed to parse remote servers")
}
return dirk.OpenWallet(ctx, walletName, credentials, endpoints)
return dirk.Open(ctx,
dirk.WithName(walletName),
dirk.WithCredentials(credentials),
dirk.WithEndpoints(endpoints),
dirk.WithTimeout(viper.GetDuration("timeout")),
)
}
wallet, err := e2wallet.OpenWallet(walletName)
if err != nil {

View File

@@ -1,4 +1,4 @@
// Copyright © 2017-2020 Weald Technology Trading
// Copyright © 2017-2023 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -24,6 +24,7 @@ import (
"github.com/wealdtech/ethdo/util"
"github.com/wealdtech/go-bytesutil"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// signatureSignCmd represents the signature sign command
@@ -52,14 +53,20 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
}
outputIf(debug, fmt.Sprintf("Domain is %#x", domain))
assert(viper.GetString("account") != "", "--account is required")
_, account, err := walletAndAccountFromInput(ctx)
var account e2wtypes.Account
switch {
case viper.GetString("account") != "":
account, err = util.ParseAccount(ctx, viper.GetString("account"), util.GetPassphrases(), true)
case viper.GetString("private-key") != "":
account, err = util.ParseAccount(ctx, viper.GetString("private-key"), nil, true)
}
errCheck(err, "Failed to obtain account")
var specDomain spec.Domain
copy(specDomain[:], domain)
var fixedSizeData [32]byte
copy(fixedSizeData[:], data)
fmt.Printf("Signing %#x with domain %#x by public key %#x\n", fixedSizeData, specDomain, account.PublicKey().Marshal())
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
errCheck(err, "Failed to sign")

View File

@@ -1,4 +1,4 @@
// Copyright © 2017-2020 Weald Technology Trading
// Copyright © 2017-2023 Weald Technology Trading
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,13 +15,10 @@ package cmd
import (
"context"
"encoding/hex"
"fmt"
"os"
"strings"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
@@ -43,6 +40,9 @@ var signatureVerifyCmd = &cobra.Command{
In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
defer cancel()
assert(viper.GetString("signature-data") != "", "--data is required")
data, err := bytesutil.FromHexString(viper.GetString("signature-data"))
errCheck(err, "Failed to parse data")
@@ -61,7 +61,15 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
assert(len(domain) == 32, "Domain data invalid")
}
account, err := signatureVerifyAccount()
var account e2wtypes.Account
switch {
case viper.GetString("account") != "":
account, err = util.ParseAccount(ctx, viper.GetString("account"), nil, false)
case viper.GetString("private-key") != "":
account, err = util.ParseAccount(ctx, viper.GetString("private-key"), nil, false)
case viper.GetString("public-key") != "":
account, err = util.ParseAccount(ctx, viper.GetString("public-key"), nil, false)
}
errCheck(err, "Failed to obtain account")
outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
@@ -78,29 +86,6 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
},
}
// signatureVerifyAccount obtains the account for the signature verify command.
func signatureVerifyAccount() (e2wtypes.Account, error) {
var account e2wtypes.Account
var err error
if 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")
}
} else {
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(signatureVerifySigner, "0x"))
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", signatureVerifySigner))
}
account, err = util.NewScratchAccount(nil, pubKeyBytes)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", signatureVerifySigner))
}
}
return account, nil
}
func init() {
signatureCmd.AddCommand(signatureVerifyCmd)
signatureFlags(signatureVerifyCmd)

View File

@@ -114,7 +114,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -59,7 +59,6 @@ func input(ctx context.Context) (*dataIn, error) {
// Chain time.
data.chainTime, err = standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithForkScheduleProvider(data.eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
)
if err != nil {

View File

@@ -37,7 +37,6 @@ func TestProcess(t *testing.T) {
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
standardchaintime.WithForkScheduleProvider(eth2Client.(eth2client.ForkScheduleProvider)),
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
)
require.NoError(t, err)

View File

@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2022, 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -14,114 +14,92 @@
package validatorcredentialsset
import (
"encoding/hex"
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"os"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/beacon"
)
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")
// obtainChainInfo obtains the chain information required to create a withdrawal credentials change operation.
func (c *command) obtainChainInfo(ctx context.Context) error {
// Use the offline preparation file if present (and we haven't been asked to recreate it).
if !c.prepareOffline {
err := c.obtainChainInfoFromFile(ctx)
if err == nil {
return nil
}
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")
if c.offline {
return fmt.Errorf("%s is unavailable or outdated; this is required to have been previously generated using --offline-preparation on an online machine and be readable in the directory in which this command is being run", offlinePreparationFilename)
}
genesisValidatorsRootBytes, err := hex.DecodeString(strings.TrimPrefix(data.GenesisValidatorsRoot, "0x"))
if err != nil {
return errors.Wrap(err, "genesis validators root invalid")
if err := c.obtainChainInfoFromNode(ctx); err != nil {
return err
}
return nil
}
// obtainChainInfoFromFile obtains chain information from a pre-generated file.
func (c *command) obtainChainInfoFromFile(_ context.Context) error {
_, err := os.Stat(offlinePreparationFilename)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "Failed to read offline preparation file: %v\n", err)
}
return errors.Wrap(err, fmt.Sprintf("cannot find %s", offlinePreparationFilename))
}
if c.debug {
fmt.Fprintf(os.Stderr, "%s found; loading chain state\n", offlinePreparationFilename)
}
data, err := os.ReadFile(offlinePreparationFilename)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "failed to load chain state: %v\n", err)
}
return errors.Wrap(err, "failed to read offline preparation file")
}
c.chainInfo = &beacon.ChainInfo{}
if err := json.Unmarshal(data, c.chainInfo); err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "chain state invalid: %v\n", err)
}
return errors.Wrap(err, "failed to parse offline preparation file")
}
return nil
}
// obtainChainInfoFromNode obtains chain info from a beacon node.
func (c *command) obtainChainInfoFromNode(ctx context.Context) error {
if c.debug {
fmt.Fprintf(os.Stderr, "Populating chain info from beacon node\n")
}
var err error
c.chainInfo, err = beacon.ObtainChainInfoFromNode(ctx, c.consensusClient, c.chainTime)
if err != nil {
return err
}
return nil
}
// writeChainInfoToFile prepares for an offline run of this command by dumping
// the chain information to a file.
func (c *command) writeChainInfoToFile(_ 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
}
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

@@ -20,8 +20,10 @@ import (
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"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/beacon"
"github.com/wealdtech/ethdo/services/chaintime"
"github.com/wealdtech/ethdo/util"
)
@@ -45,6 +47,7 @@ type command struct {
forkVersion string
genesisValidatorsRoot string
prepareOffline bool
signedOperationsInput string
// Beacon node connection.
timeout time.Duration
@@ -53,7 +56,8 @@ type command struct {
// Information required to generate the operations.
withdrawalAddress bellatrix.ExecutionAddress
chainInfo *chainInfo
chainInfo *beacon.ChainInfo
domain phase0.Domain
// Processing.
consensusClient consensusclient.Service
@@ -80,6 +84,7 @@ func newCommand(_ context.Context) (*command, error) {
mnemonic: viper.GetString("mnemonic"),
path: viper.GetString("path"),
privateKey: viper.GetString("private-key"),
signedOperationsInput: viper.GetString("signed-operations"),
validator: viper.GetString("validator"),
withdrawalAddressStr: viper.GetString("withdrawal-address"),

View File

@@ -28,11 +28,18 @@ func (c *command) output(_ context.Context) (string, error) {
return "", nil
}
if c.prepareOffline {
return fmt.Sprintf("%s generated", offlinePreparationFilename), 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 c.json {
return string(data), nil
}
if err := os.WriteFile(changeOperationsFilename, data, 0600); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", changeOperationsFilename))
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2022 Weald Technology Trading.
// Copyright © 2022, 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -21,7 +21,6 @@ import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
consensusclient "github.com/attestantio/go-eth2-client"
@@ -29,9 +28,12 @@ import (
capella "github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-ssz"
"github.com/wealdtech/ethdo/beacon"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/signing"
"github.com/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
ethutil "github.com/wealdtech/go-eth2-util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
@@ -47,15 +49,19 @@ func (c *command) process(ctx context.Context) error {
return err
}
if err := c.obtainRequiredInformation(ctx); err != nil {
if err := c.obtainChainInfo(ctx); err != nil {
return err
}
if c.prepareOffline {
return c.dumpRequiredInformation(ctx)
return c.writeChainInfoToFile(ctx)
}
if err := c.generateOperations(ctx); err != nil {
if err := c.generateDomain(ctx); err != nil {
return err
}
if err := c.obtainOperations(ctx); err != nil {
return err
}
@@ -74,202 +80,28 @@ func (c *command) process(ctx context.Context) error {
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 machine 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 {
if c.debug {
fmt.Fprintf(os.Stderr, "Populating chain info from beacon node\n")
}
// 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.
if c.genesisValidatorsRoot != "" {
// Genesis validators root supplied manually.
genesisValidatorsRoot, err := hex.DecodeString(strings.TrimPrefix(c.genesisValidatorsRoot, "0x"))
if err != nil {
return errors.Wrap(err, "invalid genesis validators root supplied")
}
if len(genesisValidatorsRoot) != phase0.RootLength {
return errors.New("invalid length for genesis validators root")
}
copy(c.chainInfo.GenesisValidatorsRoot[:], genesisValidatorsRoot)
} else {
// Genesis validators root obtained from beacon node.
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
}
if c.debug {
fmt.Fprintf(os.Stderr, "Genesis validators root is %#x\n", c.chainInfo.GenesisValidatorsRoot)
}
// Obtain epoch.
c.chainInfo.Epoch = c.chainTime.CurrentEpoch()
// Obtain fork version.
if c.forkVersion != "" {
if err := c.populateChainInfoForkVersionFromInput(ctx); err != nil {
return err
}
} else {
if err := c.populateChainInfoForkVersionFromChain(ctx); err != nil {
return err
}
}
if c.debug {
fmt.Fprintf(os.Stderr, "Fork version is %#x\n", c.chainInfo.ForkVersion)
}
// 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")
}
if c.debug {
fmt.Fprintf(os.Stderr, "Domain type is %#x\n", domainType)
}
copy(c.chainInfo.Domain[:], domainType[:])
root, err := (&phase0.ForkData{
CurrentVersion: c.chainInfo.ForkVersion,
GenesisValidatorsRoot: c.chainInfo.GenesisValidatorsRoot,
}).HashTreeRoot()
if err != nil {
return errors.Wrap(err, "failed to calculate signature domain")
}
copy(c.chainInfo.Domain[4:], root[:])
if c.debug {
fmt.Fprintf(os.Stderr, "Domain is %#x\n", c.chainInfo.Domain)
}
return nil
}
func (c *command) populateChainInfoForkVersionFromInput(_ context.Context) error {
// Fork version supplied manually.
forkVersion, err := hex.DecodeString(strings.TrimPrefix(c.forkVersion, "0x"))
if err != nil {
return errors.Wrap(err, "invalid fork version supplied")
}
if len(forkVersion) != phase0.ForkVersionLength {
return errors.New("invalid length for fork version")
}
copy(c.chainInfo.ForkVersion[:], forkVersion)
return nil
}
func (c *command) populateChainInfoForkVersionFromChain(ctx context.Context) error {
// Fetch the capella fork version from the specification.
spec, err := c.consensusClient.(consensusclient.SpecProvider).Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := spec["CAPELLA_FORK_VERSION"]
if !exists {
return errors.New("capella fork version not known by chain")
}
capellaForkVersion, isForkVersion := tmp.(phase0.Version)
if !isForkVersion {
//nolint:revive
return errors.New("CAPELLA_FORK_VERSION is not a fork version!")
}
c.chainInfo.ForkVersion = capellaForkVersion
// Work through the fork schedule to find the latest current fork post-Capella.
forkSchedule, err := c.consensusClient.(consensusclient.ForkScheduleProvider).ForkSchedule(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain fork schedule")
}
foundCapella := false
for i := range forkSchedule {
if foundCapella && forkSchedule[i].Epoch <= c.chainInfo.Epoch {
c.chainInfo.ForkVersion = forkSchedule[i].CurrentVersion
}
if bytes.Equal(forkSchedule[i].CurrentVersion[:], capellaForkVersion[:]) {
foundCapella = true
}
}
return nil
}
// dumpRequiredInformation prepares for an offline run of this command by dumping
// the chain information to a file.
func (c *command) dumpRequiredInformation(_ 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 {
func (c *command) obtainOperations(ctx context.Context) error {
if c.account == "" && c.mnemonic == "" && c.privateKey == "" && c.validator == "" {
// No input information; fetch the operations from a file.
if err := c.loadOperations(ctx); err == nil {
err := c.obtainOperationsFromFileOrInput(ctx)
if err == nil {
// Success.
return nil
}
return fmt.Errorf("no account, mnemonic or private key specified and no %s file found; cannot proceed", changeOperationsFilename)
if c.signedOperationsInput != "" {
return errors.Wrap(err, "failed to obtain supplied signed operations")
}
return errors.Wrap(err, fmt.Sprintf("no account, mnemonic or private key specified, and no %s file loaded", changeOperationsFilename))
}
if c.mnemonic != "" {
switch {
case c.path != "":
// Have a mnemonic and path.
return c.generateOperationsFromMnemonicAndPath(ctx)
return c.generateOperationFromMnemonicAndPath(ctx)
case c.validator != "":
// Have a mnemonic and validator.
return c.generateOperationsFromMnemonicAndValidator(ctx)
return c.generateOperationFromMnemonicAndValidator(ctx)
case c.privateKey != "":
// Have a mnemonic and a private key for the withdrawal address.
return c.generateOperationsFromMnemonicAndPrivateKey(ctx)
@@ -298,52 +130,82 @@ func (c *command) generateOperations(ctx context.Context) error {
return errors.New("unsupported combination of inputs; see help for details of supported combinations")
}
func (c *command) loadChainInfo(_ context.Context) error {
_, err := os.Stat(offlinePreparationFilename)
func (c *command) generateOperationFromMnemonicAndPath(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "Failed to read offline preparation file: %v\n", err)
}
return errors.Wrap(err, fmt.Sprintf("cannot find %s", offlinePreparationFilename))
return err
}
if c.debug {
fmt.Fprintf(os.Stderr, "%s found; loading chain state\n", offlinePreparationFilename)
// Turn the validators in to a map for easy lookup.
validators := make(map[string]*beacon.ValidatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
}
data, err := os.ReadFile(offlinePreparationFilename)
if err != nil {
return errors.Wrap(err, "failed to read offline preparation file")
validatorKeyPath := c.path
match := validatorPath.Match([]byte(c.path))
if !match {
return fmt.Errorf("path %s does not match EIP-2334 format for a validator", c.path)
}
if err := json.Unmarshal(data, c.chainInfo); err != nil {
return errors.Wrap(err, "failed to parse offline preparation file")
if _, err := c.generateOperationFromSeedAndPath(ctx, validators, seed, validatorKeyPath); err != nil {
return errors.Wrap(err, "failed to generate operation from seed and path")
}
return nil
}
func (c *command) loadOperations(_ context.Context) error {
_, err := os.Stat(changeOperationsFilename)
func (c *command) generateOperationFromMnemonicAndValidator(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "Failed to read change operations file: %v\n", err)
}
return err
}
if c.debug {
fmt.Fprintf(os.Stderr, "%s found; loading operations\n", changeOperationsFilename)
}
data, err := os.ReadFile(changeOperationsFilename)
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, c.validator)
if err != nil {
return errors.Wrap(err, "failed to read change operations file")
return err
}
if err := json.Unmarshal(data, &c.signedOperations); err != nil {
return errors.Wrap(err, "failed to parse change operations file")
// Scan the keys from the seed to find the path.
maxDistance := 1024
// Start scanning the validator keys.
var withdrawalAccount e2wtypes.Account
for i := 0; ; i++ {
if i == maxDistance {
if c.debug {
fmt.Fprintf(os.Stderr, "Gone %d indices without finding the validator, not scanning any further\n", maxDistance)
}
break
}
validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", i)
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, validatorKeyPath)
if err != nil {
return errors.Wrap(err, "failed to generate validator private key")
}
validatorPubkey := validatorPrivkey.PublicKey().Marshal()
if bytes.Equal(validatorPubkey, validatorInfo.Pubkey[:]) {
withdrawalKeyPath := strings.TrimSuffix(validatorKeyPath, "/0")
withdrawalAccount, err = util.ParseAccount(ctx, c.mnemonic, []string{withdrawalKeyPath}, true)
if err != nil {
return errors.Wrap(err, "failed to create withdrawal account")
}
err = c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount)
if err != nil {
return err
}
break
}
}
return nil
}
func (c *command) generateOperationsFromMnemonicAndPrivateKey(ctx context.Context) error {
// Functionally identical to a simple scan, so use that.
return c.generateOperationsFromMnemonic(ctx)
}
func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
@@ -351,7 +213,7 @@ func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
}
// Turn the validators in to a map for easy lookup.
validators := make(map[string]*validatorInfo, 0)
validators := make(map[string]*beacon.ValidatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[fmt.Sprintf("%#x", validator.Pubkey)] = validator
}
@@ -379,70 +241,141 @@ func (c *command) generateOperationsFromMnemonic(ctx context.Context) error {
return nil
}
func (c *command) generateOperationsFromMnemonicAndPrivateKey(ctx context.Context) error {
// Functionally identical to a simple scan, so use that.
return c.generateOperationsFromMnemonic(ctx)
func (c *command) generateOperationsFromAccountAndWithdrawalAccount(ctx context.Context) error {
validatorAccount, err := util.ParseAccount(ctx, c.account, nil, true)
if err != nil {
return err
}
withdrawalAccount, err := util.ParseAccount(ctx, c.withdrawalAccount, c.passphrases, true)
if err != nil {
return err
}
validatorPubkey, err := util.BestPublicKey(validatorAccount)
if err != nil {
return err
}
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, fmt.Sprintf("%#x", validatorPubkey.Marshal()))
if err != nil {
return errors.Wrap(err, "failed to obtain validator info")
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}
func (c *command) generateOperationsFromMnemonicAndValidator(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
func (c *command) generateOperationsFromAccountAndPrivateKey(ctx context.Context) error {
validatorAccount, err := util.ParseAccount(ctx, c.account, nil, true)
if err != nil {
return err
}
validator, err := c.fetchValidatorInfo(ctx)
withdrawalAccount, err := util.ParseAccount(ctx, c.privateKey, nil, true)
if err != nil {
return err
}
// Scan the keys from the seed to find the path.
maxDistance := 1024
// Start scanning the validator keys.
for i := 0; ; i++ {
if i == maxDistance {
if c.debug {
fmt.Fprintf(os.Stderr, "Gone %d indices without finding the validator, not scanning any further\n", maxDistance)
}
break
}
validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", i)
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, validatorKeyPath)
if err != nil {
return errors.Wrap(err, "failed to generate validator private key")
}
validatorPubkey := validatorPrivkey.PublicKey().Marshal()
if bytes.Equal(validatorPubkey, validator.Pubkey[:]) {
// Recreate the withdrawal credentials to ensure a match.
withdrawalKeyPath := strings.TrimSuffix(validatorKeyPath, "/0")
withdrawalPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, withdrawalKeyPath)
if err != nil {
return 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) {
return fmt.Errorf("validator %#x withdrawal credentials %#x do not match expected credentials, cannot update", validatorPubkey, validator.WithdrawalCredentials)
}
validatorPubkey, err := util.BestPublicKey(validatorAccount)
if err != nil {
return err
}
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, fmt.Sprintf("%#x", validatorPubkey.Marshal()))
if err != nil {
return errors.Wrap(err, "failed to obtain validator info")
}
withdrawalAccount, err := util.ParseAccount(ctx, c.mnemonic, []string{withdrawalKeyPath}, true)
if err != nil {
return errors.Wrap(err, "failed to create withdrawal account")
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
err = c.generateOperationFromAccount(ctx, validator, withdrawalAccount)
if err != nil {
return err
}
break
return nil
}
func (c *command) generateOperationsFromValidatorAndPrivateKey(ctx context.Context) error {
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, c.validator)
if err != nil {
return err
}
withdrawalAccount, err := util.ParseAccount(ctx, c.privateKey, nil, true)
if err != nil {
return err
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}
func (c *command) obtainOperationsFromFileOrInput(ctx context.Context) error {
// Start off by attempting to use the provided signed operations.
if c.signedOperationsInput != "" {
return c.obtainOperationsFromInput(ctx)
}
// If not, read it from the file with the standard name.
return c.obtainOperationsFromFile(ctx)
}
func (c *command) obtainOperationsFromFile(ctx context.Context) error {
_, err := os.Stat(changeOperationsFilename)
if err != nil {
return errors.Wrap(err, "failed to read change operations file")
}
if c.debug {
fmt.Fprintf(os.Stderr, "%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")
}
for _, op := range c.signedOperations {
if err := c.verifyOperation(ctx, op); err != nil {
return err
}
}
return nil
}
func (c *command) obtainOperationsFromInput(ctx context.Context) error {
if strings.HasPrefix(c.signedOperationsInput, "{") {
// This looks like a single entry; turn it in to an array.
c.signedOperationsInput = fmt.Sprintf("[%s]", c.signedOperationsInput)
}
if !strings.HasPrefix(c.signedOperationsInput, "[") {
// This looks like a file; read it in.
data, err := os.ReadFile(c.signedOperationsInput)
if err != nil {
return errors.Wrap(err, "failed to read input file")
}
c.signedOperationsInput = string(data)
}
if err := json.Unmarshal([]byte(c.signedOperationsInput), &c.signedOperations); err != nil {
return errors.Wrap(err, "failed to parse change operations input")
}
for _, op := range c.signedOperations {
if err := c.verifyOperation(ctx, op); err != nil {
return err
}
}
return nil
}
func (c *command) generateOperationFromSeedAndPath(ctx context.Context,
validators map[string]*validatorInfo,
validators map[string]*beacon.ValidatorInfo,
seed []byte,
path string,
) (
@@ -518,7 +451,7 @@ func (c *command) generateOperationFromSeedAndPath(ctx context.Context,
}
func (c *command) generateOperationFromAccount(ctx context.Context,
validator *validatorInfo,
validator *beacon.ValidatorInfo,
withdrawalAccount e2wtypes.Account,
) error {
signedOperation, err := c.createSignedOperation(ctx, validator, withdrawalAccount)
@@ -530,7 +463,7 @@ func (c *command) generateOperationFromAccount(ctx context.Context,
}
func (c *command) createSignedOperation(ctx context.Context,
validator *validatorInfo,
validator *beacon.ValidatorInfo,
withdrawalAccount e2wtypes.Account,
) (
*capella.SignedBLSToExecutionChange,
@@ -540,6 +473,9 @@ func (c *command) createSignedOperation(ctx context.Context,
if err != nil {
return nil, err
}
if c.debug {
fmt.Fprintf(os.Stderr, "Using %#x as best public key for %s\n", pubkey.Marshal(), withdrawalAccount.Name())
}
blsPubkey := phase0.BLSPubKey{}
copy(blsPubkey[:], pubkey.Marshal())
@@ -558,7 +494,10 @@ func (c *command) createSignedOperation(ctx context.Context,
}
// Sign the operation.
signature, err := signing.SignRoot(ctx, withdrawalAccount, nil, root, c.chainInfo.Domain)
if c.debug {
fmt.Fprintf(os.Stderr, "Signing %#x with domain %#x by public key %#x\n", root, c.domain, withdrawalAccount.PublicKey().Marshal())
}
signature, err := signing.SignRoot(ctx, withdrawalAccount, nil, root, c.domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign credentials change operation")
}
@@ -589,7 +528,7 @@ func (c *command) parseWithdrawalAddress(_ context.Context) error {
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)
validators := make(map[phase0.ValidatorIndex]*beacon.ValidatorInfo, 0)
for _, validator := range c.chainInfo.Validators {
validators[validator.Index] = validator
}
@@ -602,8 +541,43 @@ func (c *command) validateOperations(ctx context.Context) (bool, string) {
return true, ""
}
func (c *command) verifyOperation(ctx context.Context, op *capella.SignedBLSToExecutionChange) error {
root, err := op.Message.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "failed to generate message root")
}
sigBytes := make([]byte, len(op.Signature))
copy(sigBytes, op.Signature[:])
sig, err := e2types.BLSSignatureFromBytes(sigBytes)
if err != nil {
return errors.Wrap(err, "invalid signature")
}
container := &phase0.SigningData{
ObjectRoot: root,
Domain: c.domain,
}
signingRoot, err := ssz.HashTreeRoot(container)
if err != nil {
return errors.Wrap(err, "failed to generate signing root")
}
pubkeyBytes := make([]byte, len(op.Message.FromBLSPubkey))
copy(pubkeyBytes, op.Message.FromBLSPubkey[:])
pubkey, err := e2types.BLSPublicKeyFromBytes(pubkeyBytes)
if err != nil {
return errors.Wrap(err, "invalid public key")
}
if !sig.Verify(signingRoot[:], pubkey) {
return errors.New("signature does not verify")
}
return nil
}
func (c *command) validateOperation(_ context.Context,
validators map[phase0.ValidatorIndex]*validatorInfo,
validators map[phase0.ValidatorIndex]*beacon.ValidatorInfo,
signedOperation *capella.SignedBLSToExecutionChange,
) (
bool,
@@ -653,7 +627,6 @@ func (c *command) setup(ctx context.Context) error {
// 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 {
@@ -663,56 +636,88 @@ func (c *command) setup(ctx context.Context) error {
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
}
}
func (c *command) generateDomain(ctx context.Context) error {
genesisValidatorsRoot, err := c.obtainGenesisValidatorsRoot(ctx)
if err != nil {
return err
}
forkVersion, err := c.obtainForkVersion(ctx)
if err != nil {
return err
}
if validatorInfo == nil {
return nil, errors.New("unknown validator")
root, err := (&phase0.ForkData{
CurrentVersion: forkVersion,
GenesisValidatorsRoot: genesisValidatorsRoot,
}).HashTreeRoot()
if err != nil {
return errors.Wrap(err, "failed to calculate signature domain")
}
return validatorInfo, nil
copy(c.domain[:], c.chainInfo.BLSToExecutionChangeDomainType[:])
copy(c.domain[4:], root[:])
if c.debug {
fmt.Fprintf(os.Stderr, "Domain is %#x\n", c.domain)
}
return nil
}
func (c *command) obtainGenesisValidatorsRoot(ctx context.Context) (phase0.Root, error) {
genesisValidatorsRoot := phase0.Root{}
if c.genesisValidatorsRoot != "" {
if c.debug {
fmt.Fprintf(os.Stderr, "Genesis validators root supplied on the command line\n")
}
root, err := hex.DecodeString(strings.TrimPrefix(c.genesisValidatorsRoot, "0x"))
if err != nil {
return phase0.Root{}, errors.Wrap(err, "invalid genesis validators root supplied")
}
if len(root) != phase0.RootLength {
return phase0.Root{}, errors.New("invalid length for genesis validators root")
}
copy(genesisValidatorsRoot[:], root)
} else {
if c.debug {
fmt.Fprintf(os.Stderr, "Genesis validators root obtained from chain info\n")
}
copy(genesisValidatorsRoot[:], c.chainInfo.GenesisValidatorsRoot[:])
}
if c.debug {
fmt.Fprintf(os.Stderr, "Using genesis validators root %#x\n", genesisValidatorsRoot)
}
return genesisValidatorsRoot, nil
}
func (c *command) obtainForkVersion(ctx context.Context) (phase0.Version, error) {
forkVersion := phase0.Version{}
if c.forkVersion != "" {
if c.debug {
fmt.Fprintf(os.Stderr, "Fork version supplied on the command line\n")
}
version, err := hex.DecodeString(strings.TrimPrefix(c.forkVersion, "0x"))
if err != nil {
return phase0.Version{}, errors.Wrap(err, "invalid fork version supplied")
}
if len(version) != phase0.ForkVersionLength {
return phase0.Version{}, errors.New("invalid length for fork version")
}
copy(forkVersion[:], version)
} else {
if c.debug {
fmt.Fprintf(os.Stderr, "Fork version obtained from chain info\n")
}
// Use the genesis fork version for setting credentials as per the spec.
copy(forkVersion[:], c.chainInfo.GenesisForkVersion[:])
}
if c.debug {
fmt.Fprintf(os.Stderr, "Using fork version %#x\n", forkVersion)
}
return forkVersion, nil
}
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
@@ -733,126 +738,3 @@ func addressBytesToEIP55(address []byte) string {
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
}
func (c *command) generateOperationsFromAccountAndWithdrawalAccount(ctx context.Context) error {
validatorAccount, err := util.ParseAccount(ctx, c.account, nil, true)
if err != nil {
return err
}
withdrawalAccount, err := util.ParseAccount(ctx, c.withdrawalAccount, c.passphrases, true)
if err != nil {
return err
}
// Find the validator info given its account information.
validatorPubkey := validatorAccount.PublicKey().Marshal()
var validatorInfo *validatorInfo
for _, validator := range c.chainInfo.Validators {
if bytes.Equal(validator.Pubkey[:], validatorPubkey) {
// Found it.
validatorInfo = validator
}
}
if validatorInfo == nil {
return errors.New("could not find information for that validator on the chain")
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}
func (c *command) generateOperationsFromAccountAndPrivateKey(ctx context.Context) error {
validatorAccount, err := util.ParseAccount(ctx, c.account, nil, true)
if err != nil {
return err
}
withdrawalAccount, err := util.ParseAccount(ctx, c.privateKey, nil, true)
if err != nil {
return err
}
// Find the validator info given its account information.
validatorPubkey := validatorAccount.PublicKey().Marshal()
var validatorInfo *validatorInfo
for _, validator := range c.chainInfo.Validators {
if bytes.Equal(validator.Pubkey[:], validatorPubkey) {
// Found it.
validatorInfo = validator
}
}
if validatorInfo == nil {
return errors.New("could not find information for that validator on the chain")
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}
func (c *command) generateOperationsFromValidatorAndPrivateKey(ctx context.Context) error {
validator, err := c.fetchValidatorInfo(ctx)
if err != nil {
return err
}
validatorAccount, err := util.ParseAccount(ctx, validator.Pubkey.String(), nil, false)
if err != nil {
return err
}
withdrawalAccount, err := util.ParseAccount(ctx, c.privateKey, nil, true)
if err != nil {
return err
}
// Find the validator info given its account information.
validatorPubkey := validatorAccount.PublicKey().Marshal()
var validatorInfo *validatorInfo
for _, validator := range c.chainInfo.Validators {
if bytes.Equal(validator.Pubkey[:], validatorPubkey) {
// Found it.
validatorInfo = validator
}
}
if validatorInfo == nil {
return errors.New("could not find information for that validator on the chain")
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, withdrawalAccount); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,372 @@
// 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"
"fmt"
"testing"
"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/stretchr/testify/require"
"github.com/wealdtech/ethdo/beacon"
e2types "github.com/wealdtech/go-eth2-types/v2"
)
func TestGenerateOperationFromMnemonicAndPath(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
command *command
expected []*capella.SignedBLSToExecutionChange
err string
}{
{
name: "MnemonicInvalid",
command: &command{
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon",
path: "m/12381/3600/0/0/0",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
err: "mnemonic is invalid",
},
{
name: "PathInvalid",
command: &command{
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: "m/12381/3600/0/0",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
err: "path m/12381/3600/0/0 does not match EIP-2334 format for a validator",
},
{
name: "Good",
command: &command{
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: "m/12381/3600/0/0/0",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
expected: []*capella.SignedBLSToExecutionChange{
{
Message: &capella.BLSToExecutionChange{
ValidatorIndex: 0,
FromBLSPubkey: phase0.BLSPubKey{0x99, 0xb1, 0xf1, 0xd8, 0x4d, 0x76, 0x18, 0x54, 0x66, 0xd8, 0x6c, 0x34, 0xbd, 0xe1, 0x10, 0x13, 0x16, 0xaf, 0xdd, 0xae, 0x76, 0x21, 0x7a, 0xa8, 0x6c, 0xd0, 0x66, 0x97, 0x9b, 0x19, 0x85, 0x8c, 0x2c, 0x9d, 0x9e, 0x56, 0xee, 0xbc, 0x1e, 0x06, 0x7a, 0xc5, 0x42, 0x77, 0xa6, 0x17, 0x90, 0xdb},
ToExecutionAddress: bellatrix.ExecutionAddress{0x8c, 0x1f, 0xf9, 0x78, 0x03, 0x6f, 0x2e, 0x9d, 0x7c, 0xc3, 0x82, 0xef, 0xf7, 0xb4, 0xc8, 0xc5, 0x3c, 0x22, 0xac, 0x15},
},
Signature: phase0.BLSSignature{0xb7, 0x8a, 0x05, 0xba, 0xd9, 0x27, 0xfc, 0x89, 0x6f, 0x14, 0x06, 0xb3, 0x2d, 0x64, 0x4a, 0xe1, 0x69, 0xce, 0xcd, 0x89, 0x86, 0xc1, 0xef, 0x8c, 0x0d, 0x03, 0x7d, 0x70, 0x86, 0xf8, 0x5f, 0x13, 0xe1, 0xe1, 0x88, 0xb4, 0x30, 0x96, 0x43, 0xa2, 0xc1, 0x3f, 0xfe, 0xfb, 0x0a, 0xe8, 0x05, 0x11, 0x09, 0x98, 0x53, 0xa0, 0x58, 0x1f, 0x4b, 0x2b, 0xd2, 0xe1, 0x45, 0x41, 0x04, 0x79, 0x01, 0xe2, 0x2a, 0x94, 0x0a, 0x9c, 0x7e, 0x3a, 0xc0, 0xa8, 0x82, 0xd1, 0xa8, 0xaf, 0x6b, 0xfa, 0xea, 0x81, 0x3a, 0x6a, 0x6b, 0xe7, 0x21, 0xf9, 0x26, 0x22, 0x04, 0xaa, 0x9d, 0xa4, 0xe4, 0x77, 0x27, 0xd0},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.generateOperationFromMnemonicAndPath(ctx)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
// fmt.Printf("%v\n", test.command.signedOperations)
require.Equal(t, test.expected, test.command.signedOperations)
}
})
}
}
func TestGenerateOperationFromMnemonicAndValidator(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
command *command
expected []*capella.SignedBLSToExecutionChange
err string
}{
{
name: "MnemonicInvalid",
command: &command{
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon",
validator: "0",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
err: "mnemonic is invalid",
},
{
name: "ValidatorMissing",
command: &command{
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",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
err: "no validator specified",
},
{
name: "Good",
command: &command{
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",
validator: "0",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
expected: []*capella.SignedBLSToExecutionChange{
{
Message: &capella.BLSToExecutionChange{
ValidatorIndex: 0,
FromBLSPubkey: phase0.BLSPubKey{0x99, 0xb1, 0xf1, 0xd8, 0x4d, 0x76, 0x18, 0x54, 0x66, 0xd8, 0x6c, 0x34, 0xbd, 0xe1, 0x10, 0x13, 0x16, 0xaf, 0xdd, 0xae, 0x76, 0x21, 0x7a, 0xa8, 0x6c, 0xd0, 0x66, 0x97, 0x9b, 0x19, 0x85, 0x8c, 0x2c, 0x9d, 0x9e, 0x56, 0xee, 0xbc, 0x1e, 0x06, 0x7a, 0xc5, 0x42, 0x77, 0xa6, 0x17, 0x90, 0xdb},
ToExecutionAddress: bellatrix.ExecutionAddress{0x8c, 0x1f, 0xf9, 0x78, 0x03, 0x6f, 0x2e, 0x9d, 0x7c, 0xc3, 0x82, 0xef, 0xf7, 0xb4, 0xc8, 0xc5, 0x3c, 0x22, 0xac, 0x15},
},
Signature: phase0.BLSSignature{0xb7, 0x8a, 0x05, 0xba, 0xd9, 0x27, 0xfc, 0x89, 0x6f, 0x14, 0x06, 0xb3, 0x2d, 0x64, 0x4a, 0xe1, 0x69, 0xce, 0xcd, 0x89, 0x86, 0xc1, 0xef, 0x8c, 0x0d, 0x03, 0x7d, 0x70, 0x86, 0xf8, 0x5f, 0x13, 0xe1, 0xe1, 0x88, 0xb4, 0x30, 0x96, 0x43, 0xa2, 0xc1, 0x3f, 0xfe, 0xfb, 0x0a, 0xe8, 0x05, 0x11, 0x09, 0x98, 0x53, 0xa0, 0x58, 0x1f, 0x4b, 0x2b, 0xd2, 0xe1, 0x45, 0x41, 0x04, 0x79, 0x01, 0xe2, 0x2a, 0x94, 0x0a, 0x9c, 0x7e, 0x3a, 0xc0, 0xa8, 0x82, 0xd1, 0xa8, 0xaf, 0x6b, 0xfa, 0xea, 0x81, 0x3a, 0x6a, 0x6b, 0xe7, 0x21, 0xf9, 0x26, 0x22, 0x04, 0xaa, 0x9d, 0xa4, 0xe4, 0x77, 0x27, 0xd0},
},
},
},
{
name: "GoodPubkey",
command: &command{
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",
validator: "0xb384f767d964e100c8a9b21018d08c25ffebae268b3ab6d610353897541971726dbfc3c7463884c68a531515aab94c87",
chainInfo: chainInfo,
signedOperations: make([]*capella.SignedBLSToExecutionChange, 0),
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
expected: []*capella.SignedBLSToExecutionChange{
{
Message: &capella.BLSToExecutionChange{
ValidatorIndex: 0,
FromBLSPubkey: phase0.BLSPubKey{0x99, 0xb1, 0xf1, 0xd8, 0x4d, 0x76, 0x18, 0x54, 0x66, 0xd8, 0x6c, 0x34, 0xbd, 0xe1, 0x10, 0x13, 0x16, 0xaf, 0xdd, 0xae, 0x76, 0x21, 0x7a, 0xa8, 0x6c, 0xd0, 0x66, 0x97, 0x9b, 0x19, 0x85, 0x8c, 0x2c, 0x9d, 0x9e, 0x56, 0xee, 0xbc, 0x1e, 0x06, 0x7a, 0xc5, 0x42, 0x77, 0xa6, 0x17, 0x90, 0xdb},
ToExecutionAddress: bellatrix.ExecutionAddress{0x8c, 0x1f, 0xf9, 0x78, 0x03, 0x6f, 0x2e, 0x9d, 0x7c, 0xc3, 0x82, 0xef, 0xf7, 0xb4, 0xc8, 0xc5, 0x3c, 0x22, 0xac, 0x15},
},
Signature: phase0.BLSSignature{0xb7, 0x8a, 0x05, 0xba, 0xd9, 0x27, 0xfc, 0x89, 0x6f, 0x14, 0x06, 0xb3, 0x2d, 0x64, 0x4a, 0xe1, 0x69, 0xce, 0xcd, 0x89, 0x86, 0xc1, 0xef, 0x8c, 0x0d, 0x03, 0x7d, 0x70, 0x86, 0xf8, 0x5f, 0x13, 0xe1, 0xe1, 0x88, 0xb4, 0x30, 0x96, 0x43, 0xa2, 0xc1, 0x3f, 0xfe, 0xfb, 0x0a, 0xe8, 0x05, 0x11, 0x09, 0x98, 0x53, 0xa0, 0x58, 0x1f, 0x4b, 0x2b, 0xd2, 0xe1, 0x45, 0x41, 0x04, 0x79, 0x01, 0xe2, 0x2a, 0x94, 0x0a, 0x9c, 0x7e, 0x3a, 0xc0, 0xa8, 0x82, 0xd1, 0xa8, 0xaf, 0x6b, 0xfa, 0xea, 0x81, 0x3a, 0x6a, 0x6b, 0xe7, 0x21, 0xf9, 0x26, 0x22, 0x04, 0xaa, 0x9d, 0xa4, 0xe4, 0x77, 0x27, 0xd0},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.generateOperationFromMnemonicAndValidator(ctx)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, test.command.signedOperations)
}
})
}
}
func TestGenerateOperationFromSeedAndPath(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
{
Index: 2,
Pubkey: phase0.BLSPubKey{0xaf, 0x9c, 0xe4, 0x4f, 0x50, 0x14, 0x8d, 0xb4, 0x12, 0x19, 0x4a, 0xf0, 0xba, 0xf0, 0xba, 0xb3, 0x6b, 0xd5, 0xc3, 0xe0, 0xc4, 0x93, 0x89, 0x11, 0xa4, 0xe5, 0x02, 0xe3, 0x98, 0xb5, 0x9e, 0x5c, 0xca, 0x7c, 0x78, 0xe3, 0xfe, 0x03, 0x41, 0x95, 0x47, 0x88, 0x79, 0xee, 0xb2, 0x3d, 0xb0, 0xa6},
WithdrawalCredentials: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
{
Index: 3,
Pubkey: phase0.BLSPubKey{0x86, 0xd3, 0x30, 0xaf, 0x51, 0xfa, 0x59, 0x3f, 0xa9, 0xf9, 0x3e, 0xdb, 0x9d, 0x16, 0x64, 0x01, 0x86, 0xbe, 0x2e, 0x93, 0xea, 0x94, 0xd2, 0x59, 0x78, 0x1e, 0x1e, 0xb3, 0x4d, 0xeb, 0x84, 0x4c, 0x39, 0x68, 0xd7, 0x5e, 0xa9, 0x1d, 0x19, 0xf1, 0x59, 0xdb, 0xd0, 0x52, 0x3c, 0x6c, 0x5b, 0xa5},
WithdrawalCredentials: []byte{0x00, 0x81, 0x68, 0x45, 0x6b, 0x6d, 0x9a, 0x32, 0x83, 0x93, 0x1f, 0xea, 0x52, 0x10, 0xda, 0x12, 0x2d, 0x1e, 0x65, 0xe8, 0xed, 0x50, 0xb8, 0xe8, 0xf5, 0x91, 0x11, 0x83, 0xb0, 0x2f, 0xd1, 0x25},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
validators := make(map[string]*beacon.ValidatorInfo, len(chainInfo.Validators))
for i := range chainInfo.Validators {
validators[fmt.Sprintf("%#x", chainInfo.Validators[i].Pubkey)] = chainInfo.Validators[i]
}
tests := []struct {
name string
command *command
seed []byte
path string
generated bool
err string
expected []*capella.SignedBLSToExecutionChange
}{
{
name: "PathInvalid",
command: &command{
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",
chainInfo: chainInfo,
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "invalid",
err: "failed to generate validator private key: not master at path component 0",
},
{
name: "ValidatorUnknown",
command: &command{
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",
chainInfo: chainInfo,
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/999/0/0",
},
{
name: "ValidatorCredentialsAlreadySet",
command: &command{
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",
chainInfo: chainInfo,
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/2/0/0",
},
{
name: "PrivateKeyInvalid",
command: &command{
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",
chainInfo: chainInfo,
privateKey: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/0/0/0",
err: "failed to create account from private key: invalid private key: err blsSecretKeyDeserialize ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
{
name: "Good",
command: &command{
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",
chainInfo: chainInfo,
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/0/0/0",
generated: true,
expected: []*capella.SignedBLSToExecutionChange{
{
Message: &capella.BLSToExecutionChange{
ValidatorIndex: 0,
FromBLSPubkey: phase0.BLSPubKey{0x99, 0xb1, 0xf1, 0xd8, 0x4d, 0x76, 0x18, 0x54, 0x66, 0xd8, 0x6c, 0x34, 0xbd, 0xe1, 0x10, 0x13, 0x16, 0xaf, 0xdd, 0xae, 0x76, 0x21, 0x7a, 0xa8, 0x6c, 0xd0, 0x66, 0x97, 0x9b, 0x19, 0x85, 0x8c, 0x2c, 0x9d, 0x9e, 0x56, 0xee, 0xbc, 0x1e, 0x06, 0x7a, 0xc5, 0x42, 0x77, 0xa6, 0x17, 0x90, 0xdb},
ToExecutionAddress: bellatrix.ExecutionAddress{0x8c, 0x1f, 0xf9, 0x78, 0x03, 0x6f, 0x2e, 0x9d, 0x7c, 0xc3, 0x82, 0xef, 0xf7, 0xb4, 0xc8, 0xc5, 0x3c, 0x22, 0xac, 0x15},
},
Signature: phase0.BLSSignature{0xb7, 0x8a, 0x05, 0xba, 0xd9, 0x27, 0xfc, 0x89, 0x6f, 0x14, 0x06, 0xb3, 0x2d, 0x64, 0x4a, 0xe1, 0x69, 0xce, 0xcd, 0x89, 0x86, 0xc1, 0xef, 0x8c, 0x0d, 0x03, 0x7d, 0x70, 0x86, 0xf8, 0x5f, 0x13, 0xe1, 0xe1, 0x88, 0xb4, 0x30, 0x96, 0x43, 0xa2, 0xc1, 0x3f, 0xfe, 0xfb, 0x0a, 0xe8, 0x05, 0x11, 0x09, 0x98, 0x53, 0xa0, 0x58, 0x1f, 0x4b, 0x2b, 0xd2, 0xe1, 0x45, 0x41, 0x04, 0x79, 0x01, 0xe2, 0x2a, 0x94, 0x0a, 0x9c, 0x7e, 0x3a, 0xc0, 0xa8, 0x82, 0xd1, 0xa8, 0xaf, 0x6b, 0xfa, 0xea, 0x81, 0x3a, 0x6a, 0x6b, 0xe7, 0x21, 0xf9, 0x26, 0x22, 0x04, 0xaa, 0x9d, 0xa4, 0xe4, 0x77, 0x27, 0xd0},
},
},
},
{
name: "GoodPrivateKey",
command: &command{
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",
chainInfo: chainInfo,
withdrawalAddressStr: "0x8c1Ff978036F2e9d7CC382Eff7B4c8c53C22ac15",
privateKey: "0x67775f030068b4610d6e1bd04948f547305b2502423fcece4c1091d065b44638",
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/3/0/0",
generated: true,
expected: []*capella.SignedBLSToExecutionChange{
{
Message: &capella.BLSToExecutionChange{
ValidatorIndex: 3,
FromBLSPubkey: phase0.BLSPubKey{0x86, 0x71, 0x0a, 0xbb, 0x44, 0xb6, 0xcd, 0xa6, 0x66, 0x57, 0x7b, 0xbb, 0x25, 0x5e, 0x16, 0xd9, 0x8b, 0xf2, 0x52, 0x51, 0x76, 0x22, 0x3f, 0x35, 0x35, 0xc7, 0xdf, 0xf8, 0xe7, 0x0b, 0x3b, 0xc8, 0x92, 0xbb, 0x36, 0x11, 0x33, 0x95, 0x2b, 0x03, 0xd2, 0xb0, 0x78, 0xcd, 0x07, 0x18, 0xca, 0xf3},
ToExecutionAddress: bellatrix.ExecutionAddress{0x8c, 0x1f, 0xf9, 0x78, 0x03, 0x6f, 0x2e, 0x9d, 0x7c, 0xc3, 0x82, 0xef, 0xf7, 0xb4, 0xc8, 0xc5, 0x3c, 0x22, 0xac, 0x15},
},
Signature: phase0.BLSSignature{0x8d, 0x92, 0xb9, 0x1c, 0x5d, 0xfd, 0x98, 0xc7, 0x98, 0xfc, 0x94, 0xe1, 0xe6, 0x69, 0xf3, 0xaa, 0xae, 0x72, 0xb2, 0x36, 0x47, 0xde, 0x88, 0x54, 0xea, 0x16, 0x74, 0x7f, 0xfe, 0xf0, 0x4d, 0x46, 0x5c, 0x07, 0x56, 0x34, 0x03, 0x30, 0x2f, 0xbc, 0x26, 0xa2, 0x6d, 0xec, 0x10, 0x20, 0xe7, 0x67, 0x10, 0xb0, 0x4a, 0x7e, 0x4e, 0x25, 0x89, 0x7e, 0x87, 0x88, 0xda, 0xaf, 0x2b, 0xb5, 0xb7, 0x73, 0x25, 0x64, 0x80, 0xc1, 0xba, 0xf3, 0x1d, 0x33, 0x8f, 0x17, 0xa5, 0x35, 0x74, 0x80, 0xf3, 0x37, 0x0e, 0xea, 0x19, 0x15, 0xd5, 0x69, 0x7e, 0xf6, 0x68, 0xaa, 0x9c, 0x3d, 0x47, 0x19, 0x75, 0xfc},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
generated, err := test.command.generateOperationFromSeedAndPath(ctx, validators, test.seed, test.path)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.generated, generated)
if generated {
require.Equal(t, test.expected, test.command.signedOperations)
}
}
})
}
}

View File

@@ -0,0 +1,105 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorexit
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/beacon"
)
// obtainChainInfo obtains the chain information required to create an exit operation.
func (c *command) obtainChainInfo(ctx context.Context) error {
// Use the offline preparation file if present (and we haven't been asked to recreate it).
if !c.prepareOffline {
err := c.obtainChainInfoFromFile(ctx)
if err == nil {
return nil
}
}
if c.offline {
return fmt.Errorf("%s is unavailable or outdated; this is required to have been previously generated using --offline-preparation on an online machine and be readable in the directory in which this command is being run", offlinePreparationFilename)
}
if err := c.obtainChainInfoFromNode(ctx); err != nil {
return err
}
return nil
}
// obtainChainInfoFromFile obtains chain information from a pre-generated file.
func (c *command) obtainChainInfoFromFile(_ context.Context) error {
_, err := os.Stat(offlinePreparationFilename)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "Failed to read offline preparation file: %v\n", err)
}
return errors.Wrap(err, fmt.Sprintf("cannot find %s", offlinePreparationFilename))
}
if c.debug {
fmt.Fprintf(os.Stderr, "%s found; loading chain state\n", offlinePreparationFilename)
}
data, err := os.ReadFile(offlinePreparationFilename)
if err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "failed to load chain state: %v\n", err)
}
return errors.Wrap(err, "failed to read offline preparation file")
}
c.chainInfo = &beacon.ChainInfo{}
if err := json.Unmarshal(data, c.chainInfo); err != nil {
if c.debug {
fmt.Fprintf(os.Stderr, "chain state invalid: %v\n", err)
}
return errors.Wrap(err, "failed to parse offline preparation file")
}
return nil
}
// obtainChainInfoFromNode obtains chain info from a beacon node.
func (c *command) obtainChainInfoFromNode(ctx context.Context) error {
if c.debug {
fmt.Fprintf(os.Stderr, "Populating chain info from beacon node\n")
}
var err error
c.chainInfo, err = beacon.ObtainChainInfoFromNode(ctx, c.consensusClient, c.chainTime)
if err != nil {
return err
}
return nil
}
// writeChainInfoToFile prepares for an offline run of this command by dumping
// the chain information to a file.
func (c *command) writeChainInfoToFile(_ 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
}

View File

@@ -0,0 +1,97 @@
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validatorexit
import (
"context"
"time"
consensusclient "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/beacon"
"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.
passphrases []string
mnemonic string
path string
privateKey string
validator string
forkVersion string
genesisValidatorsRoot string
prepareOffline bool
signedOperationInput string
// Beacon node connection.
timeout time.Duration
connection string
allowInsecureConnections bool
// Information required to generate the operations.
chainInfo *beacon.ChainInfo
domain phase0.Domain
// Processing.
consensusClient consensusclient.Service
chainTime chaintime.Service
// Output.
signedOperation *phase0.SignedVoluntaryExit
}
func newCommand(_ 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"),
passphrases: util.GetPassphrases(),
mnemonic: viper.GetString("mnemonic"),
path: viper.GetString("path"),
privateKey: viper.GetString("private-key"),
signedOperationInput: viper.GetString("signed-operation"),
validator: viper.GetString("validator"),
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
}
return c, nil
}

View File

@@ -1,149 +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 validatorexit
import (
"context"
"encoding/hex"
"encoding/json"
"strings"
"time"
eth2client "github.com/attestantio/go-eth2-client"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/wealdtech/ethdo/util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
type dataIn struct {
// System.
timeout time.Duration
quiet bool
verbose bool
debug bool
// Operation.
eth2Client eth2client.Service
jsonOutput bool
// Chain information.
fork *spec.Fork
currentEpoch spec.Epoch
// Exit information.
account e2wtypes.Account
passphrases []string
epoch spec.Epoch
domain spec.Domain
signedVoluntaryExit *spec.SignedVoluntaryExit
}
func input(ctx context.Context) (*dataIn, error) {
data := &dataIn{}
if viper.GetDuration("timeout") == 0 {
return nil, errors.New("timeout is required")
}
data.timeout = viper.GetDuration("timeout")
data.quiet = viper.GetBool("quiet")
data.verbose = viper.GetBool("verbose")
data.debug = viper.GetBool("debug")
data.passphrases = util.GetPassphrases()
data.jsonOutput = viper.GetBool("json")
switch {
case viper.GetString("exit") != "":
return inputJSON(ctx, data)
case viper.GetString("account") != "":
return inputAccount(ctx, data)
case viper.GetString("key") != "":
return inputKey(ctx, data)
default:
return nil, errors.New("must supply account, key, or pre-constructed JSON")
}
}
func inputJSON(ctx context.Context, data *dataIn) (*dataIn, error) {
validatorData := &util.ValidatorExitData{}
err := json.Unmarshal([]byte(viper.GetString("exit")), validatorData)
if err != nil {
return nil, err
}
data.signedVoluntaryExit = validatorData.Exit
return inputChainData(ctx, data)
}
func inputAccount(ctx context.Context, data *dataIn) (*dataIn, error) {
var err error
_, data.account, err = util.WalletAndAccountFromInput(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain acount")
}
return inputChainData(ctx, data)
}
func inputKey(ctx context.Context, data *dataIn) (*dataIn, error) {
privKeyBytes, err := hex.DecodeString(strings.TrimPrefix(viper.GetString("key"), "0x"))
if err != nil {
return nil, errors.Wrap(err, "failed to decode key")
}
data.account, err = util.NewScratchAccount(privKeyBytes, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create acount from key")
}
if err := data.account.(e2wtypes.AccountLocker).Unlock(ctx, nil); err != nil {
return nil, errors.Wrap(err, "failed to unlock account")
}
return inputChainData(ctx, data)
}
func inputChainData(ctx context.Context, data *dataIn) (*dataIn, error) {
var err error
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
if err != nil {
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
}
// Current fork.
data.fork, err = data.eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
if err != nil {
return nil, errors.Wrap(err, "failed to connect to obtain fork information")
}
// Calculate current epoch.
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect to obtain configuration information")
}
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to connect to obtain genesis information")
}
data.currentEpoch = spec.Epoch(uint64(time.Since(genesis.GenesisTime).Seconds()) / (uint64(config["SECONDS_PER_SLOT"].(time.Duration).Seconds()) * config["SLOTS_PER_EPOCH"].(uint64)))
// Epoch.
if viper.GetInt64("epoch") == -1 {
data.epoch = data.currentEpoch
} else {
data.epoch = spec.Epoch(viper.GetUint64("epoch"))
}
// Domain.
domain, err := data.eth2Client.(eth2client.DomainProvider).Domain(ctx, config["DOMAIN_VOLUNTARY_EXIT"].(spec.DomainType), data.epoch)
if err != nil {
return nil, errors.New("failed to calculate domain")
}
data.domain = domain
return data, nil
}

View File

@@ -1,193 +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 validatorexit
import (
"context"
"os"
"testing"
"time"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/wealdtech/ethdo/testutil"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wallet "github.com/wealdtech/go-eth2-wallet"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
func TestInput(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
require.NoError(t, e2types.InitBLS())
store := scratch.New()
require.NoError(t, e2wallet.UseStore(store))
testWallet, err := nd.CreateWallet(context.Background(), "Test wallet", store, keystorev4.New())
require.NoError(t, err)
require.NoError(t, testWallet.(e2wtypes.WalletLocker).Unlock(context.Background(), nil))
viper.Set("passphrase", "pass")
_, err = testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
"Interop 0",
testutil.HexToBytes("0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866"),
[]byte("pass"),
)
require.NoError(t, err)
tests := []struct {
name string
vars map[string]interface{}
res *dataIn
err string
}{
{
name: "TimeoutMissing",
vars: map[string]interface{}{
"account": "Test wallet",
"wallet-passphrase": "ce%NohGhah4ye5ra",
"type": "nd",
},
err: "timeout is required",
},
{
name: "NoMethod",
vars: map[string]interface{}{
"timeout": "5s",
},
err: "must supply account, key, or pre-constructed JSON",
},
{
name: "KeyInvalid",
vars: map[string]interface{}{
"timeout": "5s",
"key": "0xinvalid",
},
err: "failed to decode key: encoding/hex: invalid byte: U+0069 'i'",
},
{
name: "KeyBad",
vars: map[string]interface{}{
"timeout": "5s",
"key": "0x00",
},
err: "failed to create acount from key: private key must be 32 bytes",
},
{
name: "KeyGood",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
},
res: &dataIn{
timeout: 5 * time.Second,
},
},
{
name: "AccountUnknown",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"account": "Test wallet/unknown",
},
res: &dataIn{
timeout: 5 * time.Second,
},
err: "failed to obtain acount: failed to obtain account: no account with name \"unknown\"",
},
{
name: "AccountGood",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"account": "Test wallet/Interop 0",
},
res: &dataIn{
timeout: 5 * time.Second,
},
},
{
name: "JSONInvalid",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"exit": `invalid`,
},
res: &dataIn{
timeout: 5 * time.Second,
},
err: "invalid character 'i' looking for beginning of value",
},
{
name: "JSONGood",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"exit": `{"exit":{"message":{"epoch":"123","validator_index":"456"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"fork_version":"0x00002009"}`,
},
res: &dataIn{
timeout: 5 * time.Second,
},
},
{
name: "ClientBad",
vars: map[string]interface{}{
"connection": "localhost:1",
"allow-insecure-connections": true,
"timeout": "5s",
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
},
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",
vars: map[string]interface{}{
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
"allow-insecure-connections": true,
"timeout": "5s",
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
"epoch": "123",
},
res: &dataIn{
timeout: 5 * time.Second,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
viper.Reset()
for k, v := range test.vars {
viper.Set(k, v)
}
res, err := input(context.Background())
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.res.timeout, res.timeout)
}
})
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019, 2020 Weald Technology Trading
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -16,42 +16,35 @@ package validatorexit
import (
"context"
"encoding/json"
"fmt"
"os"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/wealdtech/ethdo/util"
)
type dataOut struct {
jsonOutput bool
forkVersion spec.Version
signedVoluntaryExit *spec.SignedVoluntaryExit
}
func output(ctx context.Context, data *dataOut) (string, error) {
if data == nil {
return "", errors.New("no data")
//nolint:unparam
func (c *command) output(_ context.Context) (string, error) {
if c.quiet {
return "", nil
}
if data.signedVoluntaryExit == nil {
return "", errors.New("no signed voluntary exit")
if c.prepareOffline {
return fmt.Sprintf("%s generated", offlinePreparationFilename), nil
}
if data.jsonOutput {
return outputJSON(ctx, data)
if c.json || c.offline {
data, err := json.Marshal(c.signedOperation)
if err != nil {
return "", errors.Wrap(err, "failed to marshal signed operation")
}
if c.json {
return string(data), nil
}
if err := os.WriteFile(exitOperationFilename, data, 0600); err != nil {
return "", errors.Wrap(err, fmt.Sprintf("failed to write %s", exitOperationFilename))
}
return "", nil
}
return "", nil
}
func outputJSON(ctx context.Context, data *dataOut) (string, error) {
validatorExitData := &util.ValidatorExitData{
Exit: data.signedVoluntaryExit,
ForkVersion: data.forkVersion,
}
bytes, err := json.Marshal(validatorExitData)
if err != nil {
return "", errors.Wrap(err, "failed to generate JSON")
}
return string(bytes), nil
}

View File

@@ -1,97 +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 validatorexit
import (
"context"
"testing"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/stretchr/testify/require"
)
func TestOutput(t *testing.T) {
tests := []struct {
name string
dataOut *dataOut
res string
err string
}{
{
name: "Nil",
err: "no data",
},
{
name: "SignedVoluntaryExitNil",
dataOut: &dataOut{
jsonOutput: true,
},
err: "no signed voluntary exit",
},
{
name: "Good",
dataOut: &dataOut{
forkVersion: spec.Version{0x01, 0x02, 0x03, 0x04},
signedVoluntaryExit: &spec.SignedVoluntaryExit{
Message: &spec.VoluntaryExit{
Epoch: spec.Epoch(123),
ValidatorIndex: spec.ValidatorIndex(456),
},
Signature: spec.BLSSignature{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
},
},
},
},
{
name: "JSON",
dataOut: &dataOut{
jsonOutput: true,
forkVersion: spec.Version{0x01, 0x02, 0x03, 0x04},
signedVoluntaryExit: &spec.SignedVoluntaryExit{
Message: &spec.VoluntaryExit{
Epoch: spec.Epoch(123),
ValidatorIndex: spec.ValidatorIndex(456),
},
Signature: spec.BLSSignature{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
},
},
},
res: `{"exit":{"message":{"epoch":"123","validator_index":"456"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"fork_version":"0x01020304"}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res, err := output(context.Background(), test.dataOut)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.res, res)
}
})
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019, 2020 Weald Technology Trading
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -14,120 +14,526 @@
package validatorexit
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
consensusclient "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/prysmaticlabs/go-ssz"
"github.com/wealdtech/ethdo/beacon"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/signing"
"github.com/wealdtech/ethdo/util"
e2types "github.com/wealdtech/go-eth2-types/v2"
ethutil "github.com/wealdtech/go-eth2-util"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// maxFutureEpochs is the farthest in the future for which an exit will be created.
var maxFutureEpochs = spec.Epoch(1024)
// validatorPath is the regular expression that matches a validator path.
var validatorPath = regexp.MustCompile("^m/12381/3600/[0-9]+/0/0$")
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
if data == nil {
return nil, errors.New("no data")
var offlinePreparationFilename = "offline-preparation.json"
var exitOperationFilename = "exit-operation.json"
func (c *command) process(ctx context.Context) error {
if err := c.setup(ctx); err != nil {
return err
}
if data.epoch > data.currentEpoch {
if data.epoch-data.currentEpoch > maxFutureEpochs {
return nil, errors.New("not generating exit for an epoch in the far future")
if err := c.obtainChainInfo(ctx); err != nil {
return err
}
if c.prepareOffline {
return c.writeChainInfoToFile(ctx)
}
if err := c.generateDomain(ctx); err != nil {
return err
}
if err := c.obtainOperation(ctx); err != nil {
return err
}
if validated, reason := c.validateOperation(ctx); !validated {
return fmt.Errorf("operation failed validation: %s", reason)
}
if c.json || c.offline {
if c.debug {
fmt.Fprintf(os.Stderr, "Not broadcasting credentials change operations\n")
}
// Want JSON output, or cannot broadcast.
return nil
}
return c.broadcastOperation(ctx)
}
func (c *command) obtainOperation(ctx context.Context) error {
if (c.mnemonic == "" || c.path == "") && c.privateKey == "" && c.validator == "" {
// No input information; fetch the operation from a file.
err := c.obtainOperationFromFileOrInput(ctx)
if err == nil {
// Success.
return nil
}
if c.signedOperationInput != "" {
return errors.Wrap(err, "failed to obtain supplied signed operation")
}
return errors.Wrap(err, fmt.Sprintf("no account, mnemonic or private key specified, and no %s file loaded", exitOperationFilename))
}
if c.mnemonic != "" {
switch {
case c.path != "":
// Have a mnemonic and path.
return c.generateOperationFromMnemonicAndPath(ctx)
case c.validator != "":
// Have a mnemonic and validator.
return c.generateOperationFromMnemonicAndValidator(ctx)
default:
return errors.New("mnemonic must be supplied with either a path or validator")
}
}
results := &dataOut{
forkVersion: data.fork.CurrentVersion,
jsonOutput: data.jsonOutput,
if c.privateKey != "" {
return c.generateOperationFromPrivateKey(ctx)
}
validator, err := fetchValidator(ctx, data)
if c.validator != "" {
return c.generateOperationFromValidator(ctx)
}
return errors.New("unsupported combination of inputs; see help for details of supported combinations")
}
func (c *command) generateOperationFromMnemonicAndPath(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]*beacon.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 for a validator", 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
}
func (c *command) generateOperationFromMnemonicAndValidator(ctx context.Context) error {
seed, err := util.SeedFromMnemonic(c.mnemonic)
if err != nil {
return err
}
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, c.validator)
if err != nil {
return err
}
// Scan the keys from the seed to find the path.
maxDistance := 1024
// Start scanning the validator keys.
for i := 0; ; i++ {
if i == maxDistance {
if c.debug {
fmt.Fprintf(os.Stderr, "Gone %d indices without finding the validator, not scanning any further\n", maxDistance)
}
break
}
validatorKeyPath := fmt.Sprintf("m/12381/3600/%d/0/0", i)
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, validatorKeyPath)
if err != nil {
return errors.Wrap(err, "failed to generate validator private key")
}
validatorPubkey := validatorPrivkey.PublicKey().Marshal()
if bytes.Equal(validatorPubkey, validatorInfo.Pubkey[:]) {
validatorAccount, err := util.ParseAccount(ctx, c.mnemonic, []string{validatorKeyPath}, true)
if err != nil {
return errors.Wrap(err, "failed to create withdrawal account")
}
err = c.generateOperationFromAccount(ctx, validatorInfo, validatorAccount, c.chainInfo.Epoch)
if err != nil {
return err
}
break
}
}
return nil
}
func (c *command) generateOperationFromPrivateKey(ctx context.Context) error {
validatorAccount, err := util.ParseAccount(ctx, c.privateKey, nil, true)
if err != nil {
return errors.Wrap(err, "failed to create validator account")
}
validatorPubkey, err := util.BestPublicKey(validatorAccount)
if err != nil {
return err
}
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, fmt.Sprintf("%#x", validatorPubkey.Marshal()))
if err != nil {
return err
}
if c.verbose {
fmt.Fprintf(os.Stderr, "Validator %d found with public key %s\n", validatorInfo.Index, validatorPubkey)
}
if err = c.generateOperationFromAccount(ctx, validatorInfo, validatorAccount, c.chainInfo.Epoch); err != nil {
return err
}
return nil
}
func (c *command) generateOperationFromValidator(ctx context.Context) error {
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, c.validator)
if err != nil {
return err
}
validatorAccount, err := util.ParseAccount(ctx, c.validator, nil, true)
if err != nil {
return err
}
if err := c.generateOperationFromAccount(ctx, validatorInfo, validatorAccount, c.chainInfo.Epoch); err != nil {
return err
}
return nil
}
func (c *command) obtainOperationFromFileOrInput(ctx context.Context) error {
// Start off by attempting to use the provided signed operation.
if c.signedOperationInput != "" {
return c.obtainOperationFromInput(ctx)
}
// If not, read it from the file with the standard name.
return c.obtainOperationFromFile(ctx)
}
func (c *command) obtainOperationFromFile(ctx context.Context) error {
_, err := os.Stat(exitOperationFilename)
if err != nil {
return errors.Wrap(err, "failed to read exit operation file")
}
if c.debug {
fmt.Fprintf(os.Stderr, "%s found; loading operation\n", exitOperationFilename)
}
data, err := os.ReadFile(exitOperationFilename)
if err != nil {
return errors.Wrap(err, "failed to read exit operation file")
}
if err := json.Unmarshal(data, &c.signedOperation); err != nil {
return errors.Wrap(err, "failed to parse exit operation file")
}
if err := c.verifySignedOperation(ctx, c.signedOperation); err != nil {
return err
}
return nil
}
func (c *command) obtainOperationFromInput(ctx context.Context) error {
if !strings.HasPrefix(c.signedOperationInput, "{") {
// This looks like a file; read it in.
data, err := os.ReadFile(c.signedOperationInput)
if err != nil {
return errors.Wrap(err, "failed to read input file")
}
c.signedOperationInput = string(data)
}
if err := json.Unmarshal([]byte(c.signedOperationInput), &c.signedOperation); err != nil {
return errors.Wrap(err, "failed to parse exit operation input")
}
if err := c.verifySignedOperation(ctx, c.signedOperation); err != nil {
return err
}
return nil
}
func (c *command) generateOperationFromSeedAndPath(ctx context.Context,
validators map[string]*beacon.ValidatorInfo,
seed []byte,
path string,
) error {
validatorPrivkey, err := ethutil.PrivateKeyFromSeedAndPath(seed, path)
if err != nil {
return errors.Wrap(err, "failed to generate validator private key")
}
c.privateKey = fmt.Sprintf("%#x", validatorPrivkey.Marshal())
return c.generateOperationFromPrivateKey(ctx)
}
func (c *command) generateOperationFromAccount(ctx context.Context,
validator *beacon.ValidatorInfo,
account e2wtypes.Account,
epoch phase0.Epoch,
) error {
var err error
c.signedOperation, err = c.createSignedOperation(ctx, validator, account, epoch)
return err
}
func (c *command) createSignedOperation(ctx context.Context,
validator *beacon.ValidatorInfo,
account e2wtypes.Account,
epoch phase0.Epoch,
) (
*phase0.SignedVoluntaryExit,
error,
) {
pubkey, err := util.BestPublicKey(account)
if err != nil {
return nil, err
}
exit, err := generateExit(ctx, data, validator)
if err != nil {
return nil, errors.Wrap(err, "failed to generate voluntary exit")
}
root, err := exit.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to generate root for voluntary exit")
if c.debug {
fmt.Fprintf(os.Stderr, "Using %#x as best public key for %s\n", pubkey.Marshal(), account.Name())
}
blsPubkey := phase0.BLSPubKey{}
copy(blsPubkey[:], pubkey.Marshal())
if data.account != nil {
signature, err := signing.SignRoot(ctx, data.account, data.passphrases, root, data.domain)
if err != nil {
return nil, errors.Wrap(err, "failed to sign voluntary exit")
}
results.signedVoluntaryExit = &spec.SignedVoluntaryExit{
Message: exit,
Signature: signature,
}
} else {
results.signedVoluntaryExit = data.signedVoluntaryExit
}
if !data.jsonOutput {
if err := broadcastExit(ctx, data, results); err != nil {
return nil, errors.Wrap(err, "failed to broadcast voluntary exit")
}
}
return results, nil
}
func generateExit(ctx context.Context, data *dataIn, validator *api.Validator) (*spec.VoluntaryExit, error) {
if data == nil {
return nil, errors.New("no data")
}
if data.signedVoluntaryExit != nil {
return data.signedVoluntaryExit.Message, nil
}
if validator == nil {
return nil, errors.New("no validator")
}
exit := &spec.VoluntaryExit{
Epoch: data.epoch,
operation := &phase0.VoluntaryExit{
Epoch: epoch,
ValidatorIndex: validator.Index,
}
return exit, nil
}
func broadcastExit(ctx context.Context, data *dataIn, results *dataOut) error {
return data.eth2Client.(eth2client.VoluntaryExitSubmitter).SubmitVoluntaryExit(ctx, results.signedVoluntaryExit)
}
func fetchValidator(ctx context.Context, data *dataIn) (*api.Validator, error) {
// Validator.
if data.account == nil {
return nil, nil
}
var validator *api.Validator
validatorPubKeys := make([]spec.BLSPubKey, 1)
pubKey, err := util.BestPublicKey(data.account)
root, err := operation.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "failed to obtain public key for account")
return nil, errors.Wrap(err, "failed to generate root for exit operation")
}
copy(validatorPubKeys[0][:], pubKey.Marshal())
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", validatorPubKeys)
// Sign the operation.
if c.debug {
fmt.Fprintf(os.Stderr, "Signing %#x with domain %#x by public key %#x\n", root, c.domain, account.PublicKey().Marshal())
}
signature, err := signing.SignRoot(ctx, account, nil, root, c.domain)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validator from beacon node")
return nil, errors.Wrap(err, "failed to sign exit operation")
}
if len(validators) == 0 {
return nil, errors.New("validator not known by beacon node")
}
for _, v := range validators {
validator = v
}
if validator.Status != api.ValidatorStateActiveOngoing {
return nil, errors.New("validator is not active; cannot exit")
}
return validator, nil
return &phase0.SignedVoluntaryExit{
Message: operation,
Signature: signature,
}, nil
}
func (c *command) verifySignedOperation(ctx context.Context, op *phase0.SignedVoluntaryExit) error {
root, err := op.Message.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "failed to generate message root")
}
sigBytes := make([]byte, len(op.Signature))
copy(sigBytes, op.Signature[:])
sig, err := e2types.BLSSignatureFromBytes(sigBytes)
if err != nil {
if c.verbose {
fmt.Fprintf(os.Stderr, "Invalid signature: %v\n", err.Error())
}
return errors.New("invalid signature")
}
container := &phase0.SigningData{
ObjectRoot: root,
Domain: c.domain,
}
signingRoot, err := ssz.HashTreeRoot(container)
if err != nil {
return errors.Wrap(err, "failed to generate signing root")
}
validatorInfo, err := c.chainInfo.FetchValidatorInfo(ctx, fmt.Sprintf("%d", op.Message.ValidatorIndex))
if err != nil {
return err
}
pubkeyBytes := make([]byte, len(validatorInfo.Pubkey[:]))
copy(pubkeyBytes, validatorInfo.Pubkey[:])
pubkey, err := e2types.BLSPublicKeyFromBytes(pubkeyBytes)
if err != nil {
return errors.Wrap(err, "invalid public key")
}
if !sig.Verify(signingRoot[:], pubkey) {
return errors.New("signature does not verify")
}
return nil
}
func (c *command) validateOperation(_ context.Context,
) (
bool,
string,
) {
var validatorInfo *beacon.ValidatorInfo
for _, chainValidatorInfo := range c.chainInfo.Validators {
if chainValidatorInfo.Index == c.signedOperation.Message.ValidatorIndex {
validatorInfo = chainValidatorInfo
break
}
}
if validatorInfo == nil {
return false, "validator not known on chain"
}
if c.debug {
fmt.Fprintf(os.Stderr, "Validator exit operation: %v", c.signedOperation)
fmt.Fprintf(os.Stderr, "On-chain validator info: %v\n", validatorInfo)
}
if validatorInfo.State == apiv1.ValidatorStateActiveExiting ||
validatorInfo.State == apiv1.ValidatorStateActiveSlashed ||
validatorInfo.State == apiv1.ValidatorStateExitedUnslashed ||
validatorInfo.State == apiv1.ValidatorStateExitedSlashed ||
validatorInfo.State == apiv1.ValidatorStateWithdrawalPossible ||
validatorInfo.State == apiv1.ValidatorStateWithdrawalDone {
return false, fmt.Sprintf("validator is in state %v, not suitable to generate an exit", validatorInfo.State)
}
return true, ""
}
func (c *command) broadcastOperation(ctx context.Context) error {
return c.consensusClient.(consensusclient.VoluntaryExitSubmitter).SubmitVoluntaryExit(ctx, c.signedOperation)
}
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.WithSpecProvider(c.consensusClient.(consensusclient.SpecProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to create chaintime service")
}
return nil
}
func (c *command) generateDomain(ctx context.Context) error {
genesisValidatorsRoot, err := c.obtainGenesisValidatorsRoot(ctx)
if err != nil {
return err
}
forkVersion, err := c.obtainForkVersion(ctx)
if err != nil {
return err
}
root, err := (&phase0.ForkData{
CurrentVersion: forkVersion,
GenesisValidatorsRoot: genesisValidatorsRoot,
}).HashTreeRoot()
if err != nil {
return errors.Wrap(err, "failed to calculate signature domain")
}
copy(c.domain[:], c.chainInfo.BLSToExecutionChangeDomainType[:])
copy(c.domain[4:], root[:])
if c.debug {
fmt.Fprintf(os.Stderr, "Domain is %#x\n", c.domain)
}
return nil
}
func (c *command) obtainGenesisValidatorsRoot(ctx context.Context) (phase0.Root, error) {
genesisValidatorsRoot := phase0.Root{}
if c.genesisValidatorsRoot != "" {
if c.debug {
fmt.Fprintf(os.Stderr, "Genesis validators root supplied on the command line\n")
}
root, err := hex.DecodeString(strings.TrimPrefix(c.genesisValidatorsRoot, "0x"))
if err != nil {
return phase0.Root{}, errors.Wrap(err, "invalid genesis validators root supplied")
}
if len(root) != phase0.RootLength {
return phase0.Root{}, errors.New("invalid length for genesis validators root")
}
copy(genesisValidatorsRoot[:], root)
} else {
if c.debug {
fmt.Fprintf(os.Stderr, "Genesis validators root obtained from chain info\n")
}
copy(genesisValidatorsRoot[:], c.chainInfo.GenesisValidatorsRoot[:])
}
if c.debug {
fmt.Fprintf(os.Stderr, "Using genesis validators root %#x\n", genesisValidatorsRoot)
}
return genesisValidatorsRoot, nil
}
func (c *command) obtainForkVersion(ctx context.Context) (phase0.Version, error) {
forkVersion := phase0.Version{}
if c.forkVersion != "" {
if c.debug {
fmt.Fprintf(os.Stderr, "Fork version supplied on the command line\n")
}
version, err := hex.DecodeString(strings.TrimPrefix(c.forkVersion, "0x"))
if err != nil {
return phase0.Version{}, errors.Wrap(err, "invalid fork version supplied")
}
if len(version) != phase0.ForkVersionLength {
return phase0.Version{}, errors.New("invalid length for fork version")
}
copy(forkVersion[:], version)
} else {
if c.debug {
fmt.Fprintf(os.Stderr, "Fork version obtained from chain info\n")
}
// Use the current fork version for generating an exit as per the spec.
copy(forkVersion[:], c.chainInfo.CurrentForkVersion[:])
}
if c.debug {
fmt.Fprintf(os.Stderr, "Using fork version %#x\n", forkVersion)
}
return forkVersion, nil
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019, 2020 Weald Technology Trading
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -15,215 +15,467 @@ package validatorexit
import (
"context"
"os"
"fmt"
"testing"
"time"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/auto"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/spf13/viper"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/stretchr/testify/require"
"github.com/wealdtech/ethdo/testutil"
"github.com/wealdtech/ethdo/beacon"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wallet "github.com/wealdtech/go-eth2-wallet"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
func TestProcess(t *testing.T) {
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
}
func TestGenerateOperationFromMnemonicAndPath(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
eth2Client, err := auto.New(context.Background(),
auto.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
)
require.NoError(t, err)
store := scratch.New()
require.NoError(t, e2wallet.UseStore(store))
testWallet, err := nd.CreateWallet(context.Background(), "Test wallet", store, keystorev4.New())
require.NoError(t, err)
require.NoError(t, testWallet.(e2wtypes.WalletLocker).Unlock(context.Background(), nil))
viper.Set("passphrase", "pass")
interop0, err := testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
"Interop 0",
testutil.HexToBytes("0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866"),
[]byte("pass"),
)
require.NoError(t, err)
// activeValidator := &api.Validator{
// Index: 123,
// Balance: 32123456789,
// Status: api.ValidatorStateActiveOngoing,
// Validator: &spec.Validator{
// PublicKey: testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
// WithdrawalCredentials: nil,
// EffectiveBalance: 32000000000,
// Slashed: false,
// ActivationEligibilityEpoch: 0,
// ActivationEpoch: 0,
// ExitEpoch: 0,
// WithdrawableEpoch: 0,
// },
// }
epochFork := &spec.Fork{
PreviousVersion: spec.Version{0x00, 0x00, 0x00, 0x00},
CurrentVersion: spec.Version{0x00, 0x00, 0x00, 0x00},
Epoch: 0,
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
dataIn *dataIn
err string
name string
command *command
expected *phase0.SignedVoluntaryExit
err string
}{
{
name: "Nil",
err: "no data",
},
{
name: "EpochTooLate",
dataIn: &dataIn{
timeout: 5 * time.Second,
eth2Client: eth2Client,
fork: epochFork,
currentEpoch: 10,
account: interop0,
passphrases: []string{"pass"},
epoch: 9999999,
domain: spec.Domain{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
name: "MnemonicInvalid",
command: &command{
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon",
path: "m/12381/3600/0/0/0",
chainInfo: chainInfo,
},
err: "not generating exit for an epoch in the far future",
err: "mnemonic is invalid",
},
{
name: "AccountUnknown",
dataIn: &dataIn{
timeout: 5 * time.Second,
eth2Client: eth2Client,
fork: epochFork,
currentEpoch: 10,
account: interop0,
passphrases: []string{"pass"},
epoch: 10,
domain: spec.Domain{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
name: "PathInvalid",
command: &command{
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: "m/12381/3600/0/0",
chainInfo: chainInfo,
},
err: "validator not known by beacon node",
},
// {
// name: "Good",
// dataIn: &dataIn{
// timeout: 5 * time.Second,
// eth2Client: eth2Client,
// fork: epochFork,
// currentEpoch: 10,
// account: interop0,
// passphrases: []string{"pass"},
// epoch: 10,
// domain: spec.Domain{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
// },
// },
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := process(context.Background(), test.dataIn)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestGenerateExit(t *testing.T) {
activeValidator := &api.Validator{
Index: 123,
Balance: 32123456789,
Status: api.ValidatorStateActiveOngoing,
Validator: &spec.Validator{
PublicKey: testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
WithdrawalCredentials: nil,
EffectiveBalance: 32000000000,
Slashed: false,
ActivationEligibilityEpoch: 0,
ActivationEpoch: 0,
ExitEpoch: 0,
WithdrawableEpoch: 0,
},
}
tests := []struct {
name string
validator *api.Validator
dataIn *dataIn
err string
}{
{
name: "Nil",
err: "no data",
},
{
name: "SignedVoluntaryExitGood",
dataIn: &dataIn{
signedVoluntaryExit: &spec.SignedVoluntaryExit{
Message: &spec.VoluntaryExit{
Epoch: spec.Epoch(123),
ValidatorIndex: spec.ValidatorIndex(456),
},
Signature: spec.BLSSignature{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
},
},
},
},
{
name: "ValidatorMissing",
dataIn: &dataIn{},
err: "no validator",
},
{
name: "ValidatorGood",
dataIn: &dataIn{},
validator: activeValidator,
err: "path m/12381/3600/0/0 does not match EIP-2334 format for a validator",
},
{
name: "Good",
dataIn: &dataIn{
signedVoluntaryExit: &spec.SignedVoluntaryExit{
Message: &spec.VoluntaryExit{
Epoch: spec.Epoch(123),
ValidatorIndex: spec.ValidatorIndex(456),
},
Signature: spec.BLSSignature{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
},
},
command: &command{
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: "m/12381/3600/0/0/0",
chainInfo: chainInfo,
},
expected: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x89, 0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
validator: activeValidator,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := generateExit(context.Background(), test.dataIn, test.validator)
err := test.command.generateOperationFromMnemonicAndPath(ctx)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, test.command.signedOperation)
}
})
}
}
func TestGenerateOperationFromMnemonicAndValidator(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
command *command
expected *phase0.SignedVoluntaryExit
err string
}{
{
name: "MnemonicInvalid",
command: &command{
mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon",
validator: "0",
chainInfo: chainInfo,
},
err: "mnemonic is invalid",
},
{
name: "ValidatorMissing",
command: &command{
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",
chainInfo: chainInfo,
},
err: "no validator specified",
},
{
name: "Good",
command: &command{
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",
validator: "0",
chainInfo: chainInfo,
},
expected: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x89, 0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
},
{
name: "GoodPubkey",
command: &command{
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",
validator: "0xb384f767d964e100c8a9b21018d08c25ffebae268b3ab6d610353897541971726dbfc3c7463884c68a531515aab94c87",
chainInfo: chainInfo,
},
expected: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x89, 0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.generateOperationFromMnemonicAndValidator(ctx)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, test.command.signedOperation)
}
})
}
}
func TestGenerateOperationFromSeedAndPath(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
{
Index: 2,
Pubkey: phase0.BLSPubKey{0xaf, 0x9c, 0xe4, 0x4f, 0x50, 0x14, 0x8d, 0xb4, 0x12, 0x19, 0x4a, 0xf0, 0xba, 0xf0, 0xba, 0xb3, 0x6b, 0xd5, 0xc3, 0xe0, 0xc4, 0x93, 0x89, 0x11, 0xa4, 0xe5, 0x02, 0xe3, 0x98, 0xb5, 0x9e, 0x5c, 0xca, 0x7c, 0x78, 0xe3, 0xfe, 0x03, 0x41, 0x95, 0x47, 0x88, 0x79, 0xee, 0xb2, 0x3d, 0xb0, 0xa6},
WithdrawalCredentials: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
{
Index: 3,
Pubkey: phase0.BLSPubKey{0x86, 0xd3, 0x30, 0xaf, 0x51, 0xfa, 0x59, 0x3f, 0xa9, 0xf9, 0x3e, 0xdb, 0x9d, 0x16, 0x64, 0x01, 0x86, 0xbe, 0x2e, 0x93, 0xea, 0x94, 0xd2, 0x59, 0x78, 0x1e, 0x1e, 0xb3, 0x4d, 0xeb, 0x84, 0x4c, 0x39, 0x68, 0xd7, 0x5e, 0xa9, 0x1d, 0x19, 0xf1, 0x59, 0xdb, 0xd0, 0x52, 0x3c, 0x6c, 0x5b, 0xa5},
WithdrawalCredentials: []byte{0x00, 0x81, 0x68, 0x45, 0x6b, 0x6d, 0x9a, 0x32, 0x83, 0x93, 0x1f, 0xea, 0x52, 0x10, 0xda, 0x12, 0x2d, 0x1e, 0x65, 0xe8, 0xed, 0x50, 0xb8, 0xe8, 0xf5, 0x91, 0x11, 0x83, 0xb0, 0x2f, 0xd1, 0x25},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
validators := make(map[string]*beacon.ValidatorInfo, len(chainInfo.Validators))
for i := range chainInfo.Validators {
validators[fmt.Sprintf("%#x", chainInfo.Validators[i].Pubkey)] = chainInfo.Validators[i]
}
tests := []struct {
name string
command *command
seed []byte
path string
err string
expected *phase0.SignedVoluntaryExit
}{
{
name: "PathInvalid",
command: &command{
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",
chainInfo: chainInfo,
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "invalid",
err: "failed to generate validator private key: not master at path component 0",
},
{
name: "ValidatorUnknown",
command: &command{
chainInfo: chainInfo,
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/999/0/0",
err: "unknown validator",
},
// {
// name: "ValidatorAlreadyExited",
// command: &command{
// chainInfo: chainInfo,
// },
// seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
// path: "m/12381/3600/2/0/0",
// },
{
name: "GoodPath0",
command: &command{
chainInfo: chainInfo,
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/0/0/0",
expected: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x89, 0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
},
{
name: "GoodPath3",
command: &command{
chainInfo: chainInfo,
},
seed: []byte{0x40, 0x8b, 0x28, 0x5c, 0x12, 0x38, 0x36, 0x00, 0x4f, 0x4b, 0x88, 0x42, 0xc8, 0x93, 0x24, 0xc1, 0xf0, 0x13, 0x82, 0x45, 0x0c, 0x0d, 0x43, 0x9a, 0xf3, 0x45, 0xba, 0x7f, 0xc4, 0x9a, 0xcf, 0x70, 0x54, 0x89, 0xc6, 0xfc, 0x77, 0xdb, 0xd4, 0xe3, 0xdc, 0x1d, 0xd8, 0xcc, 0x6b, 0xc9, 0xf0, 0x43, 0xdb, 0x8a, 0xda, 0x1e, 0x24, 0x3c, 0x4a, 0x0e, 0xaf, 0xb2, 0x90, 0xd3, 0x99, 0x48, 0x08, 0x40},
path: "m/12381/3600/3/0/0",
expected: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 3,
},
Signature: phase0.BLSSignature{0x99, 0x78, 0xb4, 0x9c, 0x21, 0x60, 0x3f, 0x04, 0xa3, 0x04, 0x4e, 0x4c, 0x49, 0x0c, 0xb4, 0x68, 0x7c, 0x6e, 0x14, 0xc2, 0xda, 0xed, 0x25, 0x92, 0xe0, 0x02, 0x2d, 0xcd, 0x63, 0xeb, 0xe7, 0x4a, 0xf1, 0x1a, 0xca, 0xba, 0xae, 0x50, 0xe1, 0x8a, 0x1d, 0xae, 0x96, 0xd9, 0xd2, 0x56, 0xbf, 0x9f, 0x02, 0x48, 0x85, 0x05, 0xc1, 0xfb, 0xb3, 0x4a, 0x0b, 0x68, 0xec, 0xc5, 0xb5, 0xf5, 0xea, 0x53, 0xdb, 0xd0, 0x09, 0x08, 0xe3, 0x1e, 0xa8, 0xca, 0x9d, 0x02, 0x08, 0x3b, 0x9e, 0xf1, 0xc7, 0xd2, 0x32, 0xf4, 0xba, 0xd9, 0xea, 0x56, 0x4b, 0xc5, 0x87, 0xd5, 0x27, 0xb7, 0x74, 0x97, 0x8a, 0xee},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.generateOperationFromSeedAndPath(ctx, validators, test.seed, test.path)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, test.command.signedOperation)
}
})
}
}
func TestVerifyOperation(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
command *command
err string
}{
{
name: "SignatureMissing",
command: &command{
chainInfo: chainInfo,
signedOperation: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
},
},
err: "invalid signature",
},
{
name: "SignatureShort",
command: &command{
chainInfo: chainInfo,
signedOperation: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
},
err: "invalid signature",
},
{
name: "SignatureIncorrect",
command: &command{
chainInfo: chainInfo,
signedOperation: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x99, 0x78, 0xb4, 0x9c, 0x21, 0x60, 0x3f, 0x04, 0xa3, 0x04, 0x4e, 0x4c, 0x49, 0x0c, 0xb4, 0x68, 0x7c, 0x6e, 0x14, 0xc2, 0xda, 0xed, 0x25, 0x92, 0xe0, 0x02, 0x2d, 0xcd, 0x63, 0xeb, 0xe7, 0x4a, 0xf1, 0x1a, 0xca, 0xba, 0xae, 0x50, 0xe1, 0x8a, 0x1d, 0xae, 0x96, 0xd9, 0xd2, 0x56, 0xbf, 0x9f, 0x02, 0x48, 0x85, 0x05, 0xc1, 0xfb, 0xb3, 0x4a, 0x0b, 0x68, 0xec, 0xc5, 0xb5, 0xf5, 0xea, 0x53, 0xdb, 0xd0, 0x09, 0x08, 0xe3, 0x1e, 0xa8, 0xca, 0x9d, 0x02, 0x08, 0x3b, 0x9e, 0xf1, 0xc7, 0xd2, 0x32, 0xf4, 0xba, 0xd9, 0xea, 0x56, 0x4b, 0xc5, 0x87, 0xd5, 0x27, 0xb7, 0x74, 0x97, 0x8a, 0xee},
},
},
err: "signature does not verify",
},
{
name: "Good",
command: &command{
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",
validator: "0",
chainInfo: chainInfo,
signedOperation: &phase0.SignedVoluntaryExit{
Message: &phase0.VoluntaryExit{
Epoch: 1,
ValidatorIndex: 0,
},
Signature: phase0.BLSSignature{0x89, 0xf5, 0xc4, 0x42, 0x88, 0xf9, 0x5e, 0x19, 0xb6, 0xc1, 0x39, 0xf2, 0x62, 0x30, 0x05, 0x66, 0x5b, 0x98, 0x34, 0x62, 0xa2, 0x28, 0x12, 0x09, 0x77, 0xd8, 0x1f, 0x2e, 0xf5, 0x47, 0x56, 0x0b, 0xe2, 0x24, 0x46, 0xde, 0x21, 0xa8, 0xa9, 0x37, 0xd9, 0xdd, 0xa4, 0xe2, 0xd2, 0xec, 0x41, 0x75, 0x19, 0x64, 0x96, 0xcd, 0xd1, 0x30, 0x6d, 0xec, 0x4a, 0x12, 0x5f, 0x8c, 0x86, 0x1f, 0x80, 0x61, 0x71, 0x50, 0x4a, 0x9d, 0x6a, 0x61, 0x0e, 0xc4, 0xe1, 0x35, 0x04, 0x7e, 0x4f, 0xb6, 0x70, 0x52, 0xec, 0xc4, 0x56, 0x13, 0x60, 0xd0, 0xc3, 0xde, 0x04, 0xb6, 0xfb, 0xc4, 0x47, 0x42, 0x23, 0xff},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.verifySignedOperation(ctx, test.command.signedOperation)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestObtainOperationFromInput(t *testing.T) {
ctx := context.Background()
require.NoError(t, e2types.InitBLS())
chainInfo := &beacon.ChainInfo{
Version: 1,
Validators: []*beacon.ValidatorInfo{
{
Index: 0,
Pubkey: phase0.BLSPubKey{0xb3, 0x84, 0xf7, 0x67, 0xd9, 0x64, 0xe1, 0x00, 0xc8, 0xa9, 0xb2, 0x10, 0x18, 0xd0, 0x8c, 0x25, 0xff, 0xeb, 0xae, 0x26, 0x8b, 0x3a, 0xb6, 0xd6, 0x10, 0x35, 0x38, 0x97, 0x54, 0x19, 0x71, 0x72, 0x6d, 0xbf, 0xc3, 0xc7, 0x46, 0x38, 0x84, 0xc6, 0x8a, 0x53, 0x15, 0x15, 0xaa, 0xb9, 0x4c, 0x87},
WithdrawalCredentials: []byte{0x00, 0x8b, 0xa1, 0xcc, 0x4b, 0x09, 0x1b, 0x91, 0xc1, 0x20, 0x2b, 0xba, 0x3f, 0x50, 0x80, 0x75, 0xd6, 0xff, 0x56, 0x5c, 0x77, 0xe5, 0x59, 0xf0, 0x80, 0x3c, 0x07, 0x92, 0xe0, 0x30, 0x2b, 0xf1},
},
{
Index: 1,
Pubkey: phase0.BLSPubKey{0xb3, 0xd8, 0x9e, 0x2f, 0x29, 0xc7, 0x12, 0xc6, 0xa9, 0xf8, 0xe5, 0xa2, 0x69, 0xb9, 0x76, 0x17, 0xc4, 0xa9, 0x4d, 0xd6, 0xf6, 0x66, 0x2a, 0xb3, 0xb0, 0x7c, 0xe9, 0xe5, 0x43, 0x45, 0x73, 0xf1, 0x5b, 0x5c, 0x98, 0x8c, 0xd1, 0x4b, 0xbd, 0x58, 0x04, 0xf7, 0x71, 0x56, 0xa8, 0xaf, 0x1c, 0xfa},
WithdrawalCredentials: []byte{0x00, 0x78, 0x6c, 0xb0, 0x2e, 0xd2, 0x8e, 0x5f, 0xbb, 0x1f, 0x7f, 0x9e, 0x93, 0x1a, 0x2b, 0x72, 0x69, 0x29, 0x06, 0xe6, 0xb1, 0x2c, 0xe4, 0x64, 0x39, 0x75, 0xe3, 0x2b, 0x51, 0x76, 0x91, 0xf2},
},
},
GenesisValidatorsRoot: phase0.Root{},
Epoch: 1,
CurrentForkVersion: phase0.Version{},
}
tests := []struct {
name string
command *command
err string
}{
{
name: "InvalidFilename",
command: &command{
signedOperationInput: `[]`,
chainInfo: chainInfo,
},
err: "failed to read input file: open []: no such file or directory",
},
{
name: "InvalidJSON",
command: &command{
signedOperationInput: `{invalid}`,
chainInfo: chainInfo,
},
err: "failed to parse exit operation input: invalid character 'i' looking for beginning of object key string",
},
{
name: "Unverifable",
command: &command{
signedOperationInput: `{"message":{"epoch":"1","validator_index":"0"},"signature":"0x9978b49c21603f04a3044e4c490cb4687c6e14c2daed2592e0022dcd63ebe74af11acabaae50e18a1dae96d9d256bf9f02488505c1fbb34a0b68ecc5b5f5ea53dbd00908e31ea8ca9d02083b9ef1c7d232f4bad9ea564bc587d527b774978aee"}`,
chainInfo: chainInfo,
},
err: "signature does not verify",
},
{
name: "Good",
command: &command{
signedOperationInput: `{"message":{"epoch":"1","validator_index":"0"},"signature":"0x89f5c44288f95e19b6c139f2623005665b983462a228120977d81f2ef547560be22446de21a8a937d9dda4e2d2ec4175196496cdd1306dec4a125f8c861f806171504a9d6a610ec4e135047e4fb67052ecc4561360d0c3de04b6fbc4474223ff"}`,
chainInfo: chainInfo,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.command.obtainOperationFromInput(ctx)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {

View File

@@ -1,4 +1,4 @@
// Copyright © 2019, 2020 Weald Technology Trading
// Copyright © 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -21,19 +21,19 @@ import (
"github.com/spf13/viper"
)
// Run runs the wallet create data command.
// Run runs the command.
func Run(cmd *cobra.Command) (string, error) {
ctx := context.Background()
dataIn, err := input(ctx)
c, err := newCommand(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain input")
return "", errors.Wrap(err, "failed to set up command")
}
// Further errors do not need a usage report.
cmd.SilenceUsage = true
dataOut, err := process(ctx, dataIn)
if err != nil {
if err := c.process(ctx); err != nil {
return "", errors.Wrap(err, "failed to process")
}
@@ -41,7 +41,7 @@ func Run(cmd *cobra.Command) (string, error) {
return "", nil
}
results, err := output(ctx, dataOut)
results, err := c.output(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to obtain output")
}

View File

@@ -130,7 +130,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -384,7 +384,6 @@ func (c *command) setup(ctx context.Context) error {
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 {

View File

@@ -119,7 +119,6 @@ func (c *command) setup(ctx context.Context) error {
if c.validators == "" {
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 {

View File

@@ -60,7 +60,7 @@ func init() {
validatorCredentialsSetCmd.Flags().String("validator", "", "Validator for which to set validator credentials")
validatorCredentialsSetCmd.Flags().String("withdrawal-account", "", "Account with which the validator's withdrawal credentials were set")
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().String("signed-operations", "", "Use pre-defined JSON signed operation as created by --json to transmit the credentials change operation (reads from change-operations.json if not present)")
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 (overrides fetching from beacon node)")
@@ -74,7 +74,7 @@ func validatorCredentialsSetBindings() {
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 {
if err := viper.BindPFlag("signed-operations", validatorCredentialsSetCmd.Flags().Lookup("signed-operations")); err != nil {
panic(err)
}
if err := viper.BindPFlag("withdrawal-account", validatorCredentialsSetCmd.Flags().Lookup("withdrawal-account")); err != nil {

View File

@@ -26,9 +26,16 @@ var validatorExitCmd = &cobra.Command{
Short: "Send an exit request for a validator",
Long: `Send an exit request for a validator. For example:
ethdo validator exit --account=primary/validator --passphrase=secret
ethdo validator exit --validator=12345
In quiet mode this will return 0 if the transaction has been generated, otherwise 1.`,
The validator and key can be specified in one of a number of ways:
- mnemonic and path to the validator using --mnemonic and --path
- mnemonic and validator index or public key using --mnemonic and --validator
- validator private key using --private-key
- validator account using --validator
In quiet mode this will return 0 if the exit operation has been generated (and successfully broadcast if online), otherwise 1.`,
RunE: func(cmd *cobra.Command, args []string) error {
res, err := validatorexit.Run(cmd)
if err != nil {
@@ -48,22 +55,38 @@ func init() {
validatorCmd.AddCommand(validatorExitCmd)
validatorFlags(validatorExitCmd)
validatorExitCmd.Flags().Int64("epoch", -1, "Epoch at which to exit (defaults to current epoch)")
validatorExitCmd.Flags().String("key", "", "Private key if validator not known by ethdo")
validatorExitCmd.Flags().String("exit", "", "Use pre-defined JSON data as created by --json to exit")
validatorExitCmd.Flags().Bool("json", false, "Generate JSON data for an exit; do not broadcast to network")
validatorExitCmd.Flags().Bool("prepare-offline", false, "Create files for offline use")
validatorExitCmd.Flags().String("validator", "", "Validator to exit")
validatorExitCmd.Flags().String("signed-operation", "", "Use pre-defined JSON signed operation as created by --json to transmit the exit operation (reads from exit-operations.json if not present)")
validatorExitCmd.Flags().Bool("json", false, "Generate JSON data containing a signed operation rather than broadcast it to the network (implied when offline)")
validatorExitCmd.Flags().Bool("offline", false, "Do not attempt to connect to a beacon node to obtain information for the operation")
validatorExitCmd.Flags().String("fork-version", "", "Fork version to use for signing (overrides fetching from beacon node)")
validatorExitCmd.Flags().String("genesis-validators-root", "", "Genesis validators root to use for signing (overrides fetching from beacon node)")
}
func validatorExitBindings() {
if err := viper.BindPFlag("epoch", validatorExitCmd.Flags().Lookup("epoch")); err != nil {
panic(err)
}
if err := viper.BindPFlag("key", validatorExitCmd.Flags().Lookup("key")); err != nil {
if err := viper.BindPFlag("prepare-offline", validatorExitCmd.Flags().Lookup("prepare-offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("exit", validatorExitCmd.Flags().Lookup("exit")); err != nil {
if err := viper.BindPFlag("validator", validatorExitCmd.Flags().Lookup("validator")); err != nil {
panic(err)
}
if err := viper.BindPFlag("signed-operation", validatorExitCmd.Flags().Lookup("signed-operation")); err != nil {
panic(err)
}
if err := viper.BindPFlag("json", validatorExitCmd.Flags().Lookup("json")); err != nil {
panic(err)
}
if err := viper.BindPFlag("offline", validatorExitCmd.Flags().Lookup("offline")); err != nil {
panic(err)
}
if err := viper.BindPFlag("fork-version", validatorExitCmd.Flags().Lookup("fork-version")); err != nil {
panic(err)
}
if err := viper.BindPFlag("genesis-validators-root", validatorExitCmd.Flags().Lookup("genesis-validators-root")); err != nil {
panic(err)
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 - 2022 Weald Technology Trading.
// Copyright © 2019 - 2023 Weald Technology Trading.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -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.26.5)"
var ReleaseVersion = "local build (latest release 1.27.0)"
// versionCmd represents the version command
var versionCmd = &cobra.Command{

37
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/wealdtech/ethdo
go 1.18
require (
github.com/attestantio/go-eth2-client v0.15.0
github.com/attestantio/go-eth2-client v0.15.2
github.com/ferranbt/fastssz v0.1.2
github.com/gofrs/uuid v4.2.0+incompatible
github.com/google/uuid v1.3.0
@@ -19,14 +19,15 @@ require (
github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
github.com/tyler-smith/go-bip39 v1.1.0
github.com/wealdtech/chaind v0.6.17
github.com/wealdtech/go-bytesutil v1.2.0
github.com/wealdtech/go-ecodec v1.1.2
github.com/wealdtech/go-eth2-types/v2 v2.8.0
github.com/wealdtech/go-eth2-util v1.8.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-dirk v1.4.2
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.3.0
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.6.0
@@ -36,38 +37,44 @@ require (
github.com/wealdtech/go-eth2-wallet-store-scratch v1.7.0
github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0
github.com/wealdtech/go-string2eth v1.2.0
golang.org/x/text v0.4.0
golang.org/x/text v0.5.0
)
require (
github.com/aws/aws-sdk-go v1.44.152 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/goccy/go-yaml v1.9.4 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-yaml v1.9.5 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.15.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.2 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/protolambda/zssz v0.1.5 // indirect
github.com/r3labs/sse/v2 v2.7.4 // indirect
github.com/r3labs/sse/v2 v2.8.1 // indirect
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
@@ -75,13 +82,17 @@ require (
github.com/subosito/gotenv v1.4.1 // indirect
github.com/wealdtech/eth2-signer-api v1.7.1 // indirect
github.com/wealdtech/go-indexer v1.0.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.37.0 // indirect
go.opentelemetry.io/otel v1.11.2 // indirect
go.opentelemetry.io/otel/metric v0.34.0 // indirect
go.opentelemetry.io/otel/trace v1.11.2 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
google.golang.org/grpc v1.51.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

195
go.sum
View File

@@ -29,21 +29,22 @@ 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.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
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/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k=
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/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/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/secretmanager v1.0.0/go.mod h1:+Qkm5qxIJ5mk74xxIXA+87fseaY1JLYBcFPQoc/GQxg=
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=
@@ -54,39 +55,30 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
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/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=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
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.15.0 h1:Ia8U1EPYFJ8KB/vsQ2+oEhzuPgCePlBkWXg1R3e0oWw=
github.com/attestantio/go-eth2-client v0.15.0/go.mod h1:5kLLzdlyPGboWr8tAwnG/4Kpi43BHd/HWp++WmmP6Ws=
github.com/aws/aws-sdk-go v1.33.17/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/attestantio/go-eth2-client v0.15.2 h1:4EYeA5IBSBypkUMhkkFALzMddaFDdb5PvCl7ORXEl6w=
github.com/attestantio/go-eth2-client v0.15.2/go.mod h1:/Oh6YTuHmHhgLN/ZnQRKHGc7HdIzGlDkI2vjNZvOsvA=
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.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI=
github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
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=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -107,28 +99,18 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -139,7 +121,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
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/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=
@@ -148,15 +129,11 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
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.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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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=
@@ -167,9 +144,16 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@@ -177,19 +161,15 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-yaml v1.8.9/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.9.2/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0=
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
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/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=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@@ -242,7 +222,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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=
@@ -274,13 +253,9 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
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/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/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=
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -315,7 +290,6 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT
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 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=
@@ -323,10 +297,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
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.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=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -341,20 +313,14 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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/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.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI=
github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.2 h1:xPMwiykqNK9VK0NYC3+jTMYv9I6Vl3YdjZgPZKG3zO0=
github.com/klauspost/cpuid/v2 v2.2.2/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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -365,11 +331,9 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
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=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/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=
@@ -378,7 +342,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
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/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=
@@ -390,6 +353,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
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=
@@ -421,13 +385,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
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/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=
@@ -449,51 +408,50 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O
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/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
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/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
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/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
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/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/protolambda/zssz v0.1.5 h1:7fjJjissZIIaa2QcvmhS/pZISMX21zVITt49sW1ouek=
github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZyL28gpag=
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
github.com/prysmaticlabs/go-bitfield v0.0.0-20200322041314-62c2aee71669/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw=
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/r3labs/sse/v2 v2.8.1 h1:lZH+W4XOLIq88U5MIHOsLec7+R62uhz3bIi2yn0Sg8o=
github.com/r3labs/sse/v2 v2.8.1/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I=
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/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/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/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=
@@ -504,27 +462,19 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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/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.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/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=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
@@ -532,6 +482,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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=
@@ -540,17 +491,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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=
github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/wealdtech/chaind v0.6.17 h1:HBlmzKj9Egy9rnZKGGIwvM6mUHJ+64163hNhSwjg/FQ=
github.com/wealdtech/chaind v0.6.17/go.mod h1:g8XOXrrRtwjD6mlpn9TydRPJD+gy4iFZMlPkQrBxxQA=
github.com/wealdtech/eth2-signer-api v1.7.1 h1:XdwFuv3VWCwcPPPrfa77sUXL1GSvxDtsUZxlByz//b0=
github.com/wealdtech/eth2-signer-api v1.7.1/go.mod h1:fX8XtN9Svyjs+e7TgoOfOcwRTHeblR5SXftAVV3T1ZA=
github.com/wealdtech/go-bytesutil v1.0.1/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s=
@@ -570,8 +521,8 @@ github.com/wealdtech/go-eth2-util v1.8.0 h1:hhs3h2y3Ldty18lppFdpe46nZpdDAMbY7Qqi
github.com/wealdtech/go-eth2-util v1.8.0/go.mod h1:rSuE0v5zX+uyZrqW/iUmXOxeDpB7lTvhcZvAVh0KlMU=
github.com/wealdtech/go-eth2-wallet v1.15.0 h1:yff2rWCvHyr5bOU41wwilsNemFRYOFinZoIPjhzG10M=
github.com/wealdtech/go-eth2-wallet v1.15.0/go.mod h1:BqXnen6XCa99yaUx/XIcyImaZLJMMj4wIMcUBywlMJs=
github.com/wealdtech/go-eth2-wallet-dirk v1.2.0 h1:GNrxDo1ajDbUU8XvCHku1ImFhgtV79oNVHEZlRfu9f4=
github.com/wealdtech/go-eth2-wallet-dirk v1.2.0/go.mod h1:1lnHDSdcRxj9CFJh+jPtdgVQCI+7jLwikjsxcqksXlg=
github.com/wealdtech/go-eth2-wallet-dirk v1.4.2 h1:TV5rrXGtHPteV+Mk7v6KFdmSUDm38SDKBDHFIvKIL8w=
github.com/wealdtech/go-eth2-wallet-dirk v1.4.2/go.mod h1:WGzvX3nxQnFNv4Hjx7jfBQhlVAEsRqe1tZwC8hZWM98=
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=
@@ -600,10 +551,8 @@ github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0 h1:bivQYT1TALvU+3kC9gyx2qX+
github.com/wealdtech/go-eth2-wallet-types/v2 v2.10.0/go.mod h1:CYlvo087PNZuihF0Pdnerfs9lhXAW/wLDxySNNkgpVo=
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.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=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -611,11 +560,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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/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/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -624,17 +570,20 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.37.0 h1:+uFejS4DCfNH6d3xODVIGsdhzgzhh45p9gpbHQMbdZI=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.37.0/go.mod h1:HSmzQvagH8pS2/xrK7ScWsk0vAMtRTGbMFgInXCi8Tc=
go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0=
go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI=
go.opentelemetry.io/otel/metric v0.34.0 h1:MCPoQxcg/26EuuJwpYN1mZTeCYAUGx8ABxfW07YkjP8=
go.opentelemetry.io/otel/metric v0.34.0/go.mod h1:ZFuI4yQGNCupurTXCwkeD/zHBt+C2bR7bw5JqUm/AP8=
go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0=
go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -650,10 +599,7 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
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=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
@@ -663,7 +609,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -707,7 +652,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -740,13 +684,12 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211116231205-47ca1ff31462/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
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=
@@ -764,6 +707,8 @@ 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.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
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=
@@ -775,14 +720,14 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
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/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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/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=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -793,7 +738,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -854,8 +798,6 @@ golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -881,16 +823,14 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -902,7 +842,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -953,10 +892,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -987,7 +922,6 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -996,6 +930,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1005,7 +940,6 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
@@ -1019,16 +953,13 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200528191852-705c0b31589b/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200731012542-8145dea6a485/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -1043,7 +974,6 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
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-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=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
@@ -1059,26 +989,21 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211027162914-98a5263abeca/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211116182654-e63d96a377c4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
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-20220930163606-c98284e70a91 h1:Ezh2cpcnP5Rq60sLensUsFnxh7P6513NLvNtCm9iyJ4=
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70=
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
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=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -1103,8 +1028,8 @@ 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.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
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=
@@ -1128,13 +1053,10 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.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=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1158,6 +1080,5 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
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=

View File

@@ -20,10 +20,9 @@ import (
)
type parameters struct {
logLevel zerolog.Level
genesisTimeProvider eth2client.GenesisTimeProvider
specProvider eth2client.SpecProvider
forkScheduleProvider eth2client.ForkScheduleProvider
logLevel zerolog.Level
genesisTimeProvider eth2client.GenesisTimeProvider
specProvider eth2client.SpecProvider
}
// Parameter is the interface for service parameters.
@@ -58,13 +57,6 @@ func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
})
}
// WithForkScheduleProvider sets the fork schedule provider.
func WithForkScheduleProvider(provider eth2client.ForkScheduleProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.forkScheduleProvider = provider
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@@ -82,9 +74,6 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.genesisTimeProvider == nil {
return nil, errors.New("no genesis time provider specified")
}
if parameters.forkScheduleProvider == nil {
return nil, errors.New("no fork schedule provider specified")
}
return &parameters, nil
}

View File

@@ -14,7 +14,6 @@
package standard
import (
"bytes"
"context"
"time"
@@ -87,21 +86,21 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
epochsPerSyncCommitteePeriod = tmp2
}
altairForkEpoch, err := fetchAltairForkEpoch(ctx, parameters.forkScheduleProvider)
altairForkEpoch, err := fetchAltairForkEpoch(ctx, parameters.specProvider)
if err != nil {
// Set to far future epoch.
altairForkEpoch = 0xffffffffffffffff
}
log.Trace().Uint64("epoch", uint64(altairForkEpoch)).Msg("Obtained Altair fork epoch")
bellatrixForkEpoch, err := fetchBellatrixForkEpoch(ctx, parameters.forkScheduleProvider)
bellatrixForkEpoch, err := fetchBellatrixForkEpoch(ctx, parameters.specProvider)
if err != nil {
// Set to far future epoch.
bellatrixForkEpoch = 0xffffffffffffffff
}
log.Trace().Uint64("epoch", uint64(bellatrixForkEpoch)).Msg("Obtained Bellatrix fork epoch")
capellaForkEpoch, err := fetchCapellaForkEpoch(ctx, parameters.forkScheduleProvider)
capellaForkEpoch, err := fetchCapellaForkEpoch(ctx, parameters.specProvider)
if err != nil {
// Set to far future epoch.
capellaForkEpoch = 0xffffffffffffffff
@@ -217,19 +216,28 @@ func (s *Service) AltairInitialSyncCommitteePeriod() uint64 {
return uint64(s.altairForkEpoch) / s.epochsPerSyncCommitteePeriod
}
func fetchAltairForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
forkSchedule, err := provider.ForkSchedule(ctx)
func fetchAltairForkEpoch(ctx context.Context,
specProvider eth2client.SpecProvider,
) (
phase0.Epoch,
error,
) {
// Fetch the fork version.
spec, err := specProvider.Spec(ctx)
if err != nil {
return 0, err
return 0, errors.Wrap(err, "failed to obtain spec")
}
for i := range forkSchedule {
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
// This is the genesis fork; ignore it.
continue
}
return forkSchedule[i].Epoch, nil
tmp, exists := spec["ALTAIR_FORK_EPOCH"]
if !exists {
return 0, errors.New("altair fork version not known by chain")
}
return 0, errors.New("no altair fork obtained")
epoch, isEpoch := tmp.(uint64)
if !isEpoch {
//nolint:revive
return 0, errors.New("ALTAIR_FORK_EPOCH is not a uint64!")
}
return phase0.Epoch(epoch), nil
}
// BellatrixInitialEpoch provides the epoch at which the Bellatrix hard fork takes place.
@@ -237,24 +245,28 @@ func (s *Service) BellatrixInitialEpoch() phase0.Epoch {
return s.bellatrixForkEpoch
}
func fetchBellatrixForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
forkSchedule, err := provider.ForkSchedule(ctx)
func fetchBellatrixForkEpoch(ctx context.Context,
specProvider eth2client.SpecProvider,
) (
phase0.Epoch,
error,
) {
// Fetch the fork version.
spec, err := specProvider.Spec(ctx)
if err != nil {
return 0, err
return 0, errors.Wrap(err, "failed to obtain spec")
}
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 == 1 {
return forkSchedule[i].Epoch, nil
}
count++
tmp, exists := spec["BELLATRIX_FORK_EPOCH"]
if !exists {
return 0, errors.New("bellatrix fork version not known by chain")
}
return 0, errors.New("no bellatrix fork obtained")
epoch, isEpoch := tmp.(uint64)
if !isEpoch {
//nolint:revive
return 0, errors.New("BELLATRIX_FORK_EPOCH is not a uint64!")
}
return phase0.Epoch(epoch), nil
}
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
@@ -262,22 +274,26 @@ 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)
func fetchCapellaForkEpoch(ctx context.Context,
specProvider eth2client.SpecProvider,
) (
phase0.Epoch,
error,
) {
// Fetch the fork version.
spec, err := specProvider.Spec(ctx)
if err != nil {
return 0, err
return 0, errors.Wrap(err, "failed to obtain spec")
}
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++
tmp, exists := spec["CAPELLAELLATRIX_FORK_EPOCH"]
if !exists {
return 0, errors.New("capella fork version not known by chain")
}
return 0, errors.New("no capella fork obtained")
epoch, isEpoch := tmp.(uint64)
if !isEpoch {
//nolint:revive
return 0, errors.New("CAPELLAELLATRIX_FORK_EPOCH is not a uint64!")
}
return phase0.Epoch(epoch), nil
}

View File

@@ -31,22 +31,9 @@ func TestService(t *testing.T) {
slotDuration := 12 * time.Second
slotsPerEpoch := uint64(32)
epochsPerSyncCommitteePeriod := uint64(256)
forkSchedule := []*phase0.Fork{
{
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
CurrentVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
Epoch: 0,
},
{
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
CurrentVersion: phase0.Version{0x05, 0x06, 0x07, 0x08},
Epoch: 10,
},
}
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
mockForkScheduleProvider := mock.NewForkScheduleProvider(forkSchedule)
tests := []struct {
name string
@@ -58,7 +45,6 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithSpecProvider(mockSpecProvider),
standard.WithForkScheduleProvider(mockForkScheduleProvider),
},
err: "problem with parameters: no genesis time provider specified",
},
@@ -67,26 +53,15 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
standard.WithForkScheduleProvider(mockForkScheduleProvider),
},
err: "problem with parameters: no spec provider specified",
},
{
name: "ForkScheduleProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
standard.WithSpecProvider(mockSpecProvider),
},
err: "problem with parameters: no fork schedule provider specified",
},
{
name: "Good",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
standard.WithSpecProvider(mockSpecProvider),
standard.WithForkScheduleProvider(mockForkScheduleProvider),
},
},
}
@@ -123,11 +98,9 @@ func createService(genesisTime time.Time) (chaintime.Service, time.Duration, uin
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
mockForkScheduleProvider := mock.NewForkScheduleProvider(forkSchedule)
s, err := standard.New(context.Background(),
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
standard.WithSpecProvider(mockSpecProvider),
standard.WithForkScheduleProvider(mockForkScheduleProvider),
)
return s, slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod, forkSchedule, err
}

View File

@@ -61,7 +61,7 @@ func ParseAccount(ctx context.Context,
// Private key.
account, err = newScratchAccountFromPrivKey(data)
if err != nil {
return nil, errors.Wrap(err, "failed to create account from public key")
return nil, errors.Wrap(err, "failed to create account from private key")
}
if unlock {
_, err = UnlockAccount(ctx, account, nil)

View File

@@ -24,11 +24,15 @@ import (
// ParseEpoch parses input to calculate the desired epoch.
func ParseEpoch(ctx context.Context, chainTime chaintime.Service, epochStr string) (phase0.Epoch, error) {
currentEpoch := chainTime.CurrentEpoch()
switch epochStr {
case "", "current":
return chainTime.CurrentEpoch(), nil
case "", "current", "-0":
return currentEpoch, nil
case "last":
return chainTime.CurrentEpoch() - 1, nil
if currentEpoch > 0 {
currentEpoch--
}
return currentEpoch, nil
default:
val, err := strconv.ParseInt(epochStr, 10, 64)
if err != nil {
@@ -37,6 +41,9 @@ func ParseEpoch(ctx context.Context, chainTime chaintime.Service, epochStr strin
if val >= 0 {
return phase0.Epoch(val), nil
}
return chainTime.CurrentEpoch() + phase0.Epoch(val), nil
if phase0.Epoch(-val) > currentEpoch {
return 0, nil
}
return currentEpoch + phase0.Epoch(val), nil
}
}

105
util/epoch_test.go Normal file
View File

@@ -0,0 +1,105 @@
// Copyright © 2032 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"
"testing"
"time"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
"github.com/wealdtech/ethdo/testing/mock"
"github.com/wealdtech/ethdo/util"
)
func TestParseEpoch(t *testing.T) {
ctx := context.Background()
// genesis is 1 day ago.
genesisTime := time.Now().AddDate(0, 0, -1)
slotDuration := 12 * time.Second
slotsPerEpoch := uint64(32)
epochsPerSyncCommitteePeriod := uint64(256)
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
chainTime, err := standardchaintime.New(context.Background(),
standardchaintime.WithLogLevel(zerolog.Disabled),
standardchaintime.WithGenesisTimeProvider(mockGenesisTimeProvider),
standardchaintime.WithSpecProvider(mockSpecProvider),
)
require.NoError(t, err)
tests := []struct {
name string
input string
err string
expected phase0.Epoch
}{
{
name: "Genesis",
input: "0",
expected: 0,
},
{
name: "Invalid",
input: "invalid",
err: `failed to parse epoch: strconv.ParseInt: parsing "invalid": invalid syntax`,
},
{
name: "Absolute",
input: "15",
expected: 15,
},
{
name: "Current",
input: "current",
expected: 225,
},
{
name: "Last",
input: "last",
expected: 224,
},
{
name: "RelativeZero",
input: "-0",
expected: 225,
},
{
name: "Relative",
input: "-5",
expected: 220,
},
{
name: "RelativeFar",
input: "-500",
expected: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
epoch, err := util.ParseEpoch(ctx, chainTime, test.input)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.expected, epoch)
}
})
}
}

View File

@@ -1,4 +1,4 @@
// Copyright © 2019 Weald Technology Trading
// Copyright © 2019, 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
@@ -114,7 +114,12 @@ func WalletFromPath(ctx context.Context, path string) (e2wtypes.Wallet, error) {
return nil, errors.Wrap(err, "failed to parse remote servers")
}
return dirk.OpenWallet(ctx, walletName, credentials, endpoints)
return dirk.Open(ctx,
dirk.WithName(walletName),
dirk.WithCredentials(credentials),
dirk.WithEndpoints(endpoints),
dirk.WithTimeout(viper.GetDuration("timeout")),
)
}
wallet, err := e2wallet.OpenWallet(walletName)
if err != nil {