Altair transition tests (#2624)

* Working Altair transition tests

* with fixed upstream test vectors, remove state root workaround

* switch upgrade_to_altair() to returning a reference

* remove test_state_transition

* fix invalid fork state/block combinations error messages

* avoid memory copies by reintroducing state_transition_slots(var SomeHashedBeaconState)
This commit is contained in:
tersec 2021-06-04 10:38:00 +00:00 committed by GitHub
parent 1a76007858
commit 8ebd496fbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 326 additions and 184 deletions

View File

@ -113,15 +113,6 @@ OK: 4/4 Fail: 0/4 Skip: 0/4
+ updateStateData sanity [Preset: mainnet] OK + updateStateData sanity [Preset: mainnet] OK
``` ```
OK: 7/7 Fail: 0/7 Skip: 0/7 OK: 7/7 Fail: 0/7 Skip: 0/7
## Block processing [Preset: mainnet]
```diff
+ Attestation gets processed at epoch [Preset: mainnet] OK
+ Passes from genesis state, empty block [Preset: mainnet] OK
+ Passes from genesis state, no block [Preset: mainnet] OK
+ Passes through epoch update, empty block [Preset: mainnet] OK
+ Passes through epoch update, no block [Preset: mainnet] OK
```
OK: 5/5 Fail: 0/5 Skip: 0/5
## BlockRef and helpers [Preset: mainnet] ## BlockRef and helpers [Preset: mainnet]
```diff ```diff
+ epochAncestor sanity [Preset: mainnet] OK + epochAncestor sanity [Preset: mainnet] OK
@ -317,4 +308,4 @@ OK: 3/3 Fail: 0/3 Skip: 0/3
OK: 1/1 Fail: 0/1 Skip: 0/1 OK: 1/1 Fail: 0/1 Skip: 0/1
---TOTAL--- ---TOTAL---
OK: 179/187 Fail: 0/187 Skip: 8/187 OK: 174/182 Fail: 0/182 Skip: 8/182

View File

@ -2,6 +2,14 @@ FixtureAll-mainnet
=== ===
## ##
```diff ```diff
+ Official - Altair - Transition - normal_transition [Preset: mainnet] OK
+ Official - Altair - Transition - transition_missing_first_post_block [Preset: mainnet] OK
+ Official - Altair - Transition - transition_missing_last_pre_fork_block [Preset: mainnet] OK
+ Official - Altair - Transition - transition_only_blocks_post_fork [Preset: mainnet] OK
+ Official - Altair - Transition - transition_with_finality [Preset: mainnet] OK
+ Official - Altair - Transition - transition_with_no_attestations_until_after_fork [Preset: OK
+ Official - Altair - Transition - transition_with_random_half_participation [Preset: mainne OK
+ Official - Altair - Transition - transition_with_random_three_quarters_participation [Pres OK
+ Rewards - all_balances_too_low_for_reward [Preset: mainnet] OK + Rewards - all_balances_too_low_for_reward [Preset: mainnet] OK
+ Rewards - duplicate_attestations_at_later_slots [Preset: mainnet] OK + Rewards - duplicate_attestations_at_later_slots [Preset: mainnet] OK
+ Rewards - empty [Preset: mainnet] OK + Rewards - empty [Preset: mainnet] OK
@ -257,7 +265,7 @@ FixtureAll-mainnet
+ fork_random_low_balances OK + fork_random_low_balances OK
+ fork_random_misc_balances OK + fork_random_misc_balances OK
``` ```
OK: 254/254 Fail: 0/254 Skip: 0/254 OK: 262/262 Fail: 0/262 Skip: 0/262
## Official - Altair - Epoch Processing - Effective balance updates [Preset: mainnet] ## Official - Altair - Epoch Processing - Effective balance updates [Preset: mainnet]
```diff ```diff
+ Effective balance updates - effective_balance_hysteresis [Preset: mainnet] OK + Effective balance updates - effective_balance_hysteresis [Preset: mainnet] OK
@ -475,4 +483,4 @@ OK: 1/1 Fail: 0/1 Skip: 0/1
OK: 27/27 Fail: 0/27 Skip: 0/27 OK: 27/27 Fail: 0/27 Skip: 0/27
---TOTAL--- ---TOTAL---
OK: 385/385 Fail: 0/385 Skip: 0/385 OK: 393/393 Fail: 0/393 Skip: 0/393

View File

@ -172,10 +172,9 @@ proc checkStateTransition(
blck = shortLog(signedBlock.message) blck = shortLog(signedBlock.message)
blockRoot = shortLog(signedBlock.root) blockRoot = shortLog(signedBlock.root)
var rewards: RewardInfo
if not state_transition_block( if not state_transition_block(
dag.runtimePreset, dag.clearanceState.data, signedBlock, dag.runtimePreset, dag.clearanceState.data, signedBlock,
cache, rewards, dag.updateFlags, restore): cache, dag.updateFlags, restore):
info "Invalid block" info "Invalid block"
return (ValidationResult.Reject, Invalid) return (ValidationResult.Reject, Invalid)

View File

@ -854,7 +854,7 @@ func translate_participation(
state.previous_epoch_participation[index] = state.previous_epoch_participation[index] =
add_flag(state.previous_epoch_participation[index], flag_index) add_flag(state.previous_epoch_participation[index], flag_index)
proc upgrade_to_altair*(pre: phase0.BeaconState): altair.BeaconState = proc upgrade_to_altair*(pre: phase0.BeaconState): ref altair.BeaconState =
let epoch = get_current_epoch(pre) let epoch = get_current_epoch(pre)
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/fork.md#configuration # https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/fork.md#configuration
@ -869,7 +869,7 @@ proc upgrade_to_altair*(pre: phase0.BeaconState): altair.BeaconState =
for _ in 0 ..< len(pre.validators): for _ in 0 ..< len(pre.validators):
doAssert inactivity_scores.add 0'u64 doAssert inactivity_scores.add 0'u64
var post = altair.BeaconState( var post = (ref altair.BeaconState)(
genesis_time: pre.genesis_time, genesis_time: pre.genesis_time,
genesis_validators_root: pre.genesis_validators_root, genesis_validators_root: pre.genesis_validators_root,
slot: pre.slot, slot: pre.slot,
@ -916,12 +916,12 @@ proc upgrade_to_altair*(pre: phase0.BeaconState): altair.BeaconState =
# Fill in previous epoch participation from the pre state's pending # Fill in previous epoch participation from the pre state's pending
# attestations # attestations
translate_participation(post, pre.previous_epoch_attestations.asSeq) translate_participation(post[], pre.previous_epoch_attestations.asSeq)
# Fill in sync committees # Fill in sync committees
# Note: A duplicate committee is assigned for the current and next committee # Note: A duplicate committee is assigned for the current and next committee
# at the fork boundary # at the fork boundary
post.current_sync_committee = get_next_sync_committee(post) post[].current_sync_committee = get_next_sync_committee(post[])
post.next_sync_committee = get_next_sync_committee(post) post[].next_sync_committee = get_next_sync_committee(post[])
post post

View File

@ -45,7 +45,7 @@ import
chronicles, chronicles,
stew/results, stew/results,
../extras, ../ssz/merkleization, metrics, ../extras, ../ssz/merkleization, metrics,
./datatypes/[phase0, altair], ./crypto, ./digest, ./helpers, ./signatures, ./validator, ./datatypes/[phase0, altair], ./crypto, ./digest, ./helpers, ./signatures, ./validator, ./beaconstate,
./state_transition_block, ./state_transition_epoch, ./state_transition_block, ./state_transition_epoch,
../../nbench/bench_lab ../../nbench/bench_lab
@ -89,6 +89,20 @@ func verifyStateRoot(state: phase0.BeaconState, blck: phase0.TrustedBeaconBlock)
# This is inlined in state_transition(...) in spec. # This is inlined in state_transition(...) in spec.
true true
func verifyStateRoot(state: altair.BeaconState, blck: altair.TrustedBeaconBlock): bool =
# This is inlined in state_transition(...) in spec.
true
# one of these can happen on the fork block itself (it's a phase 0 block which
# creates an Altair state)
func verifyStateRoot(state: altair.BeaconState, blck: phase0.TrustedBeaconBlock): bool =
# This is inlined in state_transition(...) in spec.
true
func verifyStateRoot(state: phase0.BeaconState, blck: altair.TrustedBeaconBlock): bool =
# This is inlined in state_transition(...) in spec.
true
type type
RollbackProc* = proc(v: var phase0.BeaconState) {.gcsafe, raises: [Defect].} RollbackProc* = proc(v: var phase0.BeaconState) {.gcsafe, raises: [Defect].}
@ -174,31 +188,106 @@ proc process_slots*(state: var SomeHashedBeaconState, slot: Slot,
# will be incorrect # will be incorrect
state.root = hash_tree_root(state.data) state.root = hash_tree_root(state.data)
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/fork.md#upgrading-the-state
# says to put upgrading here too, TODO. It may not work in state reply
# otherwise, since updateStateData() uses this function.
true true
func noRollback*(state: var phase0.HashedBeaconState) = func noRollback*(state: var phase0.HashedBeaconState) =
trace "Skipping rollback of broken state" trace "Skipping rollback of broken state"
type
BeaconStateFork* = enum
forkPhase0,
forkAltair
ForkedHashedBeaconState* = object
case beaconStateFork*: BeaconStateFork
of forkPhase0: hbsPhase0*: phase0.HashedBeaconState
of forkAltair: hbsAltair*: altair.HashedBeaconState
# Dispatch functions
template getStateField*(x: ForkedHashedBeaconState, y: untyped): untyped =
case x.beaconStateFork:
of forkPhase0: x.hbsPhase0.data.y
of forkAltair: x.hbsAltair.data.y
template getStateField*(x: var ForkedHashedBeaconState, y: untyped): untyped =
case x.beaconStateFork:
of forkPhase0: x.hbsPhase0.data.y
of forkAltair: x.hbsAltair.data.y
template getStateRoot*(x: ForkedHashedBeaconState): Eth2Digest =
case x.beaconStateFork:
of forkPhase0: x.hbsPhase0.root
of forkAltair: x.hbsAltair.root
template hash_tree_root*(x: ForkedHashedBeaconState): Eth2Digest =
case x.beaconStateFork:
of forkPhase0: hash_tree_root(x.hbsPhase0.data)
of forkAltair: hash_tree_root(x.hbsAltair.data)
template callWithBS*(op: untyped, y: ForkedHashedBeaconState): untyped =
let bs {.inject.} =
case y.beaconStateFork:
of forkPhase0: y.hbsPhase0.data
of forkAltair: y.hbsAltair.data
op
proc maybeUpgradeStateToAltair(
state: var ForkedHashedBeaconState, altairForkSlot: Slot) =
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/fork.md#upgrading-the-state
# Both state_transition_slots() and state_transition_block() call this, so
# only run it once by checking for existing fork.
if getStateField(state, slot) == altairForkSlot and
state.beaconStateFork == forkPhase0:
var newState = upgrade_to_altair(state.hbsPhase0.data)
state = ForkedHashedBeaconState(
beaconStateFork: forkAltair,
hbsAltair: altair.HashedBeaconState(
root: hash_tree_root(newState[]), data: newState[]))
proc state_transition_slots( proc state_transition_slots(
preset: RuntimePreset, preset: RuntimePreset,
#state: var phase0.HashedBeaconState, signedBlock: phase0.SomeSignedBeaconBlock, state: var ForkedHashedBeaconState,
state: var (phase0.HashedBeaconState | altair.HashedBeaconState), signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock, signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock,
cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags): bool {.nbench.} = cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags,
## Apply a block to the state, advancing the slot counter as necessary. The altairForkSlot: Slot): bool {.nbench.} =
## given state must be of a lower slot, or, in case the `slotProcessed` flag let slot = signedBlock.message.slot
## is set, can be the slot state of the same slot as the block (where the
## slot state is the state without any block applied). To create a slot state, # Update the state so its slot matches that of the block
## advance the state corresponding to the the parent block using while getStateField(state, slot) < slot:
## `process_slots`. case state.beaconStateFork:
## of forkPhase0:
## To run the state transition function in preparation for block production, advance_slot(
## use `makeBeaconBlock` instead. state.hbsPhase0.data, state.hbsPhase0.root, flags, cache, rewards)
##
## `rollback` is called if the transition fails and the given state has been if state.hbsPhase0.data.slot < slot:
## partially changed. If a temporary state was given to `state_transition`, # Don't update state root for the slot of the block
## it is safe to use `noRollback` and leave it broken, else the state state.hbsPhase0.root = hash_tree_root(state.hbsPhase0.data)
## object should be rolled back to a consistent state. If the transition fails of forkAltair:
## before the state has been updated, `rollback` will not be called. advance_slot(
state.hbsAltair.data, state.hbsAltair.root, flags, cache, rewards)
if getStateField(state, slot) < slot:
# Don't update state root for the slot of the block
state.hbsAltair.root = hash_tree_root(state.hbsAltair.data)
maybeUpgradeStateToAltair(state, altairForkSlot)
true
proc state_transition_slots(
preset: RuntimePreset,
state: var SomeHashedBeaconState,
signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock |
phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock,
cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags):
bool {.nbench.} =
# TODO remove when the HashedBeaconState state_transition is removed; it's
# to avoid requiring a wrapped/memory-copied version
let slot = signedBlock.message.slot let slot = signedBlock.message.slot
if not (state.data.slot < slot): if not (state.data.slot < slot):
if slotProcessed notin flags or state.data.slot != slot: if slotProcessed notin flags or state.data.slot != slot:
@ -220,10 +309,17 @@ proc state_transition_slots(
proc state_transition_block*( proc state_transition_block*(
preset: RuntimePreset, preset: RuntimePreset,
#state: var phase0.HashedBeaconState, signedBlock: phase0.SomeSignedBeaconBlock, state: var SomeHashedBeaconState,
state: var (phase0.HashedBeaconState | altair.HashedBeaconState), signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock, signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock |
cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags, phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock,
rollback: RollbackHashedProc): bool {.nbench.} = cache: var StateCache, flags: UpdateFlags, rollback: RollbackHashedProc):
bool {.nbench.} =
## `rollback` is called if the transition fails and the given state has been
## partially changed. If a temporary state was given to `state_transition`,
## it is safe to use `noRollback` and leave it broken, else the state
## object should be rolled back to a consistent state. If the transition fails
## before the state has been updated, `rollback` will not be called.
# Block updates - these happen when there's a new block being suggested # Block updates - these happen when there's a new block being suggested
# by the block proposer. Every actor in the network will update its state # by the block proposer. Every actor in the network will update its state
# according to the contents of this block - but first they will validate # according to the contents of this block - but first they will validate
@ -270,15 +366,92 @@ proc state_transition_block*(
true true
proc state_transition_block*(
preset: RuntimePreset,
state: var ForkedHashedBeaconState,
signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock |
phase0.TrustedSignedBeaconBlock |
altair.SignedBeaconBlock | altair.SigVerifiedSignedBeaconBlock,
cache: var StateCache, flags: UpdateFlags,
rollback: RollbackHashedProc, altairForkSlot: Slot): bool {.nbench.} =
## `rollback` is called if the transition fails and the given state has been
## partially changed. If a temporary state was given to `state_transition`,
## it is safe to use `noRollback` and leave it broken, else the state
## object should be rolled back to a consistent state. If the transition fails
## before the state has been updated, `rollback` will not be called.
# Ensure state_transition_block()-only callers trigger this
maybeUpgradeStateToAltair(state, altairForkSlot)
case state.beaconStateFork:
of forkPhase0: state_transition_block(
preset, state.hbsPhase0, signedBlock, cache, flags, rollback)
of forkAltair: state_transition_block(
preset, state.hbsAltair, signedBlock, cache, flags, rollback)
proc state_transition*( proc state_transition*(
preset: RuntimePreset, preset: RuntimePreset,
#state: var phase0.HashedBeaconState, signedBlock: phase0.SomeSignedBeaconBlock, state: var ForkedHashedBeaconState,
state: var (phase0.HashedBeaconState | altair.HashedBeaconState), signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock, signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock |
phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock,
cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags, cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags,
rollback: RollbackHashedProc): bool {.nbench.} = rollback: RollbackHashedProc,
if not state_transition_slots(preset, state, signedBlock, cache, rewards, flags): altairForkEpoch: Epoch = FAR_FUTURE_EPOCH): bool {.nbench.} =
## Apply a block to the state, advancing the slot counter as necessary. The
## given state must be of a lower slot, or, in case the `slotProcessed` flag
## is set, can be the slot state of the same slot as the block (where the
## slot state is the state without any block applied). To create a slot state,
## advance the state corresponding to the the parent block using
## `process_slots`.
##
## To run the state transition function in preparation for block production,
## use `makeBeaconBlock` instead.
##
## `rollback` is called if the transition fails and the given state has been
## partially changed. If a temporary state was given to `state_transition`,
## it is safe to use `noRollback` and leave it broken, else the state
## object should be rolled back to a consistent state. If the transition fails
## before the state has been updated, `rollback` will not be called.
let slot = signedBlock.message.slot
if not (getStateField(state, slot) < slot):
if slotProcessed notin flags or getStateField(state, slot) != slot:
notice "State must precede block",
state_root = shortLog(getStateRoot(state)),
current_slot = getStateField(state, slot),
blck = shortLog(signedBlock)
return false
if not state_transition_slots(
preset, state, signedBlock, cache, rewards, flags,
altairForkEpoch.compute_start_slot_at_epoch):
return false return false
state_transition_block(preset, state, signedBlock, cache, rewards, flags, rollback) state_transition_block(
preset, state, signedBlock, cache, flags, rollback,
altairForkEpoch.compute_start_slot_at_epoch)
proc state_transition*(
preset: RuntimePreset,
state: var SomeHashedBeaconState,
signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock |
phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock,
cache: var StateCache, rewards: var RewardInfo, flags: UpdateFlags,
rollback: RollbackHashedProc): bool =
# Does not follow hard forks; suitable only where that's irrelevant.
# TODO remove when callers gone
let slot = signedBlock.message.slot
if not (state.data.slot < slot):
if slotProcessed notin flags or state.data.slot != slot:
notice "State must precede block",
state_root = shortLog(state.root),
current_slot = state.data.slot,
blck = shortLog(signedBlock)
return false
if not state_transition_slots(
preset, state, signedBlock, cache, rewards, flags):
return false
state_transition_block(
preset, state, signedBlock, cache, flags, rollback)
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#preparing-for-a-beaconblock # https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#preparing-for-a-beaconblock
proc makeBeaconBlock*( proc makeBeaconBlock*(

View File

@ -412,6 +412,13 @@ proc process_block*(
ok() ok()
proc process_block*(
preset: RuntimePreset,
state: var altair.BeaconState, blck: SomePhase0Block, flags: UpdateFlags,
cache: var StateCache): Result[void, cstring] {.nbench.} =
# The transition-triggering block creates, not acts on, an Altair state
err("process_block: Altair state with Phase 0 block")
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#block-processing # https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.6/specs/altair/beacon-chain.md#block-processing
# TODO workaround for https://github.com/nim-lang/Nim/issues/18095 # TODO workaround for https://github.com/nim-lang/Nim/issues/18095
# copy of datatypes/altair.nim # copy of datatypes/altair.nim
@ -432,3 +439,9 @@ proc process_block*(
? process_sync_committee(state, blck.body.sync_aggregate, cache) # [New in Altair] ? process_sync_committee(state, blck.body.sync_aggregate, cache) # [New in Altair]
ok() ok()
proc process_block*(
preset: RuntimePreset,
state: var phase0.BeaconState, blck: SomeAltairBlock, flags: UpdateFlags,
cache: var StateCache): Result[void, cstring] {.nbench.}=
err("process_block: Phase 0 state with Altair block")

View File

@ -208,7 +208,7 @@ proc cmdBench(conf: DbConf, runtimePreset: RuntimePreset) =
if conf.resetCache: if conf.resetCache:
cache = StateCache() cache = StateCache()
if not state_transition_block( if not state_transition_block(
runtimePreset, state[].data, b, cache, rewards, {}, noRollback): runtimePreset, state[].data, b, cache, {}, noRollback):
dump("./", b) dump("./", b)
echo "State transition failed (!)" echo "State transition failed (!)"
quit 1 quit 1
@ -530,7 +530,7 @@ proc cmdValidatorPerf(conf: DbConf, runtimePreset: RuntimePreset) =
processEpoch() processEpoch()
if not state_transition_block( if not state_transition_block(
runtimePreset, state[].data, blck, cache, rewards, {}, noRollback): runtimePreset, state[].data, blck, cache, {}, noRollback):
echo "State transition failed (!)" echo "State transition failed (!)"
quit 1 quit 1
@ -757,7 +757,7 @@ proc cmdValidatorDb(conf: DbConf, runtimePreset: RuntimePreset) =
processEpoch() processEpoch()
if not state_transition_block( if not state_transition_block(
runtimePreset, state[].data, blck, cache, rewards, {}, noRollback): runtimePreset, state[].data, blck, cache, {}, noRollback):
echo "State transition failed (!)" echo "State transition failed (!)"
quit 1 quit 1

View File

@ -26,7 +26,6 @@ import # Unit test
./test_interop, ./test_interop,
./test_peer_pool, ./test_peer_pool,
./test_ssz, ./test_ssz,
./test_state_transition,
./test_statediff, ./test_statediff,
./test_sync_manager, ./test_sync_manager,
./test_zero_signature, ./test_zero_signature,

View File

@ -23,4 +23,5 @@ import
./test_fixture_operations_proposer_slashings, ./test_fixture_operations_proposer_slashings,
./test_fixture_operations_sync_committee, ./test_fixture_operations_sync_committee,
./test_fixture_operations_voluntary_exit, ./test_fixture_operations_voluntary_exit,
./test_fixture_fork ./test_fixture_fork,
./test_fixture_transition

View File

@ -36,7 +36,7 @@ proc runTest(identifier: string) =
parseTest(testDir/"pre.ssz_snappy", SSZ, phase0.BeaconState)) parseTest(testDir/"pre.ssz_snappy", SSZ, phase0.BeaconState))
postState = newClone( postState = newClone(
parseTest(testDir/"post.ssz_snappy", SSZ, altair.BeaconState)) parseTest(testDir/"post.ssz_snappy", SSZ, altair.BeaconState))
upgradedState = newClone(upgrade_to_altair(preState[])) upgradedState = upgrade_to_altair(preState[])
check: upgradedState[].hash_tree_root() == postState[].hash_tree_root() check: upgradedState[].hash_tree_root() == postState[].hash_tree_root()
reportDiff(upgradedState, postState) reportDiff(upgradedState, postState)

View File

@ -0,0 +1,89 @@
# beacon_chain
# Copyright (c) 2021-Present 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.
{.used.}
import
chronicles, yaml,
# Standard library
os, sequtils,
# Status internal
faststreams, streams,
# Beacon chain internals
../../../beacon_chain/spec/[crypto, state_transition, presets],
../../../beacon_chain/spec/datatypes/[phase0, altair],
../../../beacon_chain/[extras, ssz],
# Test utilities
../../testutil,
../fixtures_utils
const
TransitionDir = SszTestsDir/const_preset/"altair"/"transition"/"core"/"pyspec_tests"
type
TransitionEpoch = object
post_fork: string
fork_epoch: int
blocks_count: int
fork_block {.defaultVal: 0.}: int
proc runTest(testName, testDir, unitTestName: string) =
# We wrap the tests in a proc to avoid running out of globals
# in the future: Nim supports up to 3500 globals
# but unittest with the macro/templates put everything as globals
# https://github.com/nim-lang/Nim/issues/12084#issue-486866402
let testPath = testDir / unitTestName
var transitionEpoch: TransitionEpoch
var s = openFileStream(testPath/"meta.yaml")
defer: close(s)
yaml.load(s, transitionEpoch)
proc `testImpl _ blck _ testName`() =
test testName & " - " & unitTestName & preset():
var
preState = newClone(parseTest(testPath/"pre.ssz_snappy", SSZ, phase0.BeaconState))
sdPreState = (ref ForkedHashedBeaconState)(hbsPhase0: phase0.HashedBeaconState(
data: preState[], root: hash_tree_root(preState[])), beaconStateFork: forkPhase0)
cache = StateCache()
rewards = RewardInfo()
# In test cases with more than 10 blocks the first 10 aren't 0-prefixed,
# so purely lexicographic sorting wouldn't sort properly.
let numBlocks = toSeq(walkPattern(testPath/"blocks_*.ssz_snappy")).len
for i in 0 ..< numBlocks:
let inBeforeTimes = i <= transitionEpoch.fork_block and transitionEpoch.fork_block > 0
if inBeforeTimes:
let blck = parseTest(testPath/"blocks_" & $i & ".ssz_snappy", SSZ, phase0.SignedBeaconBlock)
let success = state_transition(
defaultRuntimePreset, sdPreState[], blck,
cache, rewards,
flags = {skipStateRootValidation}, noRollback,
transitionEpoch.fork_epoch.Epoch)
doAssert success, "Failure when applying block " & $i
else:
let blck = parseTest(testPath/"blocks_" & $i & ".ssz_snappy", SSZ, altair.SignedBeaconBlock)
let success = state_transition(
defaultRuntimePreset, sdPreState[], blck,
cache, rewards,
flags = {skipStateRootValidation}, noRollback,
transitionEpoch.fork_epoch.Epoch)
doAssert success, "Failure when applying block " & $i
let postState = newClone(parseTest(testPath/"post.ssz_snappy", SSZ, altair.BeaconState))
when false:
reportDiff(sdPreState.data, postState)
doAssert getStateRoot(sdPreState[]) == postState[].hash_tree_root()
`testImpl _ blck _ testName`()
suite "Official - Altair - Transition " & preset():
for kind, path in walkDir(TransitionDir, true):
runTest("Official - Altair - Transition", TransitionDir, path)

View File

@ -1,131 +0,0 @@
# beacon_chain
# Copyright (c) 2018-2020 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.
{.used.}
import
chronicles,
unittest2,
./testutil, ./testblockutil,
../beacon_chain/spec/[beaconstate, datatypes, digest, crypto,
validator, state_transition, presets],
../beacon_chain/ssz
proc makeAttestation(
state: BeaconState, beacon_block_root: Eth2Digest,
validator_index: ValidatorIndex, cache: var StateCache): Attestation =
# The called functions don't use the extra-BeaconState parts of StateData.
let
stateData = (ref StateData)(
data: HashedBeaconState(data: state),
blck: BlockRef(root: beacon_block_root, slot: state.slot))
(committee, slot, index) =
find_beacon_committee(stateData[], validator_index, cache)
makeAttestation(stateData[], beacon_block_root, committee, slot, index,
validator_index, cache)
suite "Block processing" & preset():
## For now just test that we can compile and execute block processing with
## mock data.
let
# Genesis state with minimal number of deposits
# TODO bls verification is a bit of a bottleneck here
genesisState = newClone(initialize_hashed_beacon_state_from_eth1(
defaultRuntimePreset, Eth2Digest(), 0, makeInitialDeposits(), {}))
genesisBlock = get_initial_beacon_block(genesisState.data)
genesisRoot = hash_tree_root(genesisBlock.message)
setup:
var
state = newClone(genesisState[])
cache = StateCache()
rewards = RewardInfo()
test "Passes from genesis state, no block" & preset():
check:
process_slots(state[], state.data.slot + 1, cache, rewards)
state.data.slot == genesisState.data.slot + 1
test "Passes from genesis state, empty block" & preset():
var
previous_block_root = genesisBlock.root
new_block = makeTestBlock(state[], previous_block_root, cache)
let block_ok = state_transition(
defaultRuntimePreset, state[], new_block, cache, rewards, {}, noRollback)
check:
block_ok
state.data.slot == genesisState.data.slot + 1
test "Passes through epoch update, no block" & preset():
check:
process_slots(state[], Slot(SLOTS_PER_EPOCH), cache, rewards)
state.data.slot == genesisState.data.slot + SLOTS_PER_EPOCH
test "Passes through epoch update, empty block" & preset():
var
previous_block_root = genesisRoot
for i in 1..SLOTS_PER_EPOCH:
let new_block = makeTestBlock(state[], previous_block_root, cache)
let block_ok = state_transition(
defaultRuntimePreset, state[], new_block, cache, rewards, {}, noRollback)
check:
block_ok
previous_block_root = new_block.root
check:
state.data.slot == genesisState.data.slot + SLOTS_PER_EPOCH
test "Attestation gets processed at epoch" & preset():
var
previous_block_root = genesisRoot
# Slot 0 is a finalized slot - won't be making attestations for it..
check:
process_slots(state[], state.data.slot + 1, cache, rewards)
let
# Create an attestation for slot 1 signed by the only attester we have!
beacon_committee =
get_beacon_committee(state.data, state.data.slot, 0.CommitteeIndex, cache)
attestation = makeAttestation(
state.data, previous_block_root, beacon_committee[0], cache)
# Some time needs to pass before attestations are included - this is
# to let the attestation propagate properly to interested participants
check:
process_slots(
state[], GENESIS_SLOT + MIN_ATTESTATION_INCLUSION_DELAY + 1, cache,
rewards)
let
new_block = makeTestBlock(state[], previous_block_root, cache,
attestations = @[attestation]
)
check state_transition(
defaultRuntimePreset, state[], new_block, cache, rewards, {}, noRollback)
check:
# TODO epoch attestations can get multiplied now; clean up paths to
# enable exact 1-check again and keep finalization.
state.data.current_epoch_attestations.len >= 1
check:
process_slots(state[], Slot(191), cache, rewards)
# Would need to process more epochs for the attestation to be removed from
# the state! (per above bug)
#
# check:
# state.latest_attestations.len == 0