diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 2eac31387..4e5e2ccc6 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -1961,13 +1961,13 @@ proc pruneBlocksDAG(dag: ChainDAGRef) = dagPruneDur = Moment.now() - startTick # https://github.com/ethereum/consensus-specs/blob/v1.3.0/sync/optimistic.md#helpers -template is_optimistic*(dag: ChainDAGRef, root: Eth2Digest): bool = - let blck = dag.getBlockRef(root) - if blck.isSome: - not blck.get.executionValid - else: - # Either it doesn't exist at all, or it's finalized - not dag.finalizedHead.blck.executionValid +template is_optimistic*(dag: ChainDAGRef, bid: BlockId): bool = + let blck = + if bid.slot <= dag.finalizedHead.slot: + dag.finalizedHead.blck + else: + dag.getBlockRef(bid.root).expect("Non-finalized block is known") + not blck.executionValid proc markBlockVerified*(dag: ChainDAGRef, blck: BlockRef) = var cur = blck diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index 0fa9f9654..88cd8c841 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -246,7 +246,7 @@ proc initFullNode( proc onBlockAdded(data: ForkedTrustedSignedBeaconBlock) = let optimistic = if node.currentSlot().epoch() >= dag.cfg.BELLATRIX_FORK_EPOCH: - some node.dag.is_optimistic(data.root) + some node.dag.is_optimistic(data.toBlockId()) else: none[bool]() node.eventBus.blocksQueue.emit( @@ -255,7 +255,8 @@ proc initFullNode( let eventData = if node.currentSlot().epoch() >= dag.cfg.BELLATRIX_FORK_EPOCH: var res = data - res.optimistic = some node.dag.is_optimistic(data.block_root) + res.optimistic = some node.dag.is_optimistic( + BlockId(slot: data.slot, root: data.block_root)) res else: data @@ -264,7 +265,8 @@ proc initFullNode( let eventData = if node.currentSlot().epoch() >= dag.cfg.BELLATRIX_FORK_EPOCH: var res = data - res.optimistic = some node.dag.is_optimistic(data.new_head_block) + res.optimistic = some node.dag.is_optimistic( + BlockId(slot: data.slot, root: data.new_head_block)) res else: data @@ -285,7 +287,10 @@ proc initFullNode( let eventData = if node.currentSlot().epoch() >= dag.cfg.BELLATRIX_FORK_EPOCH: var res = data - res.optimistic = some node.dag.is_optimistic(data.block_root) + # `slot` in this `BlockId` may be higher than block's actual slot, + # this is alright for the purpose of calling `is_optimistic`. + res.optimistic = some node.dag.is_optimistic( + BlockId(slot: data.epoch.start_slot, root: data.block_root)) res else: data @@ -1186,7 +1191,7 @@ proc onSlotEnd(node: BeaconNode, slot: Slot) {.async.} = # Update upcoming actions - we do this every slot in case a reorg happens let head = node.dag.head - if node.isSynced(head) == SyncStatus.synced: + if node.isSynced(head) and head.executionValid: withState(node.dag.headState): if node.consensusManager[].actionTracker.needsUpdate( forkyState, slot.epoch + 1): diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index c867bbe8f..279aaeb3c 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -114,7 +114,7 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = validity: if item.invalid: RestNodeValidity.invalid - elif node.dag.is_optimistic(item.bid.root): + elif node.dag.is_optimistic(item.bid): RestNodeValidity.optimistic else: RestNodeValidity.valid, diff --git a/beacon_chain/rpc/rest_nimbus_api.nim b/beacon_chain/rpc/rest_nimbus_api.nim index 8427a2244..f8ddc849a 100644 --- a/beacon_chain/rpc/rest_nimbus_api.nim +++ b/beacon_chain/rpc/rest_nimbus_api.nim @@ -1,3 +1,4 @@ +# beacon_chain # Copyright (c) 2018-2023 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). @@ -238,9 +239,9 @@ proc installNimbusApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError, $res.error()) let tres = res.get() - if tres.optimistic: + if not tres.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) - tres.head + tres let proposalState = assignClone(node.dag.headState) node.dag.withUpdatedState( proposalState[], diff --git a/beacon_chain/rpc/rest_utils.nim b/beacon_chain/rpc/rest_utils.nim index 2079baa55..5033af075 100644 --- a/beacon_chain/rpc/rest_utils.nim +++ b/beacon_chain/rpc/rest_utils.nim @@ -33,23 +33,17 @@ func match(data: openArray[char], charset: set[char]): int = proc getSyncedHead*( node: BeaconNode, slot: Slot - ): Result[tuple[head: BlockRef, optimistic: bool], cstring] = - let - head = node.dag.head - optimistic = - case node.isSynced(head) - of SyncStatus.unsynced: - return err("Beacon node not fully and non-optimistically synced") - of SyncStatus.synced: - false - of SyncStatus.optimistic: - true + ): Result[BlockRef, cstring] = + let head = node.dag.head + + if not node.isSynced(head): + return err("Beacon node not fully and non-optimistically synced") # Enough ahead not to know the shuffling if slot > head.slot + SLOTS_PER_EPOCH * 2: return err("Requesting far ahead of the current head") - ok((head, optimistic)) + ok(head) func getCurrentSlot*(node: BeaconNode, slot: Slot): Result[Slot, cstring] = @@ -61,7 +55,7 @@ func getCurrentSlot*(node: BeaconNode, slot: Slot): proc getSyncedHead*( node: BeaconNode, epoch: Epoch, - ): Result[tuple[head: BlockRef, optimistic: bool], cstring] = + ): Result[BlockRef, cstring] = if epoch > MaxEpoch: return err("Requesting epoch for which slot would overflow") node.getSyncedHead(epoch.start_slot()) @@ -276,30 +270,22 @@ proc getShufflingOptimistic*(node: BeaconNode, dependentSlot: Slot, dependentRoot: Eth2Digest): Option[bool] = if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH: - if dependentSlot <= node.dag.finalizedHead.slot: - some[bool](false) - else: - some[bool](node.dag.is_optimistic(dependentRoot)) + # `slot` in this `BlockId` may be higher than block's actual slot, + # this is alright for the purpose of calling `is_optimistic`. + let bid = BlockId(slot: dependentSlot, root: dependentRoot) + some[bool](node.dag.is_optimistic(bid)) else: none[bool]() proc getStateOptimistic*(node: BeaconNode, state: ForkedHashedBeaconState): Option[bool] = if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH: - case state.kind - of ConsensusFork.Phase0, ConsensusFork.Altair: - some[bool](false) - of ConsensusFork.Bellatrix, ConsensusFork.Capella, - ConsensusFork.Deneb: + if state.kind >= ConsensusFork.Bellatrix: # A state is optimistic iff the block which created it is - withState(state): - # The block root which created the state at slot `n` is at slot `n-1` - if forkyState.data.slot == GENESIS_SLOT: - some[bool](false) - else: - doAssert forkyState.data.slot > 0 - some[bool](node.dag.is_optimistic( - get_block_root_at_slot(forkyState.data, forkyState.data.slot - 1))) + let stateBid = withState(state): forkyState.latest_block_id + some[bool](node.dag.is_optimistic(stateBid)) + else: + some[bool](false) else: none[bool]() @@ -307,11 +293,10 @@ proc getBlockOptimistic*(node: BeaconNode, blck: ForkedTrustedSignedBeaconBlock | ForkedSignedBeaconBlock): Option[bool] = if node.currentSlot().epoch() >= node.dag.cfg.BELLATRIX_FORK_EPOCH: - case blck.kind - of ConsensusFork.Phase0, ConsensusFork.Altair: + if blck.kind >= ConsensusFork.Bellatrix: + some[bool](node.dag.is_optimistic(blck.toBlockId())) + else: some[bool](false) - of ConsensusFork.Bellatrix, ConsensusFork.Capella, ConsensusFork.Deneb: - some[bool](node.dag.is_optimistic(blck.root)) else: none[bool]() diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index d0027f45d..5e057d7ac 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -65,7 +65,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidEpochValueError, "Cannot request duties past next epoch") res - let (qhead, _) = + let qhead = block: let res = node.getSyncedHead(qepoch) if res.isErr(): @@ -126,7 +126,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http400, InvalidEpochValueError, "Cannot request duties past next epoch") res - let (qhead, _) = + let qhead = block: let res = node.getSyncedHead(qepoch) if res.isErr(): @@ -278,7 +278,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonResponseWOpt(res, optimistic) elif qSyncPeriod > headSyncPeriod: # The requested epoch may still be too far in the future. - if node.isSynced(node.dag.head) != SyncStatus.synced: + if not node.isSynced(node.dag.head) or not node.dag.head.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) else: return RestApiResponse.jsonError(Http400, EpochFromFutureError) @@ -378,9 +378,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError, $res.error()) let tres = res.get() - if tres.optimistic: + if not tres.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) - tres.head + tres let proposer = node.dag.getProposer(qhead, qslot).valueOr: return RestApiResponse.jsonError(Http400, ProposerNotFoundError) @@ -488,9 +488,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError, $res.error()) let tres = res.get() - if tres.optimistic: + if not tres.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) - tres.head + tres let proposer = node.dag.getProposer(qhead, qslot).valueOr: return RestApiResponse.jsonError(Http400, ProposerNotFoundError) @@ -586,9 +586,9 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError, $res.error()) let tres = res.get() - if tres.optimistic: + if not tres.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) - tres.head + tres let epochRef = node.dag.getEpochRef(qhead, qslot.epoch, true).valueOr: return RestApiResponse.jsonError(Http400, PrunedStateError, $error) makeAttestationData(epochRef, qhead.atSlot(qslot), qindex) @@ -677,7 +677,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = $dres.error()) dres.get() - if node.isSynced(node.dag.head) == SyncStatus.unsynced: + if not node.isSynced(node.dag.head): return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) let @@ -833,7 +833,7 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError, $res.error()) let tres = res.get() - if tres.optimistic: + if not tres.executionValid: return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError) var contribution = SyncCommitteeContribution() diff --git a/beacon_chain/validators/validator_duties.nim b/beacon_chain/validators/validator_duties.nim index d41036032..76c318adb 100644 --- a/beacon_chain/validators/validator_duties.nim +++ b/beacon_chain/validators/validator_duties.nim @@ -90,11 +90,6 @@ type BlindedBlockResult[SBBB] = Result[tuple[blindedBlckPart: SBBB, blockValue: UInt256], string] - SyncStatus* {.pure.} = enum - synced - unsynced - optimistic - proc getValidator*(validators: auto, pubkey: ValidatorPubKey): Opt[ValidatorAndIndex] = let idx = validators.findIt(it.pubkey == pubkey) @@ -139,7 +134,7 @@ proc getValidatorForDuties*( node.attachedValidators[].getValidatorForDuties( key.toPubKey(), slot, slashingSafe) -proc isSynced*(node: BeaconNode, head: BlockRef): SyncStatus = +proc isSynced*(node: BeaconNode, head: BlockRef): bool = ## TODO This function is here as a placeholder for some better heurestics to ## determine if we're in sync and should be producing blocks and ## attestations. Generally, the problem is that slot time keeps advancing @@ -160,14 +155,8 @@ proc isSynced*(node: BeaconNode, head: BlockRef): SyncStatus = # TODO if everyone follows this logic, the network will not recover from a # halt: nobody will be producing blocks because everone expects someone # else to do it - if wallSlot.afterGenesis and - head.slot + node.config.syncHorizon < wallSlot.slot: - SyncStatus.unsynced - else: - if not head.executionValid: - SyncStatus.optimistic - else: - SyncStatus.synced + not wallSlot.afterGenesis or + head.slot + node.config.syncHorizon >= wallSlot.slot proc handleLightClientUpdates*(node: BeaconNode, slot: Slot) {.async.} = static: doAssert lightClientFinalityUpdateSlotOffset == @@ -1550,8 +1539,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} = # The dag head might be updated by sync while we're working due to the # await calls, thus we use a local variable to keep the logic straight here var head = node.dag.head - case node.isSynced(head) - of SyncStatus.unsynced: + if not node.isSynced(head): info "Beacon node not in sync; skipping validator duties for now", slot, headSlot = head.slot @@ -1560,7 +1548,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} = return - of SyncStatus.optimistic: + elif not head.executionValid: info "Execution client not in sync; skipping validator duties for now", slot, headSlot = head.slot @@ -1568,7 +1556,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} = updateValidatorMetrics(node) return - of SyncStatus.synced: + else: discard # keep going withState(node.dag.headState): @@ -1681,7 +1669,7 @@ proc registerDuties*(node: BeaconNode, wallSlot: Slot) {.async.} = ## Register upcoming duties of attached validators with the duty tracker if node.attachedValidators[].count() == 0 or - node.isSynced(node.dag.head) != SyncStatus.synced: + not node.isSynced(node.dag.head) or not node.dag.head.executionValid: # Nothing to do because we have no validator attached return