simplify state fork access pattern (#2912)

* simplify state fork access pattern

* fixes

* unsafeAddr needs to be dereferenced outside of case for best effect
* remove hash_tree_root of ForkedXxx (doesn't make sense)
* simplify state transition

* fix vc

* readd hash_tree_root(forkedbeaconblock)

* readd htr(fhbs) as well

...and add some protections to not hash the wrong items elsewhere
This commit is contained in:
Jacek Sieka 2021-09-28 20:08:03 +02:00 committed by GitHub
parent c510ffb16a
commit e5346e4e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 130 deletions

View File

@ -11,6 +11,13 @@
import
./ssz_codec,
../ssz/merkleization
../ssz/merkleization,
./datatypes/[phase0, altair]
export ssz_codec, merkleization
func hash_tree_root*(x: phase0.HashedBeaconState | altair.HashedBeaconState) {.
error: "HashedBeaconState should not be hashed".}
func hash_tree_root*(x: phase0.SomeSignedBeaconBlock | altair.SomeSignedBeaconBlock) {.
error: "SignedBeaconBlock should not be hashed".}

View File

@ -12,10 +12,11 @@ import
chronicles,
stew/[assign2, results],
../extras,
../spec/[beaconstate, helpers, state_transition_block, validator],
../spec/[
beaconstate, eth2_merkleization, helpers, state_transition_block, validator],
./datatypes/[phase0, altair, merge]
export extras, phase0, altair
export extras, phase0, altair, eth2_merkleization
type
BeaconStateFork* = enum
@ -133,7 +134,19 @@ template init*(T: type ForkedTrustedSignedBeaconBlock, blck: altair.TrustedSigne
template init*(T: type ForkedTrustedSignedBeaconBlock, blck: merge.TrustedSignedBeaconBlock): T =
T(kind: BeaconBlockFork.Merge, mergeBlock: blck)
# State-related functionality based on ForkedHashedBeaconState instead of BeaconState
# State-related functionality based on ForkedHashedBeaconState instead of HashedBeaconState
template withState*(x: ForkedHashedBeaconState, body: untyped): untyped =
case x.beaconStateFork
of forkPhase0:
template state: untyped {.inject.} = x.hbsPhase0
body
of forkAltair:
template state: untyped {.inject.} = x.hbsAltair
body
of forkMerge:
template state: untyped {.inject.} = x.hbsMerge
body
# Dispatch functions
func assign*(tgt: var ForkedHashedBeaconState, src: ForkedHashedBeaconState) =
@ -149,62 +162,31 @@ func assign*(tgt: var ForkedHashedBeaconState, src: ForkedHashedBeaconState) =
# with nimOldCaseObjects. This is infrequent.
tgt = src
template getStateField*(x, y: untyped): untyped =
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 forkPhase0: unsafeAddr (x.hbsPhase0.data.y)
of forkAltair: unsafeAddr (x.hbsAltair.data.y)
of forkMerge: unsafeAddr (x.hbsMerge.data.y))[]
of forkPhase0: unsafeAddr x.hbsPhase0.data.y
of forkAltair: unsafeAddr x.hbsAltair.data.y
of forkMerge: unsafeAddr x.hbsMerge.data.y)[]
template getStateRoot*(x: ForkedHashedBeaconState): Eth2Digest =
case x.beaconStateFork:
of forkPhase0: x.hbsPhase0.root
of forkAltair: x.hbsAltair.root
of forkMerge: x.hbsMerge.root
func getStateRoot*(x: ForkedHashedBeaconState): Eth2Digest =
withState(x): state.root
func setStateRoot*(x: var ForkedHashedBeaconState, root: Eth2Digest) =
case x.beaconStateFork:
of forkPhase0: x.hbsPhase0.root = root
of forkAltair: x.hbsAltair.root = root
of forkMerge: x.hbsMerge.root = root
withState(x): state.root = 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)
of forkMerge: hash_tree_root(x.hbsMerge.data)
template hash_tree_root*(blk: ForkedBeaconBlock): Eth2Digest =
case blk.kind
of BeaconBlockFork.Phase0:
hash_tree_root(blk.phase0Block)
of BeaconBlockFork.Altair:
hash_tree_root(blk.altairBlock)
of BeaconBlockFork.Merge:
hash_tree_root(blk.mergeBlock)
template proposer_index*(blk: ForkedBeaconBlock): uint64 =
case blk.kind
of BeaconBlockFork.Phase0:
blk.phase0Block.proposer_index
of BeaconBlockFork.Altair:
blk.altairBlock.proposer_index
of BeaconBlockFork.Merge:
blk.mergeBlock.proposer_index
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 =
case state.beaconStateFork:
of forkPhase0:
get_active_validator_indices_len(state.hbsPhase0.data, epoch)
of forkAltair:
get_active_validator_indices_len(state.hbsAltair.data, epoch)
of forkMerge:
get_active_validator_indices_len(state.hbsMerge.data, epoch)
withState(state):
get_active_validator_indices_len(state.data, epoch)
func get_beacon_committee*(
state: ForkedHashedBeaconState, slot: Slot, index: CommitteeIndex,
@ -214,56 +196,41 @@ func get_beacon_committee*(
# 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.
case state.beaconStateFork:
of forkPhase0: get_beacon_committee(state.hbsPhase0.data, slot, index, cache)
of forkAltair: get_beacon_committee(state.hbsAltair.data, slot, index, cache)
of forkMerge: get_beacon_committee(state.hbsMerge.data, slot, index, cache)
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
case state.beaconStateFork:
of forkPhase0: get_beacon_committee_len(state.hbsPhase0.data, slot, index, cache)
of forkAltair: get_beacon_committee_len(state.hbsAltair.data, slot, index, cache)
of forkMerge: get_beacon_committee_len(state.hbsMerge.data, slot, index, cache)
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``.
case state.beaconStateFork:
of forkPhase0: get_committee_count_per_slot(state.hbsPhase0.data, epoch, cache)
of forkAltair: get_committee_count_per_slot(state.hbsAltair.data, epoch, cache)
of forkMerge: get_committee_count_per_slot(state.hbsMerge.data, epoch, cache)
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] =
case state.beaconStateFork:
of forkPhase0: get_beacon_proposer_index(state.hbsPhase0.data, cache, slot)
of forkAltair: get_beacon_proposer_index(state.hbsAltair.data, cache, slot)
of forkMerge: get_beacon_proposer_index(state.hbsMerge.data, cache, slot)
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] =
case state.beaconStateFork:
of forkPhase0:
cache.get_shuffled_active_validator_indices(state.hbsPhase0.data, epoch)
of forkAltair:
cache.get_shuffled_active_validator_indices(state.hbsAltair.data, epoch)
of forkMerge:
cache.get_shuffled_active_validator_indices(state.hbsMerge.data, epoch)
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``.
case state.beaconStateFork:
of forkPhase0: get_block_root_at_slot(state.hbsPhase0.data, slot)
of forkAltair: get_block_root_at_slot(state.hbsAltair.data, slot)
of forkMerge: get_block_root_at_slot(state.hbsMerge.data, slot)
withState(state):
get_block_root_at_slot(state.data, slot)
proc get_attesting_indices*(state: ForkedHashedBeaconState;
data: AttestationData;
@ -288,43 +255,28 @@ proc get_attesting_indices*(state: ForkedHashedBeaconState;
proc check_attester_slashing*(
state: var ForkedHashedBeaconState; attester_slashing: SomeAttesterSlashing;
flags: UpdateFlags): Result[seq[ValidatorIndex], cstring] =
case state.beaconStateFork:
of forkPhase0:
check_attester_slashing(state.hbsPhase0.data, attester_slashing, flags)
of forkAltair:
check_attester_slashing(state.hbsAltair.data, attester_slashing, flags)
of forkMerge:
check_attester_slashing(state.hbsMerge.data, attester_slashing, flags)
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] =
case state.beaconStateFork:
of forkPhase0:
check_proposer_slashing(state.hbsPhase0.data, proposer_slashing, flags)
of forkAltair:
check_proposer_slashing(state.hbsAltair.data, proposer_slashing, flags)
of forkMerge:
check_proposer_slashing(state.hbsMerge.data, proposer_slashing, flags)
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] =
case state.beaconStateFork:
of forkPhase0:
check_voluntary_exit(cfg, state.hbsPhase0.data, signed_voluntary_exit, flags)
of forkAltair:
check_voluntary_exit(cfg, state.hbsAltair.data, signed_voluntary_exit, flags)
of forkMerge:
check_voluntary_exit(cfg, state.hbsMerge.data, signed_voluntary_exit, flags)
withState(state):
check_voluntary_exit(cfg, state.data, signed_voluntary_exit, flags)
# Derived utilities
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_current_epoch
func get_current_epoch*(stateData: ForkedHashedBeaconState): Epoch =
func get_current_epoch*(x: ForkedHashedBeaconState): Epoch =
## Return the current epoch.
getStateField(stateData, slot).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 =
@ -386,8 +338,18 @@ template withBlck*(x: ForkedBeaconBlock | ForkedSignedBeaconBlock | ForkedTruste
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 =
withBlck(x): blck.message.y
# 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
@ -399,7 +361,7 @@ template root*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): Eth
withBlck(x): blck.root
template slot*(x: ForkedSignedBeaconBlock | ForkedTrustedSignedBeaconBlock): Slot =
getForkedBlockField(x, slot)
withBlck(x): blck.message.slot
template shortLog*(x: ForkedBeaconBlock): auto =
withBlck(x): shortLog(blck)

View File

@ -212,34 +212,15 @@ proc process_slots*(
# Update the state so its slot matches that of the block
while getStateField(state, slot) < slot:
case state.beaconStateFork:
of forkPhase0:
withState(state):
advance_slot(
cfg,state.hbsPhase0.data, getStateRoot(state), flags, cache, rewards)
cfg, state.data, state.root, flags, cache, rewards)
if skipLastStateRootCalculation notin flags or
getStateField(state, slot) < slot:
state.data.slot < slot:
# Don't update state root for the slot of the block if going to process
# block after
state.hbsPhase0.root = hash_tree_root(state)
of forkAltair:
advance_slot(
cfg, state.hbsAltair.data, state.hbsAltair.root, flags, cache, rewards)
if skipLastStateRootCalculation notin flags or
getStateField(state, slot) < slot:
# Don't update state root for the slot of the block if going to process
# block after
state.hbsAltair.root = hash_tree_root(state)
of forkMerge:
advance_slot(
cfg, state.hbsMerge.data, state.hbsMerge.root, flags, cache, rewards)
if skipLastStateRootCalculation notin flags or
getStateField(state, slot) < slot:
# Don't update state root for the slot of the block if going to process
# block after
state.hbsMerge.root = hash_tree_root(state)
state.root = hash_tree_root(state.data)
maybeUpgradeStateToAltair(cfg, state)
@ -316,13 +297,8 @@ proc state_transition_block*(
# Ensure state_transition_block()-only callers trigger this
maybeUpgradeStateToAltair(cfg, state)
let success = case state.beaconStateFork:
of forkPhase0: state_transition_block_aux(
cfg, state.hbsPhase0, signedBlock, cache, flags)
of forkAltair: state_transition_block_aux(
cfg, state.hbsAltair, signedBlock, cache, flags)
of forkMerge: state_transition_block_aux(
cfg, state.hbsMerge, signedBlock, cache, flags)
let success = withState(state):
state_transition_block_aux(cfg, state, signedBlock, cache, flags)
if not success:
rollback(state)

View File

@ -35,7 +35,7 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
err_name = exc.name, err_msg = exc.msg
return
let blockRoot = hash_tree_root(beaconBlock)
let blockRoot = withBlck(beaconBlock): hash_tree_root(blck)
# TODO: signing_root is recomputed in signBlockProposal just after
let signing_root = compute_block_root(fork, genesisRoot, slot,
blockRoot)