nimbus-eth2/beacon_chain/spec/forks.nim

481 lines
18 KiB
Nim
Raw Normal View History

# beacon_chain
# Copyright (c) 2021 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: [Defect].}
import
std/macros,
chronicles,
stew/[assign2, results],
../extras,
../spec/[
beaconstate, eth2_merkleization, helpers, state_transition_block, validator],
./datatypes/[phase0, altair, merge]
export extras, phase0, altair, eth2_merkleization
type
BeaconStateFork* {.pure.} = enum
Phase0,
Altair,
Merge
ForkedHashedBeaconState* = object
case kind*: BeaconStateFork
of BeaconStateFork.Phase0: phase0Data*: phase0.HashedBeaconState
of BeaconStateFork.Altair: altairData*: altair.HashedBeaconState
of BeaconStateFork.Merge: mergeData*: merge.HashedBeaconState
ForkedBeaconState* = object
case kind*: BeaconStateFork
of BeaconStateFork.Phase0: phase0Data*: phase0.BeaconState
of BeaconStateFork.Altair: altairData*: altair.BeaconState
of BeaconStateFork.Merge: mergeData*: merge.BeaconState
BeaconBlockFork* {.pure.} = enum
Phase0
Altair
Merge
ForkedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Data*: phase0.BeaconBlock
of BeaconBlockFork.Altair:
altairData*: altair.BeaconBlock
of BeaconBlockFork.Merge:
mergeData*: merge.BeaconBlock
ForkedSignedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Data*: phase0.SignedBeaconBlock
of BeaconBlockFork.Altair:
altairData*: altair.SignedBeaconBlock
of BeaconBlockFork.Merge:
mergeData*: merge.SignedBeaconBlock
ForkedTrustedSignedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Data*: phase0.TrustedSignedBeaconBlock
of BeaconBlockFork.Altair:
altairData*: altair.TrustedSignedBeaconBlock
of BeaconBlockFork.Merge:
mergeData*: merge.TrustedSignedBeaconBlock
EpochInfoFork* {.pure.} = enum
Phase0
Altair
ForkedEpochInfo* = object
case kind*: EpochInfoFork
of EpochInfoFork.Phase0:
phase0Data*: phase0.EpochInfo
of EpochInfoFork.Altair:
altairData*: altair.EpochInfo
ForkyEpochInfo* = phase0.EpochInfo | altair.EpochInfo
ForkDigests* = object
phase0*: ForkDigest
altair*: ForkDigest
2021-10-06 15:47:11 +00:00
merge*: ForkDigest
ForkDigestsRef* = ref ForkDigests
template init*(T: type ForkedBeaconBlock, blck: phase0.BeaconBlock): T =
T(kind: BeaconBlockFork.Phase0, phase0Data: blck)
template init*(T: type ForkedBeaconBlock, blck: altair.BeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairData: blck)
template init*(T: type ForkedBeaconBlock, blck: merge.BeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeData: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: phase0.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Phase0, phase0Data: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: altair.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairData: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: merge.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeData: blck)
template init*(T: type ForkedBeaconState, state: phase0.BeaconState): T =
T(kind: BeaconStateFork.Phase0, phase0Data: state)
template init*(T: type ForkedBeaconState, state: altair.BeaconState): T =
T(kind: BeaconStateFork.Altair, altairData: state)
template init*(T: type ForkedBeaconState, state: merge.BeaconState): T =
T(kind: BeaconStateFork.Merge, mergeData: state)
template init*(T: type ForkedBeaconState, state: ForkedHashedBeaconState): T =
case state.kind
of BeaconStateFork.Phase0:
T(kind: BeaconStateFork.Phase0,
phase0Data: state.phase0Data.data)
of BeaconStateFork.Altair:
T(kind: BeaconStateFork.Altair,
altairData: state.altairData.data)
of BeaconStateFork.Merge:
T(kind: BeaconStateFork.Merge,
mergeData: state.mergeData.data)
template init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock,
blockRoot: Eth2Digest, signature: ValidatorSig): T =
case forked.kind
of BeaconBlockFork.Phase0:
T(kind: BeaconBlockFork.Phase0,
phase0Data: phase0.SignedBeaconBlock(message: forked.phase0Data,
root: blockRoot,
signature: signature))
of BeaconBlockFork.Altair:
T(kind: BeaconBlockFork.Altair,
altairData: altair.SignedBeaconBlock(message: forked.altairData,
root: blockRoot,
signature: signature))
of BeaconBlockFork.Merge:
T(kind: BeaconBlockFork.Merge,
mergeData: merge.SignedBeaconBlock(message: forked.mergeData,
root: blockRoot,
signature: signature))
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: phase0.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Phase0, phase0Data: blck)
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: altair.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairData: blck)
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: merge.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeData: blck)
template init*(T: type ForkedEpochInfo, info: phase0.EpochInfo): T =
T(kind: EpochInfoFork.Phase0, phase0Data: info)
template init*(T: type ForkedEpochInfo, info: altair.EpochInfo): T =
T(kind: EpochInfoFork.Altair, altairData: info)
# State-related functionality based on ForkedHashedBeaconState instead of HashedBeaconState
template withState*(x: ForkedHashedBeaconState, body: untyped): untyped =
case x.kind
of BeaconStateFork.Merge:
const stateFork {.inject.} = BeaconStateFork.Merge
template state: untyped {.inject.} = x.mergeData
body
of BeaconStateFork.Altair:
const stateFork {.inject.} = BeaconStateFork.Altair
template state: untyped {.inject.} = x.altairData
body
of BeaconStateFork.Phase0:
const stateFork {.inject.} = BeaconStateFork.Phase0
template state: untyped {.inject.} = x.phase0Data
body
template withEpochInfo*(x: ForkedEpochInfo, body: untyped): untyped =
case x.kind
of EpochInfoFork.Phase0:
template info: untyped {.inject.} = x.phase0Data
body
of EpochInfoFork.Altair:
template info: untyped {.inject.} = x.altairData
body
template withEpochInfo*(
state: phase0.BeaconState, x: var ForkedEpochInfo, body: untyped): untyped =
x.kind = EpochInfoFork.Phase0
template info: untyped {.inject.} = x.phase0Data
body
template withEpochInfo*(
state: altair.BeaconState | merge.BeaconState, x: var ForkedEpochInfo,
body: untyped): untyped =
x.kind = EpochInfoFork.Altair
template info: untyped {.inject.} = x.altairData
body
# Dispatch functions
func assign*(tgt: var ForkedHashedBeaconState, src: ForkedHashedBeaconState) =
if tgt.kind == src.kind:
case tgt.kind
of BeaconStateFork.Merge:
assign(tgt.mergeData, src.mergeData):
of BeaconStateFork.Altair:
assign(tgt.altairData, src.altairData):
of BeaconStateFork.Phase0:
assign(tgt.phase0Data, src.phase0Data):
else:
# Ensure case object and discriminator get updated simultaneously, even
# with nimOldCaseObjects. This is infrequent.
tgt = src
template getStateField*(x: ForkedHashedBeaconState, y: untyped): untyped =
# The use of `unsafeAddr` avoids excessive copying in certain situations, e.g.,
# ```
# for index, validator in getStateField(stateData.data, validators).pairs():
# ```
# Without `unsafeAddr`, the `validators` list would be copied to a temporary variable.
(case x.kind
of BeaconStateFork.Merge: unsafeAddr x.mergeData.data.y
of BeaconStateFork.Altair: unsafeAddr x.altairData.data.y
of BeaconStateFork.Phase0: unsafeAddr x.phase0Data.data.y)[]
func getStateRoot*(x: ForkedHashedBeaconState): Eth2Digest =
withState(x): state.root
func setStateRoot*(x: var ForkedHashedBeaconState, root: Eth2Digest) =
withState(x): state.root = root
func hash_tree_root*(x: ForkedHashedBeaconState): Eth2Digest =
# This is a bit of a hack because we drill into data here, unlike other places
withState(x): hash_tree_root(state.data)
func get_active_validator_indices_len*(
state: ForkedHashedBeaconState; epoch: Epoch): uint64 =
withState(state):
get_active_validator_indices_len(state.data, epoch)
func get_beacon_committee*(
state: ForkedHashedBeaconState, slot: Slot, index: CommitteeIndex,
cache: var StateCache): seq[ValidatorIndex] =
# This one is used by tests/, ncli/, and a couple of places in RPC
# TODO use the iterator version alone, to remove the risk of using
# diverging get_beacon_committee() in tests and beacon_chain/ by a
# wrapper approach (e.g., toSeq). This is a perf tradeoff for test
# correctness/consistency.
withState(state):
get_beacon_committee(state.data, slot, index, cache)
func get_beacon_committee_len*(
state: ForkedHashedBeaconState, slot: Slot, index: CommitteeIndex,
cache: var StateCache): uint64 =
# This one is used by tests
withState(state):
get_beacon_committee_len(state.data, slot, index, cache)
func get_committee_count_per_slot*(state: ForkedHashedBeaconState,
epoch: Epoch,
cache: var StateCache): uint64 =
## Return the number of committees at ``epoch``.
withState(state):
get_committee_count_per_slot(state.data, epoch, cache)
func get_beacon_proposer_index*(state: ForkedHashedBeaconState,
cache: var StateCache, slot: Slot):
Option[ValidatorIndex] =
withState(state):
get_beacon_proposer_index(state.data, cache, slot)
func get_shuffled_active_validator_indices*(
cache: var StateCache, state: ForkedHashedBeaconState, epoch: Epoch):
seq[ValidatorIndex] =
withState(state):
cache.get_shuffled_active_validator_indices(state.data, epoch)
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_block_root_at_slot
func get_block_root_at_slot*(state: ForkedHashedBeaconState,
slot: Slot): Eth2Digest =
## Return the block root at a recent ``slot``.
withState(state):
get_block_root_at_slot(state.data, slot)
proc get_attesting_indices*(state: ForkedHashedBeaconState;
data: AttestationData;
bits: CommitteeValidatorsBits;
cache: var StateCache): seq[ValidatorIndex] =
# TODO when https://github.com/nim-lang/Nim/issues/18188 fixed, use an
# iterator
var idxBuf: seq[ValidatorIndex]
withState(state):
for vidx in state.data.get_attesting_indices(data, bits, cache):
idxBuf.add vidx
idxBuf
proc check_attester_slashing*(
state: var ForkedHashedBeaconState; attester_slashing: SomeAttesterSlashing;
flags: UpdateFlags): Result[seq[ValidatorIndex], cstring] =
withState(state):
check_attester_slashing(state.data, attester_slashing, flags)
proc check_proposer_slashing*(
state: var ForkedHashedBeaconState; proposer_slashing: SomeProposerSlashing;
flags: UpdateFlags): Result[void, cstring] =
withState(state):
check_proposer_slashing(state.data, proposer_slashing, flags)
proc check_voluntary_exit*(
Implement split preset/config support (#2710) * Implement split preset/config support This is the initial bulk refactor to introduce runtime config values in a number of places, somewhat replacing the existing mechanism of loading network metadata. It still needs more work, this is the initial refactor that introduces runtime configuration in some of the places that need it. The PR changes the way presets and constants work, to match the spec. In particular, a "preset" now refers to the compile-time configuration while a "cfg" or "RuntimeConfig" is the dynamic part. A single binary can support either mainnet or minimal, but not both. Support for other presets has been removed completely (can be readded, in case there's need). There's a number of outstanding tasks: * `SECONDS_PER_SLOT` still needs fixing * loading custom runtime configs needs redoing * checking constants against YAML file * yeerongpilly support `build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG` * load fork epoch from config * fix fork digest sent in status * nicer error string for request failures * fix tools * one more * fixup * fixup * fixup * use "standard" network definition folder in local testnet Files are loaded from their standard locations, including genesis etc, to conform to the format used in the `eth2-networks` repo. * fix launch scripts, allow unknown config values * fix base config of rest test * cleanups * bundle mainnet config using common loader * fix spec links and names * only include supported preset in binary * drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
cfg: RuntimeConfig, state: ForkedHashedBeaconState;
signed_voluntary_exit: SomeSignedVoluntaryExit;
flags: UpdateFlags): Result[void, cstring] =
withState(state):
check_voluntary_exit(cfg, state.data, signed_voluntary_exit, flags)
# Derived utilities
func stateForkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): BeaconStateFork =
## Return the current fork for the given epoch.
static:
doAssert BeaconStateFork.Merge > BeaconStateFork.Altair
doAssert BeaconStateFork.Altair > BeaconStateFork.Phase0
doAssert GENESIS_EPOCH == 0
if epoch >= cfg.MERGE_FORK_EPOCH: BeaconStateFork.Merge
elif epoch >= cfg.ALTAIR_FORK_EPOCH: BeaconStateFork.Altair
else: BeaconStateFork.Phase0
func blockForkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): BeaconBlockFork =
## Return the current fork for the given epoch.
if epoch >= cfg.MERGE_FORK_EPOCH: BeaconBlockFork.Merge
elif epoch >= cfg.ALTAIR_FORK_EPOCH: BeaconBlockFork.Altair
else: BeaconBlockFork.Phase0
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_current_epoch
func get_current_epoch*(x: ForkedHashedBeaconState): Epoch =
## Return the current epoch.
withState(x): state.data.slot.epoch
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_previous_epoch
func get_previous_epoch*(stateData: ForkedHashedBeaconState): Epoch =
## Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``).
let current_epoch = get_current_epoch(stateData)
if current_epoch == GENESIS_EPOCH:
GENESIS_EPOCH
else:
current_epoch - 1
func init*(T: type ForkDigests,
Implement split preset/config support (#2710) * Implement split preset/config support This is the initial bulk refactor to introduce runtime config values in a number of places, somewhat replacing the existing mechanism of loading network metadata. It still needs more work, this is the initial refactor that introduces runtime configuration in some of the places that need it. The PR changes the way presets and constants work, to match the spec. In particular, a "preset" now refers to the compile-time configuration while a "cfg" or "RuntimeConfig" is the dynamic part. A single binary can support either mainnet or minimal, but not both. Support for other presets has been removed completely (can be readded, in case there's need). There's a number of outstanding tasks: * `SECONDS_PER_SLOT` still needs fixing * loading custom runtime configs needs redoing * checking constants against YAML file * yeerongpilly support `build/nimbus_beacon_node --network=yeerongpilly --discv5:no --log-level=DEBUG` * load fork epoch from config * fix fork digest sent in status * nicer error string for request failures * fix tools * one more * fixup * fixup * fixup * use "standard" network definition folder in local testnet Files are loaded from their standard locations, including genesis etc, to conform to the format used in the `eth2-networks` repo. * fix launch scripts, allow unknown config values * fix base config of rest test * cleanups * bundle mainnet config using common loader * fix spec links and names * only include supported preset in binary * drop yeerongpilly, add altair-devnet-0, support boot_enr.yaml
2021-07-12 13:01:38 +00:00
cfg: RuntimeConfig,
genesisValidatorsRoot: Eth2Digest): T =
2021-10-06 15:47:11 +00:00
T(
phase0:
compute_fork_digest(cfg.GENESIS_FORK_VERSION, genesisValidatorsRoot),
altair:
compute_fork_digest(cfg.ALTAIR_FORK_VERSION, genesisValidatorsRoot),
merge:
compute_fork_digest(cfg.MERGE_FORK_VERSION, genesisValidatorsRoot),
)
template asSigned*(x: phase0.TrustedSignedBeaconBlock or phase0.SigVerifiedBeaconBlock):
phase0.SignedBeaconBlock =
isomorphicCast[phase0.SignedBeaconBlock](x)
template asSigned*(x: altair.TrustedSignedBeaconBlock or altair.SigVerifiedBeaconBlock):
altair.SignedBeaconBlock =
isomorphicCast[altair.SignedBeaconBlock](x)
template asSigned*(x: ForkedTrustedSignedBeaconBlock): ForkedSignedBeaconBlock =
isomorphicCast[ForkedSignedBeaconBlock](x)
template asTrusted*(x: phase0.SignedBeaconBlock or phase0.SigVerifiedBeaconBlock):
phase0.TrustedSignedBeaconBlock =
isomorphicCast[phase0.TrustedSignedBeaconBlock](x)
template asTrusted*(x: altair.SignedBeaconBlock or altair.SigVerifiedBeaconBlock):
altair.TrustedSignedBeaconBlock =
isomorphicCast[altair.TrustedSignedBeaconBlock](x)
template asTrusted*(x: merge.SignedBeaconBlock or merge.SigVerifiedBeaconBlock):
merge.TrustedSignedBeaconBlock =
isomorphicCast[merge.TrustedSignedBeaconBlock](x)
template asTrusted*(x: ForkedSignedBeaconBlock): ForkedTrustedSignedBeaconBlock =
isomorphicCast[ForkedTrustedSignedBeaconBlock](x)
template withBlck*(
x: ForkedBeaconBlock | ForkedSignedBeaconBlock |
ForkedTrustedSignedBeaconBlock,
body: untyped): untyped =
case x.kind
of BeaconBlockFork.Phase0:
const stateFork {.inject.} = BeaconStateFork.Phase0
template blck: untyped {.inject.} = x.phase0Data
body
of BeaconBlockFork.Altair:
const stateFork {.inject.} = BeaconStateFork.Altair
template blck: untyped {.inject.} = x.altairData
body
of BeaconBlockFork.Merge:
const stateFork {.inject.} = BeaconStateFork.Merge
template blck: untyped {.inject.} = x.mergeData
body
func proposer_index*(x: ForkedBeaconBlock): uint64 =
withBlck(x): blck.proposer_index
func hash_tree_root*(x: ForkedBeaconBlock): Eth2Digest =
withBlck(x): hash_tree_root(blck)
template getForkedBlockField*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock, y: untyped): untyped =
# unsafeAddr avoids a copy of the field in some cases
(case x.kind
of BeaconBlockFork.Phase0: unsafeAddr x.phase0Data.message.y
of BeaconBlockFork.Altair: unsafeAddr x.altairData.message.y
of BeaconBlockFork.Merge: unsafeAddr x.mergeData.message.y)[]
template signature*(x: ForkedSignedBeaconBlock): ValidatorSig =
withBlck(x): blck.signature
template signature*(x: ForkedTrustedSignedBeaconBlock): TrustedSig =
withBlck(x): blck.signature
template root*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): Eth2Digest =
withBlck(x): blck.root
template slot*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): Slot =
withBlck(x): blck.message.slot
template shortLog*(x: ForkedBeaconBlock): auto =
withBlck(x): shortLog(blck)
template shortLog*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): auto =
withBlck(x): shortLog(blck)
chronicles.formatIt ForkedBeaconBlock: it.shortLog
chronicles.formatIt ForkedSignedBeaconBlock: it.shortLog
chronicles.formatIt ForkedTrustedSignedBeaconBlock: it.shortLog
template withStateAndBlck*(
s: ForkedHashedBeaconState,
b: ForkedBeaconBlock | ForkedSignedBeaconBlock |
ForkedTrustedSignedBeaconBlock,
body: untyped): untyped =
case s.kind
of BeaconStateFork.Merge:
const stateFork {.inject.} = BeaconStateFork.Merge
template state: untyped {.inject.} = s.mergeData
template blck: untyped {.inject.} = b.mergeData
body
of BeaconStateFork.Altair:
const stateFork {.inject.} = BeaconStateFork.Altair
template state: untyped {.inject.} = s.altairData
template blck: untyped {.inject.} = b.altairData
body
of BeaconStateFork.Phase0:
const stateFork {.inject.} = BeaconStateFork.Phase0
template state: untyped {.inject.} = s.phase0Data
template blck: untyped {.inject.} = b.phase0Data
body
proc forkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Fork =
case cfg.stateForkAtEpoch(epoch)
of BeaconStateFork.Merge: cfg.mergeFork
of BeaconStateFork.Altair: cfg.altairFork
of BeaconStateFork.Phase0: cfg.genesisFork
proc forkVersionAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Version =
case cfg.stateForkAtEpoch(epoch)
of BeaconStateFork.Merge: cfg.MERGE_FORK_VERSION
of BeaconStateFork.Altair: cfg.ALTAIR_FORK_VERSION
of BeaconStateFork.Phase0: cfg.GENESIS_FORK_VERSION
proc nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch =
case cfg.stateForkAtEpoch(epoch)
of BeaconStateFork.Merge: FAR_FUTURE_EPOCH
of BeaconStateFork.Altair: cfg.MERGE_FORK_EPOCH
of BeaconStateFork.Phase0: cfg.ALTAIR_FORK_EPOCH
func getForkSchedule*(cfg: RuntimeConfig): array[2, Fork] =
## This procedure returns list of known and/or scheduled forks.
##
## This procedure is used by HTTP REST framework and validator client.
##
## NOTE: Update this procedure when new fork will be scheduled.
[cfg.genesisFork(), cfg.altairFork()]