nimbus-eth2/beacon_chain/spec/forks.nim

435 lines
16 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* = enum
forkPhase0,
forkAltair,
forkMerge
ForkedHashedBeaconState* = object
case beaconStateFork*: BeaconStateFork
of forkPhase0: hbsPhase0*: phase0.HashedBeaconState
of forkAltair: hbsAltair*: altair.HashedBeaconState
of forkMerge: hbsMerge*: merge.HashedBeaconState
ForkedBeaconState* = object
case beaconStateFork*: BeaconStateFork
of forkPhase0: bsPhase0*: phase0.BeaconState
of forkAltair: bsAltair*: altair.BeaconState
of forkMerge: bsMerge*: merge.BeaconState
BeaconBlockFork* {.pure.} = enum
Phase0
Altair
Merge
ForkedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Block*: phase0.BeaconBlock
of BeaconBlockFork.Altair:
altairBlock*: altair.BeaconBlock
of BeaconBlockFork.Merge:
mergeBlock*: merge.BeaconBlock
ForkedSignedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Block*: phase0.SignedBeaconBlock
of BeaconBlockFork.Altair:
altairBlock*: altair.SignedBeaconBlock
of BeaconBlockFork.Merge:
mergeBlock*: merge.SignedBeaconBlock
ForkedTrustedSignedBeaconBlock* = object
case kind*: BeaconBlockFork
of BeaconBlockFork.Phase0:
phase0Block*: phase0.TrustedSignedBeaconBlock
of BeaconBlockFork.Altair:
altairBlock*: altair.TrustedSignedBeaconBlock
of BeaconBlockFork.Merge:
mergeBlock*: merge.TrustedSignedBeaconBlock
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, phase0Block: blck)
template init*(T: type ForkedBeaconBlock, blck: altair.BeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairBlock: blck)
template init*(T: type ForkedBeaconBlock, blck: merge.BeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeBlock: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: phase0.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Phase0, phase0Block: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: altair.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairBlock: blck)
template init*(T: type ForkedSignedBeaconBlock, blck: merge.SignedBeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeBlock: blck)
template init*(T: type ForkedBeaconState, state: phase0.BeaconState): T =
T(beaconStateFork: BeaconStateFork.forkPhase0, bsPhase0: state)
template init*(T: type ForkedBeaconState, state: altair.BeaconState): T =
T(beaconStateFork: BeaconStateFork.forkAltair, bsAltair: state)
template init*(T: type ForkedBeaconState, state: merge.BeaconState): T =
T(beaconStateFork: BeaconStateFork.forkMerge, bsMerge: state)
template init*(T: type ForkedBeaconState, state: ForkedHashedBeaconState): T =
case state.beaconStateFork
of BeaconStateFork.forkPhase0:
T(beaconStateFork: BeaconStateFork.forkPhase0,
bsPhase0: state.hbsPhase0.data)
of BeaconStateFork.forkAltair:
T(beaconStateFork: BeaconStateFork.forkAltair,
bsAltair: state.hbsAltair.data)
of BeaconStateFork.forkMerge:
T(beaconStateFork: BeaconStateFork.forkMerge,
bsMerge: state.hbsMerge.data)
template init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock,
blockRoot: Eth2Digest, signature: ValidatorSig): T =
case forked.kind
of BeaconBlockFork.Phase0:
T(kind: BeaconBlockFork.Phase0,
phase0Block: phase0.SignedBeaconBlock(message: forked.phase0Block,
root: blockRoot,
signature: signature))
of BeaconBlockFork.Altair:
T(kind: BeaconBlockFork.Altair,
altairBlock: altair.SignedBeaconBlock(message: forked.altairBlock,
root: blockRoot,
signature: signature))
of BeaconBlockFork.Merge:
T(kind: BeaconBlockFork.Merge,
mergeBlock: merge.SignedBeaconBlock(message: forked.mergeBlock,
root: blockRoot,
signature: signature))
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: phase0.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Phase0, phase0Block: blck)
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: altair.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Altair, altairBlock: blck)
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: merge.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeBlock: blck)
# State-related functionality based on ForkedHashedBeaconState instead of HashedBeaconState
template withState*(x: ForkedHashedBeaconState, body: untyped): untyped =
case x.beaconStateFork
of forkMerge:
const stateFork {.inject.} = forkMerge
template state: untyped {.inject.} = x.hbsMerge
body
of forkAltair:
const stateFork {.inject.} = forkAltair
template state: untyped {.inject.} = x.hbsAltair
body
of forkPhase0:
const stateFork {.inject.} = forkPhase0
template state: untyped {.inject.} = x.hbsPhase0
body
# Dispatch functions
func assign*(tgt: var ForkedHashedBeaconState, src: ForkedHashedBeaconState) =
if tgt.beaconStateFork == src.beaconStateFork:
case tgt.beaconStateFork
of forkMerge:
assign(tgt.hbsMerge, src.hbsMerge):
of forkAltair:
assign(tgt.hbsAltair, src.hbsAltair):
of forkPhase0:
assign(tgt.hbsPhase0, src.hbsPhase0):
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.beaconStateFork
of forkMerge: unsafeAddr x.hbsMerge.data.y
of forkAltair: unsafeAddr x.hbsAltair.data.y
of forkPhase0: unsafeAddr x.hbsPhase0.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/eth2.0-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 forkMerge > forkAltair
doAssert forkAltair > forkPhase0
doAssert GENESIS_EPOCH == 0
if epoch >= cfg.MERGE_FORK_EPOCH: forkMerge
elif epoch >= cfg.ALTAIR_FORK_EPOCH: forkAltair
else: forkPhase0
# https://github.com/ethereum/eth2.0-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/eth2.0-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.} = forkPhase0
template blck: untyped {.inject.} = x.phase0Block
body
of BeaconBlockFork.Altair:
const stateFork {.inject.} = forkAltair
template blck: untyped {.inject.} = x.altairBlock
body
of BeaconBlockFork.Merge:
const stateFork {.inject.} = forkMerge
template blck: untyped {.inject.} = x.mergeBlock
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.phase0Block.message.y
of BeaconBlockFork.Altair: unsafeAddr x.altairBlock.message.y
of BeaconBlockFork.Merge: unsafeAddr x.mergeBlock.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.beaconStateFork
of forkMerge:
const stateFork {.inject.} = forkMerge
template state: untyped {.inject.} = s.hbsMerge
template blck: untyped {.inject.} = b.mergeBlock
body
of forkAltair:
const stateFork {.inject.} = forkAltair
template state: untyped {.inject.} = s.hbsAltair
template blck: untyped {.inject.} = b.altairBlock
body
of forkPhase0:
const stateFork {.inject.} = forkPhase0
template state: untyped {.inject.} = s.hbsPhase0
template blck: untyped {.inject.} = b.phase0Block
body
proc forkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Fork =
case cfg.stateForkAtEpoch(epoch)
of forkMerge: cfg.mergeFork
of forkAltair: cfg.altairFork
of forkPhase0: cfg.genesisFork
proc forkVersionAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Version =
case cfg.stateForkAtEpoch(epoch)
of forkMerge: cfg.MERGE_FORK_VERSION
of forkAltair: cfg.ALTAIR_FORK_VERSION
of forkPhase0: cfg.GENESIS_FORK_VERSION
proc nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch =
case cfg.stateForkAtEpoch(epoch)
of forkMerge: FAR_FUTURE_EPOCH
of forkAltair: cfg.MERGE_FORK_EPOCH
of forkPhase0: 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()]