diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index a1cfdab83..4fb03bfed 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -484,6 +484,12 @@ type defaultValue: true # the use of the nimbus_signing_process binary by default will be delayed until async I/O over stdin/stdout is developed for the child process. name: "in-process-validators" .}: bool + debugForkChoice* {. + hidden + desc: "Enable debug API for fork choice (https://github.com/ethereum/beacon-APIs/pull/232)" + defaultValue: false + name: "debug-fork-choice" .}: bool + discv5Enabled* {. desc: "Enable Discovery v5" defaultValue: true diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 272c47986..cd8931254 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -1692,26 +1692,23 @@ proc pruneStateCachesDAG*(dag: ChainDAGRef) = statePruneDur = statePruneTick - startTick, epochRefPruneDur = epochRefPruneTick - statePruneTick +proc loadExecutionBlockRoot*(dag: ChainDAGRef, bid: BlockId): Eth2Digest = + if dag.cfg.blockForkAtEpoch(bid.slot.epoch) < BeaconBlockFork.Bellatrix: + return ZERO_HASH + + let blockData = dag.getForkedBlock(bid).valueOr: + return ZERO_HASH + + withBlck(blockData): + when stateFork >= BeaconStateFork.Bellatrix: + blck.message.body.execution_payload.block_hash + else: + ZERO_HASH + proc loadExecutionBlockRoot*(dag: ChainDAGRef, blck: BlockRef): Eth2Digest = - if dag.cfg.blockForkAtEpoch(blck.bid.slot.epoch) < BeaconBlockFork.Bellatrix: - return ZERO_HASH - - if blck.executionBlockRoot.isSome: - return blck.executionBlockRoot.get - - let blockData = dag.getForkedBlock(blck.bid).valueOr: - blck.executionBlockRoot = some ZERO_HASH - return ZERO_HASH - - let executionBlockRoot = - withBlck(blockData): - when stateFork >= BeaconStateFork.Bellatrix: - blck.message.body.execution_payload.block_hash - else: - ZERO_HASH - blck.executionBlockRoot = some executionBlockRoot - - executionBlockRoot + if blck.executionBlockRoot.isNone: + blck.executionBlockRoot = some dag.loadExecutionBlockRoot(blck.bid) + blck.executionBlockRoot.unsafeGet proc updateHead*( dag: ChainDAGRef, diff --git a/beacon_chain/fork_choice/proto_array.nim b/beacon_chain/fork_choice/proto_array.nim index f393307c9..6282de1a5 100644 --- a/beacon_chain/fork_choice/proto_array.nim +++ b/beacon_chain/fork_choice/proto_array.nim @@ -552,6 +552,50 @@ func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool = (self.checkpoints.finalized.epoch == GENESIS_EPOCH) ) +# Diagnostics +# ---------------------------------------------------------------------- +# Helpers to dump internal state + +type ProtoArrayItem* = object + root*: Eth2Digest + parent*: Eth2Digest + checkpoints*: FinalityCheckpoints + unrealized*: Option[FinalityCheckpoints] + weight*: int64 + bestChild*: Eth2Digest + bestDescendant*: Eth2Digest + +func root(self: ProtoNodes, logicalIdx: Option[Index]): Eth2Digest = + if logicalIdx.isNone: + return ZERO_HASH + let node = self[logicalIdx.unsafeGet] + if node.isNone: + return ZERO_HASH + node.unsafeGet.root + +iterator items*(self: ProtoArray): ProtoArrayItem = + ## Iterate over all nodes known by fork choice. + doAssert self.indices.len == self.nodes.len + for nodePhysicalIdx, node in self.nodes.buf: + if node.root.isZero: + continue + + let unrealized = block: + let nodeLogicalIdx = nodePhysicalIdx + self.nodes.offset + if self.currentEpochTips.hasKey(nodeLogicalIdx): + some self.currentEpochTips.unsafeGet(nodeLogicalIdx) + else: + none(FinalityCheckpoints) + + yield ProtoArrayItem( + root: node.root, + parent: self.nodes.root(node.parent), + checkpoints: node.checkpoints, + unrealized: unrealized, + weight: node.weight, + bestChild: self.nodes.root(node.bestChild), + bestDescendant: self.nodes.root(node.bestDescendant)) + # Sanity checks # ---------------------------------------------------------------------- # Sanity checks on internal private procedures diff --git a/beacon_chain/rpc/rest_debug_api.nim b/beacon_chain/rpc/rest_debug_api.nim index d7175c307..01535545b 100644 --- a/beacon_chain/rpc/rest_debug_api.nim +++ b/beacon_chain/rpc/rest_debug_api.nim @@ -11,6 +11,8 @@ import ".."/beacon_node, ".."/spec/forks, "."/[rest_utils, state_ttl_cache] +from ../fork_choice/proto_array import ProtoArrayItem, items + export rest_utils logScope: topics = "rest_debug" @@ -107,6 +109,74 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) = ) ) + # https://github.com/ethereum/beacon-APIs/pull/232 + if node.config.debugForkChoice: + router.api(MethodGet, + "/eth/v1/debug/fork_choice") do () -> RestApiResponse: + type + ForkChoiceResponseExtraData = object + justified_root: Eth2Digest + finalized_root: Eth2Digest + u_justified_checkpoint: Option[Checkpoint] + u_finalized_checkpoint: Option[Checkpoint] + best_child: Eth2Digest + best_descendant: Eth2Digest + + ForkChoiceResponse = object + slot: Slot + block_root: Eth2Digest + parent_root: Eth2Digest + justified_epoch: Epoch + finalized_epoch: Epoch + weight: uint64 + execution_optimistic: bool + execution_payload_root: Eth2Digest + extra_data: Option[ForkChoiceResponseExtraData] + + var responses: seq[ForkChoiceResponse] + for item in node.attestationPool[].forkChoice.backend.proto_array: + let + bid = node.dag.getBlockId(item.root) + slot = + if bid.isOk: + bid.unsafeGet.slot + else: + FAR_FUTURE_SLOT + executionPayloadRoot = + if bid.isOk: + node.dag.loadExecutionBlockRoot(bid.unsafeGet) + else: + ZERO_HASH + unrealized = item.unrealized.get(item.checkpoints) + u_justified_checkpoint = + if unrealized.justified != item.checkpoints.justified: + some unrealized.justified + else: + none(Checkpoint) + u_finalized_checkpoint = + if unrealized.finalized != item.checkpoints.finalized: + some unrealized.finalized + else: + none(Checkpoint) + + responses.add ForkChoiceResponse( + slot: slot, + block_root: item.root, + parent_root: item.parent, + justified_epoch: item.checkpoints.justified.epoch, + finalized_epoch: item.checkpoints.finalized.epoch, + weight: cast[uint64](item.weight), + execution_optimistic: node.dag.is_optimistic(item.root), + execution_payload_root: executionPayloadRoot, + extra_data: some ForkChoiceResponseExtraData( + justified_root: item.checkpoints.justified.root, + finalized_root: item.checkpoints.finalized.root, + u_justified_checkpoint: u_justified_checkpoint, + u_finalized_checkpoint: u_finalized_checkpoint, + best_child: item.bestChild, + bestDescendant: item.bestDescendant)) + return RestApiResponse.jsonResponse(responses) + # Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional # `/api` path component router.redirect(