Skip to content

Commit 574b84f

Browse files
authored
add REST endpoint for fork choice context (#4042)
Implements a proposed REST endpoint for analyzing fork choice behaviour. See ethereum/beacon-APIs#232
1 parent 613f4a9 commit 574b84f

File tree

4 files changed

+132
-15
lines changed

4 files changed

+132
-15
lines changed

beacon_chain/conf.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,12 @@ type
484484
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.
485485
name: "in-process-validators" .}: bool
486486

487+
debugForkChoice* {.
488+
hidden
489+
desc: "Enable debug API for fork choice (https://github.com/ethereum/beacon-APIs/pull/232)"
490+
defaultValue: false
491+
name: "debug-fork-choice" .}: bool
492+
487493
discv5Enabled* {.
488494
desc: "Enable Discovery v5"
489495
defaultValue: true

beacon_chain/consensus_object_pools/blockchain_dag.nim

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,26 +1692,23 @@ proc pruneStateCachesDAG*(dag: ChainDAGRef) =
16921692
statePruneDur = statePruneTick - startTick,
16931693
epochRefPruneDur = epochRefPruneTick - statePruneTick
16941694

1695-
proc loadExecutionBlockRoot*(dag: ChainDAGRef, blck: BlockRef): Eth2Digest =
1696-
if dag.cfg.blockForkAtEpoch(blck.bid.slot.epoch) < BeaconBlockFork.Bellatrix:
1695+
proc loadExecutionBlockRoot*(dag: ChainDAGRef, bid: BlockId): Eth2Digest =
1696+
if dag.cfg.blockForkAtEpoch(bid.slot.epoch) < BeaconBlockFork.Bellatrix:
16971697
return ZERO_HASH
16981698

1699-
if blck.executionBlockRoot.isSome:
1700-
return blck.executionBlockRoot.get
1701-
1702-
let blockData = dag.getForkedBlock(blck.bid).valueOr:
1703-
blck.executionBlockRoot = some ZERO_HASH
1699+
let blockData = dag.getForkedBlock(bid).valueOr:
17041700
return ZERO_HASH
17051701

1706-
let executionBlockRoot =
1707-
withBlck(blockData):
1708-
when stateFork >= BeaconStateFork.Bellatrix:
1709-
blck.message.body.execution_payload.block_hash
1710-
else:
1711-
ZERO_HASH
1712-
blck.executionBlockRoot = some executionBlockRoot
1702+
withBlck(blockData):
1703+
when stateFork >= BeaconStateFork.Bellatrix:
1704+
blck.message.body.execution_payload.block_hash
1705+
else:
1706+
ZERO_HASH
17131707

1714-
executionBlockRoot
1708+
proc loadExecutionBlockRoot*(dag: ChainDAGRef, blck: BlockRef): Eth2Digest =
1709+
if blck.executionBlockRoot.isNone:
1710+
blck.executionBlockRoot = some dag.loadExecutionBlockRoot(blck.bid)
1711+
blck.executionBlockRoot.unsafeGet
17151712

17161713
proc updateHead*(
17171714
dag: ChainDAGRef,

beacon_chain/fork_choice/proto_array.nim

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,50 @@ func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
552552
(self.checkpoints.finalized.epoch == GENESIS_EPOCH)
553553
)
554554

555+
# Diagnostics
556+
# ----------------------------------------------------------------------
557+
# Helpers to dump internal state
558+
559+
type ProtoArrayItem* = object
560+
root*: Eth2Digest
561+
parent*: Eth2Digest
562+
checkpoints*: FinalityCheckpoints
563+
unrealized*: Option[FinalityCheckpoints]
564+
weight*: int64
565+
bestChild*: Eth2Digest
566+
bestDescendant*: Eth2Digest
567+
568+
func root(self: ProtoNodes, logicalIdx: Option[Index]): Eth2Digest =
569+
if logicalIdx.isNone:
570+
return ZERO_HASH
571+
let node = self[logicalIdx.unsafeGet]
572+
if node.isNone:
573+
return ZERO_HASH
574+
node.unsafeGet.root
575+
576+
iterator items*(self: ProtoArray): ProtoArrayItem =
577+
## Iterate over all nodes known by fork choice.
578+
doAssert self.indices.len == self.nodes.len
579+
for nodePhysicalIdx, node in self.nodes.buf:
580+
if node.root.isZero:
581+
continue
582+
583+
let unrealized = block:
584+
let nodeLogicalIdx = nodePhysicalIdx + self.nodes.offset
585+
if self.currentEpochTips.hasKey(nodeLogicalIdx):
586+
some self.currentEpochTips.unsafeGet(nodeLogicalIdx)
587+
else:
588+
none(FinalityCheckpoints)
589+
590+
yield ProtoArrayItem(
591+
root: node.root,
592+
parent: self.nodes.root(node.parent),
593+
checkpoints: node.checkpoints,
594+
unrealized: unrealized,
595+
weight: node.weight,
596+
bestChild: self.nodes.root(node.bestChild),
597+
bestDescendant: self.nodes.root(node.bestDescendant))
598+
555599
# Sanity checks
556600
# ----------------------------------------------------------------------
557601
# Sanity checks on internal private procedures

beacon_chain/rpc/rest_debug_api.nim

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import ".."/beacon_node,
1111
".."/spec/forks,
1212
"."/[rest_utils, state_ttl_cache]
1313

14+
from ../fork_choice/proto_array import ProtoArrayItem, items
15+
1416
export rest_utils
1517

1618
logScope: topics = "rest_debug"
@@ -107,6 +109,74 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
107109
)
108110
)
109111

112+
# https://github.com/ethereum/beacon-APIs/pull/232
113+
if node.config.debugForkChoice:
114+
router.api(MethodGet,
115+
"/eth/v1/debug/fork_choice") do () -> RestApiResponse:
116+
type
117+
ForkChoiceResponseExtraData = object
118+
justified_root: Eth2Digest
119+
finalized_root: Eth2Digest
120+
u_justified_checkpoint: Option[Checkpoint]
121+
u_finalized_checkpoint: Option[Checkpoint]
122+
best_child: Eth2Digest
123+
best_descendant: Eth2Digest
124+
125+
ForkChoiceResponse = object
126+
slot: Slot
127+
block_root: Eth2Digest
128+
parent_root: Eth2Digest
129+
justified_epoch: Epoch
130+
finalized_epoch: Epoch
131+
weight: uint64
132+
execution_optimistic: bool
133+
execution_payload_root: Eth2Digest
134+
extra_data: Option[ForkChoiceResponseExtraData]
135+
136+
var responses: seq[ForkChoiceResponse]
137+
for item in node.attestationPool[].forkChoice.backend.proto_array:
138+
let
139+
bid = node.dag.getBlockId(item.root)
140+
slot =
141+
if bid.isOk:
142+
bid.unsafeGet.slot
143+
else:
144+
FAR_FUTURE_SLOT
145+
executionPayloadRoot =
146+
if bid.isOk:
147+
node.dag.loadExecutionBlockRoot(bid.unsafeGet)
148+
else:
149+
ZERO_HASH
150+
unrealized = item.unrealized.get(item.checkpoints)
151+
u_justified_checkpoint =
152+
if unrealized.justified != item.checkpoints.justified:
153+
some unrealized.justified
154+
else:
155+
none(Checkpoint)
156+
u_finalized_checkpoint =
157+
if unrealized.finalized != item.checkpoints.finalized:
158+
some unrealized.finalized
159+
else:
160+
none(Checkpoint)
161+
162+
responses.add ForkChoiceResponse(
163+
slot: slot,
164+
block_root: item.root,
165+
parent_root: item.parent,
166+
justified_epoch: item.checkpoints.justified.epoch,
167+
finalized_epoch: item.checkpoints.finalized.epoch,
168+
weight: cast[uint64](item.weight),
169+
execution_optimistic: node.dag.is_optimistic(item.root),
170+
execution_payload_root: executionPayloadRoot,
171+
extra_data: some ForkChoiceResponseExtraData(
172+
justified_root: item.checkpoints.justified.root,
173+
finalized_root: item.checkpoints.finalized.root,
174+
u_justified_checkpoint: u_justified_checkpoint,
175+
u_finalized_checkpoint: u_finalized_checkpoint,
176+
best_child: item.bestChild,
177+
bestDescendant: item.bestDescendant))
178+
return RestApiResponse.jsonResponse(responses)
179+
110180
# Legacy URLS - Nimbus <= 1.5.5 used to expose the REST API with an additional
111181
# `/api` path component
112182
router.redirect(

0 commit comments

Comments
 (0)