Tidy-ups.

This commit is contained in:
Jim McDonald
2021-09-15 22:50:49 +01:00
parent f052d8e307
commit 94eba96a6e
13 changed files with 809 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
Development:
1.12.0:
- add "synccommittee members"
1.11.0

View File

@@ -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")

View File

@@ -21,6 +21,8 @@ import (
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"
)
@@ -30,10 +32,9 @@ type dataIn struct {
quiet bool
verbose bool
debug bool
// Chain information.
slotsPerEpoch uint64
// Operation.
eth2Client eth2client.Service
chainTime chaintime.Service
epoch spec.Epoch
}
@@ -55,24 +56,23 @@ func input(ctx context.Context) (*dataIn, error) {
return nil, errors.Wrap(err, "failed to connect to Ethereum 2 beacon node")
}
// Chain configuration.
config, err := data.eth2Client.(eth2client.SpecProvider).Spec(ctx)
// 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 obtain beacon chain configuration")
return nil, errors.Wrap(err, "failed to configure chaintime service")
}
data.slotsPerEpoch = config["SLOTS_PER_EPOCH"].(uint64)
// Epoch
epoch := viper.GetInt64("epoch")
if epoch == -1 {
slotDuration := config["SECONDS_PER_SLOT"].(time.Duration)
genesis, err := data.eth2Client.(eth2client.GenesisProvider).Genesis(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain genesis data")
}
epoch = int64(time.Since(genesis.GenesisTime).Seconds()) / (int64(slotDuration.Seconds()) * int64(data.slotsPerEpoch))
data.epoch = data.chainTime.CurrentEpoch()
} else {
data.epoch = spec.Epoch(epoch)
}
data.epoch = spec.Epoch(epoch)
return data, nil
}

View File

@@ -57,5 +57,5 @@ func output(ctx context.Context, data *dataOut) (string, error) {
validators[i] = fmt.Sprintf("%d", data.validators[i])
}
return "[" + strings.Join(validators, ",") + "]", nil
return strings.Join(validators, ","), nil
}

View File

@@ -42,7 +42,7 @@ func TestOutput(t *testing.T) {
dataOut: &dataOut{
validators: []phase0.ValidatorIndex{1, 2, 3},
},
res: "[1,2,3]",
res: "1,2,3",
},
{
name: "JSON",

View File

@@ -26,7 +26,11 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) {
return nil, errors.New("no data")
}
syncCommittee, err := data.eth2Client.(eth2client.SyncCommitteesProvider).SyncCommittee(ctx, fmt.Sprintf("%d", uint64(data.epoch)*data.slotsPerEpoch))
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")
}

View File

@@ -18,9 +18,11 @@ import (
"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) {
@@ -33,6 +35,13 @@ func TestProcess(t *testing.T) {
)
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
@@ -45,9 +54,9 @@ func TestProcess(t *testing.T) {
{
name: "Good",
dataIn: &dataIn{
eth2Client: eth2Client,
slotsPerEpoch: 32,
epoch: 61650,
eth2Client: eth2Client,
chainTime: chainTime,
epoch: 61650,
},
},
}

View File

@@ -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.0)"
// versionCmd represents the version command
var versionCmd = &cobra.Command{

View 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
}

View 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(&parameters)
}
}
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 &parameters, nil
}

View 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")
}

View 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
View 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
}