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:
parent
1a76007858
commit
8ebd496fbe
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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*(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
|
Loading…
Reference in New Issue