mirror of
https://github.com/wealdtech/ethdo.git
synced 2026-01-08 05:34:01 -05:00
284 lines
9.1 KiB
Go
284 lines
9.1 KiB
Go
// 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
|
|
bellatrixForkEpoch phase0.Epoch
|
|
capellaForkEpoch 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")
|
|
|
|
bellatrixForkEpoch, err := fetchBellatrixForkEpoch(ctx, parameters.forkScheduleProvider)
|
|
if err != nil {
|
|
// Set to far future epoch.
|
|
bellatrixForkEpoch = 0xffffffffffffffff
|
|
}
|
|
log.Trace().Uint64("epoch", uint64(bellatrixForkEpoch)).Msg("Obtained Bellatrix fork epoch")
|
|
|
|
capellaForkEpoch, err := fetchCapellaForkEpoch(ctx, parameters.forkScheduleProvider)
|
|
if err != nil {
|
|
// Set to far future epoch.
|
|
capellaForkEpoch = 0xffffffffffffffff
|
|
}
|
|
log.Trace().Uint64("epoch", uint64(capellaForkEpoch)).Msg("Obtained Capella fork epoch")
|
|
|
|
s := &Service{
|
|
genesisTime: genesisTime,
|
|
slotDuration: slotDuration,
|
|
slotsPerEpoch: slotsPerEpoch,
|
|
epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod,
|
|
altairForkEpoch: altairForkEpoch,
|
|
bellatrixForkEpoch: bellatrixForkEpoch,
|
|
capellaForkEpoch: capellaForkEpoch,
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// BellatrixInitialEpoch provides the epoch at which the Bellatrix hard fork takes place.
|
|
func (s *Service) BellatrixInitialEpoch() phase0.Epoch {
|
|
return s.bellatrixForkEpoch
|
|
}
|
|
|
|
func fetchBellatrixForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
|
|
forkSchedule, err := provider.ForkSchedule(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
count := 0
|
|
for i := range forkSchedule {
|
|
count++
|
|
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
|
|
// This is the genesis fork; ignore it.
|
|
continue
|
|
}
|
|
if count == 1 {
|
|
return forkSchedule[i].Epoch, nil
|
|
}
|
|
count++
|
|
}
|
|
return 0, errors.New("no bellatrix fork obtained")
|
|
}
|
|
|
|
// CapellaInitialEpoch provides the epoch at which the Capella hard fork takes place.
|
|
func (s *Service) CapellaInitialEpoch() phase0.Epoch {
|
|
return s.capellaForkEpoch
|
|
}
|
|
|
|
func fetchCapellaForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) {
|
|
forkSchedule, err := provider.ForkSchedule(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
count := 0
|
|
for i := range forkSchedule {
|
|
count++
|
|
if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) {
|
|
// This is the genesis fork; ignore it.
|
|
continue
|
|
}
|
|
if count == 2 {
|
|
return forkSchedule[i].Epoch, nil
|
|
}
|
|
count++
|
|
}
|
|
return 0, errors.New("no capella fork obtained")
|
|
}
|