mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1647d2f3d | ||
|
|
c7f3275dfa | ||
|
|
7aeba43338 | ||
|
|
688db9ef8c | ||
|
|
173883da3e | ||
|
|
6077e04619 |
@@ -1,3 +1,7 @@
|
||||
1.7.3:
|
||||
- fix issue where base directory was ignored for wallet creation
|
||||
- new "validator duties" command to display known duties for a given validator
|
||||
- update go-eth2-client to display correct validator status from prysm
|
||||
1.7.2:
|
||||
- new "account derive" command to derive keys directly from a mnemonic and derivation path
|
||||
- add more output to "deposit verify" to explain operation
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
@@ -63,7 +62,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
// Wallet.
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
data.wallet, err = core.WalletFromInput(ctx)
|
||||
data.wallet, err = util.WalletFromInput(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain wallet")
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
@@ -60,7 +59,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
// Wallet.
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
data.wallet, err = core.WalletFromInput(ctx)
|
||||
data.wallet, err = util.WalletFromInput(ctx)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain wallet")
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
@@ -40,7 +39,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.timeout = viper.GetDuration("timeout")
|
||||
|
||||
// Account.
|
||||
_, data.account, err = core.WalletAndAccountFromInput(ctx)
|
||||
_, data.account, err = util.WalletAndAccountFromInput(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain acount")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
@@ -69,14 +68,15 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
}
|
||||
|
||||
// Epoch
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
|
||||
// Epoch.
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
@@ -89,17 +89,24 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
|
||||
// Validator.
|
||||
stateID := "head"
|
||||
if viper.GetInt64("epoch") != -1 {
|
||||
stateID = fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch)
|
||||
}
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := core.BestPublicKey(data.account)
|
||||
pubKey, err := util.BestPublicKey(data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), pubKeys)
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, stateID, pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
data.validator = validators[0]
|
||||
for _, validator := range validators {
|
||||
data.validator = validator
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
@@ -111,7 +118,7 @@ func attesterInclusionAccount() (e2wtypes.Account, error) {
|
||||
if viper.GetString("account") != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, account, err = core.WalletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
_, account, err = util.WalletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func duty(ctx context.Context, eth2Client eth2client.Service, validator *api.Validator, epoch spec.Epoch, slotsPerEpoch uint64) (*api.AttesterDuty, error) {
|
||||
|
||||
@@ -50,8 +50,8 @@ In quiet mode this will return 0 if the the exit is verified correctly, otherwis
|
||||
account, err := exitVerifyAccount(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
assert(viper.GetString("exit.data") != "", "exit data is required")
|
||||
data, err := obtainExitData(viper.GetString("exit.Data"))
|
||||
assert(viper.GetString("exit") != "", "exit is required")
|
||||
data, err := obtainExitData(viper.GetString("exit"))
|
||||
errCheck(err, "Failed to obtain exit data")
|
||||
|
||||
// Confirm signature is good.
|
||||
@@ -62,13 +62,17 @@ In quiet mode this will return 0 if the the exit is verified correctly, otherwis
|
||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||
|
||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||
var exitDomain spec.Domain
|
||||
copy(exitDomain[:], domain)
|
||||
exit := &spec.VoluntaryExit{
|
||||
Epoch: data.Data.Message.Epoch,
|
||||
ValidatorIndex: data.Data.Message.ValidatorIndex,
|
||||
Epoch: data.Exit.Message.Epoch,
|
||||
ValidatorIndex: data.Exit.Message.ValidatorIndex,
|
||||
}
|
||||
sig, err := e2types.BLSSignatureFromBytes(data.Data.Signature[:])
|
||||
exitRoot, err := exit.HashTreeRoot()
|
||||
errCheck(err, "Failed to obtain exit hash tree root")
|
||||
sig, err := e2types.BLSSignatureFromBytes(data.Exit.Signature[:])
|
||||
errCheck(err, "Invalid signature")
|
||||
verified, err := verifyStruct(account, exit, domain, sig)
|
||||
verified, err := util.VerifyRoot(account, exitRoot, exitDomain, sig)
|
||||
errCheck(err, "Failed to verify voluntary exit")
|
||||
assert(verified, "Voluntary exit failed to verify")
|
||||
|
||||
@@ -130,12 +134,12 @@ func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) {
|
||||
func init() {
|
||||
exitCmd.AddCommand(exitVerifyCmd)
|
||||
exitFlags(exitVerifyCmd)
|
||||
exitVerifyCmd.Flags().String("data", "", "JSON data, or path to JSON data")
|
||||
exitVerifyCmd.Flags().String("exit", "", "JSON data, or path to JSON data")
|
||||
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
||||
}
|
||||
|
||||
func exitVerifyBindings() {
|
||||
if err := viper.BindPFlag("data", exitVerifyCmd.Flags().Lookup("data")); err != nil {
|
||||
if err := viper.BindPFlag("exit", exitVerifyCmd.Flags().Lookup("exit")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
@@ -77,6 +76,8 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
exitVerifyBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "validator/duties":
|
||||
validatorDutiesBindings()
|
||||
case "validator/exit":
|
||||
validatorExitBindings()
|
||||
case "validator/info":
|
||||
@@ -94,7 +95,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Cannot supply both quiet and debug flags")
|
||||
}
|
||||
|
||||
if err := core.SetupStore(); err != nil {
|
||||
if err := util.SetupStore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
)
|
||||
@@ -54,9 +56,11 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
_, account, err := walletAndAccountFromInput(ctx)
|
||||
errCheck(err, "Failed to obtain account")
|
||||
|
||||
var specDomain spec.Domain
|
||||
copy(specDomain[:], domain)
|
||||
var fixedSizeData [32]byte
|
||||
copy(fixedSizeData[:], data)
|
||||
signature, err := signRoot(account, fixedSizeData, domain)
|
||||
signature, err := util.SignRoot(account, fixedSizeData, specDomain)
|
||||
errCheck(err, "Failed to sign")
|
||||
|
||||
outputIf(!quiet, fmt.Sprintf("%#x", signature.Marshal()))
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@@ -64,9 +65,11 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`,
|
||||
errCheck(err, "Failed to obtain account")
|
||||
outputIf(debug, fmt.Sprintf("Public key is %#x", account.PublicKey().Marshal()))
|
||||
|
||||
var specDomain spec.Domain
|
||||
copy(specDomain[:], domain)
|
||||
var root [32]byte
|
||||
copy(root[:], data)
|
||||
verified, err := verifyRoot(account, root, domain, signature)
|
||||
verified, err := util.VerifyRoot(account, root, specDomain, signature)
|
||||
errCheck(err, "Failed to verify data")
|
||||
assert(verified, "Failed to verify")
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
ethdoutil "github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
@@ -52,7 +51,7 @@ func input() (*dataIn, error) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, data.validatorAccounts, err = core.WalletAndAccountsFromPath(ctx, viper.GetString("validatoraccount"))
|
||||
_, data.validatorAccounts, err = ethdoutil.WalletAndAccountsFromPath(ctx, viper.GetString("validatoraccount"))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator account")
|
||||
}
|
||||
@@ -75,11 +74,11 @@ func input() (*dataIn, error) {
|
||||
case viper.GetString("withdrawalaccount") != "":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, withdrawalAccount, err := core.WalletAndAccountFromPath(ctx, viper.GetString("withdrawalaccount"))
|
||||
_, withdrawalAccount, err := ethdoutil.WalletAndAccountFromPath(ctx, viper.GetString("withdrawalaccount"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain withdrawal account")
|
||||
}
|
||||
pubKey, err := core.BestPublicKey(withdrawalAccount)
|
||||
pubKey, err := ethdoutil.BestPublicKey(withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for withdrawal account")
|
||||
}
|
||||
|
||||
@@ -105,6 +105,12 @@ func validatorDepositDataOutputRaw(datum *dataOut) (string, error) {
|
||||
}
|
||||
|
||||
func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
// Map of fork version to network name.
|
||||
forkVersionMap := map[spec.Version]string{
|
||||
[4]byte{0x00, 0x00, 0x00, 0x00}: "mainnet",
|
||||
[4]byte{0x00, 0x00, 0x20, 0x09}: "pyrmont",
|
||||
}
|
||||
|
||||
if datum.validatorPubKey == nil {
|
||||
return "", errors.New("validator public key required")
|
||||
}
|
||||
@@ -124,7 +130,11 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
return "", errors.New("deposit data root required")
|
||||
}
|
||||
|
||||
output := fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x"}`,
|
||||
networkName := "unknown"
|
||||
if network, exists := forkVersionMap[*datum.forkVersion]; exists {
|
||||
networkName = network
|
||||
}
|
||||
output := fmt.Sprintf(`{"pubkey":"%x","withdrawal_credentials":"%x","amount":%d,"signature":"%x","deposit_message_root":"%x","deposit_data_root":"%x","fork_version":"%x","eth2_network_name":"%s","deposit_cli_version":"1.1.0"}`,
|
||||
*datum.validatorPubKey,
|
||||
datum.withdrawalCredentials,
|
||||
datum.amount,
|
||||
@@ -132,6 +142,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
*datum.depositMessageRoot,
|
||||
*datum.depositDataRoot,
|
||||
*datum.forkVersion,
|
||||
networkName,
|
||||
)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
}
|
||||
var forkVersion *spec.Version
|
||||
{
|
||||
tmp := testutil.HexToVersion("0x01020304")
|
||||
tmp := testutil.HexToVersion("0x00002009")
|
||||
forkVersion = &tmp
|
||||
}
|
||||
var depositDataRoot *spec.Root
|
||||
@@ -418,7 +418,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"01020304"}]`,
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"1.1.0"}]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
@@ -446,7 +446,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
depositMessageRoot: depositMessageRoot2,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"01020304"},{"pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","amount":32000000000,"signature":"911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","deposit_message_root":"bb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52","deposit_data_root":"3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","fork_version":"01020304"}]`,
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"1.1.0"},{"pubkey":"b89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b","withdrawal_credentials":"00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594","amount":32000000000,"signature":"911fe0766e8b79d711dde46bc2142eb51e35be99e5f7da505af9eaad85707bbb8013f0dea35e30403b3e57bb13054c1d0d389aceeba1d4160a148026212c7e017044e3ea69cd96fbd23b6aa9fd1e6f7e82494fbd5f8fc75856711a6b8998926e","deposit_message_root":"bb4b6184b25873cdf430df3838c8d3e3d16cf3dc3b214e2f3ab7df9e6d5a9b52","deposit_data_root":"3b51670e9f266d44c879682a230d60f0d534c64ab25ee68700fe3adb17ddfcab","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"1.1.0"}]`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/signing"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
results := make([]*dataOut, 0)
|
||||
|
||||
for _, validatorAccount := range data.validatorAccounts {
|
||||
validatorPubKey, err := core.BestPublicKey(validatorAccount)
|
||||
validatorPubKey, err := util.BestPublicKey(validatorAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "validator account does not provide a public key")
|
||||
}
|
||||
|
||||
71
cmd/validator/duties/input.go
Normal file
71
cmd/validator/duties/input.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
// System.
|
||||
timeout time.Duration
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
// Ethereum 2 connection.
|
||||
eth2Client string
|
||||
allowInsecure bool
|
||||
// Operation.
|
||||
account string
|
||||
pubKey string
|
||||
index string
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// Ethereum 2 connection.
|
||||
data.eth2Client = viper.GetString("connection")
|
||||
if data.eth2Client == "" {
|
||||
return nil, errors.New("connection is required")
|
||||
}
|
||||
data.allowInsecure = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
// Account.
|
||||
data.account = viper.GetString("account")
|
||||
|
||||
// PubKey.
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// ID.
|
||||
data.index = viper.GetString("index")
|
||||
|
||||
if data.account == "" && data.pubKey == "" && data.index == "" {
|
||||
return nil, errors.New("account, pubkey or index required")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
100
cmd/validator/duties/input_internal_test.go
Normal file
100
cmd/validator/duties/input_internal_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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{}{
|
||||
"connection": "http://locahost:4000",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "AccountMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": "http://locahost:4000",
|
||||
},
|
||||
err: "account, pubkey or index required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
},
|
||||
err: "connection is required",
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
112
cmd/validator/duties/output.go
Normal file
112
cmd/validator/duties/output.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// 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 validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
debug bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
genesisTime time.Time
|
||||
slotDuration time.Duration
|
||||
slotsPerEpoch uint64
|
||||
thisEpochAttesterDuty *api.AttesterDuty
|
||||
thisEpochProposerDuties []*api.ProposerDuty
|
||||
nextEpochAttesterDuty *api.AttesterDuty
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
if data.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
now := time.Now()
|
||||
builder.WriteString("Current time: ")
|
||||
builder.WriteString(now.Format("15:04:05\n"))
|
||||
|
||||
if data.thisEpochAttesterDuty != nil {
|
||||
thisEpochAttesterSlot := data.thisEpochAttesterDuty.Slot
|
||||
thisSlotStart := data.genesisTime.Add(time.Duration(thisEpochAttesterSlot) * data.slotDuration)
|
||||
thisSlotEnd := thisSlotStart.Add(data.slotDuration)
|
||||
if thisSlotEnd.After(now) {
|
||||
builder.WriteString("Upcoming attestation slot this epoch: ")
|
||||
builder.WriteString(thisSlotStart.Format("15:04:05"))
|
||||
builder.WriteString(" - ")
|
||||
builder.WriteString(thisSlotEnd.Format("15:04:05 ("))
|
||||
until := thisSlotStart.Sub(now)
|
||||
if until > 0 {
|
||||
builder.WriteString(fmt.Sprintf("%ds until start of slot)\n", int(until.Seconds())))
|
||||
} else {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, proposerDuty := range data.thisEpochProposerDuties {
|
||||
proposerSlot := proposerDuty.Slot
|
||||
proposerSlotStart := data.genesisTime.Add(time.Duration(proposerSlot) * data.slotDuration)
|
||||
proposerSlotEnd := proposerSlotStart.Add(data.slotDuration)
|
||||
builder.WriteString("Upcoming proposer slot this epoch: ")
|
||||
builder.WriteString(proposerSlotStart.Format("15:04:05"))
|
||||
builder.WriteString(" - ")
|
||||
builder.WriteString(proposerSlotEnd.Format("15:04:05 ("))
|
||||
until := proposerSlotStart.Sub(now)
|
||||
if until > 0 {
|
||||
builder.WriteString(fmt.Sprintf("%ds until start of slot)\n", int(until.Seconds())))
|
||||
} else {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
if data.nextEpochAttesterDuty != nil {
|
||||
nextEpochAttesterSlot := data.nextEpochAttesterDuty.Slot
|
||||
nextSlotStart := data.genesisTime.Add(time.Duration(nextEpochAttesterSlot) * data.slotDuration)
|
||||
nextSlotEnd := nextSlotStart.Add(data.slotDuration)
|
||||
builder.WriteString("Upcoming attestation slot next epoch: ")
|
||||
builder.WriteString(nextSlotStart.Format("15:04:05"))
|
||||
builder.WriteString(" - ")
|
||||
builder.WriteString(nextSlotEnd.Format("15:04:05 ("))
|
||||
until := nextSlotStart.Sub(now)
|
||||
builder.WriteString(fmt.Sprintf("%ds until start of slot)\n", int(until.Seconds())))
|
||||
|
||||
nextEpoch := uint64(data.nextEpochAttesterDuty.Slot) / data.slotsPerEpoch
|
||||
nextEpochStart := data.genesisTime.Add(time.Duration(nextEpoch*data.slotsPerEpoch) * data.slotDuration)
|
||||
builder.WriteString("Next epoch starts ")
|
||||
builder.WriteString(nextEpochStart.Format("15:04:05 ("))
|
||||
until = nextEpochStart.Sub(now)
|
||||
if until > 0 {
|
||||
builder.WriteString(fmt.Sprintf("%ds until start of epoch)\n", int(until.Seconds())))
|
||||
} else {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
83
cmd/validator/duties/output_internal_test.go
Normal file
83
cmd/validator/duties/output_internal_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
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
|
||||
expected []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
dataOut: &dataOut{},
|
||||
expected: []string{"Current time"},
|
||||
},
|
||||
{
|
||||
name: "Found",
|
||||
dataOut: &dataOut{
|
||||
genesisTime: time.Unix(16000000000, 0),
|
||||
slotDuration: 12 * time.Second,
|
||||
slotsPerEpoch: 32,
|
||||
thisEpochAttesterDuty: &api.AttesterDuty{
|
||||
Slot: spec.Slot(1),
|
||||
},
|
||||
thisEpochProposerDuties: []*api.ProposerDuty{
|
||||
{
|
||||
Slot: spec.Slot(2),
|
||||
},
|
||||
},
|
||||
nextEpochAttesterDuty: &api.AttesterDuty{
|
||||
Slot: spec.Slot(40),
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"Current time",
|
||||
"Upcoming attestation slot this epoch",
|
||||
"Upcoming proposer slot this epoch",
|
||||
"Upcoming attestation slot next epoch",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
for _, expected := range test.expected {
|
||||
require.True(t, strings.Contains(res, expected))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
182
cmd/validator/duties/process.go
Normal file
182
cmd/validator/duties/process.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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 validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
// Ethereum 2 client.
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.eth2Client, data.timeout, data.allowInsecure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
validatorIndex, err := validatorIndex(ctx, eth2Client, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator index")
|
||||
}
|
||||
|
||||
// Fetch duties for this and next epoch.
|
||||
thisEpoch, err := currentEpoch(ctx, eth2Client)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to calculate current epoch")
|
||||
}
|
||||
thisEpochAttesterDuty, err := attesterDuty(ctx, eth2Client, validatorIndex, thisEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain this epoch duty for validator")
|
||||
}
|
||||
results.thisEpochAttesterDuty = thisEpochAttesterDuty
|
||||
|
||||
thisEpochProposerDuties, err := proposerDuties(ctx, eth2Client, validatorIndex, thisEpoch)
|
||||
results.thisEpochProposerDuties = thisEpochProposerDuties
|
||||
|
||||
nextEpoch := thisEpoch + 1
|
||||
nextEpochAttesterDuty, err := attesterDuty(ctx, eth2Client, validatorIndex, nextEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain next epoch duty for validator")
|
||||
}
|
||||
results.nextEpochAttesterDuty = nextEpochAttesterDuty
|
||||
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
results.genesisTime = genesis.GenesisTime
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
results.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
||||
results.slotDuration = config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func attesterDuty(ctx context.Context, eth2Client eth2client.Service, validatorIndex spec.ValidatorIndex, epoch spec.Epoch) (*api.AttesterDuty, error) {
|
||||
// Find the attesting slot for the given epoch.
|
||||
duties, err := eth2Client.(eth2client.AttesterDutiesProvider).AttesterDuties(ctx, epoch, []spec.ValidatorIndex{validatorIndex})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain attester duties")
|
||||
}
|
||||
|
||||
if len(duties) == 0 {
|
||||
return nil, errors.New("validator does not have duty for that epoch")
|
||||
}
|
||||
|
||||
return duties[0], nil
|
||||
}
|
||||
|
||||
func proposerDuties(ctx context.Context, eth2Client eth2client.Service, validatorIndex spec.ValidatorIndex, epoch spec.Epoch) ([]*api.ProposerDuty, error) {
|
||||
// Fetch the proposer duties for this epoch.
|
||||
proposerDuties, err := eth2Client.(eth2client.ProposerDutiesProvider).ProposerDuties(ctx, epoch, []spec.ValidatorIndex{validatorIndex})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain proposer duties")
|
||||
}
|
||||
|
||||
return proposerDuties, nil
|
||||
}
|
||||
|
||||
func currentEpoch(ctx context.Context, eth2Client eth2client.Service) (spec.Epoch, error) {
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
|
||||
if genesis.GenesisTime.After(time.Now()) {
|
||||
return spec.Epoch(0), nil
|
||||
}
|
||||
return spec.Epoch(uint64(time.Since(genesis.GenesisTime).Seconds()) / (uint64(slotDuration.Seconds()) * slotsPerEpoch)), nil
|
||||
}
|
||||
|
||||
// validatorIndex obtains the index of a validator
|
||||
func validatorIndex(ctx context.Context, eth2Client eth2client.Service, data *dataIn) (spec.ValidatorIndex, error) {
|
||||
switch {
|
||||
case data.account != "":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), data.timeout)
|
||||
defer cancel()
|
||||
_, account, err := util.WalletAndAccountFromPath(ctx, data.account)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
return accountToIndex(ctx, account, eth2Client)
|
||||
case data.pubKey != "":
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", data.pubKey))
|
||||
}
|
||||
account, err := util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, fmt.Sprintf("invalid public key %s", data.pubKey))
|
||||
}
|
||||
return accountToIndex(ctx, account, eth2Client)
|
||||
case data.index != "":
|
||||
val, err := strconv.ParseUint(data.index, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return spec.ValidatorIndex(val), nil
|
||||
default:
|
||||
return 0, errors.New("no validator")
|
||||
}
|
||||
}
|
||||
|
||||
func accountToIndex(ctx context.Context, account e2wtypes.Account, eth2Client eth2client.Service) (spec.ValidatorIndex, error) {
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", pubKeys)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for index := range validators {
|
||||
return index, nil
|
||||
}
|
||||
return 0, errors.New("validator not found")
|
||||
}
|
||||
60
cmd/validator/duties/process_internal_test.go
Normal file
60
cmd/validator/duties/process_internal_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Client",
|
||||
dataIn: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
eth2Client: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
allowInsecure: true,
|
||||
index: "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/validator/duties/run.go
Normal file
50
cmd/validator/duties/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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 validatorduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the wallet create data command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
dataIn, err := input(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
@@ -80,13 +79,13 @@ func inputJSON(ctx context.Context, data *dataIn) (*dataIn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.signedVoluntaryExit = validatorData.Data
|
||||
data.signedVoluntaryExit = validatorData.Exit
|
||||
return inputChainData(ctx, data)
|
||||
}
|
||||
|
||||
func inputAccount(ctx context.Context, data *dataIn) (*dataIn, error) {
|
||||
var err error
|
||||
_, data.account, err = core.WalletAndAccountFromInput(ctx)
|
||||
_, data.account, err = util.WalletAndAccountFromInput(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain acount")
|
||||
}
|
||||
|
||||
@@ -91,9 +91,10 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "KeyGood",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"allow-insecure-connections": true,
|
||||
"timeout": "5s",
|
||||
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
@@ -102,9 +103,10 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "AccountUnknown",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"account": "Test wallet/unknown",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"allow-insecure-connections": true,
|
||||
"timeout": "5s",
|
||||
"account": "Test wallet/unknown",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
@@ -114,9 +116,10 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "AccountGood",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"account": "Test wallet/Interop 0",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"allow-insecure-connections": true,
|
||||
"timeout": "5s",
|
||||
"account": "Test wallet/Interop 0",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
@@ -125,9 +128,10 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "JSONInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"exit": `invalid`,
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"allow-insecure-connections": true,
|
||||
"timeout": "5s",
|
||||
"exit": `invalid`,
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
@@ -137,9 +141,10 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "JSONGood",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"exit": `{"message":{"epoch":"123","validator_index":"456"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}`,
|
||||
"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,
|
||||
@@ -148,19 +153,21 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "ClientBad",
|
||||
vars: map[string]interface{}{
|
||||
"connection": "localhost:1",
|
||||
"timeout": "5s",
|
||||
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
|
||||
"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 connect to Ethereum 2 client with any known method",
|
||||
},
|
||||
{
|
||||
name: "EpochProvided",
|
||||
vars: map[string]interface{}{
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"timeout": "5s",
|
||||
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
|
||||
"epoch": "123",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
"allow-insecure-connections": true,
|
||||
"timeout": "5s",
|
||||
"key": "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866",
|
||||
"epoch": "123",
|
||||
},
|
||||
res: &dataIn{
|
||||
timeout: 5 * time.Second,
|
||||
|
||||
@@ -46,7 +46,7 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
|
||||
func outputJSON(ctx context.Context, data *dataOut) (string, error) {
|
||||
validatorExitData := &util.ValidatorExitData{
|
||||
Data: data.signedVoluntaryExit,
|
||||
Exit: data.signedVoluntaryExit,
|
||||
ForkVersion: data.forkVersion,
|
||||
}
|
||||
bytes, err := json.Marshal(validatorExitData)
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestOutput(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
res: `{"data":{"message":{"epoch":"123","validator_index":"456"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"fork_version":"0x01020304"}`,
|
||||
res: `{"exit":{"message":{"epoch":"123","validator_index":"456"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"fork_version":"0x01020304"}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/signing"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
// maxFutureEpochs is the farthest in the future for which an exit will be created.
|
||||
@@ -111,7 +111,7 @@ func fetchValidator(ctx context.Context, data *dataIn) (*api.Validator, error) {
|
||||
|
||||
var validator *api.Validator
|
||||
validatorPubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := core.BestPublicKey(data.account)
|
||||
pubKey, err := util.BestPublicKey(data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
|
||||
61
cmd/validatorduties.go
Normal file
61
cmd/validatorduties.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
validatorduties "github.com/wealdtech/ethdo/cmd/validator/duties"
|
||||
)
|
||||
|
||||
var validatorDutiesCmd = &cobra.Command{
|
||||
Use: "duties",
|
||||
Short: "List known duties for a validator",
|
||||
Long: `List known duties for a validator. For example:
|
||||
|
||||
ethdo validator duties --account=Validators/One
|
||||
|
||||
Attester duties are known for the current and next epoch. Proposer duties are known for the current epoch.
|
||||
|
||||
In quiet mode this will return 0 if the the duties have been obtained, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := validatorduties.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf(res)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorDutiesCmd)
|
||||
validatorFlags(validatorDutiesCmd)
|
||||
validatorDutiesCmd.Flags().String("pubkey", "", "validator public key for duties")
|
||||
validatorDutiesCmd.Flags().String("index", "", "validator index for duties")
|
||||
}
|
||||
|
||||
func validatorDutiesBindings() {
|
||||
if err := viper.BindPFlag("pubkey", validatorDutiesCmd.Flags().Lookup("pubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("index", validatorDutiesCmd.Flags().Lookup("index")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
@@ -55,7 +54,7 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
errCheck(err, "Failed to obtain validator account")
|
||||
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := core.BestPublicKey(account)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
errCheck(err, "Failed to obtain validator public key")
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, "head", pubKeys)
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// ReleaseVersion is the release version of the codebase.
|
||||
// Usually overrideen by tag names when building binaries.
|
||||
var ReleaseVersion = "local build (latest release 1.7.2)"
|
||||
var ReleaseVersion = "local build (latest release 1.7.3)"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Wallet.
|
||||
wallet, err := core.WalletFromInput(ctx)
|
||||
wallet, err := util.WalletFromInput(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to access wallet")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/core"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
@@ -51,7 +50,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Wallet.
|
||||
wallet, err := core.WalletFromInput(ctx)
|
||||
wallet, err := util.WalletFromInput(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to access wallet")
|
||||
}
|
||||
|
||||
@@ -348,12 +348,12 @@ Exit commands focus on information about validator exits generated by the `ethdo
|
||||
#### `verify`
|
||||
|
||||
`ethdo exit verify` verifies the validator exit information in a JSON file generated by the `ethdo validator exit` command. Options include:
|
||||
- `data`: either a path to the JSON file or the JSON itself
|
||||
- `exit`: either a path to the JSON file or the JSON itself
|
||||
- `account`: the account that generated the exit transaction (if available as an account, in format "wallet/account")
|
||||
- `pubkey`: the public key of the account that generated the exit transaction
|
||||
|
||||
```sh
|
||||
$ ethdo exit verify --data=${HOME}/exit.json --pubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c
|
||||
$ ethdo exit verify --exit=${HOME}/exit.json --pubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c
|
||||
```
|
||||
|
||||
### `node` commands
|
||||
|
||||
20
go.mod
20
go.mod
@@ -5,23 +5,25 @@ go 1.13
|
||||
require (
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/attestantio/dirk v0.9.3
|
||||
github.com/attestantio/go-eth2-client v0.6.10
|
||||
github.com/ferranbt/fastssz v0.0.0-20201030134205-9b9624098321
|
||||
github.com/attestantio/go-eth2-client v0.6.16
|
||||
github.com/aws/aws-sdk-go v1.36.2 // indirect
|
||||
github.com/ferranbt/fastssz v0.0.0-20201207112544-98a5de30d648
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20201104034342-d782bdf735de
|
||||
github.com/jackc/puddle v1.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/minio/highwayhash v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.0 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/protolambda/zssz v0.1.5 // indirect
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201207010723-e69ac7fa952d
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20200612203617-6d5c9aa213ae
|
||||
github.com/rs/zerolog v1.20.0
|
||||
@@ -52,10 +54,12 @@ require (
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.1
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.1
|
||||
github.com/wealdtech/go-string2eth v1.1.0
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c // indirect
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88 // indirect
|
||||
golang.org/x/text v0.3.4
|
||||
google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6 // indirect
|
||||
google.golang.org/grpc v1.33.2
|
||||
google.golang.org/genproto v0.0.0-20201204160425-06b3db808446 // indirect
|
||||
google.golang.org/grpc v1.34.0
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
36
go.sum
36
go.sum
@@ -75,6 +75,10 @@ github.com/attestantio/go-eth2-client v0.6.9 h1:Hbf4tX9MvxCsLokED8Ic3tQxmEAb/pho
|
||||
github.com/attestantio/go-eth2-client v0.6.9/go.mod h1:ODAZ4yS1YYYew/EsgGsVb/siNEoa505CrGsvlVFdkfo=
|
||||
github.com/attestantio/go-eth2-client v0.6.10 h1:PMNBMLk6xfMEUqhaUnsI0/HZRrstZF18Gt6Dm5GelW4=
|
||||
github.com/attestantio/go-eth2-client v0.6.10/go.mod h1:ODAZ4yS1YYYew/EsgGsVb/siNEoa505CrGsvlVFdkfo=
|
||||
github.com/attestantio/go-eth2-client v0.6.15 h1:GNkiSF2Dqp6qahMXMW8r8Wy61WEvytnAM+rEyutdfv8=
|
||||
github.com/attestantio/go-eth2-client v0.6.15/go.mod h1:Hya4fp1ZLWAFI64qMhNbQgfY4StWiHulW4CFwu+vP3s=
|
||||
github.com/attestantio/go-eth2-client v0.6.16 h1:2Xn5RKqXUXfxLYVHn3D6l0FK7NUCjzl5v4oYIxcxc5k=
|
||||
github.com/attestantio/go-eth2-client v0.6.16/go.mod h1:Hya4fp1ZLWAFI64qMhNbQgfY4StWiHulW4CFwu+vP3s=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.32.6 h1:HoswAabUWgnrUF7X/9dr4WRgrr8DyscxXvTDm7Qw/5c=
|
||||
@@ -94,6 +98,8 @@ github.com/aws/aws-sdk-go v1.35.14 h1:nucVVXXjAr9UkmYCBWxQWRuYa5KOlaXjuJGg2ulW0K
|
||||
github.com/aws/aws-sdk-go v1.35.14/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||
github.com/aws/aws-sdk-go v1.35.26 h1:MawRvDpAp/Ai859dPC1xo1fdU/BIkijoHj0DwXLXXkI=
|
||||
github.com/aws/aws-sdk-go v1.35.26/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||
github.com/aws/aws-sdk-go v1.36.2 h1:UAeFPct+jHqWM+tgiqDrC9/sfbWj6wkcvpsJ+zdcsvA=
|
||||
github.com/aws/aws-sdk-go v1.36.2/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
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=
|
||||
@@ -112,6 +118,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
@@ -156,6 +163,7 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
@@ -172,6 +180,8 @@ github.com/ferranbt/fastssz v0.0.0-20200826142241-3a913c5a1313/go.mod h1:DyEu2iu
|
||||
github.com/ferranbt/fastssz v0.0.0-20201020132831-68dc48984fd3/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20201030134205-9b9624098321 h1:9Pkbf8HgETu3xKpz12Sj5clUrVFp2O+ymK7pBsTPYRM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20201030134205-9b9624098321/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/ferranbt/fastssz v0.0.0-20201207112544-98a5de30d648 h1:TBgYVQ5wP1iSjg53BnlXibpYzmAZJLsZhOcGDtu0FlQ=
|
||||
github.com/ferranbt/fastssz v0.0.0-20201207112544-98a5de30d648/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -187,13 +197,17 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
|
||||
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-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
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.3 h1:VGzw2KWSUyQX0yXai02S0nttBc+Oa4Kvh6RCFoxt8SE=
|
||||
github.com/goccy/go-yaml v1.8.3/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
||||
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
|
||||
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/gofrs/uuid v1.2.0 h1:coDhrjgyJaglxSjxuJdqQSSdUpG3w6p1OwN2od6frBU=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -347,6 +361,8 @@ github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.2 h1:mpQEXihFnWGDy6X98EOTh81JYuxn7txby8ilJ3iIPGM=
|
||||
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
@@ -419,6 +435,8 @@ github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYG
|
||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
|
||||
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -515,6 +533,8 @@ github.com/prysmaticlabs/ethereumapis v0.0.0-20201003171600-a72e5f77d233 h1:dGeu
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201003171600-a72e5f77d233/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba h1:ItW6tq3B45Gws8dO0cIuU1Srlgf4qomZnWkc0sDCln0=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201207010723-e69ac7fa952d h1:HdarpPepaIp6xIFfH4hG3IU6doct9WupCYQgT97G+4g=
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20201207010723-e69ac7fa952d/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
|
||||
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-20200618145306-2ae0807bef65 h1:hJfAWrlxx7SKpn4S/h2JGl2HHwA1a2wSS3HAzzZ0F+U=
|
||||
@@ -816,6 +836,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1V
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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=
|
||||
@@ -899,6 +921,8 @@ golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCR
|
||||
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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=
|
||||
@@ -939,6 +963,7 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -981,6 +1006,11 @@ golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7Ow
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1136,6 +1166,8 @@ google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab
|
||||
google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6 h1:iRN4+t0lvZX/l9gH14ARF9i58tsVa5a97k6aH95rC3Y=
|
||||
google.golang.org/genproto v0.0.0-20201111145450-ac7456db90a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201204160425-06b3db808446 h1:65ppmIPdaZE+BO34gntwqexoTYr30IRNGmS0OGOHu3A=
|
||||
google.golang.org/genproto v0.0.0-20201204160425-06b3db808446/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
@@ -1165,6 +1197,8 @@ google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
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=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -1212,6 +1246,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -27,6 +27,10 @@ import (
|
||||
|
||||
// ConnectToBeaconNode connects to a beacon node at the given address.
|
||||
func ConnectToBeaconNode(ctx context.Context, address string, timeout time.Duration, allowInsecure bool) (eth2client.Service, error) {
|
||||
if timeout == 0 {
|
||||
return nil, errors.New("no timeout specified")
|
||||
}
|
||||
|
||||
if !allowInsecure {
|
||||
// Ensure the connection is either secure or local.
|
||||
connectionURL, err := url.Parse(address)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
dirk "github.com/wealdtech/go-eth2-wallet-dirk"
|
||||
@@ -44,20 +43,20 @@ func SetupStore() error {
|
||||
// Set up our wallet store.
|
||||
switch viper.GetString("store") {
|
||||
case "s3":
|
||||
if util.GetBaseDir() != "" {
|
||||
if GetBaseDir() != "" {
|
||||
return errors.New("basedir does not apply to the s3 store")
|
||||
}
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(util.GetStorePassphrase())))
|
||||
store, err = s3.New(s3.WithPassphrase([]byte(GetStorePassphrase())))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to access Amazon S3 wallet store")
|
||||
}
|
||||
case "filesystem":
|
||||
opts := make([]filesystem.Option, 0)
|
||||
if util.GetStorePassphrase() != "" {
|
||||
opts = append(opts, filesystem.WithPassphrase([]byte(util.GetStorePassphrase())))
|
||||
if GetStorePassphrase() != "" {
|
||||
opts = append(opts, filesystem.WithPassphrase([]byte(GetStorePassphrase())))
|
||||
}
|
||||
if util.GetBaseDir() != "" {
|
||||
opts = append(opts, filesystem.WithLocation(viper.GetString("base-dir")))
|
||||
if GetBaseDir() != "" {
|
||||
opts = append(opts, filesystem.WithLocation(GetBaseDir()))
|
||||
}
|
||||
store = filesystem.New(opts...)
|
||||
default:
|
||||
@@ -139,13 +138,13 @@ func WalletAndAccountFromPath(ctx context.Context, path string) (e2wtypes.Wallet
|
||||
}
|
||||
|
||||
if wallet.Type() == "hierarchical deterministic" && strings.HasPrefix(accountName, "m/") {
|
||||
if util.GetWalletPassphrase() == "" {
|
||||
if GetWalletPassphrase() == "" {
|
||||
return nil, nil, errors.New("walletpassphrase is required for direct path derivations")
|
||||
}
|
||||
|
||||
locker, isLocker := wallet.(e2wtypes.WalletLocker)
|
||||
if isLocker {
|
||||
err = locker.Unlock(ctx, []byte(util.GetWalletPassphrase()))
|
||||
err = locker.Unlock(ctx, []byte(GetWalletPassphrase()))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("failed to unlock wallet")
|
||||
}
|
||||
@@ -11,12 +11,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-ssz"
|
||||
"github.com/spf13/viper"
|
||||
@@ -24,66 +24,54 @@ import (
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
// verifyStruct verifies the signature of an arbitrary structure.
|
||||
func verifyStruct(account e2wtypes.Account, data interface{}, domain []byte, signature e2types.Signature) (bool, error) {
|
||||
objRoot, err := ssz.HashTreeRoot(data)
|
||||
outputIf(debug, fmt.Sprintf("Object root is %#x", objRoot))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return verifyRoot(account, objRoot, domain, signature)
|
||||
}
|
||||
|
||||
// SigningContainer is the container for signing roots with a domain.
|
||||
// Contains SSZ sizes to allow for correct calculation of root.
|
||||
type signingContainer struct {
|
||||
Root []byte `ssz-size:"32"`
|
||||
Domain []byte `ssz-size:"32"`
|
||||
}
|
||||
|
||||
// signRoot signs a root.
|
||||
func signRoot(account e2wtypes.Account, root [32]byte, domain []byte) (e2types.Signature, error) {
|
||||
// SignRoot signs the hash tree root of a data structure
|
||||
func SignRoot(account e2wtypes.Account, root spec.Root, domain spec.Domain) (e2types.Signature, error) {
|
||||
if _, isProtectingSigner := account.(e2wtypes.AccountProtectingSigner); isProtectingSigner {
|
||||
// Signer signs the data to sign itself.
|
||||
return signGeneric(account, root[:], domain)
|
||||
// Signer builds the signing data.
|
||||
return signGeneric(account, root, domain)
|
||||
}
|
||||
|
||||
// Build the signing data manually.
|
||||
container := &signingContainer{
|
||||
Root: root[:],
|
||||
Domain: domain,
|
||||
container := &spec.SigningData{
|
||||
ObjectRoot: root,
|
||||
Domain: domain,
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.Root, container.Domain))
|
||||
signingRoot, err := ssz.HashTreeRoot(container)
|
||||
// outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.ObjectRoot, container.Domain))
|
||||
signingRoot, err := container.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
// outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
return sign(account, signingRoot[:])
|
||||
}
|
||||
|
||||
func verifyRoot(account e2wtypes.Account, root [32]byte, domain []byte, signature e2types.Signature) (bool, error) {
|
||||
// VerifyRoot verifies the hash tree root of a data structure.
|
||||
func VerifyRoot(account e2wtypes.Account, root spec.Root, domain spec.Domain, signature e2types.Signature) (bool, error) {
|
||||
// Build the signing data manually.
|
||||
container := &signingContainer{
|
||||
Root: root[:],
|
||||
Domain: domain,
|
||||
container := &spec.SigningData{
|
||||
ObjectRoot: root,
|
||||
Domain: domain,
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.Root, container.Domain))
|
||||
// outputIf(debug, fmt.Sprintf("Signing container:\n root: %#x\n domain: %#x", container.ObjectRoot, container.Domain))
|
||||
signingRoot, err := ssz.HashTreeRoot(container)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
return verify(account, signingRoot[:], signature)
|
||||
// outputIf(debug, fmt.Sprintf("Signing root: %#x", signingRoot))
|
||||
pubKey, err := BestPublicKey(account)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain account public key")
|
||||
}
|
||||
return signature.Verify(signingRoot[:], pubKey), nil
|
||||
}
|
||||
|
||||
func signGeneric(account e2wtypes.Account, data []byte, domain []byte) (e2types.Signature, error) {
|
||||
// signGeneric signs generic data.
|
||||
func signGeneric(account e2wtypes.Account, data spec.Root, domain spec.Domain) (e2types.Signature, error) {
|
||||
alreadyUnlocked, err := unlock(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
// outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
@@ -92,8 +80,8 @@ func signGeneric(account e2wtypes.Account, data []byte, domain []byte) (e2types.
|
||||
return nil, errors.New("account does not provide generic signing")
|
||||
}
|
||||
|
||||
signature, err := signer.SignGeneric(ctx, data, domain)
|
||||
errCheck(err, "failed to sign")
|
||||
signature, err := signer.SignGeneric(ctx, data[:], domain[:])
|
||||
// errCheck(err, "failed to sign")
|
||||
if !alreadyUnlocked {
|
||||
if err := lock(account); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lock account")
|
||||
@@ -108,7 +96,7 @@ func sign(account e2wtypes.Account, data []byte) (e2types.Signature, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
// outputIf(debug, fmt.Sprintf("Signing %x (%d)", data, len(data)))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
|
||||
@@ -118,7 +106,7 @@ func sign(account e2wtypes.Account, data []byte) (e2types.Signature, error) {
|
||||
}
|
||||
|
||||
signature, err := signer.Sign(ctx, data)
|
||||
errCheck(err, "failed to sign")
|
||||
// errCheck(err, "failed to sign")
|
||||
if !alreadyUnlocked {
|
||||
if err := lock(account); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to lock account")
|
||||
@@ -127,20 +115,11 @@ func sign(account e2wtypes.Account, data []byte) (e2types.Signature, error) {
|
||||
return signature, err
|
||||
}
|
||||
|
||||
// verify the signature of arbitrary data.
|
||||
func verify(account e2wtypes.Account, data []byte, signature e2types.Signature) (bool, error) {
|
||||
pubKey, err := bestPublicKey(account)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain account public key")
|
||||
}
|
||||
return signature.Verify(data, pubKey), nil
|
||||
}
|
||||
|
||||
// unlock attempts to unlock an account. It returns true if the account was already unlocked.
|
||||
func unlock(account e2wtypes.Account) (bool, error) {
|
||||
locker, isAccountLocker := account.(e2wtypes.AccountLocker)
|
||||
if !isAccountLocker {
|
||||
outputIf(debug, "Account does not support unlocking")
|
||||
// outputIf(debug, "Account does not support unlocking")
|
||||
// This account doesn't support unlocking; return okay.
|
||||
return true, nil
|
||||
}
|
||||
@@ -157,7 +136,7 @@ func unlock(account e2wtypes.Account) (bool, error) {
|
||||
}
|
||||
|
||||
// Not already unlocked; attempt to unlock it.
|
||||
for _, passphrase := range getPassphrases() {
|
||||
for _, passphrase := range GetPassphrases() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
err = locker.Unlock(ctx, []byte(passphrase))
|
||||
cancel()
|
||||
@@ -25,19 +25,19 @@ import (
|
||||
|
||||
// ValidatorExitData contains data for a validator exit.
|
||||
type ValidatorExitData struct {
|
||||
Data *spec.SignedVoluntaryExit
|
||||
Exit *spec.SignedVoluntaryExit
|
||||
ForkVersion spec.Version
|
||||
}
|
||||
|
||||
type validatorExitJSON struct {
|
||||
Data *spec.SignedVoluntaryExit `json:"data"`
|
||||
Exit *spec.SignedVoluntaryExit `json:"exit"`
|
||||
ForkVersion string `json:"fork_version"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements custom JSON marshaller.
|
||||
func (d *ValidatorExitData) MarshalJSON() ([]byte, error) {
|
||||
validatorExitJSON := &validatorExitJSON{
|
||||
Data: d.Data,
|
||||
Exit: d.Exit,
|
||||
ForkVersion: fmt.Sprintf("%#x", d.ForkVersion),
|
||||
}
|
||||
return json.Marshal(validatorExitJSON)
|
||||
@@ -51,10 +51,10 @@ func (d *ValidatorExitData) UnmarshalJSON(data []byte) error {
|
||||
return errors.Wrap(err, "failed to unmarshal JSON")
|
||||
}
|
||||
|
||||
if validatorExitJSON.Data == nil {
|
||||
return errors.New("data missing")
|
||||
if validatorExitJSON.Exit == nil {
|
||||
return errors.New("exit missing")
|
||||
}
|
||||
d.Data = validatorExitJSON.Data
|
||||
d.Exit = validatorExitJSON.Exit
|
||||
|
||||
if validatorExitJSON.ForkVersion == "" {
|
||||
return errors.New("fork version missing")
|
||||
|
||||
@@ -38,28 +38,28 @@ func TestUnmarshal(t *testing.T) {
|
||||
err: "invalid character 'i' looking for beginning of value",
|
||||
},
|
||||
{
|
||||
name: "DataMissing",
|
||||
name: "ExitMissing",
|
||||
in: []byte(`{"fork_version":"0x00000001"}`),
|
||||
err: "data missing",
|
||||
err: "exit missing",
|
||||
},
|
||||
{
|
||||
name: "DataInvalid",
|
||||
in: []byte(`{"data":{},"fork_version":"0x00000001"}`),
|
||||
name: "ExitInvalid",
|
||||
in: []byte(`{"exit":{},"fork_version":"0x00000001"}`),
|
||||
err: "failed to unmarshal JSON: message missing",
|
||||
},
|
||||
{
|
||||
name: "ForkVersionMissing",
|
||||
in: []byte(`{"data":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"}}`),
|
||||
in: []byte(`{"exit":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"}}`),
|
||||
err: "fork version missing",
|
||||
},
|
||||
{
|
||||
name: "ForkVersionInvalid",
|
||||
in: []byte(`{"data":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"},"fork_version":"invalid"}`),
|
||||
in: []byte(`{"exit":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"},"fork_version":"invalid"}`),
|
||||
err: "fork version invalid: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
in: []byte(`{"data":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"},"fork_version":"0x00000001"}`),
|
||||
in: []byte(`{"exit":{"message":{"epoch":"0","validator_index":"0"},"signature":"0xb74eade64ebf1e02cc57e5d29517032c6ca99132fb8e7fb7e6d58c68713e581ef0ef88e2a6c599a007d997782abdd50b0f9763500a93a971c89cb2275583fe755d7c0e64f459ff22fcef5cab3f80848f0356e67c142b9cf3ee65613f56283d6e"},"fork_version":"0x00000001"}`),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user