# 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 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*( 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, cfg: RuntimeConfig, genesisValidatorsRoot: Eth2Digest): T = 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()]