mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 22:47:59 -05:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac18cbab3e | ||
|
|
2f1c89d0a6 | ||
|
|
a3ad4181d3 | ||
|
|
f8ac23e8d7 | ||
|
|
b6815d1a2a | ||
|
|
a79b813bd0 | ||
|
|
ad971145f0 | ||
|
|
602948921c | ||
|
|
607e969a30 | ||
|
|
79f1ae9930 | ||
|
|
a98f681f98 | ||
|
|
e0e1f697d3 | ||
|
|
1b70a66120 | ||
|
|
94eba96a6e | ||
|
|
f052d8e307 | ||
|
|
df45686828 | ||
|
|
84d228877a | ||
|
|
b2b26742b0 | ||
|
|
9dc630c809 | ||
|
|
452430db56 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Fetch xgo
|
||||
run: |
|
||||
go get github.com/wealdtech/xgo
|
||||
go install github.com/wealdtech/xgo@latest
|
||||
|
||||
- name: Cross-compile linux
|
||||
run: |
|
||||
@@ -63,20 +63,20 @@ jobs:
|
||||
- name: Create windows release files
|
||||
run: |
|
||||
mv ethdo-windows-4.0-amd64.exe ethdo.exe
|
||||
sha256sum ethdo.exe >ethdo-${RELEASE_VERSION}-windows.sha256
|
||||
zip --junk-paths ethdo-${RELEASE_VERSION}-windows-exe.zip ethdo.exe
|
||||
sha256sum ethdo-${RELEASE_VERSION}-windows-exe.zip >ethdo-${RELEASE_VERSION}-windows.sha256
|
||||
|
||||
- name: Create linux AMD64 tgz file
|
||||
run: |
|
||||
mv ethdo-linux-amd64 ethdo
|
||||
sha256sum ethdo >ethdo-${RELEASE_VERSION}-linux-amd64.sha256
|
||||
tar zcf ethdo-${RELEASE_VERSION}-linux-amd64.tar.gz ethdo
|
||||
sha256sum ethdo-${RELEASE_VERSION}-linux-amd64.tar.gz >ethdo-${RELEASE_VERSION}-linux-amd64.sha256
|
||||
|
||||
# - name: Create linux ARM64 tgz file
|
||||
# run: |
|
||||
# mv ethdo-linux-arm64 ethdo
|
||||
# sha256sum ethdo >ethdo-${RELEASE_VERSION}-linux-arm64.sha256
|
||||
# tar zcf ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz ethdo
|
||||
# sha256sum ethdo-${RELEASE_VERSION}-linux-arm64.tar.gz >ethdo-${RELEASE_VERSION}-linux-arm64.sha256
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,3 +1,14 @@
|
||||
1.14.0:
|
||||
- add "chain verify signedcontributionandproof"
|
||||
- show both block and body root in "block info"
|
||||
- add exit / withdrawable epoch to "validator info"
|
||||
|
||||
1.13.0:
|
||||
- rework and provide additional information to "chain status" output
|
||||
|
||||
1.12.0:
|
||||
- add "synccommittee members"
|
||||
|
||||
1.11.0
|
||||
- add Altair information to "block info"
|
||||
- add more information to "chain info"
|
||||
|
||||
@@ -51,6 +51,7 @@ func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
func outputBlockGeneral(ctx context.Context,
|
||||
verbose bool,
|
||||
slot phase0.Slot,
|
||||
blockRoot phase0.Root,
|
||||
bodyRoot phase0.Root,
|
||||
parentRoot phase0.Root,
|
||||
stateRoot phase0.Root,
|
||||
@@ -67,8 +68,9 @@ func outputBlockGeneral(ctx context.Context,
|
||||
res.WriteString(fmt.Sprintf("Slot: %d\n", slot))
|
||||
res.WriteString(fmt.Sprintf("Epoch: %d\n", phase0.Epoch(uint64(slot)/slotsPerEpoch)))
|
||||
res.WriteString(fmt.Sprintf("Timestamp: %v\n", time.Unix(genesisTime.Unix()+int64(slot)*int64(slotDuration.Seconds()), 0)))
|
||||
res.WriteString(fmt.Sprintf("Block root: %#x\n", bodyRoot))
|
||||
res.WriteString(fmt.Sprintf("Block root: %#x\n", blockRoot))
|
||||
if verbose {
|
||||
res.WriteString(fmt.Sprintf("Body root: %#x\n", bodyRoot))
|
||||
res.WriteString(fmt.Sprintf("Parent root: %#x\n", parentRoot))
|
||||
res.WriteString(fmt.Sprintf("State root: %#x\n", stateRoot))
|
||||
}
|
||||
@@ -271,13 +273,19 @@ func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *alta
|
||||
res := strings.Builder{}
|
||||
|
||||
// General info.
|
||||
blockRoot, err := signedBlock.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain block root")
|
||||
}
|
||||
bodyRoot, err := signedBlock.Message.Body.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to generate block root")
|
||||
return "", errors.Wrap(err, "failed to generate body root")
|
||||
}
|
||||
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
@@ -349,6 +357,10 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
res := strings.Builder{}
|
||||
|
||||
// General info.
|
||||
blockRoot, err := signedBlock.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain block root")
|
||||
}
|
||||
bodyRoot, err := signedBlock.Message.Body.HashTreeRoot()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to generate block root")
|
||||
@@ -356,6 +368,7 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas
|
||||
tmp, err := outputBlockGeneral(ctx,
|
||||
data.verbose,
|
||||
signedBlock.Message.Slot,
|
||||
blockRoot,
|
||||
bodyRoot,
|
||||
signedBlock.Message.ParentRoot,
|
||||
signedBlock.Message.StateRoot,
|
||||
|
||||
86
cmd/chain/verify/signedcontributionandproof/command.go
Normal file
86
cmd/chain/verify/signedcontributionandproof/command.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type command struct {
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
|
||||
// Beacon node connection.
|
||||
timeout time.Duration
|
||||
connection string
|
||||
allowInsecureConnections bool
|
||||
|
||||
// Input.
|
||||
data string
|
||||
item *altair.SignedContributionAndProof
|
||||
|
||||
// Data access.
|
||||
eth2Client eth2client.Service
|
||||
validatorsProvider eth2client.ValidatorsProvider
|
||||
|
||||
// Data.
|
||||
spec map[string]interface{}
|
||||
validator *api.Validator
|
||||
syncCommittee *api.SyncCommittee
|
||||
|
||||
// Output.
|
||||
itemStructureValid bool
|
||||
validatorKnown bool
|
||||
validatorInSyncCommittee bool
|
||||
validatorIsAggregator bool
|
||||
contributionSignatureValidFormat bool
|
||||
contributionAndProofSignatureValidFormat bool
|
||||
contributionAndProofSignatureValid bool
|
||||
additionalInfo string
|
||||
}
|
||||
|
||||
func newCommand(ctx context.Context) (*command, error) {
|
||||
c := &command{
|
||||
quiet: viper.GetBool("quiet"),
|
||||
verbose: viper.GetBool("verbose"),
|
||||
debug: viper.GetBool("debug"),
|
||||
}
|
||||
|
||||
// Timeout.
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
c.timeout = viper.GetDuration("timeout")
|
||||
|
||||
if viper.GetString("data") == "" {
|
||||
return nil, errors.New("data is required")
|
||||
}
|
||||
c.data = viper.GetString("data")
|
||||
|
||||
if viper.GetString("connection") == "" {
|
||||
return nil, errors.New("connection is required")
|
||||
}
|
||||
c.connection = viper.GetString("connection")
|
||||
c.allowInsecureConnections = viper.GetBool("allow-insecure-connections")
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "TimeoutMissing",
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "DataMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "data is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"data": "{}",
|
||||
},
|
||||
err: "connection is required",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"data": "{}",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
_, err := newCommand(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
127
cmd/chain/verify/signedcontributionandproof/output.go
Normal file
127
cmd/chain/verify/signedcontributionandproof/output.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *command) output(ctx context.Context) (string, error) {
|
||||
if c.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
builder.WriteString("Valid data structure: ")
|
||||
if c.itemStructureValid {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Validator known: ")
|
||||
if c.validatorKnown {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Validator in sync committee: ")
|
||||
if c.validatorInSyncCommittee {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Validator is aggregator: ")
|
||||
if c.validatorIsAggregator {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Contribution signature has valid format: ")
|
||||
if c.contributionSignatureValidFormat {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Contribution and proof signature has valid format: ")
|
||||
if c.contributionAndProofSignatureValidFormat {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
builder.WriteString("Contribution and proof signature is valid: ")
|
||||
if c.contributionAndProofSignatureValid {
|
||||
builder.WriteString("✓\n")
|
||||
} else {
|
||||
builder.WriteString("✕")
|
||||
if c.additionalInfo != "" {
|
||||
builder.WriteString(" (")
|
||||
builder.WriteString(c.additionalInfo)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
303
cmd/chain/verify/signedcontributionandproof/process.go
Normal file
303
cmd/chain/verify/signedcontributionandproof/process.go
Normal file
@@ -0,0 +1,303 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/altair"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
)
|
||||
|
||||
func (c *command) process(ctx context.Context) error {
|
||||
// Parse the data.
|
||||
if c.data == "" {
|
||||
return errors.New("no data supplied")
|
||||
}
|
||||
c.item = &altair.SignedContributionAndProof{}
|
||||
err := json.Unmarshal([]byte(c.data), c.item)
|
||||
if err != nil {
|
||||
c.additionalInfo = err.Error()
|
||||
return nil
|
||||
}
|
||||
c.itemStructureValid = true
|
||||
|
||||
// Obtain information we need to process.
|
||||
if err := c.setup(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, validatorIndex := range c.syncCommittee.Validators {
|
||||
if validatorIndex == c.item.Message.AggregatorIndex {
|
||||
c.validatorInSyncCommittee = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !c.validatorInSyncCommittee {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure the validator is an aggregator.
|
||||
isAggregator, err := c.isAggregator(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to ascertain if sync committee member is aggregator")
|
||||
}
|
||||
if !isAggregator {
|
||||
return nil
|
||||
}
|
||||
c.validatorIsAggregator = true
|
||||
|
||||
// Confirm the contribution signature.
|
||||
if err := c.confirmContributionSignature(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to confirm the contribution signature")
|
||||
}
|
||||
|
||||
// Confirm the contribution and proof signature.
|
||||
if err := c.confirmContributionAndProofSignature(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to confirm the contribution and proof signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) setup(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// Connect to the client.
|
||||
c.eth2Client, err = util.ConnectToBeaconNode(ctx, c.connection, c.timeout, c.allowInsecureConnections)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to connect to beacon node")
|
||||
}
|
||||
|
||||
// Obtain the validator.
|
||||
var isProvider bool
|
||||
c.validatorsProvider, isProvider = c.eth2Client.(eth2client.ValidatorsProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide validator information")
|
||||
}
|
||||
|
||||
stateID := fmt.Sprintf("%d", c.item.Message.Contribution.Slot)
|
||||
validators, err := c.validatorsProvider.Validators(ctx,
|
||||
stateID,
|
||||
[]phase0.ValidatorIndex{c.item.Message.AggregatorIndex},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain validator information")
|
||||
}
|
||||
|
||||
if len(validators) == 0 || validators[c.item.Message.AggregatorIndex] == nil {
|
||||
return nil
|
||||
}
|
||||
c.validatorKnown = true
|
||||
c.validator = validators[c.item.Message.AggregatorIndex]
|
||||
|
||||
// Obtain the sync committee
|
||||
syncCommitteesProvider, isProvider := c.eth2Client.(eth2client.SyncCommitteesProvider)
|
||||
if !isProvider {
|
||||
return errors.New("connection does not provide sync committee information")
|
||||
}
|
||||
c.syncCommittee, err = syncCommitteesProvider.SyncCommittee(ctx, stateID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain sync committee information")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isAggregator returns true if the given
|
||||
func (c *command) isAggregator(ctx context.Context) (bool, error) {
|
||||
// Calculate the modulo.
|
||||
specProvider, isProvider := c.eth2Client.(eth2client.SpecProvider)
|
||||
if !isProvider {
|
||||
return false, errors.New("connection does not provide spec information")
|
||||
}
|
||||
var err error
|
||||
c.spec, err = specProvider.Spec(ctx)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to obtain spec information")
|
||||
}
|
||||
|
||||
tmp, exists := c.spec["SYNC_COMMITTEE_SIZE"]
|
||||
if !exists {
|
||||
return false, errors.New("spec does not contain SYNC_COMMITTEE_SIZE")
|
||||
}
|
||||
if _, isUint64 := tmp.(uint64); !isUint64 {
|
||||
return false, errors.New("spec returned non-integer value for SYNC_COMMITTEE_SIZE")
|
||||
}
|
||||
syncCommitteeSize := tmp.(uint64)
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "sync committee size is %d\n", syncCommitteeSize)
|
||||
}
|
||||
|
||||
tmp, exists = c.spec["SYNC_COMMITTEE_SUBNET_COUNT"]
|
||||
if !exists {
|
||||
return false, errors.New("spec does not contain SYNC_COMMITTEE_SUBNET_COUNT")
|
||||
}
|
||||
if _, isUint64 := tmp.(uint64); !isUint64 {
|
||||
return false, errors.New("spec returned non-integer value for SYNC_COMMITTEE_SUBNET_COUNT")
|
||||
}
|
||||
syncCommitteeSubnetCount := tmp.(uint64)
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "sync committee subnet count is %d\n", syncCommitteeSubnetCount)
|
||||
}
|
||||
|
||||
tmp, exists = c.spec["TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE"]
|
||||
if !exists {
|
||||
return false, errors.New("spec does not contain TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE")
|
||||
}
|
||||
if _, isUint64 := tmp.(uint64); !isUint64 {
|
||||
return false, errors.New("spec returned non-integer value for TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE")
|
||||
}
|
||||
targetAggregatorsPerSyncSubcommittee := tmp.(uint64)
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "target aggregators per sync subcommittee is %d\n", targetAggregatorsPerSyncSubcommittee)
|
||||
}
|
||||
|
||||
modulo := syncCommitteeSize / syncCommitteeSubnetCount / targetAggregatorsPerSyncSubcommittee
|
||||
if modulo < 1 {
|
||||
modulo = 1
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "modulo is %d\n", modulo)
|
||||
}
|
||||
|
||||
// Hash the selection proof.
|
||||
sigHash := sha256.New()
|
||||
n, err := sigHash.Write(c.item.Message.SelectionProof[:])
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to hash the selection proof")
|
||||
}
|
||||
if n != len(c.item.Signature[:]) {
|
||||
return false, errors.New("failed to write all bytes of the selection proof to the hash")
|
||||
}
|
||||
hash := sigHash.Sum(nil)
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "hash of selection proof is %#x\n", hash)
|
||||
}
|
||||
|
||||
return binary.LittleEndian.Uint64(hash[:8])%modulo == 0, nil
|
||||
}
|
||||
|
||||
func (c *command) confirmContributionSignature(ctx context.Context) error {
|
||||
sigBytes := make([]byte, 96)
|
||||
copy(sigBytes, c.item.Message.Contribution.Signature[:])
|
||||
_, err := e2types.BLSSignatureFromBytes(sigBytes)
|
||||
if err != nil {
|
||||
c.additionalInfo = err.Error()
|
||||
return nil
|
||||
}
|
||||
c.contributionSignatureValidFormat = true
|
||||
|
||||
subCommittee := c.syncCommittee.ValidatorAggregates[c.item.Message.Contribution.SubcommitteeIndex]
|
||||
includedIndices := make([]phase0.ValidatorIndex, 0, len(subCommittee))
|
||||
for i := uint64(0); i < c.item.Message.Contribution.AggregationBits.Len(); i++ {
|
||||
if c.item.Message.Contribution.AggregationBits.BitAt(i) {
|
||||
includedIndices = append(includedIndices, subCommittee[int(i)])
|
||||
}
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "Contribution validator indices: %v (%d)\n", includedIndices, len(includedIndices))
|
||||
}
|
||||
|
||||
includedValidators, err := c.validatorsProvider.Validators(ctx, "head", includedIndices)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain subcommittee validators")
|
||||
}
|
||||
if len(includedValidators) == 0 {
|
||||
return errors.New("obtained empty subcommittee validator list")
|
||||
}
|
||||
|
||||
var aggregatePubKey *e2types.BLSPublicKey
|
||||
for _, v := range includedValidators {
|
||||
pubKeyBytes := make([]byte, 48)
|
||||
copy(pubKeyBytes, v.Validator.PublicKey[:])
|
||||
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to aggregate public key")
|
||||
}
|
||||
if aggregatePubKey == nil {
|
||||
aggregatePubKey = pubKey
|
||||
} else {
|
||||
aggregatePubKey.Aggregate(pubKey)
|
||||
}
|
||||
}
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "Aggregate public key is %#x\n", aggregatePubKey.Marshal())
|
||||
}
|
||||
|
||||
// Don't have the ability to carry out the batch verification at current.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *command) confirmContributionAndProofSignature(ctx context.Context) error {
|
||||
sigBytes := make([]byte, 96)
|
||||
copy(sigBytes, c.item.Signature[:])
|
||||
sig, err := e2types.BLSSignatureFromBytes(sigBytes)
|
||||
if err != nil {
|
||||
c.additionalInfo = err.Error()
|
||||
return nil
|
||||
}
|
||||
c.contributionAndProofSignatureValidFormat = true
|
||||
|
||||
pubKeyBytes := make([]byte, 48)
|
||||
copy(pubKeyBytes, c.validator.Validator.PublicKey[:])
|
||||
pubKey, err := e2types.BLSPublicKeyFromBytes(pubKeyBytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to configure public key")
|
||||
}
|
||||
|
||||
objectRoot, err := c.item.Message.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain signging root")
|
||||
}
|
||||
|
||||
tmp, exists := c.spec["DOMAIN_CONTRIBUTION_AND_PROOF"]
|
||||
if !exists {
|
||||
return errors.New("spec does not contain DOMAIN_CONTRIBUTION_AND_PROOF")
|
||||
}
|
||||
if _, isUint64 := tmp.(phase0.DomainType); !isUint64 {
|
||||
return errors.New("spec returned non-domain type value for DOMAIN_CONTRIBUTION_AND_PROOF")
|
||||
}
|
||||
contributionAndProofDomainType := tmp.(phase0.DomainType)
|
||||
if c.debug {
|
||||
fmt.Fprintf(os.Stderr, "contribution and proof domain type is %#x\n", contributionAndProofDomainType)
|
||||
}
|
||||
domain, err := c.eth2Client.(eth2client.DomainProvider).Domain(ctx, contributionAndProofDomainType, phase0.Epoch(c.item.Message.Contribution.Slot/32))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain domain")
|
||||
}
|
||||
|
||||
container := &phase0.SigningData{
|
||||
ObjectRoot: objectRoot,
|
||||
Domain: domain,
|
||||
}
|
||||
signingRoot, err := container.HashTreeRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to obtain signging root")
|
||||
}
|
||||
|
||||
c.contributionAndProofSignatureValid = sig.Verify(signingRoot[:], pubKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "InvalidData",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
"data": "[[",
|
||||
"connection": os.Getenv("ETHDO_TEST_CONNECTION"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
cmd, err := newCommand(context.Background())
|
||||
require.NoError(t, err)
|
||||
err = cmd.process(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/chain/verify/signedcontributionandproof/run.go
Normal file
50
cmd/chain/verify/signedcontributionandproof/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chainverifysignedcontributionandproof
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := newCommand(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to set up command")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
if err := c.process(ctx); err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := c.output(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -85,18 +85,3 @@ func init() {
|
||||
chainCmd.AddCommand(chainInfoCmd)
|
||||
chainFlags(chainInfoCmd)
|
||||
}
|
||||
|
||||
func timestampToSlot(genesis time.Time, timestamp time.Time, secondsPerSlot time.Duration) spec.Slot {
|
||||
if timestamp.Unix() < genesis.Unix() {
|
||||
return 0
|
||||
}
|
||||
return spec.Slot(uint64(timestamp.Unix()-genesis.Unix()) / uint64(secondsPerSlot.Seconds()))
|
||||
}
|
||||
|
||||
func slotToTimestamp(genesis time.Time, slot spec.Slot, slotDuration time.Duration) int64 {
|
||||
return genesis.Unix() + int64(slot)*int64(slotDuration.Seconds())
|
||||
}
|
||||
|
||||
func epochToTimestamp(genesis time.Time, slot spec.Slot, slotDuration time.Duration, slotsPerEpoch uint64) int64 {
|
||||
return genesis.Unix() + int64(slot)*int64(slotDuration.Seconds())*int64(slotsPerEpoch)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -17,12 +17,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
@@ -40,53 +41,108 @@ In quiet mode this will return 0 if the chain status can be obtained, otherwise
|
||||
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")
|
||||
|
||||
specProvider, isProvider := eth2Client.(eth2client.SpecProvider)
|
||||
assert(isProvider, "beacon node does not provide spec; cannot report on chain status")
|
||||
config, err := specProvider.Spec(ctx)
|
||||
errCheck(err, "Failed to obtain beacon chain specification")
|
||||
chainTime, err := standardchaintime.New(ctx,
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
standardchaintime.WithForkScheduleProvider(eth2Client.(eth2client.ForkScheduleProvider)),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
)
|
||||
errCheck(err, "Failed to configure chaintime service")
|
||||
|
||||
finalityProvider, isProvider := eth2Client.(eth2client.FinalityProvider)
|
||||
assert(isProvider, "beacon node does not provide finality; cannot report on chain status")
|
||||
finality, err := finalityProvider.Finality(ctx, "head")
|
||||
errCheck(err, "Failed to obtain finality information")
|
||||
|
||||
genesisProvider, isProvider := eth2Client.(eth2client.GenesisProvider)
|
||||
assert(isProvider, "beacon node does not provide genesis; cannot report on chain status")
|
||||
genesis, err := genesisProvider.Genesis(ctx)
|
||||
errCheck(err, "Failed to obtain genesis information")
|
||||
slot := chainTime.CurrentSlot()
|
||||
|
||||
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
|
||||
curSlot := timestampToSlot(genesis.GenesisTime, time.Now(), slotDuration)
|
||||
slotsPerEpoch := config["SLOTS_PER_EPOCH"].(uint64)
|
||||
curEpoch := spec.Epoch(uint64(curSlot) / slotsPerEpoch)
|
||||
fmt.Printf("Current epoch: %d\n", curEpoch)
|
||||
outputIf(verbose, fmt.Sprintf("Current slot: %d", curSlot))
|
||||
fmt.Printf("Justified epoch: %d\n", finality.Justified.Epoch)
|
||||
if verbose {
|
||||
distance := curEpoch - finality.Justified.Epoch
|
||||
fmt.Printf("Justified epoch distance: %d\n", distance)
|
||||
}
|
||||
fmt.Printf("Finalized epoch: %d\n", finality.Finalized.Epoch)
|
||||
if verbose {
|
||||
distance := curEpoch - finality.Finalized.Epoch
|
||||
fmt.Printf("Finalized epoch distance: %d\n", distance)
|
||||
}
|
||||
if verbose {
|
||||
fmt.Printf("Prior justified epoch: %d\n", finality.PreviousJustified.Epoch)
|
||||
distance := curEpoch - finality.PreviousJustified.Epoch
|
||||
fmt.Printf("Prior justified epoch distance: %d\n", distance)
|
||||
}
|
||||
nextSlot := slot + 1
|
||||
nextSlotTimestamp := chainTime.StartOfSlot(nextSlot)
|
||||
|
||||
epoch := chainTime.CurrentEpoch()
|
||||
epochStartSlot := chainTime.FirstSlotOfEpoch(epoch)
|
||||
epochEndSlot := chainTime.FirstSlotOfEpoch(epoch+1) - 1
|
||||
|
||||
nextEpoch := epoch + 1
|
||||
nextEpochStartSlot := chainTime.FirstSlotOfEpoch(nextEpoch)
|
||||
nextEpochTimestamp := chainTime.StartOfEpoch(nextEpoch)
|
||||
|
||||
res := strings.Builder{}
|
||||
|
||||
res.WriteString("Current slot: ")
|
||||
res.WriteString(fmt.Sprintf("%d", slot))
|
||||
res.WriteString("\n")
|
||||
|
||||
res.WriteString("Current epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", epoch))
|
||||
res.WriteString("\n")
|
||||
|
||||
if verbose {
|
||||
epochStartSlot := (uint64(curSlot) / slotsPerEpoch) * slotsPerEpoch
|
||||
fmt.Printf("Epoch slots: %d-%d\n", epochStartSlot, epochStartSlot+slotsPerEpoch-1)
|
||||
nextSlotTimestamp := slotToTimestamp(genesis.GenesisTime, curSlot+1, slotDuration)
|
||||
fmt.Printf("Time until next slot: %2.1fs\n", float64(time.Until(time.Unix(nextSlotTimestamp, 0)).Milliseconds())/1000)
|
||||
nextEpoch := epochToTimestamp(genesis.GenesisTime, spec.Slot(uint64(curSlot)/slotsPerEpoch+1), slotDuration, slotsPerEpoch)
|
||||
fmt.Printf("Slots until next epoch: %d\n", (uint64(curSlot)/slotsPerEpoch+1)*slotsPerEpoch-uint64(curSlot))
|
||||
fmt.Printf("Time until next epoch: %2.1fs\n", float64(time.Until(time.Unix(nextEpoch, 0)).Milliseconds())/1000)
|
||||
res.WriteString("Epoch slots: ")
|
||||
res.WriteString(fmt.Sprintf("%d", epochStartSlot))
|
||||
res.WriteString("-")
|
||||
res.WriteString(fmt.Sprintf("%d", epochEndSlot))
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
res.WriteString("Time until next slot: ")
|
||||
res.WriteString(time.Until(nextSlotTimestamp).Round(time.Second).String())
|
||||
res.WriteString("\n")
|
||||
|
||||
res.WriteString("Time until next epoch: ")
|
||||
res.WriteString(time.Until(nextEpochTimestamp).Round(time.Second).String())
|
||||
res.WriteString("\n")
|
||||
|
||||
res.WriteString("Slots until next epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", nextEpochStartSlot-slot))
|
||||
res.WriteString("\n")
|
||||
|
||||
res.WriteString("Justified epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", finality.Justified.Epoch))
|
||||
res.WriteString("\n")
|
||||
if verbose {
|
||||
distance := epoch - finality.Justified.Epoch
|
||||
res.WriteString("Justified epoch distance: ")
|
||||
res.WriteString(fmt.Sprintf("%d", distance))
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
res.WriteString("Finalized epoch: ")
|
||||
res.WriteString(fmt.Sprintf("%d", finality.Finalized.Epoch))
|
||||
res.WriteString("\n")
|
||||
if verbose {
|
||||
distance := epoch - finality.Finalized.Epoch
|
||||
res.WriteString("Finalized epoch distance: ")
|
||||
res.WriteString(fmt.Sprintf("%d", distance))
|
||||
res.WriteString("\n")
|
||||
}
|
||||
|
||||
if epoch >= chainTime.AltairInitialEpoch() {
|
||||
period := chainTime.SlotToSyncCommitteePeriod(slot)
|
||||
periodStartEpoch := chainTime.FirstEpochOfSyncPeriod(period)
|
||||
nextPeriod := period + 1
|
||||
nextPeriodStartEpoch := chainTime.FirstEpochOfSyncPeriod(nextPeriod)
|
||||
periodEndEpoch := nextPeriodStartEpoch - 1
|
||||
nextPeriodTimestamp := chainTime.StartOfEpoch(nextPeriodStartEpoch)
|
||||
|
||||
res.WriteString("Sync committee period: ")
|
||||
res.WriteString(fmt.Sprintf("%d", period))
|
||||
res.WriteString("\n")
|
||||
|
||||
if verbose {
|
||||
res.WriteString("Sync committee epochs: ")
|
||||
res.WriteString(fmt.Sprintf("%d", periodStartEpoch))
|
||||
res.WriteString("-")
|
||||
res.WriteString(fmt.Sprintf("%d", periodEndEpoch))
|
||||
res.WriteString("\n")
|
||||
|
||||
res.WriteString("Time until next sync committee period: ")
|
||||
res.WriteString(time.Until(nextPeriodTimestamp).Round(time.Second).String())
|
||||
res.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(res.String())
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
45
cmd/chainverify.go
Normal file
45
cmd/chainverify.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// chainVerifyCmd represents the chain verify command
|
||||
var chainVerifyCmd = &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify a beacon chain signature",
|
||||
Long: "Verify the signature for a given beacon chain structure is correct",
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainCmd.AddCommand(chainVerifyCmd)
|
||||
}
|
||||
|
||||
func chainVerifyFlags(cmd *cobra.Command) {
|
||||
chainFlags(cmd)
|
||||
cmd.Flags().String("validator", "", "The account, public key or index of the validator")
|
||||
cmd.Flags().String("data", "", "The data to verify, as a JSON structure")
|
||||
}
|
||||
|
||||
func chainVerifyBindings(cmd *cobra.Command) {
|
||||
if err := viper.BindPFlag("validator", cmd.Flags().Lookup("validator")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("data", cmd.Flags().Lookup("data")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
50
cmd/chainverifysignedcontributionandproof.go
Normal file
50
cmd/chainverifysignedcontributionandproof.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
chainverifysignedcontributionandproof "github.com/wealdtech/ethdo/cmd/chain/verify/signedcontributionandproof"
|
||||
)
|
||||
|
||||
var chainVerifySignedContributionAndProofCmd = &cobra.Command{
|
||||
Use: "signedcontributionandproof",
|
||||
Short: "Verify a signed contribution and proof",
|
||||
Long: `Verify a signed contribution and proof. For example:
|
||||
|
||||
ethdo chain verify signedcontributionandproof --data=... --validator=...
|
||||
|
||||
validator can be an account, a public key or an index.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := chainverifysignedcontributionandproof.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Print(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
chainVerifyCmd.AddCommand(chainVerifySignedContributionAndProofCmd)
|
||||
chainVerifyFlags(chainVerifySignedContributionAndProofCmd)
|
||||
}
|
||||
|
||||
func chainVerifySignedContributionAndProofBindings(cmd *cobra.Command) {
|
||||
chainVerifyBindings(cmd)
|
||||
}
|
||||
29
cmd/root.go
29
cmd/root.go
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 Weald Technology Trading
|
||||
// Copyright © 2019 - 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
@@ -56,12 +57,15 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disable service logging.
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
|
||||
// We bind viper here so that we bind to the correct command.
|
||||
quiet = viper.GetBool("quiet")
|
||||
verbose = viper.GetBool("verbose")
|
||||
debug = viper.GetBool("debug")
|
||||
// Command-specific bindings.
|
||||
switch fmt.Sprintf("%s/%s", cmd.Parent().Name(), cmd.Name()) {
|
||||
switch commandPath(cmd) {
|
||||
case "account/create":
|
||||
accountCreateBindings()
|
||||
case "account/derive":
|
||||
@@ -76,12 +80,16 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
blockInfoBindings()
|
||||
case "chain/time":
|
||||
chainTimeBindings()
|
||||
case "chain/verify/signedcontributionandproof":
|
||||
chainVerifySignedContributionAndProofBindings(cmd)
|
||||
case "exit/verify":
|
||||
exitVerifyBindings()
|
||||
case "node/events":
|
||||
nodeEventsBindings()
|
||||
case "slot/time":
|
||||
slotTimeBindings()
|
||||
case "synccommittee/members":
|
||||
synccommitteeMembersBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "validator/duties":
|
||||
@@ -109,11 +117,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println("Cannot supply both quiet and debug flags")
|
||||
}
|
||||
|
||||
if err := util.SetupStore(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return util.SetupStore()
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
@@ -387,3 +391,14 @@ func remotesToEndpoints(remotes []string) ([]*dirk.Endpoint, error) {
|
||||
func relockAccount(locker e2wtypes.AccountLocker) {
|
||||
errCheck(locker.Lock(context.Background()), "failed to re-lock account")
|
||||
}
|
||||
|
||||
func commandPath(cmd *cobra.Command) string {
|
||||
path := ""
|
||||
for {
|
||||
path = fmt.Sprintf("%s/%s", cmd.Name(), path)
|
||||
if cmd.Parent().Name() == "ethdo" {
|
||||
return strings.TrimRight(path, "/")
|
||||
}
|
||||
cmd = cmd.Parent()
|
||||
}
|
||||
}
|
||||
|
||||
32
cmd/synccommittee.go
Normal file
32
cmd/synccommittee.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// synccommitteeCmd represents the synccommittee command
|
||||
var synccommitteeCmd = &cobra.Command{
|
||||
Use: "synccommittee",
|
||||
Short: "Obtain information about Ethereum 2 sync committees",
|
||||
Long: "Obtain information about Ethereum 2 sync committees",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(synccommitteeCmd)
|
||||
}
|
||||
|
||||
func synccommitteeFlags(cmd *cobra.Command) {
|
||||
}
|
||||
78
cmd/synccommittee/members/input.go
Normal file
78
cmd/synccommittee/members/input.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
spec "github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
type dataIn struct {
|
||||
// System.
|
||||
timeout time.Duration
|
||||
quiet bool
|
||||
verbose bool
|
||||
debug bool
|
||||
// Operation.
|
||||
eth2Client eth2client.Service
|
||||
chainTime chaintime.Service
|
||||
epoch spec.Epoch
|
||||
}
|
||||
|
||||
func input(ctx context.Context) (*dataIn, error) {
|
||||
data := &dataIn{}
|
||||
|
||||
if viper.GetDuration("timeout") == 0 {
|
||||
return nil, errors.New("timeout is required")
|
||||
}
|
||||
data.timeout = viper.GetDuration("timeout")
|
||||
data.quiet = viper.GetBool("quiet")
|
||||
data.verbose = viper.GetBool("verbose")
|
||||
data.debug = viper.GetBool("debug")
|
||||
|
||||
// Ethereum 2 client.
|
||||
var err error
|
||||
data.eth2Client, err = util.ConnectToBeaconNode(ctx, viper.GetString("connection"), viper.GetDuration("timeout"), viper.GetBool("allow-insecure-connections"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
|
||||
}
|
||||
|
||||
// Chain time.
|
||||
data.chainTime, err = standardchaintime.New(ctx,
|
||||
standardchaintime.WithGenesisTimeProvider(data.eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
standardchaintime.WithForkScheduleProvider(data.eth2Client.(eth2client.ForkScheduleProvider)),
|
||||
standardchaintime.WithSpecProvider(data.eth2Client.(eth2client.SpecProvider)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to configure chaintime service")
|
||||
}
|
||||
|
||||
// Epoch
|
||||
epoch := viper.GetInt64("epoch")
|
||||
if epoch == -1 {
|
||||
data.epoch = data.chainTime.CurrentEpoch()
|
||||
} else {
|
||||
data.epoch = spec.Epoch(epoch)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
88
cmd/synccommittee/members/input_internal_test.go
Normal file
88
cmd/synccommittee/members/input_internal_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wealdtech/ethdo/testutil"
|
||||
e2types "github.com/wealdtech/go-eth2-types/v2"
|
||||
e2wallet "github.com/wealdtech/go-eth2-wallet"
|
||||
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
|
||||
nd "github.com/wealdtech/go-eth2-wallet-nd/v2"
|
||||
scratch "github.com/wealdtech/go-eth2-wallet-store-scratch"
|
||||
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
|
||||
)
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
|
||||
require.NoError(t, e2types.InitBLS())
|
||||
|
||||
store := scratch.New()
|
||||
require.NoError(t, e2wallet.UseStore(store))
|
||||
testWallet, err := nd.CreateWallet(context.Background(), "Test wallet", store, keystorev4.New())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, testWallet.(e2wtypes.WalletLocker).Unlock(context.Background(), nil))
|
||||
viper.Set("passphrase", "pass")
|
||||
_, err = testWallet.(e2wtypes.WalletAccountImporter).ImportAccount(context.Background(),
|
||||
"Interop 0",
|
||||
testutil.HexToBytes("0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866"),
|
||||
[]byte("pass"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vars map[string]interface{}
|
||||
res *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "TimeoutMissing",
|
||||
vars: map[string]interface{}{},
|
||||
err: "timeout is required",
|
||||
},
|
||||
{
|
||||
name: "ConnectionMissing",
|
||||
vars: map[string]interface{}{
|
||||
"timeout": "5s",
|
||||
},
|
||||
err: "failed to connect to Ethereum 2 beacon node: failed to connect to beacon node: problem with parameters: no address specified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
viper.Reset()
|
||||
|
||||
for k, v := range test.vars {
|
||||
viper.Set(k, v)
|
||||
}
|
||||
res, err := input(context.Background())
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res.timeout, res.timeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
61
cmd/synccommittee/members/output.go
Normal file
61
cmd/synccommittee/members/output.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dataOut struct {
|
||||
debug bool
|
||||
quiet bool
|
||||
verbose bool
|
||||
json bool
|
||||
validators []phase0.ValidatorIndex
|
||||
}
|
||||
|
||||
func output(ctx context.Context, data *dataOut) (string, error) {
|
||||
if data == nil {
|
||||
return "", errors.New("no data")
|
||||
}
|
||||
|
||||
if data.quiet {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if data.validators == nil {
|
||||
return "No sync committee validators found", nil
|
||||
}
|
||||
|
||||
if data.json {
|
||||
bytes, err := json.Marshal(data.validators)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to marshal JSON")
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
validators := make([]string, len(data.validators))
|
||||
for i := range data.validators {
|
||||
validators[i] = fmt.Sprintf("%d", data.validators[i])
|
||||
}
|
||||
|
||||
return strings.Join(validators, ","), nil
|
||||
}
|
||||
68
cmd/synccommittee/members/output_internal_test.go
Normal file
68
cmd/synccommittee/members/output_internal_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dataOut *dataOut
|
||||
res string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
dataOut: &dataOut{},
|
||||
res: "No sync committee validators found",
|
||||
},
|
||||
{
|
||||
name: "Present",
|
||||
dataOut: &dataOut{
|
||||
validators: []phase0.ValidatorIndex{1, 2, 3},
|
||||
},
|
||||
res: "1,2,3",
|
||||
},
|
||||
{
|
||||
name: "JSON",
|
||||
dataOut: &dataOut{
|
||||
json: true,
|
||||
validators: []phase0.ValidatorIndex{1, 2, 3},
|
||||
},
|
||||
res: "[1,2,3]",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := output(context.Background(), test.dataOut)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.res, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/synccommittee/members/process.go
Normal file
50
cmd/synccommittee/members/process.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func process(ctx context.Context, data *dataIn) (*dataOut, error) {
|
||||
if data == nil {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
|
||||
if data.epoch < data.chainTime.AltairInitialEpoch() {
|
||||
return nil, errors.New("not an Altair epoch")
|
||||
}
|
||||
|
||||
syncCommittee, err := data.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommittee(ctx, fmt.Sprintf("%d", data.chainTime.FirstSlotOfEpoch(data.epoch)))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain sync committee information")
|
||||
}
|
||||
|
||||
if syncCommittee == nil {
|
||||
return nil, errors.New("no sync committee returned")
|
||||
}
|
||||
|
||||
results := &dataOut{
|
||||
debug: data.debug,
|
||||
quiet: data.quiet,
|
||||
verbose: data.verbose,
|
||||
validators: syncCommittee.Validators,
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
74
cmd/synccommittee/members/process_internal_test.go
Normal file
74
cmd/synccommittee/members/process_internal_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/auto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
)
|
||||
|
||||
func TestProcess(t *testing.T) {
|
||||
if os.Getenv("ETHDO_TEST_CONNECTION") == "" {
|
||||
t.Skip("ETHDO_TEST_CONNECTION not configured; cannot run tests")
|
||||
}
|
||||
eth2Client, err := auto.New(context.Background(),
|
||||
auto.WithLogLevel(zerolog.Disabled),
|
||||
auto.WithAddress(os.Getenv("ETHDO_TEST_CONNECTION")),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
chainTime, err := standardchaintime.New(context.Background(),
|
||||
standardchaintime.WithGenesisTimeProvider(eth2Client.(eth2client.GenesisTimeProvider)),
|
||||
standardchaintime.WithForkScheduleProvider(eth2Client.(eth2client.ForkScheduleProvider)),
|
||||
standardchaintime.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dataIn *dataIn
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
err: "no data",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
dataIn: &dataIn{
|
||||
eth2Client: eth2Client,
|
||||
chainTime: chainTime,
|
||||
epoch: 61650,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := process(context.Background(), test.dataIn)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
cmd/synccommittee/members/run.go
Normal file
50
cmd/synccommittee/members/run.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Run runs the wallet create data command.
|
||||
func Run(cmd *cobra.Command) (string, error) {
|
||||
ctx := context.Background()
|
||||
dataIn, err := input(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain input")
|
||||
}
|
||||
|
||||
// Further errors do not need a usage report.
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
dataOut, err := process(ctx, dataIn)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to process")
|
||||
}
|
||||
|
||||
if viper.GetBool("quiet") {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
results, err := output(ctx, dataOut)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain output")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
57
cmd/synccommitteemembers.go
Normal file
57
cmd/synccommitteemembers.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
synccommitteemembers "github.com/wealdtech/ethdo/cmd/synccommittee/members"
|
||||
)
|
||||
|
||||
var synccommitteeMembersCmd = &cobra.Command{
|
||||
Use: "members",
|
||||
Short: "Obtain information about members of a synccommittee",
|
||||
Long: `Obtain information about members of a synccommittee. For example:
|
||||
|
||||
ethdo synccommittee members --epoch=12345
|
||||
|
||||
In quiet mode this will return 0 if the synccommittee members are found, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
res, err := synccommitteemembers.Run(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if viper.GetBool("quiet") {
|
||||
return nil
|
||||
}
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
synccommitteeCmd.AddCommand(synccommitteeMembersCmd)
|
||||
synccommitteeFlags(synccommitteeMembersCmd)
|
||||
synccommitteeMembersCmd.Flags().Int64("epoch", -1, "the epoch for which to fetch sync committees")
|
||||
}
|
||||
|
||||
func synccommitteeMembersBindings() {
|
||||
if err := viper.BindPFlag("epoch", synccommitteeMembersCmd.Flags().Lookup("epoch")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,7 @@ func validatorDepositDataOutputLaunchpad(datum *dataOut) (string, error) {
|
||||
forkVersionMap := map[spec.Version]string{
|
||||
[4]byte{0x00, 0x00, 0x00, 0x00}: "mainnet",
|
||||
[4]byte{0x00, 0x00, 0x20, 0x09}: "pyrmont",
|
||||
[4]byte{0x00, 0x00, 0x10, 0x20}: "prater",
|
||||
}
|
||||
|
||||
if datum.validatorPubKey == nil {
|
||||
|
||||
@@ -253,10 +253,15 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
tmp := testutil.HexToSignature("0xb7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2")
|
||||
signature = &tmp
|
||||
}
|
||||
var forkVersion *spec.Version
|
||||
var forkVersionPyrmont *spec.Version
|
||||
{
|
||||
tmp := testutil.HexToVersion("0x00002009")
|
||||
forkVersion = &tmp
|
||||
forkVersionPyrmont = &tmp
|
||||
}
|
||||
var forkVersionPrater *spec.Version
|
||||
{
|
||||
tmp := testutil.HexToVersion("0x00001020")
|
||||
forkVersionPrater = &tmp
|
||||
}
|
||||
var depositDataRoot *spec.Root
|
||||
{
|
||||
@@ -316,7 +321,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
@@ -332,7 +337,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
validatorPubKey: validatorPubKey,
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
@@ -348,7 +353,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
validatorPubKey: validatorPubKey,
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
@@ -364,7 +369,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
validatorPubKey: validatorPubKey,
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
@@ -381,7 +386,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
@@ -397,14 +402,14 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
},
|
||||
},
|
||||
err: "deposit message root required",
|
||||
},
|
||||
{
|
||||
name: "Single",
|
||||
name: "SinglePyrmont",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
@@ -413,13 +418,30 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00002009","eth2_network_name":"pyrmont","deposit_cli_version":"1.1.0"}]`,
|
||||
},
|
||||
{
|
||||
name: "SinglePrater",
|
||||
dataOut: []*dataOut{
|
||||
{
|
||||
format: "launchpad",
|
||||
account: "interop/00000",
|
||||
validatorPubKey: validatorPubKey,
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersionPrater,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
},
|
||||
res: `[{"pubkey":"a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c","withdrawal_credentials":"00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b","amount":32000000000,"signature":"b7a757a4c506ac6ac5f2d23e065de7d00dc9f5a6a3f9610a8b60b65f166379139ae382c91ecbbf5c9fabc34b1cd2cf8f0211488d50d8754716d8e72e17c1a00b5d9b37cc73767946790ebe66cf9669abfc5c25c67e1e2d1c2e11429d149c25a2","deposit_message_root":"139b510ea7f2788ab82da1f427d6cbe1db147c15a053db738ad5500cd83754a6","deposit_data_root":"9e51b386f4271c18149dd0f73297a26a4a8c15c3622c44af79c92446f44a3554","fork_version":"00001020","eth2_network_name":"prater","deposit_cli_version":"1.1.0"}]`,
|
||||
},
|
||||
{
|
||||
name: "Double",
|
||||
dataOut: []*dataOut{
|
||||
@@ -430,7 +452,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
|
||||
amount: 32000000000,
|
||||
signature: signature,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot,
|
||||
depositMessageRoot: depositMessageRoot,
|
||||
},
|
||||
@@ -441,7 +463,7 @@ func TestOutputLaunchpad(t *testing.T) {
|
||||
withdrawalCredentials: testutil.HexToBytes("0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594"),
|
||||
amount: 32000000000,
|
||||
signature: signature2,
|
||||
forkVersion: forkVersion,
|
||||
forkVersion: forkVersionPyrmont,
|
||||
depositDataRoot: depositDataRoot2,
|
||||
depositMessageRoot: depositMessageRoot2,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -54,7 +54,7 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
)
|
||||
errCheck(err, "Failed to connect to Ethereum 2 beacon node")
|
||||
|
||||
account, err := validatorInfoAccount()
|
||||
account, err := validatorInfoAccount(ctx, eth2Client)
|
||||
errCheck(err, "Failed to obtain validator account")
|
||||
|
||||
pubKeys := make([]spec.BLSPubKey, 1)
|
||||
@@ -99,6 +99,12 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
fmt.Printf("Public key: %#x\n", validator.Validator.PublicKey)
|
||||
}
|
||||
fmt.Printf("Status: %v\n", validator.Status)
|
||||
switch validator.Status {
|
||||
case api.ValidatorStateActiveExiting, api.ValidatorStateActiveSlashed:
|
||||
fmt.Printf("Exit epoch: %d\n", validator.Validator.ExitEpoch)
|
||||
case api.ValidatorStateExitedUnslashed, api.ValidatorStateExitedSlashed:
|
||||
fmt.Printf("Withdrawable epoch: %d\n", validator.Validator.WithdrawableEpoch)
|
||||
}
|
||||
fmt.Printf("Balance: %s\n", string2eth.GWeiToString(uint64(validator.Balance), true))
|
||||
if validator.Status.IsActive() {
|
||||
fmt.Printf("Effective balance: %s\n", string2eth.GWeiToString(uint64(validator.Validator.EffectiveBalance), true))
|
||||
@@ -107,31 +113,12 @@ In quiet mode this will return 0 if the validator information can be obtained, o
|
||||
fmt.Printf("Withdrawal credentials: %#x\n", validator.Validator.WithdrawalCredentials)
|
||||
}
|
||||
|
||||
// transition := time.Unix(int64(validatorInfo.TransitionTimestamp), 0)
|
||||
// transitionPassed := int64(validatorInfo.TransitionTimestamp) <= time.Now().Unix()
|
||||
// switch validatorInfo.Status {
|
||||
// case ethpb.ValidatorStatus_DEPOSITED:
|
||||
// if validatorInfo.TransitionTimestamp != 0 {
|
||||
// fmt.Printf("Inclusion in chain: %s\n", transition)
|
||||
// }
|
||||
// case ethpb.ValidatorStatus_PENDING:
|
||||
// fmt.Printf("Activation: %s\n", transition)
|
||||
// case ethpb.ValidatorStatus_EXITING, ethpb.ValidatorStatus_SLASHING:
|
||||
// fmt.Printf("Attesting finishes: %s\n", transition)
|
||||
// case ethpb.ValidatorStatus_EXITED:
|
||||
// if transitionPassed {
|
||||
// fmt.Printf("Funds withdrawable: Now\n")
|
||||
// } else {
|
||||
// fmt.Printf("Funds withdrawable: %s\n", transition)
|
||||
// }
|
||||
// }
|
||||
|
||||
os.Exit(_exitSuccess)
|
||||
},
|
||||
}
|
||||
|
||||
// validatorInfoAccount obtains the account for the validator info command.
|
||||
func validatorInfoAccount() (e2wtypes.Account, error) {
|
||||
func validatorInfoAccount(ctx context.Context, eth2Client eth2client.Service) (e2wtypes.Account, error) {
|
||||
var account e2wtypes.Account
|
||||
var err error
|
||||
switch {
|
||||
@@ -151,6 +138,26 @@ func validatorInfoAccount() (e2wtypes.Account, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
|
||||
}
|
||||
case viper.GetInt64("index") != -1:
|
||||
validatorsProvider, isValidatorsProvider := eth2Client.(eth2client.ValidatorsProvider)
|
||||
if !isValidatorsProvider {
|
||||
return nil, errors.New("client does not provide validator information")
|
||||
}
|
||||
validators, err := validatorsProvider.Validators(ctx, "head", []spec.ValidatorIndex{
|
||||
spec.ValidatorIndex(viper.GetInt64("index")),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain validator information.")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return nil, errors.New("unknown validator index")
|
||||
}
|
||||
pubKeyBytes := make([]byte, 48)
|
||||
copy(pubKeyBytes, validators[0].Validator.PublicKey[:])
|
||||
account, err = util.NewScratchAccount(nil, pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("invalid public key %s", viper.GetString("pubkey")))
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("neither account nor public key supplied")
|
||||
}
|
||||
@@ -212,6 +219,7 @@ func graphData(network string, validatorPubKey []byte) (uint64, spec.Gwei, error
|
||||
func init() {
|
||||
validatorCmd.AddCommand(validatorInfoCmd)
|
||||
validatorInfoCmd.Flags().String("pubkey", "", "Public key for which to obtain status")
|
||||
validatorInfoCmd.Flags().Int64("index", -1, "Index for which to obtain status")
|
||||
validatorFlags(validatorInfoCmd)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2019 - 2021 Weald Technology Trading
|
||||
// Copyright © 2019 - 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// ReleaseVersion is the release version of the codebase.
|
||||
// Usually overridden by tag names when building binaries.
|
||||
var ReleaseVersion = "local build (latest release 1.11.0)"
|
||||
var ReleaseVersion = "local build (latest release 1.14.0)"
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
|
||||
@@ -26,7 +26,7 @@ var walletImportCmd = &cobra.Command{
|
||||
Short: "Import a wallet",
|
||||
Long: `Import a wallet. For example:
|
||||
|
||||
ethdo wallet import --importdata=primary --importpassphrase="my export secret"
|
||||
ethdo wallet import --data=primary --passphrase="my export secret"
|
||||
|
||||
In quiet mode this will return 0 if the wallet is imported successfully, otherwise 1.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -37,7 +37,7 @@ ethdo wallet create --wallet="Recreated wallet" --type=hd --wallet-passphrase="s
|
||||
A wallet can be backed up with the `ethdo wallet export` command. This creates an encrypted backup of the wallet, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet export --wallet="My wallet" --exportpassphrase="export secret" >export.dat
|
||||
ethdo wallet export --wallet="My wallet" --passphrase="export secret" >export.dat
|
||||
```
|
||||
|
||||
Note that by default the wallet backup is printed to the console, hence the `>export.dat` to redirect it to a file.
|
||||
@@ -47,7 +47,7 @@ Note that by default the wallet backup is printed to the console, hence the `>ex
|
||||
A backed up wallet can be restored with the `ethdo wallet import` command, for example:
|
||||
|
||||
```sh
|
||||
ethdo wallet import --importdata=export.dat --importpassphrase="export secret"
|
||||
ethdo wallet import --data=export.dat --passphrase="export secret"
|
||||
```
|
||||
|
||||
In this example the wallet to be imported is being read from the `export.dat` file.
|
||||
|
||||
58
go.mod
58
go.mod
@@ -3,59 +3,43 @@ module github.com/wealdtech/ethdo
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.8 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.5 // indirect
|
||||
github.com/attestantio/dirk v1.0.2
|
||||
github.com/attestantio/go-eth2-client v0.7.0
|
||||
github.com/aws/aws-sdk-go v1.38.68 // indirect
|
||||
github.com/ferranbt/fastssz v0.0.0-20210526181520-7df50c8568f8
|
||||
github.com/attestantio/dirk v1.0.4
|
||||
github.com/attestantio/go-eth2-client v0.8.0
|
||||
github.com/ferranbt/fastssz v0.0.0-20210905181407-59cf6761a7d5
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-3 // indirect
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20210520070601-31246bfa8ac4
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/herumi/bls-eth-go-binary v0.0.0-20210902234237-7763804ee078
|
||||
github.com/minio/highwayhash v1.0.2 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/common v0.30.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.1 // indirect
|
||||
github.com/protolambda/zssz v0.1.5 // indirect
|
||||
github.com/prysmaticlabs/ethereumapis v0.0.0-20210201130911-92b2a467c108 // indirect
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20210706153858-5cb5ce8bdbfe
|
||||
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
|
||||
github.com/prysmaticlabs/go-ssz v0.0.0-20210121151755-f6208871c388
|
||||
github.com/rs/zerolog v1.23.0
|
||||
github.com/spf13/cast v1.4.0 // indirect
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tyler-smith/go-bip39 v1.1.0
|
||||
github.com/wealdtech/go-bytesutil v1.1.1
|
||||
github.com/wealdtech/go-ecodec v1.1.1
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.5
|
||||
github.com/wealdtech/go-eth2-util v1.6.4
|
||||
github.com/wealdtech/go-eth2-wallet v1.14.4
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.1.6
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.3
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.5
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.5.4
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.3
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.14
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.4
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.2
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.4
|
||||
github.com/wealdtech/go-ecodec v1.1.2
|
||||
github.com/wealdtech/go-eth2-types/v2 v2.5.6
|
||||
github.com/wealdtech/go-eth2-util v1.6.5
|
||||
github.com/wealdtech/go-eth2-wallet v1.14.6
|
||||
github.com/wealdtech/go-eth2-wallet-dirk v1.1.8
|
||||
github.com/wealdtech/go-eth2-wallet-distributed v1.1.4
|
||||
github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4 v1.1.6
|
||||
github.com/wealdtech/go-eth2-wallet-hd/v2 v2.5.5
|
||||
github.com/wealdtech/go-eth2-wallet-nd/v2 v2.3.4
|
||||
github.com/wealdtech/go-eth2-wallet-store-filesystem v1.16.15
|
||||
github.com/wealdtech/go-eth2-wallet-store-s3 v1.9.5
|
||||
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.3
|
||||
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.5
|
||||
github.com/wealdtech/go-string2eth v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||
golang.org/x/text v0.3.6
|
||||
google.golang.org/genproto v0.0.0-20210803142424-70bd63adacf2 // indirect
|
||||
google.golang.org/grpc v1.39.0
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
golang.org/x/text v0.3.7
|
||||
google.golang.org/grpc v1.40.0
|
||||
)
|
||||
|
||||
57
services/chaintime/service.go
Normal file
57
services/chaintime/service.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package chaintime
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
)
|
||||
|
||||
// Service provides a number of functions for calculating chain-related times.
|
||||
type Service interface {
|
||||
// GenesisTime provides the time of the chain's genesis.
|
||||
GenesisTime() time.Time
|
||||
// SlotsPerEpoch provides the number of slots in the chain's epoch.
|
||||
SlotsPerEpoch() uint64
|
||||
// SlotDuration provides the duration of the chain's slot.
|
||||
SlotDuration() time.Duration
|
||||
|
||||
// StartOfSlot provides the time at which a given slot starts.
|
||||
StartOfSlot(slot phase0.Slot) time.Time
|
||||
// StartOfEpoch provides the time at which a given epoch starts.
|
||||
StartOfEpoch(epoch phase0.Epoch) time.Time
|
||||
// CurrentSlot provides the current slot.
|
||||
CurrentSlot() phase0.Slot
|
||||
// CurrentEpoch provides the current epoch.
|
||||
CurrentEpoch() phase0.Epoch
|
||||
// CurrentSyncCommitteePeriod provides the current sync committee period.
|
||||
CurrentSyncCommitteePeriod() uint64
|
||||
// SlotToEpoch provides the epoch of the given slot.
|
||||
SlotToEpoch(slot phase0.Slot) phase0.Epoch
|
||||
// SlotToSyncCommitteePeriod provides the sync committee period of the given slot.
|
||||
SlotToSyncCommitteePeriod(slot phase0.Slot) uint64
|
||||
// FirstSlotOfEpoch provides the first slot of the given epoch.
|
||||
FirstSlotOfEpoch(epoch phase0.Epoch) phase0.Slot
|
||||
// TimestampToSlot provides the slot of the given timestamp.
|
||||
TimestampToSlot(timestamp time.Time) phase0.Slot
|
||||
// TimestampToEpoch provides the epoch of the given timestamp.
|
||||
TimestampToEpoch(timestamp time.Time) phase0.Epoch
|
||||
// FirstEpochOfSyncPeriod provides the first epoch of the given sync period.
|
||||
FirstEpochOfSyncPeriod(period uint64) phase0.Epoch
|
||||
// AltairInitialEpoch provides the epoch at which the Altair hard fork takes place.
|
||||
AltairInitialEpoch() phase0.Epoch
|
||||
// AltairInitialSyncCommitteePeriod provides the sync committee period in which the Altair hard fork takes place.
|
||||
AltairInitialSyncCommitteePeriod() uint64
|
||||
}
|
||||
90
services/chaintime/standard/parameters.go
Normal file
90
services/chaintime/standard/parameters.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard
|
||||
|
||||
import (
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type parameters struct {
|
||||
logLevel zerolog.Level
|
||||
genesisTimeProvider eth2client.GenesisTimeProvider
|
||||
specProvider eth2client.SpecProvider
|
||||
forkScheduleProvider eth2client.ForkScheduleProvider
|
||||
}
|
||||
|
||||
// Parameter is the interface for service parameters.
|
||||
type Parameter interface {
|
||||
apply(*parameters)
|
||||
}
|
||||
|
||||
type parameterFunc func(*parameters)
|
||||
|
||||
func (f parameterFunc) apply(p *parameters) {
|
||||
f(p)
|
||||
}
|
||||
|
||||
// WithLogLevel sets the log level for the module.
|
||||
func WithLogLevel(logLevel zerolog.Level) Parameter {
|
||||
return parameterFunc(func(p *parameters) {
|
||||
p.logLevel = logLevel
|
||||
})
|
||||
}
|
||||
|
||||
// WithGenesisTimeProvider sets the genesis time provider.
|
||||
func WithGenesisTimeProvider(provider eth2client.GenesisTimeProvider) Parameter {
|
||||
return parameterFunc(func(p *parameters) {
|
||||
p.genesisTimeProvider = provider
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpecProvider sets the spec provider.
|
||||
func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
|
||||
return parameterFunc(func(p *parameters) {
|
||||
p.specProvider = provider
|
||||
})
|
||||
}
|
||||
|
||||
// WithForkScheduleProvider sets the fork schedule provider.
|
||||
func WithForkScheduleProvider(provider eth2client.ForkScheduleProvider) Parameter {
|
||||
return parameterFunc(func(p *parameters) {
|
||||
p.forkScheduleProvider = provider
|
||||
})
|
||||
}
|
||||
|
||||
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
|
||||
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
|
||||
parameters := parameters{
|
||||
logLevel: zerolog.GlobalLevel(),
|
||||
}
|
||||
for _, p := range params {
|
||||
if params != nil {
|
||||
p.apply(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
if parameters.specProvider == nil {
|
||||
return nil, errors.New("no spec provider specified")
|
||||
}
|
||||
if parameters.genesisTimeProvider == nil {
|
||||
return nil, errors.New("no genesis time provider specified")
|
||||
}
|
||||
if parameters.forkScheduleProvider == nil {
|
||||
return nil, errors.New("no fork schedule provider specified")
|
||||
}
|
||||
|
||||
return ¶meters, nil
|
||||
}
|
||||
215
services/chaintime/standard/service.go
Normal file
215
services/chaintime/standard/service.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
zerologger "github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Service provides chain time services.
|
||||
type Service struct {
|
||||
genesisTime time.Time
|
||||
slotDuration time.Duration
|
||||
slotsPerEpoch uint64
|
||||
epochsPerSyncCommitteePeriod uint64
|
||||
altairForkEpoch phase0.Epoch
|
||||
}
|
||||
|
||||
// module-wide log.
|
||||
var log zerolog.Logger
|
||||
|
||||
// New creates a new controller.
|
||||
func New(ctx context.Context, params ...Parameter) (*Service, error) {
|
||||
parameters, err := parseAndCheckParameters(params...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "problem with parameters")
|
||||
}
|
||||
|
||||
// Set logging.
|
||||
log = zerologger.With().Str("service", "chaintime").Str("impl", "standard").Logger().Level(parameters.logLevel)
|
||||
|
||||
genesisTime, err := parameters.genesisTimeProvider.GenesisTime(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain genesis time")
|
||||
}
|
||||
log.Trace().Time("genesis_time", genesisTime).Msg("Obtained genesis time")
|
||||
|
||||
spec, err := parameters.specProvider.Spec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain spec")
|
||||
}
|
||||
|
||||
tmp, exists := spec["SECONDS_PER_SLOT"]
|
||||
if !exists {
|
||||
return nil, errors.New("SECONDS_PER_SLOT not found in spec")
|
||||
}
|
||||
slotDuration, ok := tmp.(time.Duration)
|
||||
if !ok {
|
||||
return nil, errors.New("SECONDS_PER_SLOT of unexpected type")
|
||||
}
|
||||
|
||||
tmp, exists = spec["SLOTS_PER_EPOCH"]
|
||||
if !exists {
|
||||
return nil, errors.New("SLOTS_PER_EPOCH not found in spec")
|
||||
}
|
||||
slotsPerEpoch, ok := tmp.(uint64)
|
||||
if !ok {
|
||||
return nil, errors.New("SLOTS_PER_EPOCH of unexpected type")
|
||||
}
|
||||
|
||||
var epochsPerSyncCommitteePeriod uint64
|
||||
if tmp, exists := spec["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; exists {
|
||||
tmp2, ok := tmp.(uint64)
|
||||
if !ok {
|
||||
return nil, errors.New("EPOCHS_PER_SYNC_COMMITTEE_PERIOD of unexpected type")
|
||||
}
|
||||
epochsPerSyncCommitteePeriod = tmp2
|
||||
}
|
||||
|
||||
altairForkEpoch, err := fetchAltairForkEpoch(ctx, parameters.forkScheduleProvider)
|
||||
if err != nil {
|
||||
// Set to far future epoch.
|
||||
altairForkEpoch = 0xffffffffffffffff
|
||||
}
|
||||
log.Trace().Uint64("epoch", uint64(altairForkEpoch)).Msg("Obtained Altair fork epoch")
|
||||
|
||||
s := &Service{
|
||||
genesisTime: genesisTime,
|
||||
slotDuration: slotDuration,
|
||||
slotsPerEpoch: slotsPerEpoch,
|
||||
epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod,
|
||||
altairForkEpoch: altairForkEpoch,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GenesisTime provides the time of the chain's genesis.
|
||||
func (s *Service) GenesisTime() time.Time {
|
||||
return s.genesisTime
|
||||
}
|
||||
|
||||
// SlotsPerEpoch provides the number of slots in the chain's epoch.
|
||||
func (s *Service) SlotsPerEpoch() uint64 {
|
||||
return s.slotsPerEpoch
|
||||
}
|
||||
|
||||
// SlotDuration provides the duration of the chain's slot.
|
||||
func (s *Service) SlotDuration() time.Duration {
|
||||
return s.slotDuration
|
||||
}
|
||||
|
||||
// StartOfSlot provides the time at which a given slot starts.
|
||||
func (s *Service) StartOfSlot(slot phase0.Slot) time.Time {
|
||||
return s.genesisTime.Add(time.Duration(slot) * s.slotDuration)
|
||||
}
|
||||
|
||||
// StartOfEpoch provides the time at which a given epoch starts.
|
||||
func (s *Service) StartOfEpoch(epoch phase0.Epoch) time.Time {
|
||||
return s.genesisTime.Add(time.Duration(uint64(epoch)*s.slotsPerEpoch) * s.slotDuration)
|
||||
}
|
||||
|
||||
// CurrentSlot provides the current slot.
|
||||
func (s *Service) CurrentSlot() phase0.Slot {
|
||||
if s.genesisTime.After(time.Now()) {
|
||||
return 0
|
||||
}
|
||||
return phase0.Slot(uint64(time.Since(s.genesisTime).Seconds()) / uint64(s.slotDuration.Seconds()))
|
||||
}
|
||||
|
||||
// CurrentEpoch provides the current epoch.
|
||||
func (s *Service) CurrentEpoch() phase0.Epoch {
|
||||
return phase0.Epoch(uint64(s.CurrentSlot()) / s.slotsPerEpoch)
|
||||
}
|
||||
|
||||
// CurrentSyncCommitteePeriod provides the current sync committee period.
|
||||
func (s *Service) CurrentSyncCommitteePeriod() uint64 {
|
||||
return uint64(s.CurrentEpoch()) / s.epochsPerSyncCommitteePeriod
|
||||
}
|
||||
|
||||
// SlotToEpoch provides the epoch of a given slot.
|
||||
func (s *Service) SlotToEpoch(slot phase0.Slot) phase0.Epoch {
|
||||
return phase0.Epoch(uint64(slot) / s.slotsPerEpoch)
|
||||
}
|
||||
|
||||
// SlotToSyncCommitteePeriod provides the sync committee period of the given slot.
|
||||
func (s *Service) SlotToSyncCommitteePeriod(slot phase0.Slot) uint64 {
|
||||
return uint64(s.SlotToEpoch(slot)) / s.epochsPerSyncCommitteePeriod
|
||||
}
|
||||
|
||||
// FirstSlotOfEpoch provides the first slot of the given epoch.
|
||||
func (s *Service) FirstSlotOfEpoch(epoch phase0.Epoch) phase0.Slot {
|
||||
return phase0.Slot(uint64(epoch) * s.slotsPerEpoch)
|
||||
}
|
||||
|
||||
// TimestampToSlot provides the slot of the given timestamp.
|
||||
func (s *Service) TimestampToSlot(timestamp time.Time) phase0.Slot {
|
||||
if timestamp.Before(s.genesisTime) {
|
||||
return 0
|
||||
}
|
||||
secondsSinceGenesis := uint64(timestamp.Sub(s.genesisTime).Seconds())
|
||||
return phase0.Slot(secondsSinceGenesis / uint64(s.slotDuration.Seconds()))
|
||||
}
|
||||
|
||||
// TimestampToEpoch provides the epoch of the given timestamp.
|
||||
func (s *Service) TimestampToEpoch(timestamp time.Time) phase0.Epoch {
|
||||
if timestamp.Before(s.genesisTime) {
|
||||
return 0
|
||||
}
|
||||
secondsSinceGenesis := uint64(timestamp.Sub(s.genesisTime).Seconds())
|
||||
return phase0.Epoch(secondsSinceGenesis / uint64(s.slotDuration.Seconds()) / s.slotsPerEpoch)
|
||||
}
|
||||
|
||||
// FirstEpochOfSyncPeriod provides the first epoch of the given sync period.
|
||||
// Note that epochs before the sync committee period will provide the Altair hard fork epoch.
|
||||
func (s *Service) FirstEpochOfSyncPeriod(period uint64) phase0.Epoch {
|
||||
epoch := phase0.Epoch(period * s.epochsPerSyncCommitteePeriod)
|
||||
if epoch < s.altairForkEpoch {
|
||||
epoch = s.altairForkEpoch
|
||||
}
|
||||
return epoch
|
||||
}
|
||||
|
||||
// AltairInitialEpoch provides the epoch at which the Altair hard fork takes place.
|
||||
func (s *Service) AltairInitialEpoch() phase0.Epoch {
|
||||
return s.altairForkEpoch
|
||||
}
|
||||
|
||||
// AltairInitialSyncCommitteePeriod provides the sync committee period in which the Altair hard fork takes place.
|
||||
func (s *Service) AltairInitialSyncCommitteePeriod() uint64 {
|
||||
return uint64(s.altairForkEpoch) / s.epochsPerSyncCommitteePeriod
|
||||
}
|
||||
|
||||
func fetchAltairForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
|
||||
forkSchedule, err := provider.ForkSchedule(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for i := range forkSchedule {
|
||||
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
|
||||
// This is the genesis fork; ignore it.
|
||||
continue
|
||||
}
|
||||
return forkSchedule[i].Epoch, nil
|
||||
}
|
||||
return 0, errors.New("no altair fork obtained")
|
||||
}
|
||||
258
services/chaintime/standard/service_test.go
Normal file
258
services/chaintime/standard/service_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wealdtech/ethdo/services/chaintime"
|
||||
"github.com/wealdtech/ethdo/services/chaintime/standard"
|
||||
"github.com/wealdtech/ethdo/testing/mock"
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
slotDuration := 12 * time.Second
|
||||
slotsPerEpoch := uint64(32)
|
||||
epochsPerSyncCommitteePeriod := uint64(256)
|
||||
forkSchedule := []*phase0.Fork{
|
||||
{
|
||||
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
CurrentVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
Epoch: 0,
|
||||
},
|
||||
{
|
||||
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
CurrentVersion: phase0.Version{0x05, 0x06, 0x07, 0x08},
|
||||
Epoch: 10,
|
||||
},
|
||||
}
|
||||
|
||||
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
|
||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
|
||||
mockForkScheduleProvider := mock.NewForkScheduleProvider(forkSchedule)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
params []standard.Parameter
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "GenesisTimeProviderMissing",
|
||||
params: []standard.Parameter{
|
||||
standard.WithLogLevel(zerolog.Disabled),
|
||||
standard.WithSpecProvider(mockSpecProvider),
|
||||
standard.WithForkScheduleProvider(mockForkScheduleProvider),
|
||||
},
|
||||
err: "problem with parameters: no genesis time provider specified",
|
||||
},
|
||||
{
|
||||
name: "SpecProviderMissing",
|
||||
params: []standard.Parameter{
|
||||
standard.WithLogLevel(zerolog.Disabled),
|
||||
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
|
||||
standard.WithForkScheduleProvider(mockForkScheduleProvider),
|
||||
},
|
||||
err: "problem with parameters: no spec provider specified",
|
||||
},
|
||||
{
|
||||
name: "ForkScheduleProviderMissing",
|
||||
params: []standard.Parameter{
|
||||
standard.WithLogLevel(zerolog.Disabled),
|
||||
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
|
||||
standard.WithSpecProvider(mockSpecProvider),
|
||||
},
|
||||
err: "problem with parameters: no fork schedule provider specified",
|
||||
},
|
||||
{
|
||||
name: "Good",
|
||||
params: []standard.Parameter{
|
||||
standard.WithLogLevel(zerolog.Disabled),
|
||||
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
|
||||
standard.WithSpecProvider(mockSpecProvider),
|
||||
standard.WithForkScheduleProvider(mockForkScheduleProvider),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := standard.New(context.Background(), test.params...)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// createService is a helper that creates a mock chaintime service.
|
||||
func createService(genesisTime time.Time) (chaintime.Service, time.Duration, uint64, uint64, []*phase0.Fork, error) {
|
||||
slotDuration := 12 * time.Second
|
||||
slotsPerEpoch := uint64(32)
|
||||
epochsPerSyncCommitteePeriod := uint64(256)
|
||||
forkSchedule := []*phase0.Fork{
|
||||
{
|
||||
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
CurrentVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
Epoch: 0,
|
||||
},
|
||||
{
|
||||
PreviousVersion: phase0.Version{0x01, 0x02, 0x03, 0x04},
|
||||
CurrentVersion: phase0.Version{0x05, 0x06, 0x07, 0x08},
|
||||
Epoch: 10,
|
||||
},
|
||||
}
|
||||
|
||||
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
|
||||
mockSpecProvider := mock.NewSpecProvider(slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod)
|
||||
mockForkScheduleProvider := mock.NewForkScheduleProvider(forkSchedule)
|
||||
s, err := standard.New(context.Background(),
|
||||
standard.WithGenesisTimeProvider(mockGenesisTimeProvider),
|
||||
standard.WithSpecProvider(mockSpecProvider),
|
||||
standard.WithForkScheduleProvider(mockForkScheduleProvider),
|
||||
)
|
||||
return s, slotDuration, slotsPerEpoch, epochsPerSyncCommitteePeriod, forkSchedule, err
|
||||
}
|
||||
|
||||
func TestGenesisTime(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
s, _, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, genesisTime, s.GenesisTime())
|
||||
}
|
||||
|
||||
func TestStartOfSlot(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
s, slotDuration, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, genesisTime, s.StartOfSlot(0))
|
||||
require.Equal(t, genesisTime.Add(1000*slotDuration), s.StartOfSlot(1000))
|
||||
}
|
||||
|
||||
func TestStartOfEpoch(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
s, slotDuration, slotsPerEpoch, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, genesisTime, s.StartOfEpoch(0))
|
||||
require.Equal(t, genesisTime.Add(time.Duration(1000*slotsPerEpoch)*slotDuration), s.StartOfEpoch(1000))
|
||||
}
|
||||
|
||||
func TestCurrentSlot(t *testing.T) {
|
||||
genesisTime := time.Now().Add(-60 * time.Second)
|
||||
s, _, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, phase0.Slot(5), s.CurrentSlot())
|
||||
}
|
||||
|
||||
func TestCurrentEpoch(t *testing.T) {
|
||||
genesisTime := time.Now().Add(-1000 * time.Second)
|
||||
s, _, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, phase0.Epoch(2), s.CurrentEpoch())
|
||||
}
|
||||
|
||||
func TestTimestampToSlot(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
s, _, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
timestamp time.Time
|
||||
slot phase0.Slot
|
||||
}{
|
||||
{
|
||||
name: "PreGenesis",
|
||||
timestamp: genesisTime.AddDate(0, 0, -1),
|
||||
slot: 0,
|
||||
},
|
||||
{
|
||||
name: "Genesis",
|
||||
timestamp: genesisTime,
|
||||
slot: 0,
|
||||
},
|
||||
{
|
||||
name: "Slot1",
|
||||
timestamp: genesisTime.Add(12 * time.Second),
|
||||
slot: 1,
|
||||
},
|
||||
{
|
||||
name: "Slot999",
|
||||
timestamp: genesisTime.Add(999 * 12 * time.Second),
|
||||
slot: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, test.slot, s.TimestampToSlot(test.timestamp))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampToEpoch(t *testing.T) {
|
||||
genesisTime := time.Now()
|
||||
s, _, _, _, _, err := createService(genesisTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
timestamp time.Time
|
||||
epoch phase0.Epoch
|
||||
}{
|
||||
{
|
||||
name: "PreGenesis",
|
||||
timestamp: genesisTime.AddDate(0, 0, -1),
|
||||
epoch: 0,
|
||||
},
|
||||
{
|
||||
name: "Genesis",
|
||||
timestamp: genesisTime,
|
||||
epoch: 0,
|
||||
},
|
||||
{
|
||||
name: "Epoch1",
|
||||
timestamp: genesisTime.Add(32 * 12 * time.Second),
|
||||
epoch: 1,
|
||||
},
|
||||
{
|
||||
name: "Epoch1Boundary",
|
||||
timestamp: genesisTime.Add(64 * 12 * time.Second).Add(-1 * time.Millisecond),
|
||||
epoch: 1,
|
||||
},
|
||||
{
|
||||
name: "Epoch999",
|
||||
timestamp: genesisTime.Add(999 * 32 * 12 * time.Second),
|
||||
epoch: 999,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.Equal(t, test.epoch, s.TimestampToEpoch(test.timestamp))
|
||||
})
|
||||
}
|
||||
}
|
||||
151
testing/mock/eth2client.go
Normal file
151
testing/mock/eth2client.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright © 2021 Weald Technology Trading.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
eth2client "github.com/attestantio/go-eth2-client"
|
||||
api "github.com/attestantio/go-eth2-client/api/v1"
|
||||
"github.com/attestantio/go-eth2-client/spec"
|
||||
"github.com/attestantio/go-eth2-client/spec/phase0"
|
||||
)
|
||||
|
||||
// GenesisTimeProvider is a mock for eth2client.GenesisTimeProvider.
|
||||
type GenesisTimeProvider struct {
|
||||
genesisTime time.Time
|
||||
}
|
||||
|
||||
// NewGenesisTimeProvider returns a mock genesis time provider with the provided value.
|
||||
func NewGenesisTimeProvider(genesisTime time.Time) eth2client.GenesisTimeProvider {
|
||||
return &GenesisTimeProvider{
|
||||
genesisTime: genesisTime,
|
||||
}
|
||||
}
|
||||
|
||||
// GenesisTime is a mock.
|
||||
func (m *GenesisTimeProvider) GenesisTime(ctx context.Context) (time.Time, error) {
|
||||
return m.genesisTime, nil
|
||||
}
|
||||
|
||||
// SpecProvider is a mock for eth2client.SpecProvider.
|
||||
type SpecProvider struct {
|
||||
spec map[string]interface{}
|
||||
}
|
||||
|
||||
// NewSpecProvider returns a mock spec provider with the provided values.
|
||||
func NewSpecProvider(slotDuration time.Duration,
|
||||
slotsPerEpoch uint64,
|
||||
epochsPerSyncCommitteePeriod uint64,
|
||||
) eth2client.SpecProvider {
|
||||
return &SpecProvider{
|
||||
spec: map[string]interface{}{
|
||||
"SECONDS_PER_SLOT": slotDuration,
|
||||
"SLOTS_PER_EPOCH": slotsPerEpoch,
|
||||
"EPOCHS_PER_SYNC_COMMITTEE_PERIOD": epochsPerSyncCommitteePeriod,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Spec is a mock.
|
||||
func (m *SpecProvider) Spec(ctx context.Context) (map[string]interface{}, error) {
|
||||
return m.spec, nil
|
||||
}
|
||||
|
||||
// ForkScheduleProvider is a mock for eth2client.ForkScheduleProvider.
|
||||
type ForkScheduleProvider struct {
|
||||
schedule []*phase0.Fork
|
||||
}
|
||||
|
||||
// NewForkScheduleProvider returns a mock spec provider with the provided values.
|
||||
func NewForkScheduleProvider(schedule []*phase0.Fork) eth2client.ForkScheduleProvider {
|
||||
return &ForkScheduleProvider{
|
||||
schedule: schedule,
|
||||
}
|
||||
}
|
||||
|
||||
// ForkSchedule is a mock.
|
||||
func (m *ForkScheduleProvider) ForkSchedule(ctx context.Context) ([]*phase0.Fork, error) {
|
||||
return m.schedule, nil
|
||||
}
|
||||
|
||||
// SlotsPerEpochProvider is a mock for eth2client.SlotsPerEpochProvider.
|
||||
type SlotsPerEpochProvider struct {
|
||||
slotsPerEpoch uint64
|
||||
}
|
||||
|
||||
// NewSlotsPerEpochProvider returns a mock slots per epoch provider with the provided value.
|
||||
func NewSlotsPerEpochProvider(slotsPerEpoch uint64) eth2client.SlotsPerEpochProvider {
|
||||
return &SlotsPerEpochProvider{
|
||||
slotsPerEpoch: slotsPerEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
// SlotsPerEpoch is a mock.
|
||||
func (m *SlotsPerEpochProvider) SlotsPerEpoch(ctx context.Context) (uint64, error) {
|
||||
return m.slotsPerEpoch, nil
|
||||
}
|
||||
|
||||
// AttestationsSubmitter is a mock for eth2client.AttestationsSubmitter.
|
||||
type AttestationsSubmitter struct{}
|
||||
|
||||
// NewAttestationSubmitter returns a mock attestations submitter with the provided value.
|
||||
func NewAttestationSubmitter() eth2client.AttestationsSubmitter {
|
||||
return &AttestationsSubmitter{}
|
||||
}
|
||||
|
||||
// SubmitAttestations is a mock.
|
||||
func (m *AttestationsSubmitter) SubmitAttestations(ctx context.Context, attestations []*phase0.Attestation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeaconBlockSubmitter is a mock for eth2client.BeaconBlockSubmitter.
|
||||
type BeaconBlockSubmitter struct{}
|
||||
|
||||
// NewBeaconBlockSubmitter returns a mock beacon block submitter with the provided value.
|
||||
func NewBeaconBlockSubmitter() eth2client.BeaconBlockSubmitter {
|
||||
return &BeaconBlockSubmitter{}
|
||||
}
|
||||
|
||||
// SubmitBeaconBlock is a mock.
|
||||
func (m *BeaconBlockSubmitter) SubmitBeaconBlock(ctx context.Context, bloc *spec.VersionedSignedBeaconBlock) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AggregateAttestationsSubmitter is a mock for eth2client.AggregateAttestationsSubmitter.
|
||||
type AggregateAttestationsSubmitter struct{}
|
||||
|
||||
// NewAggregateAttestationsSubmitter returns a mock aggregate attestation submitter with the provided value.
|
||||
func NewAggregateAttestationsSubmitter() eth2client.AggregateAttestationsSubmitter {
|
||||
return &AggregateAttestationsSubmitter{}
|
||||
}
|
||||
|
||||
// SubmitAggregateAttestations is a mock.
|
||||
func (m *AggregateAttestationsSubmitter) SubmitAggregateAttestations(ctx context.Context, aggregates []*phase0.SignedAggregateAndProof) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeaconCommitteeSubscriptionsSubmitter is a mock for eth2client.BeaconCommitteeSubscriptionsSubmitter.
|
||||
type BeaconCommitteeSubscriptionsSubmitter struct{}
|
||||
|
||||
// NewBeaconCommitteeSubscriptionsSubmitter returns a mock beacon committee subscription submitter with the provided value.
|
||||
func NewBeaconCommitteeSubscriptionsSubmitter() eth2client.BeaconCommitteeSubscriptionsSubmitter {
|
||||
return &BeaconCommitteeSubscriptionsSubmitter{}
|
||||
}
|
||||
|
||||
// SubmitBeaconCommitteeSubscriptions is a mock.
|
||||
func (m *BeaconCommitteeSubscriptionsSubmitter) SubmitBeaconCommitteeSubscriptions(ctx context.Context, subscriptions []*api.BeaconCommitteeSubscription) error {
|
||||
return nil
|
||||
}
|
||||
@@ -26,35 +26,32 @@ var networks = map[string]string{
|
||||
"00000000219ab540356cbb839cbe05303d7705fa": "Mainnet",
|
||||
"07b39f4fde4a38bace212b546dac87c58dfe3fdc": "Medalla",
|
||||
"8c5fecdc472e27bc447696f431e425d02dd46a8c": "Pyrmont",
|
||||
"ff50ed3d0ec03ac01d4c79aad74928bff48a7b2b": "Prater",
|
||||
}
|
||||
|
||||
// Network returns the name of the network., calculated from the deposit contract information.
|
||||
// If not known, returns "Unknown".
|
||||
func Network(ctx context.Context, eth2Client eth2client.Service) (string, error) {
|
||||
var address []byte
|
||||
var err error
|
||||
|
||||
if eth2Client == nil {
|
||||
return "", errors.New("no Ethereum 2 client supplied")
|
||||
}
|
||||
|
||||
if provider, isProvider := eth2Client.(eth2client.DepositContractProvider); isProvider {
|
||||
address, err = provider.DepositContractAddress(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain deposit contract address")
|
||||
}
|
||||
} else if provider, isProvider := eth2Client.(eth2client.SpecProvider); isProvider {
|
||||
config, err := provider.Spec(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain chain specification")
|
||||
}
|
||||
if config == nil {
|
||||
return "", errors.New("failed to return chain specification")
|
||||
}
|
||||
depositContractAddress, exists := config["DEPOSIT_CONTRACT_ADDRESS"]
|
||||
if exists {
|
||||
address = depositContractAddress.([]byte)
|
||||
}
|
||||
provider, isProvider := eth2Client.(eth2client.SpecProvider)
|
||||
if !isProvider {
|
||||
return "", errors.New("client does not provide deposit contract address")
|
||||
}
|
||||
config, err := provider.Spec(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to obtain chain specification")
|
||||
}
|
||||
if config == nil {
|
||||
return "", errors.New("failed to return chain specification")
|
||||
}
|
||||
depositContractAddress, exists := config["DEPOSIT_CONTRACT_ADDRESS"]
|
||||
if exists {
|
||||
address = depositContractAddress.([]byte)
|
||||
}
|
||||
|
||||
return network(address), nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2020 Weald Technology Trading
|
||||
// Copyright © 2020, 2021 Weald Technology Trading
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
@@ -23,38 +23,6 @@ import (
|
||||
"github.com/wealdtech/ethdo/util"
|
||||
)
|
||||
|
||||
// A mock Ethereum 2 client service that returns supplied deposit information.
|
||||
type depositETH2Client struct {
|
||||
address []byte
|
||||
chainID uint64
|
||||
networkID uint64
|
||||
}
|
||||
|
||||
// Name returns the name of the client implementation.
|
||||
func (c *depositETH2Client) Name() string {
|
||||
return "deposit mock"
|
||||
}
|
||||
|
||||
// Address returns the address of the client.
|
||||
func (c *depositETH2Client) Address() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
// DepositContractAddress provides the Ethereum 1 address of the deposit contract.
|
||||
func (c *depositETH2Client) DepositContractAddress(ctx context.Context) ([]byte, error) {
|
||||
return c.address, nil
|
||||
}
|
||||
|
||||
// DepositContractChainID provides the Ethereum 1 chain ID of the deposit contract.
|
||||
func (c *depositETH2Client) DepositContractChainID(ctx context.Context) (uint64, error) {
|
||||
return c.chainID, nil
|
||||
}
|
||||
|
||||
// DepositContractNetworkID provides the Ethereum 1 network ID of the deposit contract.
|
||||
func (c *depositETH2Client) DepositContractNetworkID(ctx context.Context) (uint64, error) {
|
||||
return c.networkID, nil
|
||||
}
|
||||
|
||||
// A mock Ethereum 2 client service that returns spec information.
|
||||
type specETH2Client struct {
|
||||
address []byte
|
||||
@@ -88,15 +56,6 @@ func TestNetworks(t *testing.T) {
|
||||
name: "Nil",
|
||||
err: "no Ethereum 2 client supplied",
|
||||
},
|
||||
{
|
||||
name: "MainnetDeposit",
|
||||
service: &depositETH2Client{
|
||||
address: testutil.HexToBytes("0x00000000219ab540356cbb839cbe05303d7705fa"),
|
||||
chainID: 0,
|
||||
networkID: 0,
|
||||
},
|
||||
network: "Mainnet",
|
||||
},
|
||||
{
|
||||
name: "MainnetSpec",
|
||||
service: &specETH2Client{
|
||||
@@ -104,15 +63,6 @@ func TestNetworks(t *testing.T) {
|
||||
},
|
||||
network: "Mainnet",
|
||||
},
|
||||
{
|
||||
name: "UnknownDeposit",
|
||||
service: &depositETH2Client{
|
||||
address: testutil.HexToBytes("0x1111111111111111111111111111111111111111"),
|
||||
chainID: 0,
|
||||
networkID: 0,
|
||||
},
|
||||
network: "Unknown",
|
||||
},
|
||||
{
|
||||
name: "UnknownSpec",
|
||||
service: &specETH2Client{
|
||||
|
||||
Reference in New Issue
Block a user