add REST endpoint for fork choice context (#4042)

Implements a proposed REST endpoint for analyzing fork choice behaviour.
See https://github.com/ethereum/beacon-APIs/pull/232
This commit is contained in:
Etan Kissling 2022-08-30 00:02:29 +02:00 committed by GitHub
parent 613f4a9a50
commit 574b84f96f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 19 deletions

View File

@ -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

View File

@ -1692,26 +1692,23 @@ proc pruneStateCachesDAG*(dag: ChainDAGRef) =
statePruneDur = statePruneTick - startTick,
epochRefPruneDur = epochRefPruneTick - statePruneTick
proc loadExecutionBlockRoot*(dag: ChainDAGRef, blck: BlockRef): Eth2Digest =
if dag.cfg.blockForkAtEpoch(blck.bid.slot.epoch) < BeaconBlockFork.Bellatrix:
proc loadExecutionBlockRoot*(dag: ChainDAGRef, bid: BlockId): Eth2Digest =
if dag.cfg.blockForkAtEpoch(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
let blockData = dag.getForkedBlock(bid).valueOr:
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
proc loadExecutionBlockRoot*(dag: ChainDAGRef, blck: BlockRef): Eth2Digest =
if blck.executionBlockRoot.isNone:
blck.executionBlockRoot = some dag.loadExecutionBlockRoot(blck.bid)
blck.executionBlockRoot.unsafeGet
proc updateHead*(
dag: ChainDAGRef,

View File

@ -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

View File

@ -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(