From a086cf01ac852abc815037a9e69df84769cbff6b Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Fri, 5 Nov 2021 08:34:34 +0100 Subject: [PATCH] altair fork handling cleanups (#3050) * fix stack overflow crash in REST/debug/getStateV2 * introduce `ForkyXxx` for generic type matching of `Xxx` across branches (SomeHashedBeaconState -> ForkyHashedBeaconState et al) - `Some` is already used for other types of type classes * consolidate function naming in BeaconChainDB, use some generics * import `forks.nim` from other spec modules and move `Forked*` helpers around to resolve circular imports * remove `ForkedBeaconState`, use `ForkedHashedBeaconState` throughout (less data shuffling between the types) * fix several cases of states being stored on stack in tests, causing random failures on some platforms * remove reading json support from ncli - this should be ported to the rest json reading instead (doesn't currently work because stack sizes) --- beacon_chain/beacon_chain_db.nim | 156 ++----- beacon_chain/beacon_chain_db_immutable.nim | 2 +- .../attestation_pool.nim | 5 +- .../block_clearance.nim | 49 +-- .../block_pools_types.nim | 2 +- .../block_quarantine.nim | 5 +- .../consensus_object_pools/blockchain_dag.nim | 44 +- .../consensus_object_pools/exit_pool.nim | 2 +- .../gossip_processing/block_processor.nim | 6 +- beacon_chain/networking/eth2_network.nim | 6 +- beacon_chain/nimbus_beacon_node.nim | 2 +- beacon_chain/rpc/rest_beacon_api.nim | 2 +- beacon_chain/rpc/rest_debug_api.nim | 3 +- beacon_chain/rpc/rpc_beacon_api.nim | 2 +- beacon_chain/spec/beaconstate.nim | 74 ++-- beacon_chain/spec/datatypes/altair.nim | 11 + beacon_chain/spec/datatypes/base.nim | 7 + beacon_chain/spec/datatypes/merge.nim | 14 +- beacon_chain/spec/datatypes/phase0.nim | 11 + .../eth2_apis/eth2_rest_serialization.nim | 76 ++-- .../spec/eth2_apis/rest_debug_calls.nim | 38 +- beacon_chain/spec/eth2_apis/rest_types.nim | 2 +- beacon_chain/spec/forks.nim | 413 ++++++++++-------- beacon_chain/spec/helpers.nim | 53 +-- beacon_chain/spec/signatures_batch.nim | 3 +- beacon_chain/spec/state_transition.nim | 14 +- beacon_chain/spec/state_transition_block.nim | 41 +- beacon_chain/spec/state_transition_epoch.nim | 16 +- beacon_chain/spec/validator.nim | 69 ++- beacon_chain/sszdump.nim | 9 +- nbench/scenarios.nim | 4 +- ncli/ncli.nim | 8 +- ncli/ncli_db.nim | 24 +- research/stack_sizes.nim | 2 +- research/state_sim.nim | 2 +- tests/mocking/mock_attestations.nim | 4 +- tests/mocking/mock_blocks.nim | 13 +- tests/test_attestation_pool.nim | 2 +- tests/test_beacon_chain_db.nim | 34 +- tests/test_block_pool.nim | 14 +- tests/test_gossip_validation.nim | 4 +- tests/test_statediff.nim | 2 +- tests/testblockutil.nim | 9 +- 43 files changed, 625 insertions(+), 634 deletions(-) diff --git a/beacon_chain/beacon_chain_db.nim b/beacon_chain/beacon_chain_db.nim index d4d8c81ac..632411b36 100644 --- a/beacon_chain/beacon_chain_db.nim +++ b/beacon_chain/beacon_chain_db.nim @@ -13,7 +13,7 @@ import serialization, chronicles, snappy, eth/db/[kvstore, kvstore_sqlite3], ./networking/network_metadata, ./beacon_chain_db_immutable, - ./spec/[eth2_ssz_serialization, eth2_merkleization, state_transition], + ./spec/[eth2_ssz_serialization, eth2_merkleization, forks, state_transition], ./spec/datatypes/[phase0, altair, merge], ./filepath @@ -93,7 +93,7 @@ type altairBlocks: KvStoreRef # BlockRoot -> altair.TrustedBeaconBlock mergeBlocks: KvStoreRef # BlockRoot -> merge.TrustedBeaconBlock stateRoots: KvStoreRef # (Slot, BlockRoot) -> StateRoot - statesNoVal: KvStoreRef # StateRoot -> BeaconStateNoImmutableValidators + statesNoVal: KvStoreRef # StateRoot -> Phase0BeaconStateNoImmutableValidators altairStatesNoVal: KvStoreRef # StateRoot -> AltairBeaconStateNoImmutableValidators mergeStatesNoVal: KvStoreRef # StateRoot -> MergeBeaconStateNoImmutableValidators stateDiffs: KvStoreRef ##\ @@ -120,7 +120,7 @@ type ## past the weak subjectivity period. kBlockSlotStateRoot ## BlockSlot -> state_root mapping - kGenesisBlockRoot + kGenesisBlock ## Immutable reference to the network genesis state ## (needed for satisfying requests to the beacon node API). kEth1PersistedTo # Obsolete @@ -168,7 +168,7 @@ func subkey(kind: type phase0.BeaconState, key: Eth2Digest): auto = subkey(kHashToState, key.data) func subkey( - kind: type BeaconStateNoImmutableValidators, key: Eth2Digest): auto = + kind: type Phase0BeaconStateNoImmutableValidators, key: Eth2Digest): auto = subkey(kHashToStateOnlyMutableValidators, key.data) func subkey(kind: type phase0.SignedBeaconBlock, key: Eth2Digest): auto = @@ -508,27 +508,34 @@ proc updateImmutableValidators*( db.immutableValidatorsDb.add immutableValidator db.immutableValidators.add immutableValidator +template toBeaconStateNoImmutableValidators(state: phase0.BeaconState): + Phase0BeaconStateNoImmutableValidators = + isomorphicCast[Phase0BeaconStateNoImmutableValidators](state) + +template toBeaconStateNoImmutableValidators(state: altair.BeaconState): + AltairBeaconStateNoImmutableValidators = + isomorphicCast[AltairBeaconStateNoImmutableValidators](state) + +template toBeaconStateNoImmutableValidators(state: merge.BeaconState): + MergeBeaconStateNoImmutableValidators = + isomorphicCast[MergeBeaconStateNoImmutableValidators](state) + proc putState*(db: BeaconChainDB, key: Eth2Digest, value: phase0.BeaconState) = db.updateImmutableValidators(value.validators.asSeq()) db.statesNoVal.putSnappySSZ( - key.data, - isomorphicCast[BeaconStateNoImmutableValidators](value)) + key.data, toBeaconStateNoImmutableValidators(value)) proc putState*(db: BeaconChainDB, key: Eth2Digest, value: altair.BeaconState) = db.updateImmutableValidators(value.validators.asSeq()) db.altairStatesNoVal.putSnappySSZ( - key.data, - isomorphicCast[AltairBeaconStateNoImmutableValidators](value)) + key.data, toBeaconStateNoImmutableValidators(value)) proc putState*(db: BeaconChainDB, key: Eth2Digest, value: merge.BeaconState) = db.updateImmutableValidators(value.validators.asSeq()) db.mergeStatesNoVal.putSnappySSZ( - key.data, - isomorphicCast[MergeBeaconStateNoImmutableValidators](value)) + key.data, toBeaconStateNoImmutableValidators(value)) -proc putState*( - db: BeaconChainDB, - value: phase0.BeaconState | altair.BeaconState | merge.BeaconState) = +proc putState*(db: BeaconChainDB, value: ForkyBeaconState) = db.putState(hash_tree_root(value), value) # For testing rollback @@ -579,14 +586,14 @@ proc putHeadBlock*(db: BeaconChainDB, key: Eth2Digest) = proc putTailBlock*(db: BeaconChainDB, key: Eth2Digest) = db.keyValues.putRaw(subkey(kTailBlock), key) -proc putGenesisBlockRoot*(db: BeaconChainDB, key: Eth2Digest) = - db.keyValues.putRaw(subkey(kGenesisBlockRoot), key) +proc putGenesisBlock*(db: BeaconChainDB, key: Eth2Digest) = + db.keyValues.putRaw(subkey(kGenesisBlock), key) proc putEth2FinalizedTo*(db: BeaconChainDB, eth1Checkpoint: DepositContractSnapshot) = db.keyValues.putSnappySSZ(subkey(kDepositsFinalizedByEth2), eth1Checkpoint) -proc getBlock(db: BeaconChainDBV0, key: Eth2Digest): Opt[phase0.TrustedSignedBeaconBlock] = +proc getPhase0Block(db: BeaconChainDBV0, key: Eth2Digest): Opt[phase0.TrustedSignedBeaconBlock] = # We only store blocks that we trust in the database result.ok(default(phase0.TrustedSignedBeaconBlock)) if db.backend.getSnappySSZ( @@ -596,12 +603,12 @@ proc getBlock(db: BeaconChainDBV0, key: Eth2Digest): Opt[phase0.TrustedSignedBea # set root after deserializing (so it doesn't get zeroed) result.get().root = key -proc getBlock*(db: BeaconChainDB, key: Eth2Digest): +proc getPhase0Block*(db: BeaconChainDB, key: Eth2Digest): Opt[phase0.TrustedSignedBeaconBlock] = # We only store blocks that we trust in the database result.ok(default(phase0.TrustedSignedBeaconBlock)) if db.blocks.getSnappySSZ(key.data, result.get) != GetResult.found: - result = db.v0.getBlock(key) + result = db.v0.getPhase0Block(key) else: # set root after deserializing (so it doesn't get zeroed) result.get().root = key @@ -628,7 +635,7 @@ proc getMergeBlock*(db: BeaconChainDB, key: Eth2Digest): proc getStateOnlyMutableValidators( immutableValidators: openArray[ImmutableValidatorData2], - store: KvStoreRef, key: openArray[byte], output: var phase0.BeaconState, + store: KvStoreRef, key: openArray[byte], output: var ForkyBeaconState, rollback: RollbackProc): bool = ## Load state into `output` - BeaconState is large so we want to avoid ## re-allocating it if possible @@ -641,92 +648,7 @@ proc getStateOnlyMutableValidators( # TODO RVO is inefficient for large objects: # https://github.com/nim-lang/Nim/issues/13879 - case store.getSnappySSZ( - key, isomorphicCast[BeaconStateNoImmutableValidators](output)) - of GetResult.found: - let numValidators = output.validators.len - doAssert immutableValidators.len >= numValidators - - for i in 0 ..< numValidators: - let - # Bypass hash cache invalidation - dstValidator = addr output.validators.data[i] - - assign( - dstValidator.pubkey, - immutableValidators[i].pubkey.loadValid().toPubKey()) - assign( - dstValidator.withdrawal_credentials, - immutableValidators[i].withdrawal_credentials) - - output.validators.resetCache() - - true - of GetResult.notFound: - false - of GetResult.corrupted: - rollback() - false - -proc getAltairStateOnlyMutableValidators( - immutableValidators: openArray[ImmutableValidatorData2], - store: KvStoreRef, key: openArray[byte], output: var altair.BeaconState, - rollback: RollbackProc): bool = - ## Load state into `output` - BeaconState is large so we want to avoid - ## re-allocating it if possible - ## Return `true` iff the entry was found in the database and `output` was - ## overwritten. - ## Rollback will be called only if output was partially written - if it was - ## not found at all, rollback will not be called - # TODO rollback is needed to deal with bug - use `noRollback` to ignore: - # https://github.com/nim-lang/Nim/issues/14126 - # TODO RVO is inefficient for large objects: - # https://github.com/nim-lang/Nim/issues/13879 - - case store.getSnappySSZ( - key, isomorphicCast[AltairBeaconStateNoImmutableValidators](output)) - of GetResult.found: - let numValidators = output.validators.len - doAssert immutableValidators.len >= numValidators - - for i in 0 ..< numValidators: - let - # Bypass hash cache invalidation - dstValidator = addr output.validators.data[i] - - assign( - dstValidator.pubkey, - immutableValidators[i].pubkey.loadValid().toPubKey()) - assign( - dstValidator.withdrawal_credentials, - immutableValidators[i].withdrawal_credentials) - - output.validators.resetCache() - - true - of GetResult.notFound: - false - of GetResult.corrupted: - rollback() - false - -proc getMergeStateOnlyMutableValidators( - immutableValidators: openArray[ImmutableValidatorData2], - store: KvStoreRef, key: openArray[byte], output: var merge.BeaconState, - rollback: RollbackProc): bool = - ## Load state into `output` - BeaconState is large so we want to avoid - ## re-allocating it if possible - ## Return `true` iff the entry was found in the database and `output` was - ## overwritten. - ## Rollback will be called only if output was partially written - if it was - ## not found at all, rollback will not be called - # TODO rollback is needed to deal with bug - use `noRollback` to ignore: - # https://github.com/nim-lang/Nim/issues/14126 - # TODO RVO is inefficient for large objects: - # https://github.com/nim-lang/Nim/issues/13879 - - case store.getSnappySSZ( - key, isomorphicCast[MergeBeaconStateNoImmutableValidators](output)) + case store.getSnappySSZ(key, toBeaconStateNoImmutableValidators(output)) of GetResult.found: let numValidators = output.validators.len doAssert immutableValidators.len >= numValidators @@ -765,11 +687,11 @@ proc getState( # from `stateStore`. We will try to read the state from all these locations. if getStateOnlyMutableValidators( immutableValidators, db.stateStore, - subkey(BeaconStateNoImmutableValidators, key), output, rollback): + subkey(Phase0BeaconStateNoImmutableValidators, key), output, rollback): return true if getStateOnlyMutableValidators( immutableValidators, db.backend, - subkey(BeaconStateNoImmutableValidators, key), output, rollback): + subkey(Phase0BeaconStateNoImmutableValidators, key), output, rollback): return true case db.backend.getSnappySSZ(subkey(phase0.BeaconState, key), output) @@ -800,7 +722,7 @@ proc getState*( else: true -proc getAltairState*( +proc getState*( db: BeaconChainDB, key: Eth2Digest, output: var altair.BeaconState, rollback: RollbackProc): bool = ## Load state into `output` - BeaconState is large so we want to avoid @@ -813,10 +735,10 @@ proc getAltairState*( # https://github.com/nim-lang/Nim/issues/14126 # TODO RVO is inefficient for large objects: # https://github.com/nim-lang/Nim/issues/13879 - getAltairStateOnlyMutableValidators( + getStateOnlyMutableValidators( db.immutableValidators, db.altairStatesNoVal, key.data, output, rollback) -proc getMergeState*( +proc getState*( db: BeaconChainDB, key: Eth2Digest, output: var merge.BeaconState, rollback: RollbackProc): bool = ## Load state into `output` - BeaconState is large so we want to avoid @@ -829,7 +751,7 @@ proc getMergeState*( # https://github.com/nim-lang/Nim/issues/14126 # TODO RVO is inefficient for large objects: # https://github.com/nim-lang/Nim/issues/13879 - getMergeStateOnlyMutableValidators( + getStateOnlyMutableValidators( db.immutableValidators, db.mergeStatesNoVal, key.data, output, rollback) proc getStateRoot(db: BeaconChainDBV0, @@ -863,12 +785,12 @@ proc getTailBlock*(db: BeaconChainDB): Opt[Eth2Digest] = db.keyValues.getRaw(subkey(kTailBlock), Eth2Digest) or db.v0.getTailBlock() -proc getGenesisBlockRoot(db: BeaconChainDBV0): Opt[Eth2Digest] = - db.backend.getRaw(subkey(kGenesisBlockRoot), Eth2Digest) +proc getGenesisBlock(db: BeaconChainDBV0): Opt[Eth2Digest] = + db.backend.getRaw(subkey(kGenesisBlock), Eth2Digest) -proc getGenesisBlockRoot*(db: BeaconChainDB): Opt[Eth2Digest] = - db.keyValues.getRaw(subkey(kGenesisBlockRoot), Eth2Digest) or - db.v0.getGenesisBlockRoot() +proc getGenesisBlock*(db: BeaconChainDB): Opt[Eth2Digest] = + db.keyValues.getRaw(subkey(kGenesisBlock), Eth2Digest) or + db.v0.getGenesisBlock() proc getEth2FinalizedTo(db: BeaconChainDBV0): Opt[DepositContractSnapshot] = result.ok(DepositContractSnapshot()) @@ -898,7 +820,7 @@ proc containsBlock*(db: BeaconChainDB, key: Eth2Digest): bool = db.containsBlockPhase0(key) proc containsState*(db: BeaconChainDBV0, key: Eth2Digest): bool = - let sk = subkey(BeaconStateNoImmutableValidators, key) + let sk = subkey(Phase0BeaconStateNoImmutableValidators, key) db.stateStore.contains(sk).expectDb() or db.backend.contains(sk).expectDb() or db.backend.contains(subkey(phase0.BeaconState, key)).expectDb() diff --git a/beacon_chain/beacon_chain_db_immutable.nim b/beacon_chain/beacon_chain_db_immutable.nim index cf627f81f..6bf084b09 100644 --- a/beacon_chain/beacon_chain_db_immutable.nim +++ b/beacon_chain/beacon_chain_db_immutable.nim @@ -17,7 +17,7 @@ import type # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#beaconstate # Memory-representation-equivalent to a phase0 BeaconState for in-place SSZ reading and writing - BeaconStateNoImmutableValidators* = object + Phase0BeaconStateNoImmutableValidators* = object # Versioning genesis_time*: uint64 genesis_validators_root*: Eth2Digest diff --git a/beacon_chain/consensus_object_pools/attestation_pool.nim b/beacon_chain/consensus_object_pools/attestation_pool.nim index 160d21cfb..7fb1c9340 100644 --- a/beacon_chain/consensus_object_pools/attestation_pool.nim +++ b/beacon_chain/consensus_object_pools/attestation_pool.nim @@ -374,8 +374,7 @@ proc addAttestation*(pool: var AttestationPool, proc addForkChoice*(pool: var AttestationPool, epochRef: EpochRef, blckRef: BlockRef, - blck: phase0.TrustedBeaconBlock | altair.TrustedBeaconBlock | - merge.TrustedBeaconBlock, + blck: ForkyTrustedBeaconBlock, wallSlot: Slot) = ## Add a verified block to the fork choice context let state = pool.forkChoice.process_block( @@ -510,7 +509,7 @@ proc score( bitsScore proc getAttestationsForBlock*(pool: var AttestationPool, - state: SomeHashedBeaconState, + state: ForkyHashedBeaconState, cache: var StateCache): seq[Attestation] = ## Retrieve attestations that may be added to a new block at the slot of the ## given state diff --git a/beacon_chain/consensus_object_pools/block_clearance.nim b/beacon_chain/consensus_object_pools/block_clearance.nim index d4f39ae81..2c6038f60 100644 --- a/beacon_chain/consensus_object_pools/block_clearance.nim +++ b/beacon_chain/consensus_object_pools/block_clearance.nim @@ -31,43 +31,6 @@ export results, ValidationResult logScope: topics = "clearance" -## At the GC-level, the GC is type-agnostic; it's all type-erased so -## casting between seq[Attestation] and seq[TrustedAttestation] will -## not disrupt GC operations. -## -## These SHOULD be used in function calls to avoid expensive temporary. -## see https://github.com/status-im/nimbus-eth2/pull/2250#discussion_r562010679 -template asSigVerified(x: phase0.SignedBeaconBlock): - phase0.SigVerifiedSignedBeaconBlock = - ## This converts a signed beacon block to a sig verified beacon clock. - ## This verifies that their bytes representation is the same. - isomorphicCast[phase0.SigVerifiedSignedBeaconBlock](x) - -template asSigVerified(x: altair.SignedBeaconBlock): - altair.SigVerifiedSignedBeaconBlock = - ## This converts a signed beacon block to a sig verified beacon clock. - ## This verifies that their bytes representation is the same. - isomorphicCast[altair.SigVerifiedSignedBeaconBlock](x) - -template asSigVerified(x: merge.SignedBeaconBlock): - merge.SigVerifiedSignedBeaconBlock = - ## This converts a signed beacon block to a sig verified beacon clock. - ## This verifies that their bytes representation is the same. - isomorphicCast[merge.SigVerifiedSignedBeaconBlock](x) - -# TODO aren't these in forks.nim? -template asTrusted(x: phase0.SignedBeaconBlock or phase0.SigVerifiedBeaconBlock): - phase0.TrustedSignedBeaconBlock = - ## This converts a sigverified beacon block to a trusted beacon clock. - ## This verifies that their bytes representation is the same. - isomorphicCast[phase0.TrustedSignedBeaconBlock](x) - -template asTrusted(x: altair.SignedBeaconBlock or altair.SigVerifiedBeaconBlock): - altair.TrustedSignedBeaconBlock = - ## This converts a sigverified beacon block to a trusted beacon clock. - ## This verifies that their bytes representation is the same. - isomorphicCast[altair.TrustedSignedBeaconBlock](x) - proc batchVerify(quarantine: QuarantineRef, sigs: openArray[SignatureSet]): bool = var secureRandomBytes: array[32, byte] quarantine.rng[].brHmacDrbgGenerate(secureRandomBytes) @@ -78,8 +41,7 @@ proc batchVerify(quarantine: QuarantineRef, sigs: openArray[SignatureSet]): bool proc addRawBlock*( dag: ChainDAGRef, quarantine: QuarantineRef, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signedBlock: ForkySignedBeaconBlock, onBlockAdded: OnPhase0BlockAdded | OnAltairBlockAdded | OnMergeBlockAdded ): Result[BlockRef, (ValidationResult, BlockError)] {.gcsafe.} @@ -144,8 +106,7 @@ proc resolveQuarantinedBlocks( proc addResolvedBlock( dag: ChainDAGRef, quarantine: QuarantineRef, state: var StateData, - trustedBlock: phase0.TrustedSignedBeaconBlock | altair.TrustedSignedBeaconBlock | - merge.TrustedSignedBeaconBlock, + trustedBlock: ForkyTrustedSignedBeaconBlock, parent: BlockRef, cache: var StateCache, onBlockAdded: OnPhase0BlockAdded | OnAltairBlockAdded | OnMergeBlockAdded, stateDataDur, sigVerifyDur, stateVerifyDur: Duration @@ -261,8 +222,7 @@ proc advanceClearanceState*(dag: ChainDAGRef) = proc addRawBlockKnownParent( dag: ChainDAGRef, quarantine: QuarantineRef, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signedBlock: ForkySignedBeaconBlock, parent: BlockRef, onBlockAdded: OnPhase0BlockAdded | OnAltairBlockAdded | OnMergeBlockAdded ): Result[BlockRef, (ValidationResult, BlockError)] = @@ -390,8 +350,7 @@ proc addRawBlockUnresolved( proc addRawBlock( dag: ChainDAGRef, quarantine: QuarantineRef, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signedBlock: ForkySignedBeaconBlock, onBlockAdded: OnPhase0BlockAdded | OnAltairBlockAdded | OnMergeBlockAdded ): Result[BlockRef, (ValidationResult, BlockError)] = ## Try adding a block to the chain, verifying first that it passes the state diff --git a/beacon_chain/consensus_object_pools/block_pools_types.nim b/beacon_chain/consensus_object_pools/block_pools_types.nim index 1e37323a2..416d02c9d 100644 --- a/beacon_chain/consensus_object_pools/block_pools_types.nim +++ b/beacon_chain/consensus_object_pools/block_pools_types.nim @@ -183,7 +183,7 @@ type ## block - we limit the number of held EpochRefs to put a cap on ## memory usage - forkDigests*: ForkDigestsRef + forkDigests*: ref ForkDigests ## Cached copy of the fork digests associated with the current ## database. We use a ref type to facilitate sharing this small ## value with other components which don't have access to the diff --git a/beacon_chain/consensus_object_pools/block_quarantine.nim b/beacon_chain/consensus_object_pools/block_quarantine.nim index 68316975b..e99d9acc1 100644 --- a/beacon_chain/consensus_object_pools/block_quarantine.nim +++ b/beacon_chain/consensus_object_pools/block_quarantine.nim @@ -12,6 +12,7 @@ import chronicles, stew/bitops2, eth/keys, + ../spec/forks, ../spec/datatypes/[phase0, altair, merge], ./block_pools_types @@ -102,9 +103,7 @@ func removeOrphan*( quarantine.orphansMerge.del((signedBlock.root, signedBlock.signature)) func isViableOrphan( - dag: ChainDAGRef, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock): bool = + dag: ChainDAGRef, signedBlock: ForkySignedBeaconBlock): bool = # The orphan must be newer than the finalization point so that its parent # either is the finalized block or more recent signedBlock.message.slot > dag.finalizedHead.slot diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 940618a20..a49439905 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -18,7 +18,10 @@ import ".."/beacon_chain_db, "."/[block_pools_types, block_quarantine, forkedbeaconstate_dbhelpers] -export block_pools_types, results +export + forks, block_pools_types, results, forkedbeaconstate_dbhelpers, + beacon_chain_db, + eth2_merkleization, eth2_ssz_serialization # https://github.com/ethereum/eth2.0-metrics/blob/master/metrics.md#interop-metrics declareGauge beacon_head_root, "Root of the head block of the beacon chain" @@ -46,9 +49,7 @@ declareGauge beacon_processed_deposits_total, "Number of total deposits included logScope: topics = "chaindag" proc putBlock*( - dag: ChainDAGRef, - signedBlock: phase0.TrustedSignedBeaconBlock | altair.TrustedSignedBeaconBlock | - merge.TrustedSignedBeaconBlock) = + dag: ChainDAGRef, signedBlock: ForkyTrustedSignedBeaconBlock) = dag.db.putBlock(signedBlock) proc updateStateData*( @@ -343,23 +344,18 @@ proc getStateData( if not root.isSome(): return false - case cfg.stateForkAtEpoch(bs.slot.epoch) - of BeaconStateFork.Merge: - if state.data.kind != BeaconStateFork.Merge: - state.data = (ref ForkedHashedBeaconState)(kind: BeaconStateFork.Merge)[] + let expectedFork = cfg.stateForkAtEpoch(bs.slot.epoch) + if state.data.kind != expectedFork: + state.data = (ref ForkedHashedBeaconState)(kind: expectedFork)[] - if not db.getMergeState(root.get(), state.data.mergeData.data, rollback): + case expectedFork + of BeaconStateFork.Merge: + if not db.getState(root.get(), state.data.mergeData.data, rollback): return false of BeaconStateFork.Altair: - if state.data.kind != BeaconStateFork.Altair: - state.data = (ref ForkedHashedBeaconState)(kind: BeaconStateFork.Altair)[] - - if not db.getAltairState(root.get(), state.data.altairData.data, rollback): + if not db.getState(root.get(), state.data.altairData.data, rollback): return false of BeaconStateFork.Phase0: - if state.data.kind != BeaconStateFork.Phase0: - state.data = (ref ForkedHashedBeaconState)(kind: BeaconStateFork.Phase0)[] - if not db.getState(root.get(), state.data.phase0Data.data, rollback): return false @@ -384,7 +380,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB, let tailRoot = tailBlockRoot.get() - tailBlock = db.getBlock(tailRoot).get() + tailBlock = db.getPhase0Block(tailRoot).get() tailRef = BlockRef.init(tailRoot, tailBlock.message) headRoot = headBlockRoot.get() @@ -392,9 +388,9 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB, tailRef else: let - genesisBlockRoot = db.getGenesisBlockRoot().expect( + genesisBlockRoot = db.getGenesisBlock().expect( "preInit should have initialized the database with a genesis block root") - genesisBlock = db.getBlock(genesisBlockRoot).expect( + genesisBlock = db.getPhase0Block(genesisBlockRoot).expect( "preInit should have initialized the database with a genesis block") BlockRef.init(genesisBlockRoot, genesisBlock.message) @@ -727,7 +723,7 @@ func getBlockBySlot*(dag: ChainDAGRef, slot: Slot): BlockRef = proc getForkedBlock*(dag: ChainDAGRef, blck: BlockRef): ForkedTrustedSignedBeaconBlock = case dag.cfg.blockForkAtEpoch(blck.slot.epoch) of BeaconBlockFork.Phase0: - let data = dag.db.getBlock(blck.root) + let data = dag.db.getPhase0Block(blck.root) if data.isOk(): return ForkedTrustedSignedBeaconBlock.init(data.get) of BeaconBlockFork.Altair: @@ -1324,9 +1320,9 @@ proc isInitialized*(T: type ChainDAGRef, db: BeaconChainDB): bool = return false let - headBlockPhase0 = db.getBlock(headBlockRoot.get()) + headBlockPhase0 = db.getPhase0Block(headBlockRoot.get()) headBlockAltair = db.getAltairBlock(headBlockRoot.get()) - tailBlock = db.getBlock(tailBlockRoot.get()) + tailBlock = db.getPhase0Block(tailBlockRoot.get()) if not ((headBlockPhase0.isSome() or headBlockAltair.isSome()) and tailBlock.isSome()): @@ -1359,14 +1355,14 @@ proc preInit*( db.putStateRoot(tailBlock.root, tailState.slot, tailBlock.message.state_root) if tailState.slot == GENESIS_SLOT: - db.putGenesisBlockRoot(tailBlock.root) + db.putGenesisBlock(tailBlock.root) else: doAssert genesisState.slot == GENESIS_SLOT db.putState(genesisState) let genesisBlock = get_initial_beacon_block(genesisState) db.putBlock(genesisBlock) db.putStateRoot(genesisBlock.root, GENESIS_SLOT, genesisBlock.message.state_root) - db.putGenesisBlockRoot(genesisBlock.root) + db.putGenesisBlock(genesisBlock.root) func setTailState*(dag: ChainDAGRef, checkpointState: phase0.BeaconState, diff --git a/beacon_chain/consensus_object_pools/exit_pool.nim b/beacon_chain/consensus_object_pools/exit_pool.nim index 236afa09f..a6b045337 100644 --- a/beacon_chain/consensus_object_pools/exit_pool.nim +++ b/beacon_chain/consensus_object_pools/exit_pool.nim @@ -132,7 +132,7 @@ func getExitMessagesForBlock( subpool.clear() -func getBeaconBlockExits*(pool: var ExitPool, state: SomeBeaconState): BeaconBlockExits = +func getBeaconBlockExits*(pool: var ExitPool, state: ForkyBeaconState): BeaconBlockExits = var indices: HashSet[uint64] res: BeaconBlockExits diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim index 4c7b95712..cd99de185 100644 --- a/beacon_chain/gossip_processing/block_processor.nim +++ b/beacon_chain/gossip_processing/block_processor.nim @@ -130,8 +130,7 @@ proc addBlock*( proc dumpBlock*[T]( self: BlockProcessor, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signedBlock: ForkySignedBeaconBlock, res: Result[T, (ValidationResult, BlockError)]) = if self.dumpEnabled and res.isErr: case res.error[1] @@ -146,8 +145,7 @@ proc dumpBlock*[T]( proc storeBlock*( self: var BlockProcessor, - signedBlock: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signedBlock: ForkySignedBeaconBlock, wallSlot: Slot): Result[BlockRef, BlockError] = let attestationPool = self.consensusManager.attestationPool diff --git a/beacon_chain/networking/eth2_network.nim b/beacon_chain/networking/eth2_network.nim index 9ce0fa617..1b978b13c 100644 --- a/beacon_chain/networking/eth2_network.nim +++ b/beacon_chain/networking/eth2_network.nim @@ -76,7 +76,7 @@ type connTable: HashSet[PeerID] forkId*: ENRForkID discoveryForkId*: ENRForkID - forkDigests*: ForkDigestsRef + forkDigests*: ref ForkDigests rng*: ref BrHmacDrbgContext peers*: Table[PeerID, Peer] validTopics: HashSet[string] @@ -1302,7 +1302,7 @@ proc onConnEvent(node: Eth2Node, peerId: PeerID, event: ConnEvent) {.async.} = peer.connectionState = Disconnected proc new*(T: type Eth2Node, config: BeaconNodeConf, runtimeCfg: RuntimeConfig, - enrForkId: ENRForkID, discoveryForkId: ENRForkId, forkDigests: ForkDigestsRef, + enrForkId: ENRForkID, discoveryForkId: ENRForkId, forkDigests: ref ForkDigests, getBeaconTime: GetBeaconTimeFn, switch: Switch, pubsub: GossipSub, ip: Option[ValidIpAddress], tcpPort, udpPort: Option[Port], privKey: keys.PrivateKey, discovery: bool, @@ -1827,7 +1827,7 @@ proc createEth2Node*(rng: ref BrHmacDrbgContext, config: BeaconNodeConf, netKeys: NetKeyPair, cfg: RuntimeConfig, - forkDigests: ForkDigestsRef, + forkDigests: ref ForkDigests, getBeaconTime: GetBeaconTimeFn, genesisValidatorsRoot: Eth2Digest): Eth2Node {.raises: [Defect, CatchableError].} = diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 15940cc61..d65234506 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -12,7 +12,7 @@ import tables, times, terminal], # Nimble packages - serialization, json_serialization, spec/eth2_apis/eth2_rest_serialization, + spec/eth2_apis/eth2_rest_serialization, stew/[objects, byteutils, endians2, io2], stew/shims/macros, chronos, confutils, metrics, metrics/chronos_httpserver, chronicles, bearssl, blscurve, presto, diff --git a/beacon_chain/rpc/rest_beacon_api.nim b/beacon_chain/rpc/rest_beacon_api.nim index fcd763c41..c441e2373 100644 --- a/beacon_chain/rpc/rest_beacon_api.nim +++ b/beacon_chain/rpc/rest_beacon_api.nim @@ -14,7 +14,7 @@ import ../beacon_node, ../networking/eth2_network, ../consensus_object_pools/[blockchain_dag, exit_pool, spec_cache], ../validators/validator_duties, - ../spec/[eth2_merkleization, forks, network], + ../spec/[eth2_merkleization, forks, network, validator], ../spec/datatypes/[phase0, altair] export rest_utils diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index c1fcb9bb0..0e4dafa0c 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -77,8 +77,7 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = return case contentType of "application/json": - RestApiResponse.jsonResponsePlain( - ForkedBeaconState.init(stateData.data)) + RestApiResponse.jsonResponsePlain(stateData.data) of "application/octet-stream": withState(stateData.data): RestApiResponse.sszResponse(state.data) diff --git a/beacon_chain/rpc/rpc_beacon_api.nim b/beacon_chain/rpc/rpc_beacon_api.nim index 7ea909c4c..bea8cdf48 100644 --- a/beacon_chain/rpc/rpc_beacon_api.nim +++ b/beacon_chain/rpc/rpc_beacon_api.nim @@ -17,7 +17,7 @@ import ../networking/eth2_network, ../validators/validator_duties, ../consensus_object_pools/blockchain_dag, - ../spec/[eth2_merkleization, forks, network], + ../spec/[eth2_merkleization, forks, network, validator], ../spec/datatypes/[phase0], ./rpc_utils diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index 0ecee6322..6319202a2 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -14,17 +14,17 @@ import chronicles, ../extras, ./datatypes/[phase0, altair, merge], - "."/[eth2_merkleization, helpers, signatures, validator], + "."/[eth2_merkleization, forks, signatures, validator], ../../nbench/bench_lab -export extras, phase0, altair, merge +export extras, forks, validator # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#increase_balance func increase_balance*(balance: var Gwei, delta: Gwei) = balance += delta func increase_balance*( - state: var SomeBeaconState, index: ValidatorIndex, delta: Gwei) = + state: var ForkyBeaconState, index: ValidatorIndex, delta: Gwei) = ## Increase the validator balance at index ``index`` by ``delta``. if delta != 0: # avoid dirtying the balance cache if not needed increase_balance(state.balances[index], delta) @@ -38,7 +38,7 @@ func decrease_balance*(balance: var Gwei, delta: Gwei) = balance - delta func decrease_balance*( - state: var SomeBeaconState, index: ValidatorIndex, delta: Gwei) = + state: var ForkyBeaconState, index: ValidatorIndex, delta: Gwei) = ## Decrease the validator balance at index ``index`` by ``delta``, with ## underflow protection. if delta != 0: # avoid dirtying the balance cache if not needed @@ -71,7 +71,7 @@ func compute_activation_exit_epoch*(epoch: Epoch): Epoch = # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_validator_churn_limit func get_validator_churn_limit*( - cfg: RuntimeConfig, state: SomeBeaconState, cache: var StateCache): + cfg: RuntimeConfig, state: ForkyBeaconState, cache: var StateCache): uint64 = ## Return the validator churn limit for the current epoch. max( @@ -80,7 +80,7 @@ func get_validator_churn_limit*( state, state.get_current_epoch(), cache) div cfg.CHURN_LIMIT_QUOTIENT) # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#initiate_validator_exit -func initiate_validator_exit*(cfg: RuntimeConfig, state: var SomeBeaconState, +func initiate_validator_exit*(cfg: RuntimeConfig, state: var ForkyBeaconState, index: ValidatorIndex, cache: var StateCache) = ## Initiate the exit of the validator with index ``index``. @@ -122,7 +122,7 @@ func initiate_validator_exit*(cfg: RuntimeConfig, state: var SomeBeaconState, # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#slash_validator # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/altair/beacon-chain.md#modified-slash_validator proc slash_validator*( - cfg: RuntimeConfig, state: var SomeBeaconState, + cfg: RuntimeConfig, state: var ForkyBeaconState, slashed_index: ValidatorIndex, cache: var StateCache) = ## Slash the validator with index ``index``. let epoch = get_current_epoch(state) @@ -184,26 +184,6 @@ proc slash_validator*( func genesis_time_from_eth1_timestamp*(cfg: RuntimeConfig, eth1_timestamp: uint64): uint64 = eth1_timestamp + cfg.GENESIS_DELAY -func genesisFork*(cfg: RuntimeConfig): Fork = - Fork( - previous_version: cfg.GENESIS_FORK_VERSION, - current_version: cfg.GENESIS_FORK_VERSION, - epoch: GENESIS_EPOCH) - -func altairFork*(cfg: RuntimeConfig): Fork = - Fork( - previous_version: cfg.GENESIS_FORK_VERSION, - current_version: cfg.ALTAIR_FORK_VERSION, - epoch: cfg.ALTAIR_FORK_EPOCH) - -func mergeFork*(cfg: RuntimeConfig): Fork = - # TODO in theory, the altair + merge forks could be in same epoch, so the - # previous fork version would be the GENESIS_FORK_VERSION - Fork( - previous_version: cfg.ALTAIR_FORK_VERSION, - current_version: cfg.MERGE_FORK_VERSION, - epoch: cfg.MERGE_FORK_EPOCH) - # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#genesis proc initialize_beacon_state_from_eth1*( cfg: RuntimeConfig, @@ -315,8 +295,7 @@ func get_initial_beacon_block*(state: phase0.BeaconState): message: message, root: hash_tree_root(message)) # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_block_root_at_slot -func get_block_root_at_slot*(state: SomeBeaconState, - slot: Slot): Eth2Digest = +func get_block_root_at_slot*(state: ForkyBeaconState, slot: Slot): Eth2Digest = ## Return the block root at a recent ``slot``. # Potential overflow/wrap shouldn't occur, as get_block_root_at_slot() called @@ -327,14 +306,20 @@ func get_block_root_at_slot*(state: SomeBeaconState, doAssert slot < state.slot state.block_roots[slot mod SLOTS_PER_HISTORICAL_ROOT] +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) + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_block_root -func get_block_root*(state: SomeBeaconState, epoch: Epoch): Eth2Digest = +func get_block_root*(state: ForkyBeaconState, epoch: Epoch): Eth2Digest = ## Return the block root at the start of a recent ``epoch``. get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch)) # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_total_balance template get_total_balance( - state: SomeBeaconState, validator_indices: untyped): Gwei = + state: ForkyBeaconState, validator_indices: untyped): Gwei = ## Return the combined effective balance of the ``indices``. ## ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. ## Math safe up to ~10B ETH, afterwhich this overflows uint64. @@ -350,7 +335,7 @@ func is_eligible_for_activation_queue*(validator: Validator): bool = validator.effective_balance == MAX_EFFECTIVE_BALANCE # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#is_eligible_for_activation -func is_eligible_for_activation*(state: SomeBeaconState, validator: Validator): +func is_eligible_for_activation*(state: ForkyBeaconState, validator: Validator): bool = ## Check if ``validator`` is eligible for activation. @@ -361,7 +346,7 @@ func is_eligible_for_activation*(state: SomeBeaconState, validator: Validator): # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#is_valid_indexed_attestation proc is_valid_indexed_attestation*( - state: SomeBeaconState, indexed_attestation: SomeIndexedAttestation, + state: ForkyBeaconState, indexed_attestation: SomeIndexedAttestation, flags: UpdateFlags): Result[void, cstring] = ## Check if ``indexed_attestation`` is not empty, has sorted and unique ## indices and has a valid aggregate signature. @@ -398,7 +383,7 @@ proc is_valid_indexed_attestation*( ok() # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_attesting_indices -func get_attesting_indices*(state: SomeBeaconState, +func get_attesting_indices*(state: ForkyBeaconState, data: AttestationData, bits: CommitteeValidatorsBits, cache: var StateCache): seq[ValidatorIndex] = @@ -419,8 +404,21 @@ func get_attesting_indices*(state: SomeBeaconState, res +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 is_valid_indexed_attestation*( - state: SomeBeaconState, attestation: SomeAttestation, flags: UpdateFlags, + state: ForkyBeaconState, attestation: SomeAttestation, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] = # This is a variation on `is_valid_indexed_attestation` that works directly # with an attestation instead of first constructing an `IndexedAttestation` @@ -524,7 +522,7 @@ func get_attestation_participation_flag_indices(state: altair.BeaconState | merg # better to centralize around that if feasible # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_total_active_balance -func get_total_active_balance*(state: SomeBeaconState, cache: var StateCache): Gwei = +func get_total_active_balance*(state: ForkyBeaconState, cache: var StateCache): Gwei = ## Return the combined effective balance of the active validators. # Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei # minimum to avoid divisions by zero. @@ -555,7 +553,7 @@ func get_base_reward( # https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/phase0/beacon-chain.md#attestations proc check_attestation*( - state: SomeBeaconState, attestation: SomeAttestation, flags: UpdateFlags, + state: ForkyBeaconState, attestation: SomeAttestation, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] = ## Check that an attestation follows the rules of being included in the state ## at the current slot. When acting as a proposer, the same rules need to @@ -589,7 +587,7 @@ proc check_attestation*( ok() proc process_attestation*( - state: var SomeBeaconState, attestation: SomeAttestation, flags: UpdateFlags, + state: var ForkyBeaconState, attestation: SomeAttestation, flags: UpdateFlags, base_reward_per_increment: Gwei, cache: var StateCache): Result[void, cstring] {.nbench.} = # In the spec, attestation validation is mixed with state mutation, so here diff --git a/beacon_chain/spec/datatypes/altair.nim b/beacon_chain/spec/datatypes/altair.nim index 2469986f4..991e17adf 100644 --- a/beacon_chain/spec/datatypes/altair.nim +++ b/beacon_chain/spec/datatypes/altair.nim @@ -543,3 +543,14 @@ template hash*(x: LightClientUpdate): Hash = func clear*(info: var EpochInfo) = info.validators.setLen(0) info.balances = UnslashedParticipatingBalances() + +template asSigned*(x: SigVerifiedSignedBeaconBlock | TrustedSignedBeaconBlock): + SignedBeaconBlock = + isomorphicCast[SignedBeaconBlock](x) + +template asSigVerified*(x: SignedBeaconBlock | TrustedSignedBeaconBlock): SigVerifiedSignedBeaconBlock = + isomorphicCast[SigVerifiedSignedBeaconBlock](x) + +template asTrusted*( + x: SignedBeaconBlock | SigVerifiedSignedBeaconBlock): TrustedSignedBeaconBlock = + isomorphicCast[TrustedSignedBeaconBlock](x) diff --git a/beacon_chain/spec/datatypes/base.nim b/beacon_chain/spec/datatypes/base.nim index e235612c8..65f7be563 100644 --- a/beacon_chain/spec/datatypes/base.nim +++ b/beacon_chain/spec/datatypes/base.nim @@ -734,6 +734,7 @@ template assignClone*[T: not ref](x: T): ref T = # This is a bit of a mess: if x is an rvalue (temporary), RVO kicks in for # newClone - if it's not, `genericAssign` will be called which is ridiculously # slow - so `assignClone` should be used when RVO doesn't work. sigh. + mixin assign let res = new typeof(x) # TODO safe to do noinit here? assign(res[], x) res @@ -938,6 +939,12 @@ func getSizeofSig(x: auto, n: int = 0): seq[(string, int, int)] = # is still better to keep field names parallel. result.add((name.replace("blob", "data"), sizeof(value), n)) +## At the GC-level, the GC is type-agnostic; it's all type-erased so +## casting between seq[Attestation] and seq[TrustedAttestation] will +## not disrupt GC operations. +## +## These SHOULD be used in function calls to avoid expensive temporary. +## see https://github.com/status-im/nimbus-eth2/pull/2250#discussion_r562010679 template isomorphicCast*[T, U](x: U): T = # Each of these pairs of types has ABI-compatible memory representations. static: diff --git a/beacon_chain/spec/datatypes/merge.nim b/beacon_chain/spec/datatypes/merge.nim index 419a1b6fc..87dd3becf 100644 --- a/beacon_chain/spec/datatypes/merge.nim +++ b/beacon_chain/spec/datatypes/merge.nim @@ -167,9 +167,6 @@ type data*: BeaconState root*: Eth2Digest # hash_tree_root(data) - SomeBeaconState* = BeaconState | altair.BeaconState | phase0.BeaconState - SomeHashedBeaconState* = HashedBeaconState | altair.HashedBeaconState | phase0.HashedBeaconState - # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#beaconblock BeaconBlock* = object ## For each slot, a proposer is chosen from the validator pool to propose @@ -397,3 +394,14 @@ func shortLog*(v: SomeSignedBeaconBlock): auto = blck: shortLog(v.message), signature: shortLog(v.signature) ) + +template asSigned*(x: SigVerifiedSignedBeaconBlock | TrustedSignedBeaconBlock): + SignedBeaconBlock = + isomorphicCast[SignedBeaconBlock](x) + +template asSigVerified*(x: SignedBeaconBlock | TrustedSignedBeaconBlock): SigVerifiedSignedBeaconBlock = + isomorphicCast[SigVerifiedSignedBeaconBlock](x) + +template asTrusted*( + x: SignedBeaconBlock | SigVerifiedSignedBeaconBlock): TrustedSignedBeaconBlock = + isomorphicCast[TrustedSignedBeaconBlock](x) diff --git a/beacon_chain/spec/datatypes/phase0.nim b/beacon_chain/spec/datatypes/phase0.nim index c30294823..a3a906186 100644 --- a/beacon_chain/spec/datatypes/phase0.nim +++ b/beacon_chain/spec/datatypes/phase0.nim @@ -289,3 +289,14 @@ func shortLog*(v: SomeSignedBeaconBlock): auto = blck: shortLog(v.message), signature: shortLog(v.signature) ) + +template asSigned*(x: SigVerifiedSignedBeaconBlock | TrustedSignedBeaconBlock): + SignedBeaconBlock = + isomorphicCast[SignedBeaconBlock](x) + +template asSigVerified*(x: SignedBeaconBlock | TrustedSignedBeaconBlock): SigVerifiedSignedBeaconBlock = + isomorphicCast[SigVerifiedSignedBeaconBlock](x) + +template asTrusted*( + x: SignedBeaconBlock | SigVerifiedSignedBeaconBlock): TrustedSignedBeaconBlock = + isomorphicCast[TrustedSignedBeaconBlock](x) diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index ef9828868..827853c66 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -5,7 +5,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import std/typetraits -import stew/[results, base10, byteutils, endians2], presto/common, +import stew/[assign2, results, base10, byteutils, endians2], presto/common, libp2p/peerid, serialization, json_serialization, json_serialization/std/[options, net, sets], nimcrypto/utils as ncrutils @@ -821,9 +821,10 @@ proc writeValue*(writer: var JsonWriter[RestJson], writer.writeField("data", value.mergeData) writer.endRecord() -# ForkedBeaconState +# ForkedHashedBeaconState is used where a `ForkedBeaconState` normally would +# be used, mainly because caching the hash early on is easier to do proc readValue*(reader: var JsonReader[RestJson], - value: var ForkedBeaconState) {. + value: var ForkedHashedBeaconState) {. raises: [IOError, SerializationError, Defect].} = var version: Option[BeaconStateFork] @@ -854,53 +855,58 @@ proc readValue*(reader: var JsonReader[RestJson], if data.isNone(): reader.raiseUnexpectedValue("Field data is missing") + # Use a temporary to avoid stack instances and `value` mutation in case of + # exception + let + tmp = (ref ForkedHashedBeaconState)(kind: version.get()) + + template toValue(field: untyped) = + if tmp[].kind == value.kind: + assign(value.field, tmp[].field) + else: + value = tmp[] # slow, but rare (hopefully) + value.field.root = hash_tree_root(value.field.data) + case version.get(): of BeaconStateFork.Phase0: - let res = - try: - some(RestJson.decode(string(data.get()), phase0.BeaconState, - requireAllFields = true)) - except SerializationError: - none[phase0.BeaconState]() - if res.isNone(): + try: + tmp[].phase0Data.data = RestJson.decode( + string(data.get()), phase0.BeaconState, requireAllFields = true) + except SerializationError: reader.raiseUnexpectedValue("Incorrect phase0 beacon state format") - value = ForkedBeaconState.init(res.get()) - of BeaconStateFork.Altair: - let res = - try: - some(RestJson.decode(string(data.get()), altair.BeaconState, - requireAllFields = true)) - except SerializationError: - none[altair.BeaconState]() - if res.isNone(): - reader.raiseUnexpectedValue("Incorrect altair beacon state format") - value = ForkedBeaconState.init(res.get()) - of BeaconStateFork.Merge: - let res = - try: - some(RestJson.decode(string(data.get()), merge.BeaconState, - requireAllFields = true)) - except SerializationError: - none[merge.BeaconState]() - if res.isNone(): - reader.raiseUnexpectedValue("Incorrect merge beacon state format") - value = ForkedBeaconState.init(res.get()) -proc writeValue*(writer: var JsonWriter[RestJson], value: ForkedBeaconState) {. + toValue(phase0Data) + of BeaconStateFork.Altair: + try: + tmp[].altairData.data = RestJson.decode( + string(data.get()), altair.BeaconState, requireAllFields = true) + except SerializationError: + reader.raiseUnexpectedValue("Incorrect altair beacon state format") + + toValue(altairData) + of BeaconStateFork.Merge: + try: + tmp[].mergeData.data = RestJson.decode( + string(data.get()), merge.BeaconState, requireAllFields = true) + except SerializationError: + reader.raiseUnexpectedValue("Incorrect altair beacon state format") + toValue(mergeData) + +proc writeValue*(writer: var JsonWriter[RestJson], value: ForkedHashedBeaconState) {. raises: [IOError, Defect].} = writer.beginRecord() case value.kind of BeaconStateFork.Phase0: writer.writeField("version", "phase0") - writer.writeField("data", value.phase0Data) + writer.writeField("data", value.phase0Data.data) of BeaconStateFork.Altair: writer.writeField("version", "altair") - writer.writeField("data", value.altairData) + writer.writeField("data", value.altairData.data) of BeaconStateFork.Merge: writer.writeField("version", "merge") when false: # TODO SerializationError - writer.writeField("data", value.mergeData) + writer.writeField("data", value.mergeData.data) writer.endRecord() # SyncSubcommitteeIndex diff --git a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim index 5affd25a1..b9ea4bafe 100644 --- a/beacon_chain/spec/eth2_apis/rest_debug_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_debug_calls.nim @@ -20,7 +20,7 @@ proc getStatePlain*(state_id: StateIdent): RestPlainResponse {. ## https://ethereum.github.io/beacon-APIs/#/Beacon/getState proc getState*(client: RestClientRef, state_id: StateIdent, - restAccept = ""): Future[ForkedBeaconState] {.async.} = + restAccept = ""): Future[phase0.BeaconState] {.async.} = let resp = if len(restAccept) > 0: await client.getStatePlain(state_id, restAcceptType = restAccept) @@ -38,7 +38,7 @@ proc getState*(client: RestClientRef, state_id: StateIdent, if res.isErr(): raise newException(RestError, $res.error()) res.get() - ForkedBeaconState.init(state.data) + state.data of "application/octet-stream": let state = block: @@ -47,7 +47,7 @@ proc getState*(client: RestClientRef, state_id: StateIdent, if res.isErr(): raise newException(RestError, $res.error()) res.get() - ForkedBeaconState.init(state) + state else: raise newException(RestError, "Unsupported content-type") of 400, 404, 500: @@ -79,7 +79,7 @@ proc getStateV2Plain*(state_id: StateIdent): RestPlainResponse {. proc getStateV2*(client: RestClientRef, state_id: StateIdent, forks: array[2, Fork], - restAccept = ""): Future[ForkedBeaconState] {.async.} = + restAccept = ""): Future[ForkedHashedBeaconState] {.async.} = let resp = if len(restAccept) > 0: await client.getStateV2Plain(state_id, restAcceptType = restAccept) @@ -106,23 +106,25 @@ proc getStateV2*(client: RestClientRef, state_id: StateIdent, raise newException(RestError, $res.error()) res.get() if header.slot.epoch() < forks[1].epoch: - let state = + let state = newClone( block: - let res = decodeBytes(GetPhase0StateSszResponse, resp.data, - resp.contentType) - if res.isErr(): - raise newException(RestError, $res.error()) - res.get() - ForkedBeaconState.init(state) + let res = newClone(decodeBytes( + GetPhase0StateSszResponse, resp.data, resp.contentType)) + if res[].isErr(): + raise newException(RestError, $res[].error()) + res[].get()) + ForkedHashedBeaconState.init(phase0.HashedBeaconState( + data: state[], root: hash_tree_root(state[]))) else: - let blck = + let state = newClone( block: - let res = decodeBytes(GetAltairStateSszResponse, resp.data, - resp.contentType) - if res.isErr(): - raise newException(RestError, $res.error()) - res.get() - ForkedBeaconState.init(blck) + let res = newClone(decodeBytes( + GetAltairStateSszResponse, resp.data, resp.contentType)) + if res[].isErr(): + raise newException(RestError, $res[].error()) + res[].get()) + ForkedHashedBeaconState.init(altair.HashedBeaconState( + data: state[], root: hash_tree_root(state[]))) else: raise newException(RestError, "Unsupported content-type") of 400, 404, 500: diff --git a/beacon_chain/spec/eth2_apis/rest_types.nim b/beacon_chain/spec/eth2_apis/rest_types.nim index a6c1c99a0..bbed40309 100644 --- a/beacon_chain/spec/eth2_apis/rest_types.nim +++ b/beacon_chain/spec/eth2_apis/rest_types.nim @@ -414,7 +414,7 @@ type GetStateResponse* = DataEnclosedObject[phase0.BeaconState] GetBlockV2Response* = ForkedSignedBeaconBlock GetBlockV2Header* = ForkedSignedBlockHeader - GetStateV2Response* = ForkedBeaconState + GetStateV2Response* = ForkedHashedBeaconState GetStateV2Header* = ForkedBeaconStateHeader GetPhase0StateSszResponse* = phase0.BeaconState GetAltairStateSszResponse* = altair.BeaconState diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index eece336b3..2d2aa60c5 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -8,15 +8,31 @@ {.push raises: [Defect].} import - std/macros, + stew/[assign2], chronicles, - stew/[assign2, results], ../extras, - ../spec/[ - beaconstate, eth2_merkleization, helpers, state_transition_block, validator], + "."/[eth2_merkleization, eth2_ssz_serialization, presets], ./datatypes/[phase0, altair, merge] -export extras, phase0, altair, eth2_merkleization +export + extras, phase0, altair, merge, eth2_merkleization, eth2_ssz_serialization, + presets + +# This file contains helpers for dealing with forks - we have two ways we can +# deal with forks: +# * generics - this means using the static typing and differentiating forks +# at compile time - this is preferred in fork-specific code where the fork +# is known up-front, for example spec functions. +# * variants - this means using a variant object and determining the fork at +# runtime - this carries the obvious risk and complexity of dealing with +# runtime checking, but is of course needed for external data that may be +# of any fork kind. +# +# For generics, we define `Forky*` type classes that cover "similar" objects +# across forks - for variants, they're called `Forked*` instead. +# See withXxx and `init` for convenient ways of moving between these two worlds. +# A clever programmer would use templates, macros and dark magic to create all +# these types and converters :) type BeaconStateFork* {.pure.} = enum @@ -24,49 +40,70 @@ type Altair, Merge + ForkyBeaconState* = + phase0.BeaconState | + altair.BeaconState | + merge.BeaconState + + ForkyHashedBeaconState* = + phase0.HashedBeaconState | + altair.HashedBeaconState | + merge.HashedBeaconState + ForkedHashedBeaconState* = object case kind*: BeaconStateFork of BeaconStateFork.Phase0: phase0Data*: phase0.HashedBeaconState of BeaconStateFork.Altair: altairData*: altair.HashedBeaconState of BeaconStateFork.Merge: mergeData*: merge.HashedBeaconState - ForkedBeaconState* = object - case kind*: BeaconStateFork - of BeaconStateFork.Phase0: phase0Data*: phase0.BeaconState - of BeaconStateFork.Altair: altairData*: altair.BeaconState - of BeaconStateFork.Merge: mergeData*: merge.BeaconState - BeaconBlockFork* {.pure.} = enum Phase0 Altair Merge + ForkyBeaconBlock* = + phase0.BeaconBlock | + altair.BeaconBlock | + merge.BeaconBlock + + ForkyTrustedBeaconBlock* = + phase0.TrustedBeaconBlock | + altair.TrustedBeaconBlock | + merge.TrustedBeaconBlock + ForkedBeaconBlock* = object case kind*: BeaconBlockFork - of BeaconBlockFork.Phase0: - phase0Data*: phase0.BeaconBlock - of BeaconBlockFork.Altair: - altairData*: altair.BeaconBlock - of BeaconBlockFork.Merge: - mergeData*: merge.BeaconBlock + of BeaconBlockFork.Phase0: phase0Data*: phase0.BeaconBlock + of BeaconBlockFork.Altair: altairData*: altair.BeaconBlock + of BeaconBlockFork.Merge: mergeData*: merge.BeaconBlock + + ForkedTrustedBeaconBlock* = object + case kind*: BeaconBlockFork + of BeaconBlockFork.Phase0: phase0Data*: phase0.TrustedBeaconBlock + of BeaconBlockFork.Altair: altairData*: altair.TrustedBeaconBlock + of BeaconBlockFork.Merge: mergeData*: merge.TrustedBeaconBlock + + ForkySignedBeaconBlock* = + phase0.SignedBeaconBlock | + altair.SignedBeaconBlock | + merge.SignedBeaconBlock ForkedSignedBeaconBlock* = object case kind*: BeaconBlockFork - of BeaconBlockFork.Phase0: - phase0Data*: phase0.SignedBeaconBlock - of BeaconBlockFork.Altair: - altairData*: altair.SignedBeaconBlock - of BeaconBlockFork.Merge: - mergeData*: merge.SignedBeaconBlock + of BeaconBlockFork.Phase0: phase0Data*: phase0.SignedBeaconBlock + of BeaconBlockFork.Altair: altairData*: altair.SignedBeaconBlock + of BeaconBlockFork.Merge: mergeData*: merge.SignedBeaconBlock + + ForkyTrustedSignedBeaconBlock* = + phase0.TrustedSignedBeaconBlock | + altair.TrustedSignedBeaconBlock | + merge.TrustedSignedBeaconBlock ForkedTrustedSignedBeaconBlock* = object case kind*: BeaconBlockFork - of BeaconBlockFork.Phase0: - phase0Data*: phase0.TrustedSignedBeaconBlock - of BeaconBlockFork.Altair: - altairData*: altair.TrustedSignedBeaconBlock - of BeaconBlockFork.Merge: - mergeData*: merge.TrustedSignedBeaconBlock + of BeaconBlockFork.Phase0: phase0Data*: phase0.TrustedSignedBeaconBlock + of BeaconBlockFork.Altair: altairData*: altair.TrustedSignedBeaconBlock + of BeaconBlockFork.Merge: mergeData*: merge.TrustedSignedBeaconBlock EpochInfoFork* {.pure.} = enum Phase0 @@ -74,10 +111,8 @@ type ForkedEpochInfo* = object case kind*: EpochInfoFork - of EpochInfoFork.Phase0: - phase0Data*: phase0.EpochInfo - of EpochInfoFork.Altair: - altairData*: altair.EpochInfo + of EpochInfoFork.Phase0: phase0Data*: phase0.EpochInfo + of EpochInfoFork.Altair: altairData*: altair.EpochInfo ForkyEpochInfo* = phase0.EpochInfo | altair.EpochInfo @@ -86,7 +121,22 @@ type altair*: ForkDigest merge*: ForkDigest - ForkDigestsRef* = ref ForkDigests +template toFork*[T: phase0.BeaconState | phase0.HashedBeaconState]( + t: type T): BeaconStateFork = + BeaconStateFork.Phase0 +template toFork*[T: altair.BeaconState | altair.HashedBeaconState]( + t: type T): BeaconStateFork = + BeaconStateFork.Altair +template toFork*[T: merge.BeaconState | merge.HashedBeaconState]( + t: type T): BeaconStateFork = + BeaconStateFork.Merge + +template init*(T: type ForkedHashedBeaconState, data: phase0.HashedBeaconState): T = + T(kind: BeaconStateFork.Phase0, phase0Data: data) +template init*(T: type ForkedHashedBeaconState, data: altair.HashedBeaconState): T = + T(kind: BeaconStateFork.Altair, altairData: data) +template init*(T: type ForkedHashedBeaconState, data: merge.HashedBeaconState): T = + T(kind: BeaconStateFork.Merge, mergeData: data) template init*(T: type ForkedBeaconBlock, blck: phase0.BeaconBlock): T = T(kind: BeaconBlockFork.Phase0, phase0Data: blck) @@ -95,6 +145,13 @@ template init*(T: type ForkedBeaconBlock, blck: altair.BeaconBlock): T = template init*(T: type ForkedBeaconBlock, blck: merge.BeaconBlock): T = T(kind: BeaconBlockFork.Merge, mergeData: blck) +template init*(T: type ForkedTrustedBeaconBlock, blck: phase0.TrustedBeaconBlock): T = + T(kind: BeaconBlockFork.Phase0, phase0Data: blck) +template init*(T: type ForkedTrustedBeaconBlock, blck: altair.TrustedBeaconBlock): T = + T(kind: BeaconBlockFork.Altair, altairData: blck) +template init*(T: type ForkedTrustedBeaconBlock, blck: merge.TrustedBeaconBlock): T = + T(kind: BeaconBlockFork.Merge, mergeData: blck) + template init*(T: type ForkedSignedBeaconBlock, blck: phase0.SignedBeaconBlock): T = T(kind: BeaconBlockFork.Phase0, phase0Data: blck) template init*(T: type ForkedSignedBeaconBlock, blck: altair.SignedBeaconBlock): T = @@ -102,37 +159,19 @@ template init*(T: type ForkedSignedBeaconBlock, blck: altair.SignedBeaconBlock): template init*(T: type ForkedSignedBeaconBlock, blck: merge.SignedBeaconBlock): T = T(kind: BeaconBlockFork.Merge, mergeData: blck) -template init*(T: type ForkedBeaconState, state: phase0.BeaconState): T = - T(kind: BeaconStateFork.Phase0, phase0Data: state) -template init*(T: type ForkedBeaconState, state: altair.BeaconState): T = - T(kind: BeaconStateFork.Altair, altairData: state) -template init*(T: type ForkedBeaconState, state: merge.BeaconState): T = - T(kind: BeaconStateFork.Merge, mergeData: state) -template init*(T: type ForkedBeaconState, state: ForkedHashedBeaconState): T = - case state.kind - of BeaconStateFork.Phase0: - T(kind: BeaconStateFork.Phase0, - phase0Data: state.phase0Data.data) - of BeaconStateFork.Altair: - T(kind: BeaconStateFork.Altair, - altairData: state.altairData.data) - of BeaconStateFork.Merge: - T(kind: BeaconStateFork.Merge, - mergeData: state.mergeData.data) - template init*(T: type ForkedSignedBeaconBlock, forked: ForkedBeaconBlock, blockRoot: Eth2Digest, signature: ValidatorSig): T = case forked.kind of BeaconBlockFork.Phase0: T(kind: BeaconBlockFork.Phase0, phase0Data: phase0.SignedBeaconBlock(message: forked.phase0Data, - root: blockRoot, - signature: signature)) + root: blockRoot, + signature: signature)) of BeaconBlockFork.Altair: T(kind: BeaconBlockFork.Altair, altairData: altair.SignedBeaconBlock(message: forked.altairData, - root: blockRoot, - signature: signature)) + root: blockRoot, + signature: signature)) of BeaconBlockFork.Merge: T(kind: BeaconBlockFork.Merge, mergeData: merge.SignedBeaconBlock(message: forked.mergeData, @@ -146,13 +185,21 @@ template init*(T: type ForkedTrustedSignedBeaconBlock, blck: altair.TrustedSigne template init*(T: type ForkedTrustedSignedBeaconBlock, blck: merge.TrustedSignedBeaconBlock): T = T(kind: BeaconBlockFork.Merge, mergeData: blck) +template toFork*[T: phase0.TrustedSignedBeaconBlock]( + t: type T): BeaconBlockFork = + BeaconBlockFork.Phase0 +template toFork*[T: altair.TrustedSignedBeaconBlock]( + t: type T): BeaconBlockFork = + BeaconBlockFork.Altair +template toFork*[T: merge.TrustedSignedBeaconBlock]( + t: type T): BeaconBlockFork = + BeaconBlockFork.Merge + template init*(T: type ForkedEpochInfo, info: phase0.EpochInfo): T = T(kind: EpochInfoFork.Phase0, phase0Data: info) template init*(T: type ForkedEpochInfo, info: altair.EpochInfo): T = T(kind: EpochInfoFork.Altair, altairData: info) -# State-related functionality based on ForkedHashedBeaconState instead of HashedBeaconState - template withState*(x: ForkedHashedBeaconState, body: untyped): untyped = case x.kind of BeaconStateFork.Merge: @@ -190,7 +237,6 @@ template withEpochInfo*( template info: untyped {.inject.} = x.altairData body -# Dispatch functions func assign*(tgt: var ForkedHashedBeaconState, src: ForkedHashedBeaconState) = if tgt.kind == src.kind: case tgt.kind @@ -222,93 +268,6 @@ func getStateRoot*(x: ForkedHashedBeaconState): Eth2Digest = 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/consensus-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: @@ -326,55 +285,9 @@ func blockForkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): BeaconBlockFork = elif epoch >= cfg.ALTAIR_FORK_EPOCH: BeaconBlockFork.Altair else: BeaconBlockFork.Phase0 -# https://github.com/ethereum/consensus-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/consensus-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) @@ -453,6 +366,26 @@ template withStateAndBlck*( template blck: untyped {.inject.} = b.phase0Data body +func genesisFork*(cfg: RuntimeConfig): Fork = + Fork( + previous_version: cfg.GENESIS_FORK_VERSION, + current_version: cfg.GENESIS_FORK_VERSION, + epoch: GENESIS_EPOCH) + +func altairFork*(cfg: RuntimeConfig): Fork = + Fork( + previous_version: cfg.GENESIS_FORK_VERSION, + current_version: cfg.ALTAIR_FORK_VERSION, + epoch: cfg.ALTAIR_FORK_EPOCH) + +func mergeFork*(cfg: RuntimeConfig): Fork = + # TODO in theory, the altair + merge forks could be in same epoch, so the + # previous fork version would be the GENESIS_FORK_VERSION + Fork( + previous_version: cfg.ALTAIR_FORK_VERSION, + current_version: cfg.MERGE_FORK_VERSION, + epoch: cfg.MERGE_FORK_EPOCH) + proc forkAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Fork = case cfg.stateForkAtEpoch(epoch) of BeaconStateFork.Merge: cfg.mergeFork @@ -478,3 +411,99 @@ func getForkSchedule*(cfg: RuntimeConfig): array[2, Fork] = ## ## NOTE: Update this procedure when new fork will be scheduled. [cfg.genesisFork(), cfg.altairFork()] + +func readSszForkedHashedBeaconState*( + data: openArray[byte], likelyFork: BeaconStateFork): + ForkedHashedBeaconState {.raises: [Defect, SszError].} = + ## Helper to read a state from bytes when it's not certain what kind of state + ## it is - this happens for example when loading an SSZ state from command + ## line - we'll use wall time to "guess" which state to start with + # careful - `result` is used, RVO didn't seem to work without + result = ForkedHashedBeaconState(kind: likelyFork) + var tried: set[BeaconStateFork] + + template readFork() = + withState(result): + try: + readSszBytes(data, state.data) + state.root = hash_tree_root(state.data) + return result + except SszError as exc: + tried.incl result.kind + + readFork() + + for fork in BeaconStateFork: + if fork in tried: continue + result = ForkedHashedBeaconState(kind: fork) + readFork() + + raise (ref SszError)(msg: "Unable to match data to any known fork") + +func readSszForkedTrustedSignedBeaconBlock*( + data: openArray[byte], likelyFork: BeaconBlockFork): + ForkedTrustedSignedBeaconBlock {.raises: [Defect, SszError].} = + ## Helper to read a state from bytes when it's not certain what kind of state + ## it is - this happens for example when loading an SSZ state from command + ## line - we'll use wall time to "guess" which state to start with + + var + res = ForkedTrustedSignedBeaconBlock(kind: likelyFork) + tried: set[BeaconBlockFork] + + template readFork() = + withBlck(res): + try: + readSszBytes(data, blck) + return res + except SszError as exc: + tried.incl res.kind + + readFork() + + for fork in BeaconBlockFork: + if fork in tried: continue + res = ForkedTrustedSignedBeaconBlock(kind: fork) + readFork() + raise (ref SszError)(msg: "Unable to match data to any known fork") + +func toBeaconBlockFork*(fork: BeaconStateFork): BeaconBlockFork = + case fork + of BeaconStateFork.Phase0: BeaconBlockFork.Phase0 + of BeaconStateFork.Altair: BeaconBlockFork.Altair + of BeaconStateFork.Merge: BeaconBlockFork.Merge + +# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#compute_fork_data_root +func compute_fork_data_root*(current_version: Version, + genesis_validators_root: Eth2Digest): Eth2Digest = + ## Return the 32-byte fork data root for the ``current_version`` and + ## ``genesis_validators_root``. + ## This is used primarily in signature domains to avoid collisions across + ## forks/chains. + hash_tree_root(ForkData( + current_version: current_version, + genesis_validators_root: genesis_validators_root + )) + +# https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#compute_fork_digest +func compute_fork_digest*(current_version: Version, + genesis_validators_root: Eth2Digest): ForkDigest = + ## Return the 4-byte fork digest for the ``current_version`` and + ## ``genesis_validators_root``. + ## This is a digest primarily used for domain separation on the p2p layer. + ## 4-bytes suffices for practical separation of forks/chains. + array[4, byte](result)[0..3] = + compute_fork_data_root( + current_version, genesis_validators_root).data.toOpenArray(0, 3) + +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), + ) diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 045f3945e..800309de1 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -17,12 +17,12 @@ import chronicles, # Internal ./datatypes/[phase0, altair, merge], - ./eth2_merkleization, ./ssz_codec + "."/[eth2_merkleization, forks, ssz_codec] # TODO although eth2_merkleization already exports ssz_codec, *sometimes* code # fails to compile if the export is not done here also export - phase0, altair, eth2_merkleization, ssz_codec + forks, eth2_merkleization, ssz_codec # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#integer_squareroot func integer_squareroot*(n: SomeInteger): SomeInteger = @@ -370,13 +370,13 @@ func is_active_validator*(validator: Validator, epoch: Epoch): bool = validator.activation_epoch <= epoch and epoch < validator.exit_epoch # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_active_validator_indices -iterator get_active_validator_indices*(state: SomeBeaconState, epoch: Epoch): +iterator get_active_validator_indices*(state: ForkyBeaconState, epoch: Epoch): ValidatorIndex = for idx in 0..= GENESIS_SLOT, $state.slot compute_epoch_at_slot(state.slot) +# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_current_epoch +func get_current_epoch*(state: ForkedHashedBeaconState): Epoch = + ## Return the current epoch. + withState(state): state.data.slot.epoch + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_randao_mix -func get_randao_mix*(state: SomeBeaconState, epoch: Epoch): Eth2Digest = +func get_randao_mix*(state: ForkyBeaconState, epoch: Epoch): Eth2Digest = ## Returns the randao mix at a recent ``epoch``. state.randao_mixes[epoch mod EPOCHS_PER_HISTORICAL_VECTOR] @@ -421,29 +431,6 @@ func uint_to_bytes4*(x: uint64): array[4, byte] = result[2] = ((x shr 16) and 0xff).byte result[3] = ((x shr 24) and 0xff).byte -# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#compute_fork_data_root -func compute_fork_data_root(current_version: Version, - genesis_validators_root: Eth2Digest): Eth2Digest = - ## Return the 32-byte fork data root for the ``current_version`` and - ## ``genesis_validators_root``. - ## This is used primarily in signature domains to avoid collisions across - ## forks/chains. - hash_tree_root(ForkData( - current_version: current_version, - genesis_validators_root: genesis_validators_root - )) - -# https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#compute_fork_digest -func compute_fork_digest*(current_version: Version, - genesis_validators_root: Eth2Digest): ForkDigest = - ## Return the 4-byte fork digest for the ``current_version`` and - ## ``genesis_validators_root``. - ## This is a digest primarily used for domain separation on the p2p layer. - ## 4-bytes suffices for practical separation of forks/chains. - array[4, byte](result)[0..3] = - compute_fork_data_root( - current_version, genesis_validators_root).data.toOpenArray(0, 3) - # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#compute_domain func compute_domain*( domain_type: DomainType, @@ -471,7 +458,7 @@ func get_domain*( compute_domain(domain_type, fork_version, genesis_validators_root) func get_domain*( - state: SomeBeaconState, domain_type: DomainType, epoch: Epoch): Eth2Domain = + state: ForkyBeaconState, domain_type: DomainType, epoch: Epoch): Eth2Domain = ## Return the signature domain (fork version concatenated with domain type) ## of a message. get_domain(state.fork, domain_type, epoch, state.genesis_validators_root) @@ -487,7 +474,7 @@ func compute_signing_root*(ssz_object: auto, domain: Eth2Domain): Eth2Digest = hash_tree_root(domain_wrapped_object) # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_seed -func get_seed*(state: SomeBeaconState, epoch: Epoch, domain_type: DomainType): +func get_seed*(state: ForkyBeaconState, epoch: Epoch, domain_type: DomainType): Eth2Digest = ## Return the seed at ``epoch``. @@ -539,7 +526,7 @@ func is_execution_enabled*( is_merge_block(state, body) or is_merge_complete(state) # https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/merge/beacon-chain.md#compute_timestamp_at_slot -func compute_timestamp_at_slot*(state: SomeBeaconState, slot: Slot): uint64 = +func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 = # Note: This function is unsafe with respect to overflows and underflows. let slots_since_genesis = slot - GENESIS_SLOT state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT diff --git a/beacon_chain/spec/signatures_batch.nim b/beacon_chain/spec/signatures_batch.nim index 892c65f22..06e778ef5 100644 --- a/beacon_chain/spec/signatures_batch.nim +++ b/beacon_chain/spec/signatures_batch.nim @@ -250,8 +250,7 @@ proc addAggregateAndProofSignature*( proc collectSignatureSets*( sigs: var seq[SignatureSet], - signed_block: phase0.SignedBeaconBlock | altair.SignedBeaconBlock | - merge.SignedBeaconBlock, + signed_block: ForkySignedBeaconBlock, validatorKeys: auto, state: ForkedHashedBeaconState, cache: var StateCache): Result[void, cstring] = diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index ba36e4135..5944c7dc6 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -58,9 +58,9 @@ type Foo = phase0.SignedBeaconBlock | altair.SignedBeaconBlock | phase0.TrustedS # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function proc verify_block_signature( - #state: SomeBeaconState, signed_block: SomeSomeSignedBeaconBlock): bool {.nbench.} = - state: SomeBeaconState, signed_block: Foo): bool {.nbench.} = - #state: SomeBeaconState, signed_block: phase0.SomeSignedBeaconBlock | altair.SomeSignedBeaconBlock): bool {.nbench.} = + #state: ForkyBeaconState, signed_block: SomeSomeSignedBeaconBlock): bool {.nbench.} = + state: ForkyBeaconState, signed_block: Foo): bool {.nbench.} = + #state: ForkyBeaconState, signed_block: phase0.SomeSignedBeaconBlock | altair.SomeSignedBeaconBlock): bool {.nbench.} = let proposer_index = signed_block.message.proposer_index if proposer_index >= state.validators.lenu64: @@ -79,7 +79,7 @@ proc verify_block_signature( true # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function -proc verifyStateRoot(state: SomeBeaconState, blck: phase0.BeaconBlock or phase0.SigVerifiedBeaconBlock or altair.BeaconBlock or altair.SigVerifiedBeaconBlock or merge.BeaconBlock or merge.SigVerifiedBeaconBlock or merge.TrustedBeaconBlock): bool = +proc verifyStateRoot(state: ForkyBeaconState, blck: phase0.BeaconBlock or phase0.SigVerifiedBeaconBlock or altair.BeaconBlock or altair.SigVerifiedBeaconBlock or merge.BeaconBlock or merge.SigVerifiedBeaconBlock or merge.TrustedBeaconBlock): bool = # This is inlined in state_transition(...) in spec. let state_root = hash_tree_root(state) if state_root != blck.state_root: @@ -135,7 +135,7 @@ type # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function func process_slot*( - state: var SomeBeaconState, pre_state_root: Eth2Digest) {.nbench.} = + state: var ForkyBeaconState, pre_state_root: Eth2Digest) {.nbench.} = # `process_slot` is the first stage of per-slot processing - it is run for # every slot, including epoch slots - it does not however update the slot # number! `pre_state_root` refers to the state root of the incoming @@ -164,7 +164,7 @@ func clear_epoch_from_cache(cache: var StateCache, epoch: Epoch) = # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function proc advance_slot( cfg: RuntimeConfig, - state: var SomeBeaconState, previous_slot_state_root: Eth2Digest, + state: var ForkyBeaconState, previous_slot_state_root: Eth2Digest, flags: UpdateFlags, cache: var StateCache, info: var ForkyEpochInfo) {.nbench.} = # Do the per-slot and potentially the per-epoch processing, then bump the # slot number - we've now arrived at the slot state on top of which a block @@ -249,7 +249,7 @@ proc process_slots*( proc state_transition_block_aux( cfg: RuntimeConfig, - state: var SomeHashedBeaconState, + state: var ForkyHashedBeaconState, signedBlock: phase0.SignedBeaconBlock | phase0.SigVerifiedSignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock | altair.SigVerifiedSignedBeaconBlock | altair.TrustedSignedBeaconBlock | diff --git a/beacon_chain/spec/state_transition_block.nim b/beacon_chain/spec/state_transition_block.nim index 6b40a94c3..db18bd6a8 100644 --- a/beacon_chain/spec/state_transition_block.nim +++ b/beacon_chain/spec/state_transition_block.nim @@ -31,7 +31,7 @@ export extras, phase0, altair # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#block-header func process_block_header*( - state: var SomeBeaconState, blck: SomeSomeBeaconBlock, flags: UpdateFlags, + state: var ForkyBeaconState, blck: SomeSomeBeaconBlock, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] {.nbench.} = # Verify that the slots match if not (blck.slot == state.slot): @@ -74,7 +74,7 @@ func `xor`[T: array](a, b: T): T = # https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/phase0/beacon-chain.md#randao proc process_randao( - state: var SomeBeaconState, body: SomeSomeBeaconBlockBody, flags: UpdateFlags, + state: var ForkyBeaconState, body: SomeSomeBeaconBlockBody, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] {.nbench.} = let proposer_index = get_beacon_proposer_index(state, cache) @@ -106,7 +106,7 @@ proc process_randao( ok() # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#eth1-data -func process_eth1_data(state: var SomeBeaconState, body: SomeSomeBeaconBlockBody): Result[void, cstring] {.nbench.}= +func process_eth1_data(state: var ForkyBeaconState, body: SomeSomeBeaconBlockBody): Result[void, cstring] {.nbench.}= if not state.eth1_data_votes.add body.eth1_data: # Count is reset in process_final_updates, so this should never happen return err("process_eth1_data: no more room for eth1 data") @@ -125,7 +125,7 @@ func is_slashable_validator(validator: Validator, epoch: Epoch): bool = # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#proposer-slashings proc check_proposer_slashing*( - state: var SomeBeaconState, proposer_slashing: SomeProposerSlashing, + state: var ForkyBeaconState, proposer_slashing: SomeProposerSlashing, flags: UpdateFlags): Result[void, cstring] {.nbench.} = @@ -166,9 +166,15 @@ proc check_proposer_slashing*( ok() +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) + # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#proposer-slashings proc process_proposer_slashing*( - cfg: RuntimeConfig, state: var SomeBeaconState, + cfg: RuntimeConfig, state: var ForkyBeaconState, proposer_slashing: SomeProposerSlashing, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] {.nbench.} = @@ -193,7 +199,7 @@ func is_slashable_attestation_data( # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#attester-slashings proc check_attester_slashing*( - state: var SomeBeaconState, + state: var ForkyBeaconState, attester_slashing: SomeAttesterSlashing, flags: UpdateFlags ): Result[seq[ValidatorIndex], cstring] {.nbench.} = @@ -225,10 +231,16 @@ proc check_attester_slashing*( ok slashed_indices +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) + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#attester-slashings proc process_attester_slashing*( cfg: RuntimeConfig, - state: var SomeBeaconState, + state: var ForkyBeaconState, attester_slashing: SomeAttesterSlashing, flags: UpdateFlags, cache: var StateCache @@ -245,7 +257,7 @@ proc process_attester_slashing*( ok() proc process_deposit*(cfg: RuntimeConfig, - state: var SomeBeaconState, + state: var ForkyBeaconState, deposit: Deposit, flags: UpdateFlags): Result[void, cstring] {.nbench.} = ## Process an Eth1 deposit, registering a validator or increasing its balance. @@ -314,7 +326,7 @@ proc process_deposit*(cfg: RuntimeConfig, # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#voluntary-exits proc check_voluntary_exit*( cfg: RuntimeConfig, - state: SomeBeaconState, + state: ForkyBeaconState, signed_voluntary_exit: SomeSignedVoluntaryExit, flags: UpdateFlags): Result[void, cstring] {.nbench.} = @@ -364,10 +376,17 @@ proc check_voluntary_exit*( ok() +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) + # https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#voluntary-exits proc process_voluntary_exit*( cfg: RuntimeConfig, - state: var SomeBeaconState, + state: var ForkyBeaconState, signed_voluntary_exit: SomeSignedVoluntaryExit, flags: UpdateFlags, cache: var StateCache): Result[void, cstring] {.nbench.} = @@ -379,7 +398,7 @@ proc process_voluntary_exit*( # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#operations proc process_operations(cfg: RuntimeConfig, - state: var SomeBeaconState, + state: var ForkyBeaconState, body: SomeSomeBeaconBlockBody, base_reward_per_increment: Gwei, flags: UpdateFlags, diff --git a/beacon_chain/spec/state_transition_epoch.nim b/beacon_chain/spec/state_transition_epoch.nim index c249b9062..bb39d9359 100644 --- a/beacon_chain/spec/state_transition_epoch.nim +++ b/beacon_chain/spec/state_transition_epoch.nim @@ -461,7 +461,7 @@ func get_proposer_reward(base_reward: Gwei): Gwei = func is_in_inactivity_leak(finality_delay: uint64): bool = finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY -func get_finality_delay(state: SomeBeaconState): uint64 = +func get_finality_delay(state: ForkyBeaconState): uint64 = get_previous_epoch(state) - state.finalized_checkpoint.epoch # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#rewards-and-penalties-1 @@ -746,7 +746,7 @@ func process_rewards_and_penalties( # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#registry-updates func process_registry_updates*( - cfg: RuntimeConfig, state: var SomeBeaconState, cache: var StateCache) {.nbench.} = + cfg: RuntimeConfig, state: var ForkyBeaconState, cache: var StateCache) {.nbench.} = ## Process activation eligibility and ejections # Make visible, e.g., @@ -796,7 +796,7 @@ func process_registry_updates*( # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#slashings # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/altair/beacon-chain.md#slashings -func process_slashings*(state: var SomeBeaconState, total_balance: Gwei) {.nbench.} = +func process_slashings*(state: var ForkyBeaconState, total_balance: Gwei) {.nbench.} = let epoch = get_current_epoch(state) multiplier = @@ -824,7 +824,7 @@ func process_slashings*(state: var SomeBeaconState, total_balance: Gwei) {.nbenc decrease_balance(state, index.ValidatorIndex, penalty) # https://github.com/ethereum/consensus-specs/blob/34cea67b91/specs/phase0/beacon-chain.md#eth1-data-votes-updates -func process_eth1_data_reset*(state: var SomeBeaconState) {.nbench.} = +func process_eth1_data_reset*(state: var ForkyBeaconState) {.nbench.} = let next_epoch = get_current_epoch(state) + 1 # Reset eth1 data votes @@ -832,7 +832,7 @@ func process_eth1_data_reset*(state: var SomeBeaconState) {.nbench.} = state.eth1_data_votes = default(type state.eth1_data_votes) # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#effective-balances-updates -func process_effective_balance_updates*(state: var SomeBeaconState) {.nbench.} = +func process_effective_balance_updates*(state: var ForkyBeaconState) {.nbench.} = # Update effective balances with hysteresis for index in 0..= uint64(result) -iterator committee_indices_per_slot*(state: SomeBeaconState, +func get_committee_count_per_slot*(state: ForkedHashedBeaconState, + epoch: Epoch, + cache: var StateCache): uint64 = + withState(state): + get_committee_count_per_slot(state.data, epoch, cache) + +iterator committee_indices_per_slot*(state: ForkyBeaconState, epoch: Epoch, cache: var StateCache): CommitteeIndex = for idx in 0'u64 ..< get_committee_count_per_slot(state, epoch, cache): yield CommitteeIndex.verifiedValue(idx) -func get_committee_count_per_slot*(state: SomeBeaconState, +func get_committee_count_per_slot*(state: ForkyBeaconState, slot: Slot, cache: var StateCache): uint64 = get_committee_count_per_slot(state, slot.compute_epoch_at_slot, cache) @@ -190,11 +202,16 @@ func get_previous_epoch*(current_epoch: Epoch): Epoch = else: current_epoch - 1 -func get_previous_epoch*(state: SomeBeaconState): Epoch = +func get_previous_epoch*(state: ForkyBeaconState): Epoch = ## Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). # Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). get_previous_epoch(get_current_epoch(state)) +# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#get_previous_epoch +func get_previous_epoch*(state: ForkedHashedBeaconState): Epoch = + ## Return the previous epoch (unless the current epoch is ``GENESIS_EPOCH``). + get_previous_epoch(get_current_epoch(state)) + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#compute_committee func compute_committee_slice*( active_validators, index, count: uint64): Slice[int] = @@ -241,7 +258,7 @@ func compute_committee_len*( # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_beacon_committee iterator get_beacon_committee*( - state: SomeBeaconState, slot: Slot, index: CommitteeIndex, + state: ForkyBeaconState, slot: Slot, index: CommitteeIndex, cache: var StateCache): ValidatorIndex = ## Return the beacon committee at ``slot`` for ``index``. let @@ -255,7 +272,7 @@ iterator get_beacon_committee*( ): yield idx func get_beacon_committee*( - state: SomeBeaconState, slot: Slot, index: CommitteeIndex, + state: ForkyBeaconState, slot: Slot, index: CommitteeIndex, cache: var StateCache): seq[ValidatorIndex] = ## Return the beacon committee at ``slot`` for ``index``. let @@ -268,9 +285,20 @@ func get_beacon_committee*( committees_per_slot * SLOTS_PER_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) + # https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/phase0/beacon-chain.md#get_beacon_committee func get_beacon_committee_len*( - state: SomeBeaconState, slot: Slot, index: CommitteeIndex, + state: ForkyBeaconState, slot: Slot, index: CommitteeIndex, cache: var StateCache): uint64 = # Return the number of members in the beacon committee at ``slot`` for ``index``. let @@ -284,6 +312,13 @@ func get_beacon_committee_len*( committees_per_slot * SLOTS_PER_EPOCH ) +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) + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#compute_shuffled_index func compute_shuffled_index*( index: uint64, index_count: uint64, seed: Eth2Digest): uint64 = @@ -320,7 +355,7 @@ func compute_shuffled_index*( cur_idx_permuted # https://github.com/ethereum/consensus-specs/blob/v1.1.0/specs/phase0/beacon-chain.md#compute_proposer_index -func compute_proposer_index(state: SomeBeaconState, +func compute_proposer_index(state: ForkyBeaconState, indices: seq[ValidatorIndex], seed: Eth2Digest): Option[ValidatorIndex] = ## Return from ``indices`` a random index sampled by effective balance. const MAX_RANDOM_BYTE = 255 @@ -348,7 +383,7 @@ func compute_proposer_index(state: SomeBeaconState, # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_beacon_proposer_index func get_beacon_proposer_index*( - state: SomeBeaconState, cache: var StateCache, slot: Slot): + state: ForkyBeaconState, cache: var StateCache, slot: Slot): Option[ValidatorIndex] = cache.beacon_proposer_indices.withValue(slot, proposer) do: return proposer[] @@ -380,10 +415,16 @@ func get_beacon_proposer_index*( return res # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/beacon-chain.md#get_beacon_proposer_index -func get_beacon_proposer_index*(state: SomeBeaconState, cache: var StateCache): +func get_beacon_proposer_index*(state: ForkyBeaconState, cache: var StateCache): Option[ValidatorIndex] = get_beacon_proposer_index(state, cache, state.slot) +func get_beacon_proposer_index*(state: ForkedHashedBeaconState, + cache: var StateCache, slot: Slot): + Option[ValidatorIndex] = + withState(state): + get_beacon_proposer_index(state.data, cache, slot) + # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/phase0/validator.md#aggregation-selection func is_aggregator*(committee_len: uint64, slot_signature: ValidatorSig): bool = let diff --git a/beacon_chain/sszdump.nim b/beacon_chain/sszdump.nim index 1841d6632..e28b4274f 100644 --- a/beacon_chain/sszdump.nim +++ b/beacon_chain/sszdump.nim @@ -10,10 +10,13 @@ import std/[os, strformat], chronicles, - ./spec/[eth2_ssz_serialization, eth2_merkleization], + ./spec/[eth2_ssz_serialization, eth2_merkleization, forks], ./spec/datatypes/[phase0, altair, merge], ./consensus_object_pools/block_pools_types +export + eth2_ssz_serialization, eth2_merkleization, forks, block_pools_types + # Dump errors are generally not fatal where used currently - the code calling # these functions, like most code, is not exception safe template logErrors(body: untyped) = @@ -46,14 +49,14 @@ proc dump*(dir: string, v: merge.SignedBeaconBlock) = logErrors: SSZ.saveFile(dir / &"block-{v.message.slot}-{shortLog(v.root)}.ssz", v) -proc dump*(dir: string, v: SomeHashedBeaconState, blck: BlockRef) = +proc dump*(dir: string, v: ForkyHashedBeaconState, blck: BlockRef) = mixin saveFile logErrors: SSZ.saveFile( dir / &"state-{v.data.slot}-{shortLog(blck.root)}-{shortLog(v.root)}.ssz", v.data) -proc dump*(dir: string, v: SomeHashedBeaconState) = +proc dump*(dir: string, v: ForkyHashedBeaconState) = mixin saveFile logErrors: SSZ.saveFile( diff --git a/nbench/scenarios.nim b/nbench/scenarios.nim index b1259f460..f83f561f7 100644 --- a/nbench/scenarios.nim +++ b/nbench/scenarios.nim @@ -152,7 +152,7 @@ proc runFullTransition*(dir, preState, blocksPrefix: string, blocksQty: int, ski phase0Data: phase0.HashedBeaconState(data: parseSSZ(prePath, phase0.BeaconState)), kind: BeaconStateFork.Phase0 ) - setStateRoot(state[], hash_tree_root(state[])) + setStateRoot(state[], hash_tree_root(state[].phase0Data.data)) for i in 0 ..< blocksQty: let blockPath = dir / blocksPrefix & $i & ".ssz" @@ -177,7 +177,7 @@ proc runProcessSlots*(dir, preState: string, numSlots: uint64) = phase0Data: phase0.HashedBeaconState( data: parseSSZ(prePath, phase0.BeaconState)), kind: BeaconStateFork.Phase0) - setStateRoot(state[], hash_tree_root(state[])) + setStateRoot(state[], hash_tree_root(state[].phase0Data.data)) # Shouldn't necessarily assert, because nbench can run test suite discard process_slots( diff --git a/ncli/ncli.nim b/ncli/ncli.nim index accc9dd65..45ad0a5ff 100644 --- a/ncli/ncli.nim +++ b/ncli/ncli.nim @@ -89,7 +89,7 @@ proc doTransition(conf: NcliConf) = blckX = SSZ.loadFile(conf.blck, phase0.SignedBeaconBlock) flags = if not conf.verifyStateRoot: {skipStateRootValidation} else: {} - setStateRoot(stateY[], hash_tree_root(stateY[])) + setStateRoot(stateY[], hash_tree_root(stateY[].phase0Data.data)) var cache = StateCache() @@ -117,7 +117,7 @@ proc doSlots(conf: NcliConf) = kind: BeaconStateFork.Phase0 ) - setStateRoot(stateY[], hash_tree_root(stateY[])) + setStateRoot(stateY[], hash_tree_root(stateY[].phase0Data.data)) var cache = StateCache() @@ -147,7 +147,9 @@ proc doSSZ(conf: NcliConf) = if cmpIgnoreCase(ext, ".ssz") == 0: SSZ.loadFile(file, t) elif cmpIgnoreCase(ext, ".json") == 0: - JSON.loadFile(file, t) + # JSON.loadFile(file, t) + echo "TODO needs porting to RestJson" + quit 1 else: echo "Unknown file type: ", ext quit 1 diff --git a/ncli/ncli_db.nim b/ncli/ncli_db.nim index 91515e307..a9fb7cee4 100644 --- a/ncli/ncli_db.nim +++ b/ncli/ncli_db.nim @@ -7,7 +7,7 @@ import blockchain_dag, forkedbeaconstate_dbhelpers], ../beacon_chain/spec/datatypes/phase0, ../beacon_chain/spec/[ - forks, helpers, state_transition, state_transition_epoch], + beaconstate, helpers, state_transition, state_transition_epoch, validator], ../beacon_chain/sszdump, ../research/simutils, ./e2store @@ -183,7 +183,7 @@ proc cmdBench(conf: DbConf, cfg: RuntimeConfig) = for b in 0..