mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-10 14:37:57 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a98f681f98 | ||
|
|
e0e1f697d3 | ||
|
|
1b70a66120 | ||
|
|
94eba96a6e | ||
|
|
f052d8e307 | ||
|
|
df45686828 | ||
|
|
84d228877a | ||
|
|
b2b26742b0 | ||
|
|
9dc630c809 | ||
|
|
452430db56 |
2
.github/workflows/release.yml
vendored
2
.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: |
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
1.12.0:
|
||||
- add "synccommittee members"
|
||||
|
||||
1.11.0
|
||||
- add Altair information to "block info"
|
||||
- add more information to "chain info"
|
||||
|
||||
12
cmd/root.go
12
cmd/root.go
@@ -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,6 +57,9 @@ 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")
|
||||
@@ -82,6 +86,8 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
nodeEventsBindings()
|
||||
case "slot/time":
|
||||
slotTimeBindings()
|
||||
case "synccommittee/members":
|
||||
synccommitteeMembersBindings()
|
||||
case "validator/depositdata":
|
||||
validatorDepositdataBindings()
|
||||
case "validator/duties":
|
||||
@@ -109,11 +115,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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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.12.2)"
|
||||
|
||||
// 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.
|
||||
|
||||
59
go.mod
59
go.mod
@@ -3,59 +3,46 @@ 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.3
|
||||
github.com/attestantio/go-eth2-client v0.7.2
|
||||
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.7
|
||||
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
|
||||
)
|
||||
|
||||
replace github.com/fsnotify/fsnotify v1.5.0 => github.com/fsnotify/fsnotify v1.5.1
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user