mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 22:47:59 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7ad5194e6 | ||
|
|
ddb866131b | ||
|
|
49fb03aa3a | ||
|
|
1ed3a51117 | ||
|
|
4d5660ccbb | ||
|
|
7596d271ad | ||
|
|
943f9350f3 | ||
|
|
07863846e6 | ||
|
|
cc59ab618d | ||
|
|
9794949e8a |
23
.github/workflows/golangci-lint.yml
vendored
Normal file
23
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: golangci-lint
|
||||
on: [ push, pull_request ]
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.29
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
args: --timeout=10m
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,4 +1,14 @@
|
||||
1.7.4:
|
||||
1.9.0
|
||||
- allow use of Ethereum 1 address as withdrawal credentials
|
||||
|
||||
1.8.1
|
||||
- fix issue where 'attester duties' and 'attester inclusion' could crash
|
||||
|
||||
1.8.0
|
||||
- add "chain time"
|
||||
- add "validator keycheck"
|
||||
|
||||
1.7.5:
|
||||
- add "slot time"
|
||||
- add "attester duties"
|
||||
- add "node events"
|
||||
|
||||
@@ -25,6 +25,9 @@ import (
|
||||
|
||||
func blsPrivateKey(input string) *e2types.BLSPrivateKey {
|
||||
data, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
key, err := e2types.BLSPrivateKeyFromBytes(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -58,7 +61,7 @@ func TestOutput(t *testing.T) {
|
||||
{
|
||||
name: "PrivatKey",
|
||||
dataOut: &dataOut{
|
||||
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
|
||||
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
|
||||
showPrivateKey: true,
|
||||
},
|
||||
needs: []string{"Public key", "Private key"},
|
||||
@@ -75,7 +78,7 @@ func TestOutput(t *testing.T) {
|
||||
name: "All",
|
||||
dataOut: &dataOut{
|
||||
key: blsPrivateKey("0x068dce0c90cb428ab37a74af0191eac49648035f1aaef077734b91e05985ec55"),
|
||||
showPrivateKey: true,
|
||||
showPrivateKey: true,
|
||||
showWithdrawalCredentials: true,
|
||||
},
|
||||
needs: []string{"Public key", "Private key", "Withdrawal credentials"},
|
||||
|
||||
@@ -27,6 +27,3 @@ var attestationCmd = &cobra.Command{
|
||||
func init() {
|
||||
RootCmd.AddCommand(attestationCmd)
|
||||
}
|
||||
|
||||
func attestationFlags(cmd *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -15,18 +15,13 @@ package attesterduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"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/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
@@ -39,10 +34,10 @@ type dataIn struct {
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
validator *api.Validator
|
||||
account string
|
||||
pubKey string
|
||||
eth2Client eth2client.Service
|
||||
epoch spec.Epoch
|
||||
account e2wtypes.Account
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -57,14 +52,15 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.debug = viper.GetBool("debug")
|
||||
data.json = viper.GetBool("json")
|
||||
|
||||
// Account.
|
||||
var err error
|
||||
data.account, err = attesterDutiesAccount()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
// Account or pubkey.
|
||||
if viper.GetString("account") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account or pubkey is required")
|
||||
}
|
||||
data.account = viper.GetString("account")
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
@@ -87,45 +83,5 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
}
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
data.validator = validators[0]
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// attesterDutiesAccount obtains the account for the attester duties command.
|
||||
func attesterDutiesAccount() (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 = util.WalletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKey := viper.GetString("pubkey")
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", pubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to obtain account: invalid public key : public key must be 48 bytes",
|
||||
err: "account or pubkey is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
|
||||
@@ -15,11 +15,16 @@ package attesterduties
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
@@ -27,13 +32,52 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if data.account != "" {
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
_, account, err = util.WalletAndAccountFromPath(ctx, data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", data.pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", data.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch validator
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, data.validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -48,10 +47,8 @@ func TestProcess(t *testing.T) {
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
validator: &api.Validator{
|
||||
Index: 0,
|
||||
},
|
||||
epoch: 100,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,18 +15,13 @@ package attesterinclusion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"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/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
@@ -38,10 +33,10 @@ type dataIn struct {
|
||||
// Chain information.
|
||||
slotsPerEpoch uint64
|
||||
// Operation.
|
||||
validator *api.Validator
|
||||
eth2Client eth2client.Service
|
||||
epoch spec.Epoch
|
||||
account e2wtypes.Account
|
||||
account string
|
||||
pubKey string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
@@ -55,14 +50,15 @@ func input(ctx context.Context) (*dataIn, error) {
|
||||
data.verbose = viper.GetBool("verbose")
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Account.
|
||||
var err error
|
||||
data.account, err = attesterInclusionAccount()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
// Account or pubkey.
|
||||
if viper.GetString("account") == "" && viper.GetString("pubkey") == "" {
|
||||
return nil, errors.New("account or pubkey is required")
|
||||
}
|
||||
data.account = viper.GetString("account")
|
||||
data.pubKey = viper.GetString("pubkey")
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
@@ -89,49 +85,5 @@ 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 := 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, stateID, pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
for _, validator := range validators {
|
||||
data.validator = validator
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// attesterInclusionAccount obtains the account for the attester inclusion command.
|
||||
func attesterInclusionAccount() (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 = util.WalletAndAccountFromPath(ctx, viper.GetString("account"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKey := viper.GetString("pubkey")
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", pubKey))
|
||||
}
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestInput(t *testing.T) {
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to obtain account: invalid public key : public key must be 48 bytes",
|
||||
err: "account or pubkey is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
|
||||
@@ -15,12 +15,16 @@ package attesterinclusion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
@@ -28,13 +32,52 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
if data.account != "" {
|
||||
ctx, cancel := context.WithTimeout(ctx, data.timeout)
|
||||
defer cancel()
|
||||
_, account, err = util.WalletAndAccountFromPath(ctx, data.account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain account")
|
||||
}
|
||||
} else {
|
||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.pubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", data.pubKey))
|
||||
}
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", data.pubKey))
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch validator
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
pubKey, err := util.BestPublicKey(account)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
||||
}
|
||||
copy(pubKeys[0][:], pubKey.Marshal())
|
||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).ValidatorsByPubKey(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), pubKeys)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to obtain validator information")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("validator is not known")
|
||||
}
|
||||
var validator *api.Validator
|
||||
for _, v := range validators {
|
||||
validator = v
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
duty, err := duty(ctx, data.eth2Client, data.validator, data.epoch, data.slotsPerEpoch)
|
||||
duty, err := duty(ctx, data.eth2Client, validator, data.epoch, data.slotsPerEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain duty for validator")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -48,10 +47,8 @@ func TestProcess(t *testing.T) {
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
slotsPerEpoch: 32,
|
||||
validator: &api.Validator{
|
||||
Index: 0,
|
||||
},
|
||||
epoch: 100,
|
||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||
epoch: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
81
cmd/chain/time/input.go
Normal file
81
cmd/chain/time/input.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
// System.
|
||||
timeout time.Duration
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
json bool
|
||||
// Input
|
||||
connection string
|
||||
allowInsecureConnections bool
|
||||
timestamp string
|
||||
slot string
|
||||
epoch 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")
|
||||
data.json = viper.GetBool("json")
|
||||
|
||||
haveInput := false
|
||||
if viper.GetString("timestamp") != "" {
|
||||
data.timestamp = viper.GetString("timestamp")
|
||||
haveInput = true
|
||||
}
|
||||
if viper.GetString("slot") != "" {
|
||||
if haveInput {
|
||||
return nil, errors.New("only one of timestamp, slot and epoch allowed")
|
||||
}
|
||||
data.slot = viper.GetString("slot")
|
||||
haveInput = true
|
||||
}
|
||||
if viper.GetString("epoch") != "" {
|
||||
if haveInput {
|
||||
return nil, errors.New("only one of timestamp, slot and epoch allowed")
|
||||
}
|
||||
data.epoch = viper.GetString("epoch")
|
||||
haveInput = true
|
||||
}
|
||||
if !haveInput {
|
||||
return nil, errors.New("one of timestamp, slot or epoch required")
|
||||
}
|
||||
|
||||
if viper.GetString("connection") == "" {
|
||||
return nil, errors.New("connection is required")
|
||||
}
|
||||
data.connection = viper.GetString("connection")
|
||||
data.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
return data, nil
|
||||
}
|
||||
97
cmd/chain/time/input_internal_test.go
Normal file
97
cmd/chain/time/input_internal_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
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{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"slot": "1",
|
||||
},
|
||||
err: "connection is required",
|
||||
},
|
||||
{
|
||||
name: "IDMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
err: "one of timestamp, slot or epoch 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
66
cmd/chain/time/output.go
Normal file
66
cmd/chain/time/output.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
debug bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
|
||||
epoch spec.Epoch
|
||||
epochStart time.Time
|
||||
epochEnd time.Time
|
||||
slot spec.Slot
|
||||
slotStart time.Time
|
||||
slotEnd time.Time
|
||||
}
|
||||
|
||||
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{}
|
||||
|
||||
builder.WriteString("Epoch ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.epoch))
|
||||
builder.WriteString("\n Epoch start ")
|
||||
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString("\n Epoch end ")
|
||||
builder.WriteString(data.epochEnd.Format("2006-01-02 15:04:05"))
|
||||
|
||||
builder.WriteString("\nSlot ")
|
||||
builder.WriteString(fmt.Sprintf("%d", data.slot))
|
||||
builder.WriteString("\n Slot start ")
|
||||
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString("\n Slot end ")
|
||||
builder.WriteString(data.slotEnd.Format("2006-01-02 15:04:05"))
|
||||
builder.WriteString("\n")
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
85
cmd/chain/time/output_internal_test.go
Normal file
85
cmd/chain/time/output_internal_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
//
|
||||
// api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// "github.com/wealdtech/ethdo/testutil"
|
||||
// )
|
||||
//
|
||||
// func TestOutput(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// dataOut *dataOut
|
||||
// res string
|
||||
// err string
|
||||
// }{
|
||||
// {
|
||||
// name: "Nil",
|
||||
// err: "no data",
|
||||
// },
|
||||
// {
|
||||
// name: "Empty",
|
||||
// dataOut: &dataOut{},
|
||||
// res: "No duties found",
|
||||
// },
|
||||
// {
|
||||
// name: "Present",
|
||||
// dataOut: &dataOut{
|
||||
// duty: &api.AttesterDuty{
|
||||
// PubKey: testutil.HexToPubKey("0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95"),
|
||||
// Slot: 1,
|
||||
// ValidatorIndex: 2,
|
||||
// CommitteeIndex: 3,
|
||||
// CommitteeLength: 4,
|
||||
// CommitteesAtSlot: 5,
|
||||
// ValidatorCommitteeIndex: 6,
|
||||
// },
|
||||
// },
|
||||
// res: "Validator attesting in slot 1 committee 3",
|
||||
// },
|
||||
// {
|
||||
// name: "JSON",
|
||||
// dataOut: &dataOut{
|
||||
// json: true,
|
||||
// duty: &api.AttesterDuty{
|
||||
// PubKey: testutil.HexToPubKey("0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95"),
|
||||
// Slot: 1,
|
||||
// ValidatorIndex: 2,
|
||||
// CommitteeIndex: 3,
|
||||
// CommitteeLength: 4,
|
||||
// CommitteesAtSlot: 5,
|
||||
// ValidatorCommitteeIndex: 6,
|
||||
// },
|
||||
// },
|
||||
// res: `{"pubkey":"0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95","slot":"1","validator_index":"2","committee_index":"3","committee_length":"4","committees_at_slot":"5","validator_committee_index":"6"}`,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// 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)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
89
cmd/chain/time/process.go
Normal file
89
cmd/chain/time/process.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.connection, data.timeout, data.allowInsecureConnections)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
}
|
||||
|
||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
||||
}
|
||||
|
||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
}
|
||||
|
||||
// Calculate the slot given the input.
|
||||
switch {
|
||||
case data.slot != "":
|
||||
slot, err := strconv.ParseUint(data.slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse slot")
|
||||
}
|
||||
results.slot = spec.Slot(slot)
|
||||
case data.epoch != "":
|
||||
epoch, err := strconv.ParseUint(data.epoch, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse epoch")
|
||||
}
|
||||
results.slot = spec.Slot(epoch * slotsPerEpoch)
|
||||
case data.timestamp != "":
|
||||
timestamp, err := time.Parse("2006-01-02T15:04:05", data.timestamp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse timestamp")
|
||||
}
|
||||
secs := timestamp.Sub(genesis.GenesisTime)
|
||||
if secs < 0 {
|
||||
return nil, errors.New("timestamp prior to genesis")
|
||||
}
|
||||
results.slot = spec.Slot(secs / slotDuration)
|
||||
}
|
||||
|
||||
// Fill in the info given the slot.
|
||||
results.slotStart = genesis.GenesisTime.Add(time.Duration(results.slot) * slotDuration)
|
||||
results.slotEnd = genesis.GenesisTime.Add(time.Duration(results.slot+1) * slotDuration)
|
||||
results.epoch = spec.Epoch(uint64(results.slot) / slotsPerEpoch)
|
||||
results.epochStart = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch)*slotsPerEpoch) * slotDuration)
|
||||
results.epochEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch+1)*slotsPerEpoch) * slotDuration)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
103
cmd/chain/time/process_internal_test.go
Normal file
103
cmd/chain/time/process_internal_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
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
|
||||
expected *dataOut
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Slot",
|
||||
dataIn: &dataIn{
|
||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
timeout: 10 * time.Second,
|
||||
allowInsecureConnections: true,
|
||||
slot: "1",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epochStart: time.Unix(1606824023, 0),
|
||||
epochEnd: time.Unix(1606824407, 0),
|
||||
slot: 1,
|
||||
slotStart: time.Unix(1606824035, 0),
|
||||
slotEnd: time.Unix(1606824047, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
dataIn: &dataIn{
|
||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
timeout: 10 * time.Second,
|
||||
allowInsecureConnections: true,
|
||||
epoch: "2",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 2,
|
||||
epochStart: time.Unix(1606824791, 0),
|
||||
epochEnd: time.Unix(1606825175, 0),
|
||||
slot: 64,
|
||||
slotStart: time.Unix(1606824791, 0),
|
||||
slotEnd: time.Unix(1606824803, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Timestamp",
|
||||
dataIn: &dataIn{
|
||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
timeout: 10 * time.Second,
|
||||
allowInsecureConnections: true,
|
||||
timestamp: "2021-01-01T00:00:00",
|
||||
},
|
||||
expected: &dataOut{
|
||||
epoch: 6862,
|
||||
epochStart: time.Unix(1609459031, 0),
|
||||
epochEnd: time.Unix(1609459415, 0),
|
||||
slot: 219598,
|
||||
slotStart: time.Unix(1609459199, 0),
|
||||
slotEnd: time.Unix(1609459211, 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := process(context.Background(), test.dataIn)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/chain/time/run.go
Normal file
50
cmd/chain/time/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
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
|
||||
}
|
||||
60
cmd/chaintime.go
Normal file
60
cmd/chaintime.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
chaintime "github.com/wealdtech/ethdo/cmd/chain/time"
|
||||
)
|
||||
|
||||
var chainTimeCmd = &cobra.Command{
|
||||
Use: "time",
|
||||
Short: "Obtain info about the chain at a given time",
|
||||
Long: `Obtain info about the chain at a given time. For example:
|
||||
|
||||
ethdo chain time --slot=12345`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := chaintime.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Print(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainTimeCmd)
|
||||
chainFlags(chainTimeCmd)
|
||||
chainTimeCmd.Flags().String("slot", "", "The slot for which to obtain information")
|
||||
chainTimeCmd.Flags().String("epoch", "", "The epoch for which to obtain information")
|
||||
chainTimeCmd.Flags().String("timestamp", "", "The timestamp for which to obtain information (format YYYY-MM-DDTHH:MM:SS)")
|
||||
}
|
||||
|
||||
func chainTimeBindings() {
|
||||
if err := viper.BindPFlag("slot", chainTimeCmd.Flags().Lookup("slot")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("epoch", chainTimeCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("timestamp", chainTimeCmd.Flags().Lookup("timestamp")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019-2021 Weald Technology Limited.
|
||||
// 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
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
var depositVerifyData string
|
||||
var depositVerifyWithdrawalPubKey string
|
||||
var depositVerifyWithdrawalAddress string
|
||||
var depositVerifyValidatorPubKey string
|
||||
var depositVerifyDepositAmount string
|
||||
var depositVerifyForkVersion string
|
||||
@@ -81,7 +82,14 @@ In quiet mode this will return 0 if the the data is verified correctly, otherwis
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
errCheck(err, "Value supplied with --withdrawalpubkey is not a valid public key")
|
||||
withdrawalCredentials = eth2util.SHA256(withdrawalPubKey.Marshal())
|
||||
withdrawalCredentials[0] = 0 // BLS_WITHDRAWAL_PREFIX
|
||||
withdrawalCredentials[0] = 0x00 // BLS_WITHDRAWAL_PREFIX
|
||||
} else if depositVerifyWithdrawalAddress != "" {
|
||||
withdrawalAddressBytes, err := hex.DecodeString(strings.TrimPrefix(depositVerifyWithdrawalAddress, "0x"))
|
||||
errCheck(err, "Invalid withdrawal address")
|
||||
assert(len(withdrawalAddressBytes) == 20, "address should be 20 bytes")
|
||||
withdrawalCredentials = make([]byte, 32)
|
||||
withdrawalCredentials[0] = 0x01 // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
copy(withdrawalCredentials[12:], withdrawalAddressBytes)
|
||||
}
|
||||
outputIf(debug, fmt.Sprintf("Withdrawal credentials are %#x", withdrawalCredentials))
|
||||
|
||||
@@ -181,10 +189,10 @@ func validatorPubKeysFromInput(input string) (map[[48]byte]bool, error) {
|
||||
|
||||
func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, validatorPubKeys map[[48]byte]bool, amount uint64) (bool, error) {
|
||||
if withdrawalCredentials == nil {
|
||||
outputIf(!quiet, "Withdrawal public key not supplied; withdrawal credentials NOT checked")
|
||||
outputIf(!quiet, "Withdrawal public key or address not supplied; withdrawal credentials NOT checked")
|
||||
} else {
|
||||
if !bytes.Equal(deposit.WithdrawalCredentials, withdrawalCredentials) {
|
||||
outputIf(!quiet, "Withdrawal public key incorrect")
|
||||
outputIf(!quiet, "Withdrawal credentials incorrect")
|
||||
return false, nil
|
||||
}
|
||||
outputIf(!quiet, "Withdrawal credentials verified")
|
||||
@@ -246,7 +254,7 @@ func verifyDeposit(deposit *util.DepositInfo, withdrawalCredentials []byte, vali
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to decode fork version")
|
||||
}
|
||||
if bytes.Equal(deposit.ForkVersion, forkVersion[:]) {
|
||||
if bytes.Equal(deposit.ForkVersion, forkVersion) {
|
||||
outputIf(!quiet, "Fork version verified")
|
||||
} else {
|
||||
outputIf(!quiet, "Fork version incorrect")
|
||||
@@ -263,6 +271,7 @@ func init() {
|
||||
depositFlags(depositVerifyCmd)
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyData, "data", "", "JSON data, or path to JSON data")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyWithdrawalPubKey, "withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyWithdrawalAddress, "withdrawaladdress", "", "Ethereum 1 address of the account to which the validator funds will be withdrawn")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyDepositAmount, "depositvalue", "32 Ether", "Value of the amount to be deposited")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyValidatorPubKey, "validatorpubkey", "", "Public key(s) of the account(s) that will be carrying out validation")
|
||||
depositVerifyCmd.Flags().StringVar(&depositVerifyForkVersion, "forkversion", "0x00000000", "Fork version of the chain of the deposit")
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var jsonOutput bool
|
||||
|
||||
func process(ctx context.Context, data *dataIn) error {
|
||||
if data == nil {
|
||||
return errors.New("no data")
|
||||
|
||||
@@ -27,6 +27,3 @@ var proposerCmd = &cobra.Command{
|
||||
func init() {
|
||||
RootCmd.AddCommand(proposerCmd)
|
||||
}
|
||||
|
||||
func proposerFlags(cmd *cobra.Command) {
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
attesterInclusionBindings()
|
||||
case "block/info":
|
||||
blockInfoBindings()
|
||||
case "chain/time":
|
||||
chainTimeBindings()
|
||||
case "exit/verify":
|
||||
exitVerifyBindings()
|
||||
case "node/events":
|
||||
@@ -88,6 +90,8 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
validatorExitBindings()
|
||||
case "validator/info":
|
||||
validatorInfoBindings()
|
||||
case "validator/keycheck":
|
||||
validatorKeycheckBindings()
|
||||
case "wallet/create":
|
||||
walletCreateBindings()
|
||||
case "wallet/import":
|
||||
|
||||
@@ -40,5 +40,5 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
if data.verbose {
|
||||
return fmt.Sprintf("%s - %s", data.startTime, data.endTime), nil
|
||||
}
|
||||
return fmt.Sprintf("%s", data.startTime), nil
|
||||
return data.startTime.String(), nil
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
|
||||
results.startTime = genesis.GenesisTime.Add(time.Duration((time.Duration(slot*int64(slotDuration.Seconds())) * time.Second)))
|
||||
results.startTime = genesis.GenesisTime.Add((time.Duration(slot*int64(slotDuration.Seconds())) * time.Second))
|
||||
results.endTime = results.startTime.Add(slotDuration)
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -17,25 +17,28 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
ethdoutil "github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
string2eth "github.com/wealdtech/go-string2eth"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
format string
|
||||
withdrawalCredentials []byte
|
||||
amount spec.Gwei
|
||||
validatorAccounts []e2wtypes.Account
|
||||
forkVersion *spec.Version
|
||||
domain *spec.Domain
|
||||
passphrases []string
|
||||
format string
|
||||
timeout time.Duration
|
||||
withdrawalAccount string
|
||||
withdrawalPubKey string
|
||||
withdrawalAddress string
|
||||
amount spec.Gwei
|
||||
validatorAccounts []e2wtypes.Account
|
||||
forkVersion *spec.Version
|
||||
domain *spec.Domain
|
||||
passphrases []string
|
||||
}
|
||||
|
||||
func input() (*dataIn, error) {
|
||||
@@ -49,6 +52,11 @@ func input() (*dataIn, error) {
|
||||
return nil, errors.New("validator account is required")
|
||||
}
|
||||
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
data.timeout = viper.GetDuration("timeout")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, data.validatorAccounts, err = ethdoutil.WalletAndAccountsFromPath(ctx, viper.GetString("validatoraccount"))
|
||||
@@ -70,37 +78,25 @@ func input() (*dataIn, error) {
|
||||
|
||||
data.passphrases = ethdoutil.GetPassphrases()
|
||||
|
||||
switch {
|
||||
case viper.GetString("withdrawalaccount") != "":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout"))
|
||||
defer cancel()
|
||||
_, withdrawalAccount, err := ethdoutil.WalletAndAccountFromPath(ctx, viper.GetString("withdrawalaccount"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain withdrawal account")
|
||||
}
|
||||
pubKey, err := ethdoutil.BestPublicKey(withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for withdrawal account")
|
||||
}
|
||||
data.withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
case viper.GetString("withdrawalpubkey") != "":
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(viper.GetString("withdrawalpubkey"), "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode withdrawal public key")
|
||||
}
|
||||
if len(withdrawalPubKeyBytes) != 48 {
|
||||
return nil, errors.New("withdrawal public key must be exactly 48 bytes in length")
|
||||
}
|
||||
withdrawalPubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "withdrawal public key is not valid")
|
||||
}
|
||||
data.withdrawalCredentials = util.SHA256(withdrawalPubKey.Marshal())
|
||||
default:
|
||||
return nil, errors.New("withdrawalaccount or withdrawal public key is required")
|
||||
data.withdrawalAccount = viper.GetString("withdrawalaccount")
|
||||
data.withdrawalPubKey = viper.GetString("withdrawalpubkey")
|
||||
data.withdrawalAddress = viper.GetString("withdrawaladdress")
|
||||
withdrawalDetailsPresent := 0
|
||||
if data.withdrawalAccount != "" {
|
||||
withdrawalDetailsPresent++
|
||||
}
|
||||
if data.withdrawalPubKey != "" {
|
||||
withdrawalDetailsPresent++
|
||||
}
|
||||
if data.withdrawalAddress != "" {
|
||||
withdrawalDetailsPresent++
|
||||
}
|
||||
if withdrawalDetailsPresent == 0 {
|
||||
return nil, errors.New("withdrawal account, public key or address is required")
|
||||
}
|
||||
if withdrawalDetailsPresent > 1 {
|
||||
return nil, errors.New("only one of withdrawal account, public key or address is allowed")
|
||||
}
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
data.withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
|
||||
if viper.GetString("depositvalue") == "" {
|
||||
return nil, errors.New("deposit value is required")
|
||||
@@ -135,7 +131,7 @@ func inputForkVersion(ctx context.Context) (*spec.Version, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode fork version")
|
||||
}
|
||||
if len(forkVersion) != 4 {
|
||||
if len(data) != 4 {
|
||||
return nil, errors.New("fork version must be exactly 4 bytes in length")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019-2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -84,9 +84,20 @@ func TestInput(t *testing.T) {
|
||||
name: "Nil",
|
||||
err: "validator account is required",
|
||||
},
|
||||
{
|
||||
name: "TimeoutMissing",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ValidatorAccountMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
@@ -96,6 +107,7 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "ValidatorAccountUnknown",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Unknown",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
@@ -104,59 +116,74 @@ func TestInput(t *testing.T) {
|
||||
err: "unknown validator account",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAccountMissing",
|
||||
name: "WithdrawalDetailsMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawalaccount or withdrawal public key is required",
|
||||
err: "withdrawal account, public key or address is required",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAccountUnknown",
|
||||
name: "WithdrawalDetailsTooMany1",
|
||||
vars: map[string]interface{}{
|
||||
"raw": true,
|
||||
"timeout": "10s",
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Unknown",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "failed to obtain withdrawal account: failed to obtain account: no account with name \"Unknown\"",
|
||||
err: "only one of withdrawal account, public key or address is allowed",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyInvalid",
|
||||
name: "WithdrawalDetailsTooMany2",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "invalid",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
"timeout": "10s",
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"withdrawaladdress": "0x30C99930617B7b793beaB603ecEB08691005f2E5",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "failed to decode withdrawal public key: encoding/hex: invalid byte: U+0069 'i'",
|
||||
err: "only one of withdrawal account, public key or address is allowed",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyWrongLength",
|
||||
name: "WithdrawalDetailsTooMany3",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0bff",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
"timeout": "10s",
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"withdrawaladdress": "0x30C99930617B7b793beaB603ecEB08691005f2E5",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawal public key must be exactly 48 bytes in length",
|
||||
err: "only one of withdrawal account, public key or address is allowed",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyNotPubKey",
|
||||
name: "WithdrawalDetailsTooMany4",
|
||||
vars: map[string]interface{}{
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0x089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
"timeout": "10s",
|
||||
"launchpad": true,
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"withdrawaladdress": "0x30C99930617B7b793beaB603ecEB08691005f2E5",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
err: "withdrawal public key is not valid: failed to deserialize public key: err blsPublicKeyDeserialize 089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
err: "only one of withdrawal account, public key or address is allowed",
|
||||
},
|
||||
{
|
||||
name: "DepositValueMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"forkversion": "0x01020304",
|
||||
@@ -166,6 +193,7 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "DepositValueTooSmall",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "1000 Wei",
|
||||
@@ -176,6 +204,7 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "DepositValueInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "1 groat",
|
||||
@@ -186,6 +215,7 @@ func TestInput(t *testing.T) {
|
||||
{
|
||||
name: "ForkVersionInvalid",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
@@ -193,54 +223,68 @@ func TestInput(t *testing.T) {
|
||||
},
|
||||
err: "failed to obtain fork version: failed to decode fork version: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "ForkVersionShort",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01",
|
||||
},
|
||||
err: "failed to obtain fork version: fork version must be exactly 4 bytes in length",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
},
|
||||
res: &dataIn{
|
||||
format: "json",
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: mainnetForkVersion,
|
||||
domain: mainnetDomain,
|
||||
format: "json",
|
||||
withdrawalAccount: "Test/Interop 0",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: mainnetForkVersion,
|
||||
domain: mainnetDomain,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GoodForkVersionOverride",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalaccount": "Test/Interop 0",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
res: &dataIn{
|
||||
format: "json",
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
format: "json",
|
||||
withdrawalAccount: "Test/Interop 0",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GoodWithdrawalPubKey",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "10s",
|
||||
"validatoraccount": "Test/Interop 0",
|
||||
"withdrawalpubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
"depositvalue": "32 Ether",
|
||||
"forkversion": "0x01020304",
|
||||
},
|
||||
res: &dataIn{
|
||||
format: "json",
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
format: "json",
|
||||
withdrawalPubKey: "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -258,7 +302,9 @@ func TestInput(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
// Cannot compare accounts directly, so need to check each element individually.
|
||||
require.Equal(t, test.res.format, res.format)
|
||||
require.Equal(t, test.res.withdrawalCredentials, res.withdrawalCredentials)
|
||||
require.Equal(t, test.res.withdrawalAccount, res.withdrawalAccount)
|
||||
require.Equal(t, test.res.withdrawalAddress, res.withdrawalAddress)
|
||||
require.Equal(t, test.res.withdrawalPubKey, res.withdrawalPubKey)
|
||||
require.Equal(t, test.res.amount, res.amount)
|
||||
require.Equal(t, test.res.forkVersion, res.forkVersion)
|
||||
require.Equal(t, test.res.domain, res.domain)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 Weald Technology Trading
|
||||
// Copyright © 2019-2021 Weald Technology Limited.
|
||||
// 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,12 +15,16 @@ package depositdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/signing"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
ethdoutil "github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
@@ -31,8 +35,13 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
|
||||
results := make([]*dataOut, 0)
|
||||
|
||||
withdrawalCredentials, err := createWithdrawalCredentials(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, validatorAccount := range data.validatorAccounts {
|
||||
validatorPubKey, err := util.BestPublicKey(validatorAccount)
|
||||
validatorPubKey, err := ethdoutil.BestPublicKey(validatorAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "validator account does not provide a public key")
|
||||
}
|
||||
@@ -41,7 +50,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
copy(pubKey[:], validatorPubKey.Marshal())
|
||||
depositMessage := &spec.DepositMessage{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: data.withdrawalCredentials,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: data.amount,
|
||||
}
|
||||
root, err := depositMessage.HashTreeRoot()
|
||||
@@ -58,7 +67,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
|
||||
depositData := &spec.DepositData{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: data.withdrawalCredentials,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: data.amount,
|
||||
Signature: sig,
|
||||
}
|
||||
@@ -75,7 +84,7 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
format: data.format,
|
||||
account: fmt.Sprintf("%s/%s", validatorWallet.Name(), validatorAccount.Name()),
|
||||
validatorPubKey: &pubKey,
|
||||
withdrawalCredentials: data.withdrawalCredentials,
|
||||
withdrawalCredentials: withdrawalCredentials,
|
||||
amount: data.amount,
|
||||
signature: &sig,
|
||||
forkVersion: data.forkVersion,
|
||||
@@ -85,3 +94,80 @@ func process(data *dataIn) ([]*dataOut, error) {
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// createWithdrawalCredentials creates withdrawal credentials given an account, public key or Ethereum 1 address.
|
||||
func createWithdrawalCredentials(data *dataIn) ([]byte, error) {
|
||||
var withdrawalCredentials []byte
|
||||
|
||||
switch {
|
||||
case data.withdrawalAccount != "":
|
||||
ctx, cancel := context.WithTimeout(context.Background(), data.timeout)
|
||||
defer cancel()
|
||||
_, withdrawalAccount, err := ethdoutil.WalletAndAccountFromPath(ctx, data.withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain withdrawal account")
|
||||
}
|
||||
pubKey, err := ethdoutil.BestPublicKey(withdrawalAccount)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain public key for withdrawal account")
|
||||
}
|
||||
withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
case data.withdrawalPubKey != "":
|
||||
withdrawalPubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(data.withdrawalPubKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode withdrawal public key")
|
||||
}
|
||||
if len(withdrawalPubKeyBytes) != 48 {
|
||||
return nil, errors.New("withdrawal public key must be exactly 48 bytes in length")
|
||||
}
|
||||
pubKey, err := e2types.BLSPublicKeyFromBytes(withdrawalPubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "withdrawal public key is not valid")
|
||||
}
|
||||
withdrawalCredentials = util.SHA256(pubKey.Marshal())
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
case data.withdrawalAddress != "":
|
||||
withdrawalAddressBytes, err := hex.DecodeString(strings.TrimPrefix(data.withdrawalAddress, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode withdrawal address")
|
||||
}
|
||||
if len(withdrawalAddressBytes) != 20 {
|
||||
return nil, errors.New("withdrawal address must be exactly 20 bytes in length")
|
||||
}
|
||||
// Ensure the address is properly checksummed.
|
||||
checksummedAddress := addressBytesToEIP55(withdrawalAddressBytes)
|
||||
if checksummedAddress != data.withdrawalAddress {
|
||||
return nil, fmt.Errorf("withdrawal address checksum does not match (expected %s)", checksummedAddress)
|
||||
}
|
||||
withdrawalCredentials = make([]byte, 32)
|
||||
copy(withdrawalCredentials[12:32], withdrawalAddressBytes)
|
||||
// This is hard-coded, to allow deposit data to be generated without a connection to the beacon node.
|
||||
withdrawalCredentials[0] = byte(1) // ETH1_ADDRESS_WITHDRAWAL_PREFIX
|
||||
default:
|
||||
return nil, errors.New("withdrawal account, public key or address is required")
|
||||
}
|
||||
|
||||
return withdrawalCredentials, nil
|
||||
}
|
||||
|
||||
// addressBytesToEIP55 converts a byte array in to an EIP-55 string format.
|
||||
func addressBytesToEIP55(address []byte) string {
|
||||
bytes := []byte(fmt.Sprintf("%x", address))
|
||||
hash := util.Keccak256(bytes)
|
||||
for i := 0; i < len(bytes); i++ {
|
||||
hashByte := hash[i/2]
|
||||
if i%2 == 0 {
|
||||
hashByte >>= 4
|
||||
} else {
|
||||
hashByte &= 0xf
|
||||
}
|
||||
if bytes[i] > '9' && hashByte > 7 {
|
||||
bytes[i] -= 32
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("0x%s", string(bytes))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019, 2020 eald Technology Trading
|
||||
// Copyright © 2019-2021 Weald Technology Limited.
|
||||
// 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,6 +15,8 @@ package depositdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
@@ -49,6 +51,10 @@ func TestProcess(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalAccount := "Test/Interop 0"
|
||||
withdrawalPubKey := "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"
|
||||
withdrawalAddress := "0x30C99930617B7b793beaB603ecEB08691005f2E5"
|
||||
|
||||
var validatorPubKey *spec.BLSPubKey
|
||||
{
|
||||
tmp := testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c")
|
||||
@@ -101,6 +107,22 @@ func TestProcess(t *testing.T) {
|
||||
depositMessageRoot2 = &tmp
|
||||
}
|
||||
|
||||
var depositDataRoot3 *spec.Root
|
||||
{
|
||||
tmp := testutil.HexToRoot("0x489500535b03dd9deffa0f00cb38d82346111856fb58a9541fe1f01a1a97429c")
|
||||
depositDataRoot3 = &tmp
|
||||
}
|
||||
var depositMessageRoot3 *spec.Root
|
||||
{
|
||||
tmp := testutil.HexToRoot("0x7b8ee5694e4338cf2bfe5a4d2f46540f0ade85ebd30713673cf5783c4e925681")
|
||||
depositMessageRoot3 = &tmp
|
||||
}
|
||||
var signature3 *spec.BLSSignature
|
||||
{
|
||||
tmp := testutil.HexToSignature("0xba0019d5c421f205d845782f52a87ab95cd489fbef2911f8a1f9cf7c14b4ce59eefa82641e770a4cb405534b7776d0f801b0a8b178c1b71b718c104e89f4e633da10a398c7919a00c403d58f3f4b827af8adb263b192e7a45b0ed1926dff5f66")
|
||||
signature3 = &tmp
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
@@ -111,16 +133,119 @@ func TestProcess(t *testing.T) {
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalDetailsMissing",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "withdrawal account, public key or address is required",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAccountUnknown",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAccount: "Unknown",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "failed to obtain withdrawal account: failed to open wallet for account: wallet not found",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyInvalid",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalPubKey: "invalid",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "failed to decode withdrawal public key: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyWrongLength",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalPubKey: "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0bff",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "withdrawal public key must be exactly 48 bytes in length",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalPubKeyNotPubKey",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalPubKey: "0x089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "withdrawal public key is not valid: failed to deserialize public key: err blsPublicKeyDeserialize 089bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAddressInvalid",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAddress: "invalid",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "failed to decode withdrawal address: encoding/hex: invalid byte: U+0069 'i'",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAddressWrongLength",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAddress: "0x30C99930617B7b793beaB603ecEB08691005f2",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "withdrawal address must be exactly 20 bytes in length",
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAddressIncorrectChecksum",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAddress: "0x30c99930617b7b793beab603eceb08691005f2e5",
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
err: "withdrawal address checksum does not match (expected 0x30C99930617B7b793beaB603ecEB08691005f2E5)",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAccount: withdrawalAccount,
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
res: []*dataOut{
|
||||
{
|
||||
@@ -139,13 +264,13 @@ func TestProcess(t *testing.T) {
|
||||
{
|
||||
name: "Double",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0, interop1},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalPubKey: withdrawalPubKey,
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0, interop1},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
res: []*dataOut{
|
||||
{
|
||||
@@ -172,6 +297,31 @@ func TestProcess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WithdrawalAddress",
|
||||
dataIn: &dataIn{
|
||||
format: "raw",
|
||||
passphrases: []string{"pass"},
|
||||
withdrawalAddress: withdrawalAddress,
|
||||
amount: 32000000000,
|
||||
validatorAccounts: []e2wtypes.Account{interop0},
|
||||
forkVersion: forkVersion,
|
||||
domain: domain,
|
||||
},
|
||||
res: []*dataOut{
|
||||
{
|
||||
format: "raw",
|
||||
account: "Test/Interop 0",
|
||||
validatorPubKey: validatorPubKey,
|
||||
amount: 32000000000,
|
||||
withdrawalCredentials: testutil.HexToBytes("0x01000000000000000000000030C99930617B7b793beaB603ecEB08691005f2E5"),
|
||||
signature: signature3,
|
||||
forkVersion: forkVersion,
|
||||
depositDataRoot: depositDataRoot3,
|
||||
depositMessageRoot: depositMessageRoot3,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -186,3 +336,18 @@ func TestProcess(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressBytesToEIP55(t *testing.T) {
|
||||
tests := []string{
|
||||
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
|
||||
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
|
||||
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
bytes, err := hex.DecodeString(strings.TrimPrefix(test, "0x"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addressBytesToEIP55(bytes), test)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,17 +58,20 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
}
|
||||
thisEpochAttesterDuty, err := attesterDuty(ctx, eth2Client, validatorIndex, thisEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain this epoch duty for validator")
|
||||
return nil, errors.Wrap(err, "failed to obtain this epoch attester duty for validator")
|
||||
}
|
||||
results.thisEpochAttesterDuty = thisEpochAttesterDuty
|
||||
|
||||
thisEpochProposerDuties, err := proposerDuties(ctx, eth2Client, validatorIndex, thisEpoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain this epoch proposer duties for validator")
|
||||
}
|
||||
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")
|
||||
return nil, errors.Wrap(err, "failed to obtain next epoch attester duty for validator")
|
||||
}
|
||||
results.nextEpochAttesterDuty = nextEpochAttesterDuty
|
||||
|
||||
|
||||
55
cmd/validator/keycheck/input.go
Normal file
55
cmd/validator/keycheck/input.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
// System.
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
// Withdrawal credentials.
|
||||
withdrawalCredentials string
|
||||
// Operation.
|
||||
mnemonic string
|
||||
privKey string
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
data.quiet = viper.GetBool("quiet")
|
||||
data.verbose = viper.GetBool("verbose")
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Withdrawal credentials.
|
||||
data.withdrawalCredentials = viper.GetString("withdrawal-credentials")
|
||||
if data.withdrawalCredentials == "" {
|
||||
return nil, errors.New("withdrawal credentials are required")
|
||||
}
|
||||
|
||||
data.mnemonic = viper.GetString("mnemonic")
|
||||
data.privKey = viper.GetString("privkey")
|
||||
if data.mnemonic == "" && data.privKey == "" {
|
||||
return nil, errors.New("mnemonic or privkey is required")
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
71
cmd/validator/keycheck/input_internal_test.go
Normal file
71
cmd/validator/keycheck/input_internal_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
res *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "WithdrawalCredentialsMissing",
|
||||
vars: map[string]interface{}{},
|
||||
err: "withdrawal credentials are required",
|
||||
},
|
||||
{
|
||||
name: "MnemonicAndPrivateKeyMissing",
|
||||
vars: map[string]interface{}{
|
||||
"withdrawal-credentials": "0x007e28dcf9029e8d92ca4b5d01c66c934e7f3110606f34ae3052cbf67bd3fc02",
|
||||
},
|
||||
err: "mnemonic or privkey is required",
|
||||
},
|
||||
{
|
||||
name: "GoodWithMnemonic",
|
||||
vars: map[string]interface{}{
|
||||
"withdrawal-credentials": "0x007e28dcf9029e8d92ca4b5d01c66c934e7f3110606f34ae3052cbf67bd3fc02",
|
||||
"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",
|
||||
},
|
||||
res: &dataIn{
|
||||
withdrawalCredentials: "0x007e28dcf9029e8d92ca4b5d01c66c934e7f3110606f34ae3052cbf67bd3fc02",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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.withdrawalCredentials, res.withdrawalCredentials)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
52
cmd/validator/keycheck/output.go
Normal file
52
cmd/validator/keycheck/output.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
debug bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
match bool
|
||||
path string
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, int, error) {
|
||||
if data == nil {
|
||||
return "", 1, errors.New("no data")
|
||||
}
|
||||
|
||||
if data.quiet {
|
||||
if !data.match {
|
||||
os.Exit(1)
|
||||
}
|
||||
return "", 1, nil
|
||||
}
|
||||
|
||||
if data.match {
|
||||
if data.path == "" {
|
||||
return "Withdrawal credentials confirmed", 0, nil
|
||||
}
|
||||
return fmt.Sprintf("Withdrawal credentials confirmed at path %s", data.path), 0, nil
|
||||
}
|
||||
|
||||
return "Could not confirm withdrawal credentials with given information", 1, nil
|
||||
}
|
||||
81
cmd/validator/keycheck/output_internal_test.go
Normal file
81
cmd/validator/keycheck/output_internal_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dataOut *dataOut
|
||||
exitCode int
|
||||
expected []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Not found",
|
||||
dataOut: &dataOut{
|
||||
match: false,
|
||||
},
|
||||
exitCode: 1,
|
||||
expected: []string{
|
||||
"Could not confirm withdrawal credentials with given information",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Found",
|
||||
dataOut: &dataOut{
|
||||
match: true,
|
||||
},
|
||||
expected: []string{
|
||||
"Withdrawal credentials confirmed",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FoundWithPath",
|
||||
dataOut: &dataOut{
|
||||
match: true,
|
||||
path: "m/12381/3600/10/0",
|
||||
},
|
||||
expected: []string{
|
||||
"Withdrawal credentials confirmed at path m/12381/3600/10/0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, exitCode, err := output(context.Background(), test.dataOut)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.exitCode, exitCode)
|
||||
for _, expected := range test.expected {
|
||||
require.True(t, strings.Contains(res, expected))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
123
cmd/validator/keycheck/process.go
Normal file
123
cmd/validator/keycheck/process.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
util "github.com/wealdtech/go-eth2-util"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
validatorWithdrawalCredentials, err := hex.DecodeString(strings.TrimPrefix(data.withdrawalCredentials, "0x"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse withdrawal credentials")
|
||||
}
|
||||
|
||||
match := false
|
||||
path := ""
|
||||
if data.privKey != "" {
|
||||
// Single private key to check.
|
||||
keyBytes, err := hex.DecodeString(strings.TrimPrefix(data.privKey, "0x"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := e2types.BLSPrivateKeyFromBytes(keyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
match, err = checkPrivKey(ctx, data.debug, validatorWithdrawalCredentials, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Mnemonic to check.
|
||||
match, path, err = checkMnemonic(ctx, data.debug, validatorWithdrawalCredentials, data.mnemonic)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
match: match,
|
||||
path: path,
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func checkPrivKey(ctx context.Context, debug bool, validatorWithdrawalCredentials []byte, key *e2types.BLSPrivateKey) (bool, error) {
|
||||
pubKey := key.PublicKey()
|
||||
|
||||
withdrawalCredentials := util.SHA256(pubKey.Marshal())
|
||||
withdrawalCredentials[0] = byte(0) // BLS_WITHDRAWAL_PREFIX
|
||||
|
||||
return bytes.Equal(withdrawalCredentials, validatorWithdrawalCredentials), nil
|
||||
}
|
||||
|
||||
func checkMnemonic(ctx context.Context, debug bool, validatorWithdrawalCredentials []byte, mnemonic string) (bool, string, error) {
|
||||
// If there are more than 24 words we treat the additional characters as the passphrase.
|
||||
mnemonicParts := strings.Split(mnemonic, " ")
|
||||
mnemonicPassphrase := ""
|
||||
if len(mnemonicParts) > 24 {
|
||||
mnemonic = strings.Join(mnemonicParts[:24], " ")
|
||||
mnemonicPassphrase = strings.Join(mnemonicParts[24:], " ")
|
||||
}
|
||||
// Normalise the input.
|
||||
mnemonic = string(norm.NFKD.Bytes([]byte(mnemonic)))
|
||||
mnemonicPassphrase = string(norm.NFKD.Bytes([]byte(mnemonicPassphrase)))
|
||||
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return false, "", errors.New("mnemonic is invalid")
|
||||
}
|
||||
|
||||
// Create seed from mnemonic and passphrase.
|
||||
seed := bip39.NewSeed(mnemonic, mnemonicPassphrase)
|
||||
// Check first 1024 indices.
|
||||
for i := 0; i < 1024; i++ {
|
||||
path := fmt.Sprintf("m/12381/3600/%d/0", i)
|
||||
if debug {
|
||||
fmt.Printf("Checking path %s\n", path)
|
||||
}
|
||||
key, err := util.PrivateKeyFromSeedAndPath(seed, path)
|
||||
if err != nil {
|
||||
return false, "", errors.Wrap(err, "failed to generate key")
|
||||
}
|
||||
match, err := checkPrivKey(ctx, debug, validatorWithdrawalCredentials, key)
|
||||
if err != nil {
|
||||
return false, "", errors.Wrap(err, "failed to match key")
|
||||
}
|
||||
if match {
|
||||
return true, path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", nil
|
||||
}
|
||||
51
cmd/validator/keycheck/run.go
Normal file
51
cmd/validator/keycheck/run.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validatorkeycheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// 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 "", err
|
||||
}
|
||||
|
||||
results, exitCode, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if exitCode != 0 {
|
||||
fmt.Println(results)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -49,9 +49,10 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorDepositDataCmd)
|
||||
validatorFlags(validatorDepositDataCmd)
|
||||
validatorDepositDataCmd.Flags().String("validatoraccount", "", "Account of the account carrying out the validation")
|
||||
validatorDepositDataCmd.Flags().String("withdrawalaccount", "", "Account of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("validatoraccount", "", "Account carrying out the validation")
|
||||
validatorDepositDataCmd.Flags().String("withdrawalaccount", "", "Account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("withdrawalpubkey", "", "Public key of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("withdrawaladdress", "", "Ethereum 1 address of the account to which the validator funds will be withdrawn")
|
||||
validatorDepositDataCmd.Flags().String("depositvalue", "", "Value of the amount to be deposited")
|
||||
validatorDepositDataCmd.Flags().Bool("raw", false, "Print raw deposit data transaction data")
|
||||
validatorDepositDataCmd.Flags().String("forkversion", "", "Use a hard-coded fork version (default is to fetch it from the node)")
|
||||
@@ -68,6 +69,9 @@ func validatorDepositdataBindings() {
|
||||
if err := viper.BindPFlag("withdrawalpubkey", validatorDepositDataCmd.Flags().Lookup("withdrawalpubkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("withdrawaladdress", validatorDepositDataCmd.Flags().Lookup("withdrawaladdress")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("depositvalue", validatorDepositDataCmd.Flags().Lookup("depositvalue")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ In quiet mode this will return 0 if the the duties have been obtained, otherwise
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf(res)
|
||||
fmt.Print(res)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
67
cmd/validatorkeycheck.go
Normal file
67
cmd/validatorkeycheck.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
validatorkeycheck "github.com/wealdtech/ethdo/cmd/validator/keycheck"
|
||||
)
|
||||
|
||||
var validatorKeycheckCmd = &cobra.Command{
|
||||
Use: "keycheck",
|
||||
Short: "Check that the withdrawal credentials for a validator matches the given key.",
|
||||
Long: `Check that the withdrawal credentials for a validator matches the given key. For example:
|
||||
|
||||
ethdo validator keycheck --withdrawal-credentials=0x007e28dcf9029e8d92ca4b5d01c66c934e7f3110606f34ae3052cbf67bd3fc02 --privkey=0x1b46e61babc7a6a0fbfe8e416de3c71f85e367f24e0bfcb12e57adb11117662c
|
||||
|
||||
A mnemonic can be used in place of a private key, in which case the first 1,024 indices of the standard withdrawal key path will be scanned for a matching key.
|
||||
|
||||
In quiet mode this will return 0 if the withdrawal credentials match the key, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := validatorkeycheck.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorKeycheckCmd)
|
||||
validatorFlags(validatorKeycheckCmd)
|
||||
validatorKeycheckCmd.Flags().String("withdrawal-credentials", "", "Withdrawal credentials to check (can run offline)")
|
||||
validatorKeycheckCmd.Flags().String("mnemonic", "", "Mnemonic from which to generate withdrawal credentials")
|
||||
validatorKeycheckCmd.Flags().String("privkey", "", "Private key from which to generate withdrawal credentials")
|
||||
}
|
||||
|
||||
func validatorKeycheckBindings() {
|
||||
if err := viper.BindPFlag("withdrawal-credentials", validatorKeycheckCmd.Flags().Lookup("withdrawal-credentials")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("mnemonic", validatorKeycheckCmd.Flags().Lookup("mnemonic")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("privkey", validatorKeycheckCmd.Flags().Lookup("privkey")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ 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.4)"
|
||||
// Usually overridden by tag names when building binaries.
|
||||
var ReleaseVersion = "local build (latest release 1.9.0)"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
|
||||
@@ -15,7 +15,7 @@ The first thing you need to do is to create a wallet. To do this run the comman
|
||||
- rename the wallet to something other than `Wallet` if you so desire. If so, you will need to change it in all subsequent commands
|
||||
|
||||
```
|
||||
$ ethdo wallet create --type=hd --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' --wallet=Wallet --wallet-passphrase=secret
|
||||
$ ethdo wallet create --type=hd --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" --wallet=Wallet --wallet-passphrase=secret
|
||||
```
|
||||
|
||||
### I want an account with a specific public key.
|
||||
@@ -87,5 +87,5 @@ If you wish to have this data for a particular test network you will need to sup
|
||||
It is possible to derive keys directly from a mnemonic and path without going through the interim steps. Note that this will _not_ create accounts, and cannot be used to then sign data or requests. This may or not be desirable, depending on your requirements.
|
||||
|
||||
```
|
||||
$ ethdo account derive --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
|
||||
$ ethdo account derive --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
|
||||
```
|
||||
|
||||
@@ -325,6 +325,23 @@ Prior justified epoch: 3
|
||||
Prior justified epoch distance: 4
|
||||
```
|
||||
|
||||
#### `time`
|
||||
|
||||
`ethdo chain time` calculates the time period of Ethereum 2 epochs and slots. Options include:
|
||||
- `epoch` show epoch and slot times for the given epoch
|
||||
- `slot` show epoch and slot times for the given slot
|
||||
- `timestamp` show epoch and slot times for the given timestamp
|
||||
|
||||
```sh
|
||||
$ ethdo chain time --epoch=1234
|
||||
Epoch 1234
|
||||
Epoch start 2020-12-06 23:37:59
|
||||
Epoch end 2020-12-06 23:44:23
|
||||
Slot 39488
|
||||
Slot start 2020-12-06 23:37:59
|
||||
Slot end 2020-12-06 23:38:11
|
||||
```
|
||||
|
||||
### `deposit` comands
|
||||
|
||||
Deposit commands focus on information about deposit data information in a JSON file generated by the `ethdo validator depositdata` command.
|
||||
@@ -471,6 +488,18 @@ Balance: 3.201850307 Ether
|
||||
Effective balance: 3.1 Ether
|
||||
```
|
||||
|
||||
#### `keycheck`
|
||||
|
||||
`ethdo validator keycheck` checks if a given key matches a validator's withdrawal credentials. Options include:
|
||||
- `withdrawal-credentials` the withdrawal credentials against which to match
|
||||
- `privkey` the private key used to generat matching withdrawal credentials
|
||||
- `mnemonic` the mnemonic used to generate matching withdrawal credentials
|
||||
|
||||
```sh
|
||||
$ ethdo validator keycheck --withdrawal-credentials=0x007e28dcf9029e8d92ca4b5d01c66c934e7f3110606f34ae3052cbf67bd3fc02 --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'
|
||||
Withdrawal credentials confirmed at path m/12381/3600/10/0
|
||||
```
|
||||
|
||||
### `attester` commands
|
||||
|
||||
Attester commands focus on Ethereum 2 validators' actions as attesters.
|
||||
|
||||
1
go.mod
1
go.mod
@@ -54,6 +54,7 @@ require (
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.2
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.2
|
||||
github.com/wealdtech/go-string2eth v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/text v0.3.5
|
||||
google.golang.org/genproto v0.0.0-20210201184850-646a494a81ea // indirect
|
||||
|
||||
Reference in New Issue
Block a user