mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-08 21:48:05 -05:00
Update parameter names; add fallback connection.
Update parameter names to meet newer standards. Provide a fallback beacon node if none other supplied.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
dev:
|
1.29.0:
|
||||||
- allow use of keystores with validator credentials set
|
- allow use of keystores with validator credentials set
|
||||||
|
- tidy up various command options to provide more standard usage
|
||||||
|
- add mainnet fallback beacon node
|
||||||
|
|
||||||
1.28.4:
|
1.28.4:
|
||||||
- allow validator exit to use a keystore as its validator parameter
|
- allow validator exit to use a keystore as its validator parameter
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,13 +33,11 @@ type dataIn struct {
|
|||||||
verbose bool
|
verbose bool
|
||||||
debug bool
|
debug bool
|
||||||
json bool
|
json bool
|
||||||
// Chain information.
|
|
||||||
slotsPerEpoch uint64
|
|
||||||
// Operation.
|
// Operation.
|
||||||
account string
|
validator string
|
||||||
pubKey string
|
|
||||||
eth2Client eth2client.Service
|
eth2Client eth2client.Service
|
||||||
epoch spec.Epoch
|
chainTime chaintime.Service
|
||||||
|
epoch phase0.Epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
func input(ctx context.Context) (*dataIn, error) {
|
func input(ctx context.Context) (*dataIn, error) {
|
||||||
@@ -52,38 +52,37 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
data.debug = viper.GetBool("debug")
|
data.debug = viper.GetBool("debug")
|
||||||
data.json = viper.GetBool("json")
|
data.json = viper.GetBool("json")
|
||||||
|
|
||||||
// Account or pubkey.
|
// Validator.
|
||||||
if viper.GetString("account") == "" && viper.GetString("pubkey") == "" {
|
data.validator = viper.GetString("validator")
|
||||||
return nil, errors.New("account or pubkey is required")
|
if data.validator == "" {
|
||||||
|
return nil, errors.New("validator is required")
|
||||||
}
|
}
|
||||||
data.account = viper.GetString("account")
|
|
||||||
data.pubKey = viper.GetString("pubkey")
|
|
||||||
|
|
||||||
// Ethereum 2 client.
|
// Ethereum 2 client.
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required data.
|
data.chainTime, err = standardchaintime.New(ctx,
|
||||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||||
}
|
}
|
||||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
|
||||||
|
|
||||||
// Epoch
|
// Epoch.
|
||||||
epoch := viper.GetInt64("epoch")
|
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||||
if epoch == -1 {
|
if err != nil {
|
||||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
return nil, err
|
||||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
|
||||||
}
|
|
||||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
|
||||||
}
|
}
|
||||||
data.epoch = spec.Epoch(epoch)
|
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
|||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AccountMissing",
|
name: "ValidatorMissing",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
},
|
},
|
||||||
err: "account or pubkey is required",
|
err: "validator is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ConnectionMissing",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||||
|
},
|
||||||
|
res: &dataIn{
|
||||||
|
timeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,12 @@ package attesterduties
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||||
@@ -32,43 +28,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return nil, errors.New("no data")
|
return nil, errors.New("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
var account e2wtypes.Account
|
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain public key for account")
|
return nil, err
|
||||||
}
|
|
||||||
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{
|
results := &dataOut{
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
"github.com/attestantio/go-eth2-client/auto"
|
"github.com/attestantio/go-eth2-client/auto"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProcess(t *testing.T) {
|
func TestProcess(t *testing.T) {
|
||||||
@@ -33,6 +35,12 @@ func TestProcess(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
chainTime, err := standardchaintime.New(context.Background(),
|
||||||
|
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
dataIn *dataIn
|
dataIn *dataIn
|
||||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Client",
|
name: "Client",
|
||||||
dataIn: &dataIn{
|
dataIn: &dataIn{
|
||||||
eth2Client: eth2Client,
|
eth2Client: eth2Client,
|
||||||
slotsPerEpoch: 32,
|
chainTime: chainTime,
|
||||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||||
epoch: 100,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ package attesterinclusion
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
@@ -23,6 +22,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/wealdtech/ethdo/services/chaintime"
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,15 +32,11 @@ type dataIn struct {
|
|||||||
quiet bool
|
quiet bool
|
||||||
verbose bool
|
verbose bool
|
||||||
debug bool
|
debug bool
|
||||||
// Chain information.
|
|
||||||
slotsPerEpoch uint64
|
|
||||||
// Operation.
|
// Operation.
|
||||||
eth2Client eth2client.Service
|
eth2Client eth2client.Service
|
||||||
chainTime chaintime.Service
|
chainTime chaintime.Service
|
||||||
epoch spec.Epoch
|
epoch spec.Epoch
|
||||||
account string
|
validator string
|
||||||
pubKey string
|
|
||||||
index string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func input(ctx context.Context) (*dataIn, error) {
|
func input(ctx context.Context) (*dataIn, error) {
|
||||||
@@ -54,48 +50,35 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
data.verbose = viper.GetBool("verbose")
|
data.verbose = viper.GetBool("verbose")
|
||||||
data.debug = viper.GetBool("debug")
|
data.debug = viper.GetBool("debug")
|
||||||
|
|
||||||
// Account.
|
data.validator = viper.GetString("validator")
|
||||||
data.account = viper.GetString("account")
|
if data.validator == "" {
|
||||||
|
return nil, errors.New("validator is required")
|
||||||
// PubKey.
|
|
||||||
data.pubKey = viper.GetString("pubkey")
|
|
||||||
|
|
||||||
// ID.
|
|
||||||
data.index = viper.GetString("index")
|
|
||||||
|
|
||||||
if viper.GetString("account") == "" && viper.GetString("index") == "" && viper.GetString("pubkey") == "" {
|
|
||||||
return nil, errors.New("account, index or pubkey is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ethereum 2 client.
|
// Ethereum 2 client.
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
data.chainTime, err = standardchaintime.New(ctx,
|
||||||
|
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||||
}
|
}
|
||||||
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
|
|
||||||
|
|
||||||
// Epoch.
|
// Epoch.
|
||||||
epoch := viper.GetInt64("epoch")
|
data.epoch, err = util.ParseEpoch(ctx, data.chainTime, viper.GetString("epoch"))
|
||||||
if epoch == -1 {
|
if err != nil {
|
||||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
return nil, err
|
||||||
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
|
||||||
}
|
|
||||||
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
|
|
||||||
if epoch > 0 {
|
|
||||||
epoch--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.epoch = spec.Epoch(epoch)
|
|
||||||
if data.debug {
|
|
||||||
fmt.Printf("Epoch is %d\n", data.epoch)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -61,19 +62,21 @@ func TestInput(t *testing.T) {
|
|||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IndexMissing",
|
name: "ValidatorMissing",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
},
|
},
|
||||||
err: "account, index or pubkey is required",
|
err: "validator is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ConnectionMissing",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"pubkey": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
"validator": "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
|
||||||
|
},
|
||||||
|
res: &dataIn{
|
||||||
|
timeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return nil, errors.New("no data")
|
return nil, errors.New("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
validator, err := util.ParseValidator(ctx, data.eth2Client.(eth2client.ValidatorsProvider), data.validator, "head")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
data.chainTime, err = standardchaintime.New(ctx,
|
data.chainTime, err = standardchaintime.New(ctx,
|
||||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||||
@@ -41,23 +44,6 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||||
}
|
}
|
||||||
|
|
||||||
validatorIndex, err := util.ValidatorIndex(ctx, data.eth2Client, data.account, data.pubKey, data.index)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain validator index")
|
|
||||||
}
|
|
||||||
|
|
||||||
validators, err := data.eth2Client.(eth2client.ValidatorsProvider).Validators(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch), []phase0.ValidatorIndex{validatorIndex})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to obtain validator information")
|
|
||||||
}
|
|
||||||
if len(validators) == 0 {
|
|
||||||
return nil, errors.New("validator is not known")
|
|
||||||
}
|
|
||||||
var validator *api.Validator
|
|
||||||
for _, v := range validators {
|
|
||||||
validator = v
|
|
||||||
}
|
|
||||||
|
|
||||||
results := &dataOut{
|
results := &dataOut{
|
||||||
debug: data.debug,
|
debug: data.debug,
|
||||||
quiet: data.quiet,
|
quiet: data.quiet,
|
||||||
|
|||||||
@@ -18,18 +18,26 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/attestantio/go-eth2-client/auto"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
|
"github.com/attestantio/go-eth2-client/http"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProcess(t *testing.T) {
|
func TestProcess(t *testing.T) {
|
||||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||||
}
|
}
|
||||||
eth2Client, err := auto.New(context.Background(),
|
eth2Client, err := http.New(context.Background(),
|
||||||
auto.WithLogLevel(zerolog.Disabled),
|
http.WithLogLevel(zerolog.Disabled),
|
||||||
auto.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
http.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
chainTime, err := standardchaintime.New(context.Background(),
|
||||||
|
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -45,10 +53,9 @@ func TestProcess(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Client",
|
name: "Client",
|
||||||
dataIn: &dataIn{
|
dataIn: &dataIn{
|
||||||
eth2Client: eth2Client,
|
eth2Client: eth2Client,
|
||||||
slotsPerEpoch: 32,
|
chainTime: chainTime,
|
||||||
pubKey: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
validator: "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95",
|
||||||
epoch: 100,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ var attesterDutiesCmd = &cobra.Command{
|
|||||||
Short: "Obtain information about duties of an attester",
|
Short: "Obtain information about duties of an attester",
|
||||||
Long: `Obtain information about dutes of an attester. For example:
|
Long: `Obtain information about dutes of an attester. For example:
|
||||||
|
|
||||||
ethdo attester duties --account=Validators/00001 --epoch=12345
|
ethdo attester duties --validator=Validators/00001 --epoch=12345
|
||||||
|
|
||||||
In quiet mode this will return 0 if a duty from the attester is found, otherwise 1.`,
|
In quiet mode this will return 0 if a duty from the attester is found, otherwise 1.`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -47,8 +47,8 @@ In quiet mode this will return 0 if a duty from the attester is found, otherwise
|
|||||||
func init() {
|
func init() {
|
||||||
attesterCmd.AddCommand(attesterDutiesCmd)
|
attesterCmd.AddCommand(attesterDutiesCmd)
|
||||||
attesterFlags(attesterDutiesCmd)
|
attesterFlags(attesterDutiesCmd)
|
||||||
attesterDutiesCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
attesterDutiesCmd.Flags().String("epoch", "head", "the epoch for which to obtain the duties")
|
||||||
attesterDutiesCmd.Flags().String("pubkey", "", "the public key of the attester")
|
attesterDutiesCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
||||||
attesterDutiesCmd.Flags().Bool("json", false, "Generate JSON data for an exit; do not broadcast to network")
|
attesterDutiesCmd.Flags().Bool("json", false, "Generate JSON data for an exit; do not broadcast to network")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func attesterDutiesBindings() {
|
|||||||
if err := viper.BindPFlag("epoch", attesterDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", attesterDutiesCmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := viper.BindPFlag("pubkey", attesterDutiesCmd.Flags().Lookup("pubkey")); err != nil {
|
if err := viper.BindPFlag("validator", attesterDutiesCmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := viper.BindPFlag("json", attesterDutiesCmd.Flags().Lookup("json")); err != nil {
|
if err := viper.BindPFlag("json", attesterDutiesCmd.Flags().Lookup("json")); err != nil {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ var attesterInclusionCmd = &cobra.Command{
|
|||||||
Short: "Obtain information about attester inclusion",
|
Short: "Obtain information about attester inclusion",
|
||||||
Long: `Obtain information about attester inclusion. For example:
|
Long: `Obtain information about attester inclusion. For example:
|
||||||
|
|
||||||
ethdo attester inclusion --account=Validators/00001 --epoch=12345
|
ethdo attester inclusion --validator=Validators/00001 --epoch=12345
|
||||||
|
|
||||||
In quiet mode this will return 0 if an attestation from the attester is found on the block of the given epoch, otherwise 1.`,
|
In quiet mode this will return 0 if an attestation from the attester is found on the block of the given epoch, otherwise 1.`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -47,8 +47,8 @@ In quiet mode this will return 0 if an attestation from the attester is found on
|
|||||||
func init() {
|
func init() {
|
||||||
attesterCmd.AddCommand(attesterInclusionCmd)
|
attesterCmd.AddCommand(attesterInclusionCmd)
|
||||||
attesterFlags(attesterInclusionCmd)
|
attesterFlags(attesterInclusionCmd)
|
||||||
attesterInclusionCmd.Flags().Int64("epoch", -1, "the last complete epoch")
|
attesterInclusionCmd.Flags().String("epoch", "-1", "the epoch for which to obtain the inclusion")
|
||||||
attesterInclusionCmd.Flags().String("pubkey", "", "the public key of the attester")
|
attesterInclusionCmd.Flags().String("validator", "", "the index, public key, or account of the validator")
|
||||||
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
|
attesterInclusionCmd.Flags().String("index", "", "the index of the attester")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func attesterInclusionBindings() {
|
|||||||
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", attesterInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := viper.BindPFlag("pubkey", attesterInclusionCmd.Flags().Lookup("pubkey")); err != nil {
|
if err := viper.BindPFlag("validator", attesterInclusionCmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := viper.BindPFlag("index", attesterInclusionCmd.Flags().Lookup("index")); err != nil {
|
if err := viper.BindPFlag("index", attesterInclusionCmd.Flags().Lookup("index")); err != nil {
|
||||||
|
|||||||
@@ -255,7 +255,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestProcess(t *testing.T) {
|
|||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
"blockid": "invalid",
|
"blockid": "invalid",
|
||||||
},
|
},
|
||||||
err: "failed to obtain beacon block: failed to request signed beacon block: GET failed with status 400: {\"code\":400,\"message\":\"Invalid block: invalid\"}",
|
err: "failed to obtain beacon block: failed to request signed beacon block: GET failed with status 400: {\"code\":400,\"message\":\"BAD_REQUEST: Unsupported endpoint version: v2\",\"stacktraces\":[]}",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
data.stream = viper.GetBool("stream")
|
data.stream = viper.GetBool("stream")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
|||||||
vars: map[string]interface{}{},
|
vars: map[string]interface{}{},
|
||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ConnectionMissing",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
},
|
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "ConnectionBad",
|
name: "ConnectionBad",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2019, 2020 Weald Technology Trading
|
// Copyright © 2019 - 2023 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -39,6 +39,9 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
if data == nil {
|
if data == nil {
|
||||||
return nil, errors.New("no data")
|
return nil, errors.New("no data")
|
||||||
}
|
}
|
||||||
|
if data.blockID == "" {
|
||||||
|
return nil, errors.New("no block ID")
|
||||||
|
}
|
||||||
|
|
||||||
results = &dataOut{
|
results = &dataOut{
|
||||||
debug: data.debug,
|
debug: data.debug,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2019, 2020 Weald Technology Trading
|
// Copyright © 2019 - 2023 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -43,11 +43,11 @@ func TestProcess(t *testing.T) {
|
|||||||
err: "no data",
|
err: "no data",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Client",
|
name: "NoBlockID",
|
||||||
dataIn: &dataIn{
|
dataIn: &dataIn{
|
||||||
eth2Client: eth2Client,
|
eth2Client: eth2Client,
|
||||||
},
|
},
|
||||||
err: "empty beacon block",
|
err: "no block ID",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type dataOut struct {
|
|||||||
slot spec.Slot
|
slot spec.Slot
|
||||||
slotStart time.Time
|
slotStart time.Time
|
||||||
slotEnd time.Time
|
slotEnd time.Time
|
||||||
|
hasSyncCommittees bool
|
||||||
syncCommitteePeriod uint64
|
syncCommitteePeriod uint64
|
||||||
syncCommitteePeriodStart time.Time
|
syncCommitteePeriodStart time.Time
|
||||||
syncCommitteePeriodEpochStart spec.Epoch
|
syncCommitteePeriodEpochStart spec.Epoch
|
||||||
@@ -56,27 +57,59 @@ func output(_ context.Context, data *dataOut) (string, error) {
|
|||||||
builder.WriteString(fmt.Sprintf("%d", data.epoch))
|
builder.WriteString(fmt.Sprintf("%d", data.epoch))
|
||||||
builder.WriteString("\n Epoch start ")
|
builder.WriteString("\n Epoch start ")
|
||||||
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
|
builder.WriteString(data.epochStart.Format("2006-01-02 15:04:05"))
|
||||||
|
if data.verbose {
|
||||||
|
builder.WriteString(" (")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.epochStart.Unix()))
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
builder.WriteString("\n Epoch end ")
|
builder.WriteString("\n Epoch end ")
|
||||||
builder.WriteString(data.epochEnd.Format("2006-01-02 15:04:05"))
|
builder.WriteString(data.epochEnd.Format("2006-01-02 15:04:05"))
|
||||||
|
if data.verbose {
|
||||||
|
builder.WriteString(" (")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.epochEnd.Unix()))
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
|
||||||
builder.WriteString("\nSlot ")
|
builder.WriteString("\nSlot ")
|
||||||
builder.WriteString(fmt.Sprintf("%d", data.slot))
|
builder.WriteString(fmt.Sprintf("%d", data.slot))
|
||||||
builder.WriteString("\n Slot start ")
|
builder.WriteString("\n Slot start ")
|
||||||
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
|
builder.WriteString(data.slotStart.Format("2006-01-02 15:04:05"))
|
||||||
|
if data.verbose {
|
||||||
|
builder.WriteString(" (")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.slotStart.Unix()))
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
builder.WriteString("\n Slot end ")
|
builder.WriteString("\n Slot end ")
|
||||||
builder.WriteString(data.slotEnd.Format("2006-01-02 15:04:05"))
|
builder.WriteString(data.slotEnd.Format("2006-01-02 15:04:05"))
|
||||||
|
if data.verbose {
|
||||||
|
builder.WriteString(" (")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.slotEnd.Unix()))
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
|
||||||
builder.WriteString("\nSync committee period ")
|
if data.hasSyncCommittees {
|
||||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
builder.WriteString("\nSync committee period ")
|
||||||
builder.WriteString("\n Sync committee period start ")
|
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriod))
|
||||||
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
builder.WriteString("\n Sync committee period start ")
|
||||||
builder.WriteString(" (epoch ")
|
builder.WriteString(data.syncCommitteePeriodStart.Format("2006-01-02 15:04:05"))
|
||||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
builder.WriteString(" (epoch ")
|
||||||
builder.WriteString(")\n Sync committee period end ")
|
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochStart))
|
||||||
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
if data.verbose {
|
||||||
builder.WriteString(" (epoch ")
|
builder.WriteString(", ")
|
||||||
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodStart.Unix()))
|
||||||
builder.WriteString(")\n")
|
}
|
||||||
|
builder.WriteString(")\n Sync committee period end ")
|
||||||
|
builder.WriteString(data.syncCommitteePeriodEnd.Format("2006-01-02 15:04:05"))
|
||||||
|
builder.WriteString(" (epoch ")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEpochEnd))
|
||||||
|
if data.verbose {
|
||||||
|
builder.WriteString(", ")
|
||||||
|
builder.WriteString(fmt.Sprintf("%d", data.syncCommitteePeriodEnd.Unix()))
|
||||||
|
}
|
||||||
|
builder.WriteString(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString("\n")
|
||||||
|
|
||||||
return builder.String(), nil
|
return builder.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,22 +30,22 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return nil, errors.New("no data")
|
return nil, errors.New("no data")
|
||||||
}
|
}
|
||||||
|
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.connection, data.timeout, data.allowInsecureConnections)
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: data.connection,
|
||||||
|
Timeout: data.timeout,
|
||||||
|
AllowInsecure: data.allowInsecureConnections,
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
chainTime, err := standardchaintime.New(ctx,
|
||||||
|
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||||
|
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to obtain beacon chain configuration")
|
return nil, errors.Wrap(err, "failed to set up chaintime service")
|
||||||
}
|
|
||||||
|
|
||||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
|
||||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
|
||||||
epochsPerSyncCommitteePeriod := config["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"].(uint64)
|
|
||||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain genesis data")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results := &dataOut{
|
results := &dataOut{
|
||||||
@@ -62,34 +63,33 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
}
|
}
|
||||||
results.slot = phase0.Slot(slot)
|
results.slot = phase0.Slot(slot)
|
||||||
case data.epoch != "":
|
case data.epoch != "":
|
||||||
epoch, err := strconv.ParseUint(data.epoch, 10, 64)
|
epoch, err := util.ParseEpoch(ctx, chainTime, data.epoch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse epoch")
|
return nil, errors.Wrap(err, "failed to parse epoch")
|
||||||
}
|
}
|
||||||
results.slot = phase0.Slot(epoch * slotsPerEpoch)
|
results.slot = chainTime.FirstSlotOfEpoch(epoch)
|
||||||
case data.timestamp != "":
|
case data.timestamp != "":
|
||||||
timestamp, err := time.Parse("2006-01-02T15:04:05-0700", data.timestamp)
|
timestamp, err := time.Parse("2006-01-02T15:04:05-0700", data.timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse timestamp")
|
return nil, errors.Wrap(err, "failed to parse timestamp")
|
||||||
}
|
}
|
||||||
secs := timestamp.Sub(genesis.GenesisTime)
|
results.slot = chainTime.TimestampToSlot(timestamp)
|
||||||
if secs < 0 {
|
|
||||||
return nil, errors.New("timestamp prior to genesis")
|
|
||||||
}
|
|
||||||
results.slot = phase0.Slot(secs / slotDuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the info given the slot.
|
// Fill in the info given the slot.
|
||||||
results.slotStart = genesis.GenesisTime.Add(time.Duration(results.slot) * slotDuration)
|
results.slotStart = chainTime.StartOfSlot(results.slot)
|
||||||
results.slotEnd = genesis.GenesisTime.Add(time.Duration(results.slot+1) * slotDuration)
|
results.slotEnd = chainTime.StartOfSlot(results.slot + 1)
|
||||||
results.epoch = phase0.Epoch(uint64(results.slot) / slotsPerEpoch)
|
results.epoch = chainTime.SlotToEpoch(results.slot)
|
||||||
results.epochStart = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch)*slotsPerEpoch) * slotDuration)
|
results.epochStart = chainTime.StartOfEpoch(results.epoch)
|
||||||
results.epochEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.epoch+1)*slotsPerEpoch) * slotDuration)
|
results.epochEnd = chainTime.StartOfEpoch(results.epoch + 1)
|
||||||
results.syncCommitteePeriod = uint64(results.epoch) / epochsPerSyncCommitteePeriod
|
if results.epoch >= chainTime.FirstEpochOfSyncPeriod(chainTime.AltairInitialSyncCommitteePeriod()) {
|
||||||
results.syncCommitteePeriodEpochStart = phase0.Epoch(results.syncCommitteePeriod * epochsPerSyncCommitteePeriod)
|
results.hasSyncCommittees = true
|
||||||
results.syncCommitteePeriodEpochEnd = phase0.Epoch((results.syncCommitteePeriod+1)*epochsPerSyncCommitteePeriod) - 1
|
results.syncCommitteePeriod = chainTime.SlotToSyncCommitteePeriod(results.slot)
|
||||||
results.syncCommitteePeriodStart = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochStart)*slotsPerEpoch) * slotDuration)
|
results.syncCommitteePeriodEpochStart = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod)
|
||||||
results.syncCommitteePeriodEnd = genesis.GenesisTime.Add(time.Duration(uint64(results.syncCommitteePeriodEpochEnd)*slotsPerEpoch) * slotDuration)
|
results.syncCommitteePeriodEpochEnd = chainTime.FirstEpochOfSyncPeriod(results.syncCommitteePeriod + 1)
|
||||||
|
results.syncCommitteePeriodStart = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochStart)
|
||||||
|
results.syncCommitteePeriodEnd = chainTime.StartOfEpoch(results.syncCommitteePeriodEpochEnd)
|
||||||
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2021 Weald Technology Trading
|
// Copyright © 2021 - 2023 Weald Technology Trading.
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -15,7 +15,6 @@ package chaintime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -47,16 +46,11 @@ func TestProcess(t *testing.T) {
|
|||||||
slot: "1",
|
slot: "1",
|
||||||
},
|
},
|
||||||
expected: &dataOut{
|
expected: &dataOut{
|
||||||
epochStart: time.Unix(1606824023, 0),
|
epochStart: time.Unix(1606824023, 0),
|
||||||
epochEnd: time.Unix(1606824407, 0),
|
epochEnd: time.Unix(1606824407, 0),
|
||||||
slot: 1,
|
slot: 1,
|
||||||
slotStart: time.Unix(1606824035, 0),
|
slotStart: time.Unix(1606824035, 0),
|
||||||
slotEnd: time.Unix(1606824047, 0),
|
slotEnd: time.Unix(1606824047, 0),
|
||||||
syncCommitteePeriod: 0,
|
|
||||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
|
||||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
|
||||||
syncCommitteePeriodEpochStart: 0,
|
|
||||||
syncCommitteePeriodEpochEnd: 255,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -68,17 +62,12 @@ func TestProcess(t *testing.T) {
|
|||||||
epoch: "2",
|
epoch: "2",
|
||||||
},
|
},
|
||||||
expected: &dataOut{
|
expected: &dataOut{
|
||||||
epoch: 2,
|
epoch: 2,
|
||||||
epochStart: time.Unix(1606824791, 0),
|
epochStart: time.Unix(1606824791, 0),
|
||||||
epochEnd: time.Unix(1606825175, 0),
|
epochEnd: time.Unix(1606825175, 0),
|
||||||
slot: 64,
|
slot: 64,
|
||||||
slotStart: time.Unix(1606824791, 0),
|
slotStart: time.Unix(1606824791, 0),
|
||||||
slotEnd: time.Unix(1606824803, 0),
|
slotEnd: time.Unix(1606824803, 0),
|
||||||
syncCommitteePeriod: 0,
|
|
||||||
syncCommitteePeriodStart: time.Unix(1606824023, 0),
|
|
||||||
syncCommitteePeriodEnd: time.Unix(1606921943, 0),
|
|
||||||
syncCommitteePeriodEpochStart: 0,
|
|
||||||
syncCommitteePeriodEpochEnd: 255,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,20 +76,21 @@ func TestProcess(t *testing.T) {
|
|||||||
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
connection: os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
timeout: 10 * time.Second,
|
timeout: 10 * time.Second,
|
||||||
allowInsecureConnections: true,
|
allowInsecureConnections: true,
|
||||||
timestamp: "2021-01-01T00:00:00+0000",
|
timestamp: "2023-01-01T00:00:00+0000",
|
||||||
},
|
},
|
||||||
expected: &dataOut{
|
expected: &dataOut{
|
||||||
epoch: 6862,
|
epoch: 171112,
|
||||||
epochStart: time.Unix(1609459031, 0),
|
epochStart: time.Unix(1672531031, 0),
|
||||||
epochEnd: time.Unix(1609459415, 0),
|
epochEnd: time.Unix(1672531415, 0),
|
||||||
slot: 219598,
|
slot: 5475598,
|
||||||
slotStart: time.Unix(1609459199, 0),
|
slotStart: time.Unix(1672531199, 0),
|
||||||
slotEnd: time.Unix(1609459211, 0),
|
slotEnd: time.Unix(1672531211, 0),
|
||||||
syncCommitteePeriod: 26,
|
hasSyncCommittees: true,
|
||||||
syncCommitteePeriodStart: time.Unix(1609379927, 0),
|
syncCommitteePeriod: 668,
|
||||||
syncCommitteePeriodEnd: time.Unix(1609477847, 0),
|
syncCommitteePeriodStart: time.Unix(1672491095, 0),
|
||||||
syncCommitteePeriodEpochStart: 6656,
|
syncCommitteePeriodEnd: time.Unix(1672589399, 0),
|
||||||
syncCommitteePeriodEpochEnd: 6911,
|
syncCommitteePeriodEpochStart: 171008,
|
||||||
|
syncCommitteePeriodEpochEnd: 171264,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -112,7 +102,6 @@ func TestProcess(t *testing.T) {
|
|||||||
require.EqualError(t, err, test.err)
|
require.EqualError(t, err, test.err)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
fmt.Printf("****** %d %d\n", res.syncCommitteePeriodStart.Unix(), res.syncCommitteePeriodEnd.Unix())
|
|
||||||
require.Equal(t, test.expected, res)
|
require.Equal(t, test.expected, res)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -85,7 +85,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ In quiet mode this will return 0 if the chain information can be obtained, other
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !viper.GetBool("quiet"),
|
||||||
|
})
|
||||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
config, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !viper.GetBool("quiet"),
|
||||||
|
})
|
||||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
chainTime, err := standardchaintime.New(ctx,
|
chainTime, err := standardchaintime.New(ctx,
|
||||||
|
|||||||
@@ -324,7 +324,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,80 +14,97 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
consensusclient "github.com/attestantio/go-eth2-client"
|
||||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
v1 "github.com/attestantio/go-eth2-client/api/v1"
|
||||||
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var exitVerifyPubKey string
|
|
||||||
|
|
||||||
var exitVerifyCmd = &cobra.Command{
|
var exitVerifyCmd = &cobra.Command{
|
||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify exit data is valid",
|
Short: "Verify exit data is valid",
|
||||||
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
Long: `Verify that exit data generated by "ethdo validator exit" is correct for a given account. For example:
|
||||||
|
|
||||||
ethdo exit verify --data=exitdata.json --account=primary/current
|
ethdo exit verify --signed-operation=exitdata.json --validator=primary/current
|
||||||
|
|
||||||
In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.`,
|
In quiet mode this will return 0 if the exit is verified correctly, otherwise 1.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
assert(viper.GetString("account") != "" || exitVerifyPubKey != "", "account or public key is required")
|
assert(viper.GetString("signed-operation") != "", "signed-operation is required")
|
||||||
account, err := exitVerifyAccount(ctx)
|
signedOp, err := obtainSignedOperation(viper.GetString("signed-operation"))
|
||||||
errCheck(err, "Failed to obtain account")
|
errCheck(err, "Failed to obtain signed operation")
|
||||||
|
|
||||||
assert(viper.GetString("exit") != "", "exit is required")
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
data, err := obtainExitData(viper.GetString("exit"))
|
Address: viper.GetString("connection"),
|
||||||
errCheck(err, "Failed to obtain exit data")
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
// Confirm signature is good.
|
LogFallback: !viper.GetBool("quiet"),
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
})
|
||||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
genesis, err := eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
|
validator, err := util.ParseValidator(ctx, eth2Client.(consensusclient.ValidatorsProvider), fmt.Sprintf("%d", signedOp.Message.ValidatorIndex), "head")
|
||||||
|
errCheck(err, "failed to obtain validator")
|
||||||
|
pubkey, err := validator.PubKey(ctx)
|
||||||
|
errCheck(err, "failed to obtain validator public key")
|
||||||
|
account, err := util.ParseAccount(ctx, pubkey.String(), nil, false)
|
||||||
|
errCheck(err, "failed to obtain account")
|
||||||
|
|
||||||
|
// Ensure the validator is in a suitable state.
|
||||||
|
assert(validator.Status == v1.ValidatorStateActiveOngoing, "validator not in a suitable state to exit")
|
||||||
|
|
||||||
|
// Obtain the hash tree root of the message to check the signature.
|
||||||
|
opRoot, err := signedOp.Message.HashTreeRoot()
|
||||||
|
errCheck(err, "Failed to obtain exit hash tree root")
|
||||||
|
|
||||||
|
genesis, err := eth2Client.(consensusclient.GenesisProvider).Genesis(ctx)
|
||||||
errCheck(err, "Failed to obtain beacon chain genesis")
|
errCheck(err, "Failed to obtain beacon chain genesis")
|
||||||
|
|
||||||
domain := e2types.Domain(e2types.DomainVoluntaryExit, data.ForkVersion[:], genesis.GenesisValidatorsRoot[:])
|
fork, err := eth2Client.(consensusclient.ForkProvider).Fork(ctx, "head")
|
||||||
var exitDomain spec.Domain
|
errCheck(err, "Failed to obtain fork information")
|
||||||
copy(exitDomain[:], domain)
|
|
||||||
exit := &spec.VoluntaryExit{
|
// Check against current and prior fork versions.
|
||||||
Epoch: data.Exit.Message.Epoch,
|
|
||||||
ValidatorIndex: data.Exit.Message.ValidatorIndex,
|
|
||||||
}
|
|
||||||
exitRoot, err := exit.HashTreeRoot()
|
|
||||||
errCheck(err, "Failed to obtain exit hash tree root")
|
|
||||||
signatureBytes := make([]byte, 96)
|
signatureBytes := make([]byte, 96)
|
||||||
copy(signatureBytes, data.Exit.Signature[:])
|
copy(signatureBytes, signedOp.Signature[:])
|
||||||
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
sig, err := e2types.BLSSignatureFromBytes(signatureBytes)
|
||||||
errCheck(err, "Invalid signature")
|
errCheck(err, "Invalid signature")
|
||||||
verified, err := util.VerifyRoot(account, exitRoot, exitDomain, sig)
|
|
||||||
errCheck(err, "Failed to verify voluntary exit")
|
|
||||||
assert(verified, "Voluntary exit failed to verify")
|
|
||||||
|
|
||||||
fork, err := eth2Client.(eth2client.ForkProvider).Fork(ctx, "head")
|
verified := false
|
||||||
errCheck(err, "Failed to obtain current fork")
|
|
||||||
assert(bytes.Equal(data.ForkVersion[:], fork.CurrentVersion[:]) || bytes.Equal(data.ForkVersion[:], fork.PreviousVersion[:]), "Exit is for an old fork version and is no longer valid")
|
// Try with the current fork.
|
||||||
|
domain := phase0.Domain{}
|
||||||
|
currentExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.CurrentVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||||
|
errCheck(err, "Failed to compute domain")
|
||||||
|
copy(domain[:], currentExitDomain)
|
||||||
|
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||||
|
errCheck(err, "Failed to verify voluntary exit")
|
||||||
|
if !verified {
|
||||||
|
// Try again with the previous fork.
|
||||||
|
previousExitDomain, err := e2types.ComputeDomain(e2types.DomainVoluntaryExit, fork.PreviousVersion[:], genesis.GenesisValidatorsRoot[:])
|
||||||
|
copy(domain[:], previousExitDomain)
|
||||||
|
errCheck(err, "Failed to compute domain")
|
||||||
|
verified, err = util.VerifyRoot(account, opRoot, domain, sig)
|
||||||
|
errCheck(err, "Failed to verify voluntary exit")
|
||||||
|
}
|
||||||
|
assert(verified, "Voluntary exit failed to verify against current and previous fork versions")
|
||||||
|
|
||||||
outputIf(verbose, "Verified")
|
outputIf(verbose, "Verified")
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// obtainExitData obtains exit data from an input, could be JSON itself or a path to JSON.
|
// obtainSignedOperation obtains exit data from an input, could be JSON itself or a path to JSON.
|
||||||
func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
func obtainSignedOperation(input string) (*phase0.SignedVoluntaryExit, error) {
|
||||||
var err error
|
var err error
|
||||||
var data []byte
|
var data []byte
|
||||||
// Input could be JSON or a path to JSON
|
// Input could be JSON or a path to JSON
|
||||||
@@ -101,46 +118,23 @@ func obtainExitData(input string) (*util.ValidatorExitData, error) {
|
|||||||
return nil, errors.Wrap(err, "failed to find deposit data file")
|
return nil, errors.Wrap(err, "failed to find deposit data file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exitData := &util.ValidatorExitData{}
|
signedOp := &phase0.SignedVoluntaryExit{}
|
||||||
err = json.Unmarshal(data, exitData)
|
err = json.Unmarshal(data, signedOp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "data is not valid JSON")
|
return nil, errors.Wrap(err, "data is not valid JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitData, nil
|
return signedOp, nil
|
||||||
}
|
|
||||||
|
|
||||||
// exitVerifyAccount obtains the account for the exitVerify command.
|
|
||||||
func exitVerifyAccount(ctx context.Context) (e2wtypes.Account, error) {
|
|
||||||
var account e2wtypes.Account
|
|
||||||
var err error
|
|
||||||
if viper.GetString("account") != "" {
|
|
||||||
_, account, err = walletAndAccountFromPath(ctx, viper.GetString("account"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to obtain account")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pubKeyBytes, err := hex.DecodeString(strings.TrimPrefix(exitVerifyPubKey, "0x"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, fmt.Sprintf("failed to decode public key %s", exitVerifyPubKey))
|
|
||||||
}
|
|
||||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", exitVerifyPubKey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return account, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
exitCmd.AddCommand(exitVerifyCmd)
|
exitCmd.AddCommand(exitVerifyCmd)
|
||||||
exitFlags(exitVerifyCmd)
|
exitFlags(exitVerifyCmd)
|
||||||
exitVerifyCmd.Flags().String("exit", "", "JSON data, or path to JSON data")
|
exitVerifyCmd.Flags().String("signed-operation", "", "JSON data, or path to JSON data")
|
||||||
exitVerifyCmd.Flags().StringVar(&exitVerifyPubKey, "pubkey", "", "Public key for which to verify exit")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitVerifyBindings() {
|
func exitVerifyBindings() {
|
||||||
if err := viper.BindPFlag("exit", exitVerifyCmd.Flags().Lookup("exit")); err != nil {
|
if err := viper.BindPFlag("signed-operation", exitVerifyCmd.Flags().Lookup("signed-operation")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
data.topics = viper.GetStringSlice("topics")
|
data.topics = viper.GetStringSlice("topics")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ func TestInput(t *testing.T) {
|
|||||||
vars: map[string]interface{}{},
|
vars: map[string]interface{}{},
|
||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ConnectionMissing",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
},
|
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "ConnectionBad",
|
name: "ConnectionBad",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ import (
|
|||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultBeaconNode is used if no other connection is supplied.
|
|
||||||
var defaultBeaconNode = "http://mainnet-consensus.attestant.io/"
|
|
||||||
|
|
||||||
var nodeInfoCmd = &cobra.Command{
|
var nodeInfoCmd = &cobra.Command{
|
||||||
Use: "info",
|
Use: "info",
|
||||||
Short: "Obtain information about a node",
|
Short: "Obtain information about a node",
|
||||||
@@ -38,27 +35,13 @@ In quiet mode this will return 0 if the node information can be obtained, otherw
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
if err != nil {
|
Address: viper.GetString("connection"),
|
||||||
if viper.GetString("connection") != "" {
|
Timeout: viper.GetDuration("timeout"),
|
||||||
// The user provided a connection, so don't second-guess them by using a different node.
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
LogFallback: !viper.GetBool("quiet"),
|
||||||
return
|
})
|
||||||
}
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
// The user did not provide a connection, so attempt to use the default node.
|
|
||||||
if viper.GetBool("debug") {
|
|
||||||
fmt.Fprintf(os.Stderr, "No node connection, attempting to use %s\n", defaultBeaconNode)
|
|
||||||
}
|
|
||||||
eth2Client, err = util.ConnectToBeaconNode(ctx, defaultBeaconNode, viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !viper.GetBool("quiet") {
|
|
||||||
fmt.Fprintf(os.Stderr, "No connection supplied; using mainnet public access endpoint\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if quiet {
|
if quiet {
|
||||||
os.Exit(_exitSuccess)
|
os.Exit(_exitSuccess)
|
||||||
|
|||||||
@@ -46,7 +46,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
|
|
||||||
// Ethereum 2 client.
|
// Ethereum 2 client.
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,14 +67,6 @@ func TestInput(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: "slot is required",
|
err: "slot is required",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ConnectionMissing",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
"slot": "1",
|
|
||||||
},
|
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/wealdtech/ethdo/services/chaintime"
|
"github.com/wealdtech/ethdo/services/chaintime"
|
||||||
@@ -34,16 +35,15 @@ type command struct {
|
|||||||
allowInsecureConnections bool
|
allowInsecureConnections bool
|
||||||
|
|
||||||
// Input.
|
// Input.
|
||||||
account string
|
validator string
|
||||||
pubKey string
|
epochStr string
|
||||||
index string
|
|
||||||
epoch int64
|
|
||||||
|
|
||||||
// Data access.
|
// Data access.
|
||||||
eth2Client eth2client.Service
|
eth2Client eth2client.Service
|
||||||
chainTime chaintime.Service
|
chainTime chaintime.Service
|
||||||
|
|
||||||
// Output.
|
// Output.
|
||||||
|
epoch phase0.Epoch
|
||||||
inCommittee bool
|
inCommittee bool
|
||||||
committeeIndex uint64
|
committeeIndex uint64
|
||||||
inclusions []int
|
inclusions []int
|
||||||
@@ -67,15 +67,13 @@ func newCommand(_ context.Context) (*command, error) {
|
|||||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||||
|
|
||||||
// Validator.
|
// Validator.
|
||||||
c.account = viper.GetString("account")
|
c.validator = viper.GetString("validator")
|
||||||
c.pubKey = viper.GetString("pubkey")
|
if c.validator == "" {
|
||||||
c.index = viper.GetString("index")
|
return nil, errors.New("validator is required")
|
||||||
if c.account == "" && c.pubKey == "" && c.index == "" {
|
|
||||||
return nil, errors.New("account, pubkey or index required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Epoch.
|
// Epoch.
|
||||||
c.epoch = viper.GetInt64("epoch")
|
c.epochStr = viper.GetString("epoch")
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ func TestInput(t *testing.T) {
|
|||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
},
|
},
|
||||||
err: "account, pubkey or index required",
|
err: "validator is required",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Good",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"validators": "1",
|
"validators": "1",
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"index": "1",
|
"validator": "1",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
"github.com/attestantio/go-eth2-client/spec"
|
"github.com/attestantio/go-eth2-client/spec"
|
||||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||||
"github.com/wealdtech/ethdo/util"
|
"github.com/wealdtech/ethdo/util"
|
||||||
@@ -32,14 +31,17 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
firstSlot, lastSlot := c.calculateSlots(ctx)
|
validator, err := util.ParseValidator(ctx, c.eth2Client.(eth2client.ValidatorsProvider), c.validator, "head")
|
||||||
|
|
||||||
validatorIndex, err := util.ValidatorIndex(ctx, c.eth2Client, c.account, c.pubKey, c.index)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", phase0.Epoch(c.epoch))
|
c.epoch, err = util.ParseEpoch(ctx, c.chainTime, c.epochStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
syncCommittee, err := c.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommitteeAtEpoch(ctx, "head", c.epoch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to obtain sync committee information")
|
return errors.Wrap(err, "failed to obtain sync committee information")
|
||||||
}
|
}
|
||||||
@@ -49,7 +51,7 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range syncCommittee.Validators {
|
for i := range syncCommittee.Validators {
|
||||||
if syncCommittee.Validators[i] == validatorIndex {
|
if syncCommittee.Validators[i] == validator.Index {
|
||||||
c.inCommittee = true
|
c.inCommittee = true
|
||||||
c.committeeIndex = uint64(i)
|
c.committeeIndex = uint64(i)
|
||||||
break
|
break
|
||||||
@@ -57,6 +59,8 @@ func (c *command) process(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.inCommittee {
|
if c.inCommittee {
|
||||||
|
firstSlot := c.chainTime.FirstSlotOfEpoch(c.epoch)
|
||||||
|
lastSlot := c.chainTime.LastSlotOfEpoch(c.epoch)
|
||||||
// This validator is in the sync committee. Check blocks to see where it has been included.
|
// This validator is in the sync committee. Check blocks to see where it has been included.
|
||||||
c.inclusions = make([]int, 0)
|
c.inclusions = make([]int, 0)
|
||||||
if lastSlot > c.chainTime.CurrentSlot() {
|
if lastSlot > c.chainTime.CurrentSlot() {
|
||||||
@@ -107,7 +111,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -122,15 +131,3 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *command) calculateSlots(_ context.Context) (phase0.Slot, phase0.Slot) {
|
|
||||||
var firstSlot phase0.Slot
|
|
||||||
var lastSlot phase0.Slot
|
|
||||||
if c.epoch == -1 {
|
|
||||||
c.epoch = int64(c.chainTime.CurrentEpoch()) - 1
|
|
||||||
}
|
|
||||||
firstSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch))
|
|
||||||
lastSlot = c.chainTime.FirstSlotOfEpoch(phase0.Epoch(c.epoch) + 1)
|
|
||||||
|
|
||||||
return firstSlot, lastSlot
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,28 +32,20 @@ func TestProcess(t *testing.T) {
|
|||||||
vars map[string]interface{}
|
vars map[string]interface{}
|
||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
{
|
|
||||||
name: "MissingConnection",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
"index": "1",
|
|
||||||
},
|
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "InvalidConnection",
|
name: "InvalidConnection",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"index": "1",
|
"validator": "1",
|
||||||
"connection": "invalid",
|
"connection": "invalid",
|
||||||
},
|
},
|
||||||
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid: no such host",
|
err: "failed to connect to beacon node: failed to confirm node connection: failed to fetch genesis: failed to request genesis: failed to call GET endpoint: Get \"http://invalid/eth/v1/beacon/genesis\": dial tcp: lookup invalid on 127.0.0.53:53: no such host",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Good",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"index": "1",
|
"validator": "1",
|
||||||
"epoch": "-1",
|
"epoch": "-1",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type dataIn struct {
|
|||||||
// Operation.
|
// Operation.
|
||||||
eth2Client eth2client.Service
|
eth2Client eth2client.Service
|
||||||
chainTime chaintime.Service
|
chainTime chaintime.Service
|
||||||
epoch int64
|
epoch string
|
||||||
period string
|
period string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,12 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
|
|
||||||
// Ethereum 2 client.
|
// Ethereum 2 client.
|
||||||
var err error
|
var err error
|
||||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
data.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: viper.GetString("connection"),
|
||||||
|
Timeout: viper.GetDuration("timeout"),
|
||||||
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -66,7 +71,7 @@ func input(ctx context.Context) (*dataIn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Epoch
|
// Epoch
|
||||||
data.epoch = viper.GetInt64("epoch")
|
data.epoch = viper.GetString("epoch")
|
||||||
data.period = viper.GetString("period")
|
data.period = viper.GetString("period")
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -60,13 +60,6 @@ func TestInput(t *testing.T) {
|
|||||||
vars: map[string]interface{}{},
|
vars: map[string]interface{}{},
|
||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ConnectionMissing",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
},
|
|
||||||
err: "failed to connect to any beacon node",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "ConnectionInvalid",
|
name: "ConnectionInvalid",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
eth2client "github.com/attestantio/go-eth2-client"
|
eth2client "github.com/attestantio/go-eth2-client"
|
||||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wealdtech/ethdo/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||||
@@ -52,10 +53,11 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateEpoch(_ context.Context, data *dataIn) (phase0.Epoch, error) {
|
func calculateEpoch(ctx context.Context, data *dataIn) (phase0.Epoch, error) {
|
||||||
var epoch phase0.Epoch
|
var epoch phase0.Epoch
|
||||||
if data.epoch != -1 {
|
var err error
|
||||||
epoch = phase0.Epoch(data.epoch)
|
if data.epoch != "" {
|
||||||
|
epoch, err = util.ParseEpoch(ctx, data.chainTime, data.epoch)
|
||||||
} else {
|
} else {
|
||||||
switch strings.ToLower(data.period) {
|
switch strings.ToLower(data.period) {
|
||||||
case "", "current":
|
case "", "current":
|
||||||
@@ -68,6 +70,9 @@ func calculateEpoch(_ context.Context, data *dataIn) (phase0.Epoch, error) {
|
|||||||
return 0, fmt.Errorf("period %s not known", data.period)
|
return 0, fmt.Errorf("period %s not known", data.period)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
if data.debug {
|
if data.debug {
|
||||||
fmt.Printf("epoch is %d\n", epoch)
|
fmt.Printf("epoch is %d\n", epoch)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestProcess(t *testing.T) {
|
|||||||
dataIn: &dataIn{
|
dataIn: &dataIn{
|
||||||
eth2Client: eth2Client,
|
eth2Client: eth2Client,
|
||||||
chainTime: chainTime,
|
chainTime: chainTime,
|
||||||
epoch: -1,
|
epoch: "-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,19 +49,15 @@ epoch can be a specific epoch; If not supplied all slots for the current sync co
|
|||||||
func init() {
|
func init() {
|
||||||
synccommitteeCmd.AddCommand(synccommitteeInclusionCmd)
|
synccommitteeCmd.AddCommand(synccommitteeInclusionCmd)
|
||||||
synccommitteeFlags(synccommitteeInclusionCmd)
|
synccommitteeFlags(synccommitteeInclusionCmd)
|
||||||
synccommitteeInclusionCmd.Flags().Int64("epoch", -1, "the epoch for which to fetch sync committee inclusion")
|
synccommitteeInclusionCmd.Flags().String("epoch", "", "the epoch for which to fetch sync committee inclusion")
|
||||||
synccommitteeInclusionCmd.Flags().String("pubkey", "", "validator public key for sync committee")
|
synccommitteeInclusionCmd.Flags().String("validator", "", "the index, public key, or acount of the validator")
|
||||||
synccommitteeInclusionCmd.Flags().String("index", "", "validator index for sync committee")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func synccommitteeInclusionBindings() {
|
func synccommitteeInclusionBindings() {
|
||||||
if err := viper.BindPFlag("epoch", synccommitteeInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
if err := viper.BindPFlag("epoch", synccommitteeInclusionCmd.Flags().Lookup("epoch")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := viper.BindPFlag("pubkey", synccommitteeInclusionCmd.Flags().Lookup("pubkey")); err != nil {
|
if err := viper.BindPFlag("validator", synccommitteeInclusionCmd.Flags().Lookup("validator")); err != nil {
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := viper.BindPFlag("index", synccommitteeInclusionCmd.Flags().Lookup("index")); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,24 +43,14 @@ func TestInput(t *testing.T) {
|
|||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
},
|
},
|
||||||
err: "one of account, index or pubkey required",
|
err: "validator is required",
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MultipleValidatorInfo",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
|
||||||
"index": "1",
|
|
||||||
"pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
},
|
|
||||||
err: "only one of account, index and pubkey allowed",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Good",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
"index": "1",
|
"validator": "1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the consensus node.
|
// Connect to the consensus node.
|
||||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to consensus node")
|
return errors.Wrap(err, "failed to connect to consensus node")
|
||||||
}
|
}
|
||||||
@@ -57,7 +62,7 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var isProvider bool
|
var isProvider bool
|
||||||
c.validatorsProvider, isProvider = c.consensusClient.(eth2client.ValidatorsProvider)
|
c.validatorsProvider, isProvider = c.consensusClient.(eth2client.ValidatorsProvider)
|
||||||
if !isProvider {
|
if !isProvider {
|
||||||
return errors.New("consensu node does not provide validator information")
|
return errors.New("consensus node does not provide validator information")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -37,30 +37,12 @@ func TestInput(t *testing.T) {
|
|||||||
vars: map[string]interface{}{},
|
vars: map[string]interface{}{},
|
||||||
err: "timeout is required",
|
err: "timeout is required",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "NoValidatorInfo",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
|
||||||
},
|
|
||||||
err: "one of account, index or pubkey required",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MultipleValidatorInfo",
|
|
||||||
vars: map[string]interface{}{
|
|
||||||
"timeout": "5s",
|
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
|
||||||
"index": "1",
|
|
||||||
"pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
},
|
|
||||||
err: "only one of account, index and pubkey allowed",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "Good",
|
name: "Good",
|
||||||
vars: map[string]interface{}{
|
vars: map[string]interface{}{
|
||||||
"timeout": "5s",
|
"timeout": "5s",
|
||||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||||
"index": "1",
|
"validator": "1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ import (
|
|||||||
// a lot of data for an unsophisticated audience so it's easier to set a higher timeout..
|
// a lot of data for an unsophisticated audience so it's easier to set a higher timeout..
|
||||||
var minTimeout = 5 * time.Minute
|
var minTimeout = 5 * time.Minute
|
||||||
|
|
||||||
// defaultBeaconNode is used if no other connection is supplied.
|
|
||||||
var defaultBeaconNode = "http://mainnet-consensus.attestant.io/"
|
|
||||||
|
|
||||||
// validatorPath is the regular expression that matches a validator path.
|
// validatorPath is the regular expression that matches a validator path.
|
||||||
var validatorPath = regexp.MustCompile("^m/12381/3600/[0-9]+/0/0$")
|
var validatorPath = regexp.MustCompile("^m/12381/3600/[0-9]+/0/0$")
|
||||||
|
|
||||||
@@ -707,24 +704,14 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
|
|
||||||
// Connect to the consensus node.
|
// Connect to the consensus node.
|
||||||
var err error
|
var err error
|
||||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.connection != "" {
|
return err
|
||||||
// The user provided a connection, so don't second-guess them by using a different node.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user did not provide a connection, so attempt to use the default node.
|
|
||||||
if c.debug {
|
|
||||||
fmt.Fprintf(os.Stderr, "No node connection, attempting to use %s\n", defaultBeaconNode)
|
|
||||||
}
|
|
||||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, defaultBeaconNode, c.timeout, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !c.quiet {
|
|
||||||
fmt.Fprintf(os.Stderr, "No connection supplied; using mainnet public access endpoint\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up chaintime.
|
// Set up chaintime.
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ethereum 2 client.
|
// Ethereum 2 client.
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx, data.eth2Client, data.timeout, data.allowInsecure)
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: data.eth2Client,
|
||||||
|
Timeout: data.timeout,
|
||||||
|
AllowInsecure: data.allowInsecure,
|
||||||
|
LogFallback: !data.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ var (
|
|||||||
exitOperationFilename = "exit-operation.json"
|
exitOperationFilename = "exit-operation.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultBeaconNode is used if no other connection is supplied.
|
|
||||||
var defaultBeaconNode = "http://mainnet-consensus.attestant.io/"
|
|
||||||
|
|
||||||
func (c *command) process(ctx context.Context) error {
|
func (c *command) process(ctx context.Context) error {
|
||||||
if err := c.setup(ctx); err != nil {
|
if err := c.setup(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -420,24 +417,14 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
|
|
||||||
// Connect to the consensus node.
|
// Connect to the consensus node.
|
||||||
var err error
|
var err error
|
||||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.consensusClient, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.connection != "" {
|
return err
|
||||||
// The user provided a connection, so don't second-guess them by using a different node.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user did not provide a connection, so attempt to use the default node.
|
|
||||||
if c.debug {
|
|
||||||
fmt.Fprintf(os.Stderr, "No node connection, attempting to use %s\n", defaultBeaconNode)
|
|
||||||
}
|
|
||||||
c.consensusClient, err = util.ConnectToBeaconNode(ctx, defaultBeaconNode, c.timeout, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !c.quiet {
|
|
||||||
fmt.Fprintf(os.Stderr, "No connection supplied; using mainnet public access endpoint\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up chaintime.
|
// Set up chaintime.
|
||||||
|
|||||||
@@ -123,7 +123,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -377,7 +377,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,12 @@ func (c *command) setup(ctx context.Context) error {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Connect to the client.
|
// Connect to the client.
|
||||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
c.eth2Client, err = util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
|
Address: c.connection,
|
||||||
|
Timeout: c.timeout,
|
||||||
|
AllowInsecure: c.allowInsecureConnections,
|
||||||
|
LogFallback: !c.quiet,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to connect to beacon node")
|
return errors.Wrap(err, "failed to connect to beacon node")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,12 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
eth2Client, err := util.ConnectToBeaconNode(ctx,
|
eth2Client, err := util.ConnectToBeaconNode(ctx, &util.ConnectOpts{
|
||||||
viper.GetString("connection"),
|
Address: viper.GetString("connection"),
|
||||||
viper.GetDuration("timeout"),
|
Timeout: viper.GetDuration("timeout"),
|
||||||
viper.GetBool("allow-insecure-connections"),
|
AllowInsecure: viper.GetBool("allow-insecure-connections"),
|
||||||
)
|
LogFallback: !viper.GetBool("quiet"),
|
||||||
|
})
|
||||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||||
|
|
||||||
if viper.GetString("validator") == "" {
|
if viper.GetString("validator") == "" {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
// ReleaseVersion is the release version of the codebase.
|
// ReleaseVersion is the release version of the codebase.
|
||||||
// Usually overridden by tag names when building binaries.
|
// Usually overridden by tag names when building binaries.
|
||||||
var ReleaseVersion = "local build (latest release 1.28.5)"
|
var ReleaseVersion = "local build (latest release 1.29.0)"
|
||||||
|
|
||||||
// versionCmd represents the version command.
|
// versionCmd represents the version command.
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -481,12 +481,10 @@ Exit commands focus on information about validator exits generated by the `ethdo
|
|||||||
#### `verify`
|
#### `verify`
|
||||||
|
|
||||||
`ethdo exit verify` verifies the validator exit information in a JSON file generated by the `ethdo validator exit` command. Options include:
|
`ethdo exit verify` verifies the validator exit information in a JSON file generated by the `ethdo validator exit` command. Options include:
|
||||||
- `exit`: either a path to the JSON file or the JSON itself
|
- `signed-operation`: either a path to the JSON file or the JSON itself
|
||||||
- `account`: the account that generated the exit transaction (if available as an account, in format "wallet/account")
|
|
||||||
- `pubkey`: the public key of the account that generated the exit transaction
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo exit verify --exit=${HOME}/exit.json --pubkey=0xa951530887ae2494a8cc4f11cf186963b0051ac4f7942375585b9cf98324db1e532a67e521d0fcaab510edad1352394c
|
$ ethdo exit verify --signed-operation=${HOME}/exit.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### `node` commands
|
### `node` commands
|
||||||
@@ -547,9 +545,7 @@ Sync committee commands focus on information about sync committees.
|
|||||||
#### `inclusion`
|
#### `inclusion`
|
||||||
|
|
||||||
`ethdo synccommittee inclusion` provides information about the inclusion, or not, of a validator's sync committee messages. Options include:
|
`ethdo synccommittee inclusion` provides information about the inclusion, or not, of a validator's sync committee messages. Options include:
|
||||||
- `account` the account of the validator for which to print sync committee contributions
|
- `validator`: the index, public key or account of the validator in format "wallet/account"
|
||||||
- `index` the index of the validator for which to print sync committee contributions
|
|
||||||
- `pubkey` the public key of the validator for which to print sync committee contributions
|
|
||||||
- `epoch` the specific epoch for which to print sync committee contributions. Defaults to the last complete epoch
|
- `epoch` the specific epoch for which to print sync committee contributions. Defaults to the last complete epoch
|
||||||
|
|
||||||
|
|
||||||
@@ -686,23 +682,21 @@ Attester commands focus on Ethereum 2 validators' actions as attesters.
|
|||||||
|
|
||||||
`ethdo attester duties` provides information on the duties that a given validator has in a given epoch. Options include:
|
`ethdo attester duties` provides information on the duties that a given validator has in a given epoch. Options include:
|
||||||
- `epoch` the epoch in which to obtain the duties (defaults to current epoch)
|
- `epoch` the epoch in which to obtain the duties (defaults to current epoch)
|
||||||
- `account` the account for which to fetch the duties (in format "wallet/account")
|
- `validator` the validator for which to fetch the duties, as an index, publi key or account in the format "wallet/account"
|
||||||
- `pubkey` the public key for which to fetch the duties
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo attester duties --account=Validators/0 --epoch=5
|
$ ethdo attester duties --validator=Validators/0 --epoch=5
|
||||||
Validator attesting in slot 186 committee 3
|
Validator attesting in slot 186 committee 3
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `inclusion`
|
#### `inclusion`
|
||||||
|
|
||||||
`ethdo attester inclusion` finds the block with wihch an attestation is included on the chain. Options include:
|
`ethdo attester inclusion` finds the block with wihch an attestation is included on the chain. Options include:
|
||||||
- `epoch` the epoch in which to obtain the inclusion information (defaults to current epoch)
|
- `epoch` the epoch in which to obtain the inclusion information (defaults to previous epoch)
|
||||||
- `account` the account for which to fetch the inclusion information (in format "wallet/account")
|
- `validator` the validator for which to fetch the duties, as an index, publi key or account in the format "wallet/account"
|
||||||
- `pubkey` the public key for which to fetch the inclusion information
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ethdo attester inclusion --account=Validators/1 --epoch=6484
|
$ ethdo attester inclusion --validator=Validators/1 --epoch=6484
|
||||||
Attestation included in block 207492 (inclusion delay 1)
|
Attestation included in block 207492 (inclusion delay 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ type Service interface {
|
|||||||
SlotToSyncCommitteePeriod(slot phase0.Slot) uint64
|
SlotToSyncCommitteePeriod(slot phase0.Slot) uint64
|
||||||
// FirstSlotOfEpoch provides the first slot of the given epoch.
|
// FirstSlotOfEpoch provides the first slot of the given epoch.
|
||||||
FirstSlotOfEpoch(epoch phase0.Epoch) phase0.Slot
|
FirstSlotOfEpoch(epoch phase0.Epoch) phase0.Slot
|
||||||
|
// LastSlotOfEpoch provides the last slot of the given epoch.
|
||||||
|
LastSlotOfEpoch(epoch phase0.Epoch) phase0.Slot
|
||||||
// TimestampToSlot provides the slot of the given timestamp.
|
// TimestampToSlot provides the slot of the given timestamp.
|
||||||
TimestampToSlot(timestamp time.Time) phase0.Slot
|
TimestampToSlot(timestamp time.Time) phase0.Slot
|
||||||
// TimestampToEpoch provides the epoch of the given timestamp.
|
// TimestampToEpoch provides the epoch of the given timestamp.
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ func (s *Service) FirstSlotOfEpoch(epoch phase0.Epoch) phase0.Slot {
|
|||||||
return phase0.Slot(uint64(epoch) * s.slotsPerEpoch)
|
return phase0.Slot(uint64(epoch) * s.slotsPerEpoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LastSlotOfEpoch provides the last slot of the given epoch.
|
||||||
|
func (s *Service) LastSlotOfEpoch(epoch phase0.Epoch) phase0.Slot {
|
||||||
|
return phase0.Slot(uint64(epoch)*s.slotsPerEpoch + s.slotsPerEpoch - 1)
|
||||||
|
}
|
||||||
|
|
||||||
// TimestampToSlot provides the slot of the given timestamp.
|
// TimestampToSlot provides the slot of the given timestamp.
|
||||||
func (s *Service) TimestampToSlot(timestamp time.Time) phase0.Slot {
|
func (s *Service) TimestampToSlot(timestamp time.Time) phase0.Slot {
|
||||||
if timestamp.Before(s.genesisTime) {
|
if timestamp.Before(s.genesisTime) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright © 2020 Weald Technology Trading
|
// Copyright © 2020, 2023 Weald Technology Trading
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
// You may obtain a copy of the License at
|
// You may obtain a copy of the License at
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,25 +34,48 @@ var defaultBeaconNodeAddresses = []string{
|
|||||||
"localhost:3500", // Prysm
|
"localhost:3500", // Prysm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fallbackBeaconNode is used if no other connection is supplied.
|
||||||
|
var fallbackBeaconNode = "http://mainnet-consensus.attestant.io/"
|
||||||
|
|
||||||
|
type ConnectOpts struct {
|
||||||
|
Address string
|
||||||
|
Timeout time.Duration
|
||||||
|
AllowInsecure bool
|
||||||
|
LogFallback bool
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectToBeaconNode connects to a beacon node at the given address.
|
// ConnectToBeaconNode connects to a beacon node at the given address.
|
||||||
func ConnectToBeaconNode(ctx context.Context, address string, timeout time.Duration, allowInsecure bool) (eth2client.Service, error) {
|
func ConnectToBeaconNode(ctx context.Context, opts *ConnectOpts) (eth2client.Service, error) {
|
||||||
if timeout == 0 {
|
if opts == nil {
|
||||||
|
return nil, errors.New("no options specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Timeout == 0 {
|
||||||
return nil, errors.New("no timeout specified")
|
return nil, errors.New("no timeout specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
if address != "" {
|
if opts.Address != "" {
|
||||||
// We have an explicit address; use it.
|
// We have an explicit address; use it.
|
||||||
return connectToBeaconNode(ctx, address, timeout, allowInsecure)
|
return connectToBeaconNode(ctx, opts.Address, opts.Timeout, opts.AllowInsecure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the defaults.
|
// Try the defaults.
|
||||||
for _, address := range defaultBeaconNodeAddresses {
|
for _, address := range defaultBeaconNodeAddresses {
|
||||||
client, err := connectToBeaconNode(ctx, address, timeout, allowInsecure)
|
client, err := connectToBeaconNode(ctx, address, opts.Timeout, opts.AllowInsecure)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The user did not provide a connection, so attempt to use the fallback node.
|
||||||
|
if opts.LogFallback {
|
||||||
|
fmt.Fprintf(os.Stderr, "No connection supplied with --connection parameter and no local beacon node found, attempting to use mainnet fallback\n")
|
||||||
|
}
|
||||||
|
client, err := connectToBeaconNode(ctx, fallbackBeaconNode, opts.Timeout, true)
|
||||||
|
if err == nil {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, errors.New("failed to connect to any beacon node")
|
return nil, errors.New("failed to connect to any beacon node")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
func ParseEpoch(_ context.Context, chainTime chaintime.Service, epochStr string) (phase0.Epoch, error) {
|
func ParseEpoch(_ context.Context, chainTime chaintime.Service, epochStr string) (phase0.Epoch, error) {
|
||||||
currentEpoch := chainTime.CurrentEpoch()
|
currentEpoch := chainTime.CurrentEpoch()
|
||||||
switch epochStr {
|
switch epochStr {
|
||||||
case "", "current", "-0":
|
case "", "current", "head", "-0":
|
||||||
return currentEpoch, nil
|
return currentEpoch, nil
|
||||||
case "last":
|
case "last":
|
||||||
if currentEpoch > 0 {
|
if currentEpoch > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user