nimbus-eth2/beacon_chain/spec/beacon_time.nim

284 lines
12 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
import
std/[hashes, typetraits],
chronicles,
chronos/timer,
json_serialization,
./presets
export hashes, timer, json_serialization, presets
# A collection of time units that permeate the spec - common to all of them is
# that they expressed relative to the genesis of the chain at varying
# granularities:
#
# * BeaconTime - nanoseconds since genesis
# * Slot - SLOTS_PER_SECOND seconds since genesis
# * Epoch - EPOCHS_PER_SLOT slots since genesis
# * SyncCommitteePeriod - EPOCHS_PER_SYNC_COMMITTEE_PERIOD epochs since genesis
type
BeaconTime* = object
## A point in time, relative to the genesis of the chain
##
## Implemented as nanoseconds since genesis - negative means before
## the chain started.
ns_since_genesis*: int64
TimeDiff* = object
nanoseconds*: int64
## Difference between two points in time with nanosecond granularity
## Can be negative (unlike timer.Duration)
const
# Earlier spec versions had these at a different slot
GENESIS_SLOT* = Slot(0)
GENESIS_EPOCH* = Epoch(0) # compute_epoch_at_slot(GENESIS_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/phase0/fork-choice.md#constant
INTERVALS_PER_SLOT* = 3
FAR_FUTURE_BEACON_TIME* = BeaconTime(ns_since_genesis: int64.high())
NANOSECONDS_PER_SLOT* = SECONDS_PER_SLOT * 1_000_000_000'u64
template ethTimeUnit*(typ: type) {.dirty.} =
func `+`*(x: typ, y: uint64): typ {.borrow.}
func `-`*(x: typ, y: uint64): typ {.borrow.}
func `-`*(x: uint64, y: typ): typ {.borrow.}
# Not closed over type in question (Slot or Epoch)
func `mod`*(x: typ, y: uint64): uint64 {.borrow.}
func `div`*(x: typ, y: uint64): uint64 {.borrow.}
func `div`*(x: uint64, y: typ): uint64 {.borrow.}
func `-`*(x: typ, y: typ): uint64 {.borrow.}
iterator countdown*(a, b: typ, step: Positive = 1): typ =
# otherwise we use the signed version that breaks at the boundary
for i in countdown(distinctBase(a), distinctBase(b), step):
yield typ(i)
func `*`*(x: typ, y: uint64): uint64 {.borrow.}
func `+=`*(x: var typ, y: typ) {.borrow.}
func `+=`*(x: var typ, y: uint64) {.borrow.}
func `-=`*(x: var typ, y: typ) {.borrow.}
func `-=`*(x: var typ, y: uint64) {.borrow.}
# Comparison operators
func `<`*(x: typ, y: typ): bool {.borrow.}
func `<`*(x: typ, y: uint64): bool {.borrow.}
func `<`*(x: uint64, y: typ): bool {.borrow.}
func `<=`*(x: typ, y: typ): bool {.borrow.}
func `<=`*(x: typ, y: uint64): bool {.borrow.}
func `<=`*(x: uint64, y: typ): bool {.borrow.}
func `==`*(x: typ, y: typ): bool {.borrow.}
func `==`*(x: typ, y: uint64): bool {.borrow.}
func `==`*(x: uint64, y: typ): bool {.borrow.}
# Nim integration
func `$`*(x: typ): string {.borrow.}
func hash*(x: typ): Hash {.borrow.}
template asUInt64*(v: typ): uint64 = distinctBase(v)
template shortLog*(v: typ): auto = distinctBase(v)
# Serialization
proc writeValue*(writer: var JsonWriter, value: typ) {.raises: [IOError].} =
writeValue(writer, uint64 value)
proc readValue*(reader: var JsonReader, value: var typ)
{.raises: [IOError, SerializationError].} =
value = typ reader.readValue(uint64)
ethTimeUnit Slot
ethTimeUnit Epoch
ethTimeUnit SyncCommitteePeriod
template `<`*(a, b: BeaconTime): bool = a.ns_since_genesis < b.ns_since_genesis
template `<=`*(a, b: BeaconTime): bool = a.ns_since_genesis <= b.ns_since_genesis
template `<`*(a, b: TimeDiff): bool = a.nanoseconds < b.nanoseconds
template `<=`*(a, b: TimeDiff): bool = a.nanoseconds <= b.nanoseconds
template `<`*(a: TimeDiff, b: Duration): bool = a.nanoseconds < b.nanoseconds
func toSlot*(t: BeaconTime): tuple[afterGenesis: bool, slot: Slot] =
if t == FAR_FUTURE_BEACON_TIME:
(true, FAR_FUTURE_SLOT)
elif t.ns_since_genesis >= 0:
(true, Slot(uint64(t.ns_since_genesis) div NANOSECONDS_PER_SLOT))
else:
(false, Slot(uint64(-t.ns_since_genesis) div NANOSECONDS_PER_SLOT))
template `+`*(t: BeaconTime, offset: Duration | TimeDiff): BeaconTime =
BeaconTime(ns_since_genesis: t.ns_since_genesis + offset.nanoseconds)
template `-`*(t: BeaconTime, offset: Duration | TimeDiff): BeaconTime =
BeaconTime(ns_since_genesis: t.ns_since_genesis - offset.nanoseconds)
template `-`*(a, b: BeaconTime): TimeDiff =
TimeDiff(nanoseconds: a.ns_since_genesis - b.ns_since_genesis)
template `+`*(a: TimeDiff, b: Duration): TimeDiff =
TimeDiff(nanoseconds: a.nanoseconds + b.nanoseconds)
const
# Offsets from the start of the slot to when the corresponding message should
# be sent
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#attesting
attestationSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#broadcast-aggregate
aggregateSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 * 2 div INTERVALS_PER_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/validator.md#prepare-sync-committee-message
syncCommitteeMessageSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/validator.md#broadcast-sync-committee-contribution
syncContributionSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 * 2 div INTERVALS_PER_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/light-client/p2p-interface.md#sync-committee
lightClientFinalityUpdateSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/light-client/p2p-interface.md#sync-committee
lightClientOptimisticUpdateSlotOffset* = TimeDiff(nanoseconds:
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
func toFloatSeconds*(t: TimeDiff): float =
float(t.nanoseconds) / 1_000_000_000.0
func start_beacon_time*(s: Slot): BeaconTime =
# The point in time that a slot begins
const maxSlot = Slot(
uint64(FAR_FUTURE_BEACON_TIME.ns_since_genesis) div NANOSECONDS_PER_SLOT)
if s > maxSlot: FAR_FUTURE_BEACON_TIME
else: BeaconTime(ns_since_genesis: int64(uint64(s) * NANOSECONDS_PER_SLOT))
func block_deadline*(s: Slot): BeaconTime =
s.start_beacon_time
func attestation_deadline*(s: Slot): BeaconTime =
s.start_beacon_time + attestationSlotOffset
func aggregate_deadline*(s: Slot): BeaconTime =
s.start_beacon_time + aggregateSlotOffset
func sync_committee_message_deadline*(s: Slot): BeaconTime =
s.start_beacon_time + syncCommitteeMessageSlotOffset
func sync_contribution_deadline*(s: Slot): BeaconTime =
s.start_beacon_time + syncContributionSlotOffset
func light_client_finality_update_time*(s: Slot): BeaconTime =
s.start_beacon_time + lightClientFinalityUpdateSlotOffset
func light_client_optimistic_update_time*(s: Slot): BeaconTime =
s.start_beacon_time + lightClientOptimisticUpdateSlotOffset
func slotOrZero*(time: BeaconTime): Slot =
let exSlot = time.toSlot
if exSlot.afterGenesis: exSlot.slot
else: Slot(0)
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#compute_epoch_at_slot
func epoch*(slot: Slot): Epoch = # aka compute_epoch_at_slot
## Return the epoch number at ``slot``.
if slot == FAR_FUTURE_SLOT: FAR_FUTURE_EPOCH
else: Epoch(slot div SLOTS_PER_EPOCH)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/phase0/fork-choice.md#compute_slots_since_epoch_start
func since_epoch_start*(slot: Slot): uint64 = # aka compute_slots_since_epoch_start
## How many slots since the beginning of the epoch (`[0..SLOTS_PER_EPOCH-1]`)
(slot mod SLOTS_PER_EPOCH)
template is_epoch*(slot: Slot): bool =
slot.since_epoch_start == 0
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/phase0/beacon-chain.md#compute_start_slot_at_epoch
func start_slot*(epoch: Epoch): Slot = # aka compute_start_slot_at_epoch
## Return the start slot of ``epoch``.
const maxEpoch = Epoch(FAR_FUTURE_SLOT div SLOTS_PER_EPOCH)
if epoch >= maxEpoch: FAR_FUTURE_SLOT
else: Slot(epoch * SLOTS_PER_EPOCH)
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#get_previous_epoch
func get_previous_epoch*(current_epoch: Epoch): Epoch =
## Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``).
if current_epoch == GENESIS_EPOCH:
current_epoch
else:
current_epoch - 1
iterator slots*(epoch: Epoch): Slot =
let start_slot = start_slot(epoch)
for slot in start_slot ..< start_slot + SLOTS_PER_EPOCH:
yield slot
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.5/specs/altair/validator.md#sync-committee
template sync_committee_period*(epoch: Epoch): SyncCommitteePeriod =
if epoch == FAR_FUTURE_EPOCH: FAR_FUTURE_PERIOD
else: SyncCommitteePeriod(epoch div EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
template sync_committee_period*(slot: Slot): SyncCommitteePeriod =
if slot == FAR_FUTURE_SLOT: FAR_FUTURE_PERIOD
else: SyncCommitteePeriod(slot div SLOTS_PER_SYNC_COMMITTEE_PERIOD)
func since_sync_committee_period_start*(slot: Slot): uint64 =
## How many slots since the beginning of the epoch (`[0..SLOTS_PER_SYNC_COMMITTEE_PERIOD-1]`)
(slot mod SLOTS_PER_SYNC_COMMITTEE_PERIOD)
func since_sync_committee_period_start*(epoch: Epoch): uint64 =
## How many slots since the beginning of the epoch (`[0..EPOCHS_PER_SYNC_COMMITTEE_PERIOD-1]`)
(epoch mod EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
template is_sync_committee_period*(slot: Slot): bool =
slot.since_sync_committee_period_start() == 0
template is_sync_committee_period*(epoch: Epoch): bool =
epoch.since_sync_committee_period_start() == 0
template start_epoch*(period: SyncCommitteePeriod): Epoch =
## Return the start epoch of ``period``.
const maxPeriod = SyncCommitteePeriod(
FAR_FUTURE_EPOCH div EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
if period >= maxPeriod: FAR_FUTURE_EPOCH
else: Epoch(period * EPOCHS_PER_SYNC_COMMITTEE_PERIOD)
template start_slot*(period: SyncCommitteePeriod): Slot =
## Return the start slot of ``period``.
const maxPeriod = SyncCommitteePeriod(
FAR_FUTURE_SLOT div SLOTS_PER_SYNC_COMMITTEE_PERIOD)
if period >= maxPeriod: FAR_FUTURE_SLOT
else: Slot(period * SLOTS_PER_SYNC_COMMITTEE_PERIOD)
func proposer_dependent_slot*(epoch: Epoch): Slot =
if epoch >= 1: epoch.start_slot() - 1 else: Slot(0)
func attester_dependent_slot*(epoch: Epoch): Slot =
if epoch >= 2: (epoch - 1).start_slot() - 1 else: Slot(0)
func `$`*(t: BeaconTime): string =
if t.ns_since_genesis >= 0:
$(timer.nanoseconds(t.ns_since_genesis))
else:
"-" & $(timer.nanoseconds(-t.ns_since_genesis))
func `$`*(t: TimeDiff): string =
if t.nanoseconds >= 0:
$(timer.nanoseconds(t.nanoseconds))
else:
"-" & $(timer.nanoseconds(-t.nanoseconds))
func shortLog*(t: BeaconTime | TimeDiff): string = $t
chronicles.formatIt BeaconTime: it.shortLog
chronicles.formatIt TimeDiff: it.shortLog
chronicles.formatIt Slot: it.shortLog
chronicles.formatIt Epoch: it.shortLog
chronicles.formatIt SyncCommitteePeriod: it.shortLog
const defaultSyncHorizon* =
(uint64(10*60) + SECONDS_PER_SLOT - 1) div SECONDS_PER_SLOT