track slot as part of fork choice debug API (#4565)

Extends fork choice state to also track slot numbers to improve accuracy
of `/eth/v1/debug/fork_choice` endpoint. Autoenable this API on devnet,
and disable some extra checks on devnet to aid focused testing efforts.
Align fork choice pruning logic with API based on checkpoints vs root.
This commit is contained in:
Etan Kissling 2023-01-31 13:35:01 +01:00 committed by GitHub
parent 58ed9308d2
commit ea6a6b1acd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 252 additions and 142 deletions

View File

@ -261,8 +261,8 @@ local-testnet-mainnet:
# test binaries that can output an XML report
XML_TEST_BINARIES_CORE := \
consensus_spec_tests_mainnet \
consensus_spec_tests_minimal
consensus_spec_tests_minimal \
consensus_spec_tests_mainnet
XML_TEST_BINARIES := \
$(XML_TEST_BINARIES_CORE) \

View File

@ -134,7 +134,7 @@ proc init*(T: type AttestationPool, dag: ChainDAGRef,
# and then to make sure the fork choice data structure doesn't grow
# too big - getting an EpochRef can be expensive.
forkChoice.backend.process_block(
blckRef.root, blckRef.parent.root, epochRef.checkpoints)
blckRef.bid, blckRef.parent.root, epochRef.checkpoints)
else:
epochRef = dag.getEpochRef(blckRef, blckRef.slot.epoch, false).expect(
"Getting an EpochRef should always work for non-finalized blocks")

View File

@ -79,15 +79,18 @@ template withUpdatedState*(
else:
failureBody
func get_effective_balances(validators: openArray[Validator], epoch: Epoch):
seq[Gwei] =
func get_effective_balances(
validators: openArray[Validator],
epoch: Epoch,
ignoreSlashed: bool): seq[Gwei] =
## Get the balances from a state as counted for fork choice
result.newSeq(validators.len) # zero-init
for i in 0 ..< result.len:
# All non-active validators have a 0 balance
let validator = unsafeAddr validators[i]
if validator[].is_active_validator(epoch):
if validator[].is_active_validator(epoch) and (
ignoreSlashed or not validator[].slashed):
result[i] = validator[].effective_balance
proc updateValidatorKeys*(dag: ChainDAGRef, validators: openArray[Validator]) =
@ -569,7 +572,9 @@ func init*(
epochRef.effective_balances_bytes =
snappyEncode(SSZ.encode(
List[Gwei, Limit VALIDATOR_REGISTRY_LIMIT](
get_effective_balances(getStateField(state, validators).asSeq, epoch))))
get_effective_balances(
getStateField(state, validators).asSeq, epoch,
experimental notin dag.updateFlags))))
epochRef
@ -898,7 +903,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
cfg.checkForkConsistency()
doAssert updateFlags - {
strictVerification, enableTestFeatures, lowParticipation
strictVerification, experimental, enableTestFeatures, lowParticipation
} == {}, "Other flags not supported in ChainDAG"
# TODO we require that the db contains both a head and a tail block -
@ -928,7 +933,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
# The only allowed flag right now is strictVerification, as the others all
# allow skipping some validation.
updateFlags: updateFlags * {
strictVerification, enableTestFeatures, lowParticipation
strictVerification, experimental, enableTestFeatures, lowParticipation
},
cfg: cfg,

View File

@ -36,6 +36,8 @@ type
## When process_slots() is being called as part of a state_transition(),
## the hash_tree_root() from the block will fill in the state.root so it
## should skip calculating that last state root.
experimental ##\
## Whether to enable extra features in development.
enableTestFeatures ##\
## Whether to enable extra features for testing.
lowParticipation ##\

View File

@ -50,12 +50,13 @@ logScope: topics = "fork_choice"
func init*(
T: type ForkChoiceBackend, checkpoints: FinalityCheckpoints,
hasLowParticipation = false): T =
T(proto_array: ProtoArray.init(checkpoints, hasLowParticipation))
experimental = false, hasLowParticipation = false): T =
T(proto_array: ProtoArray.init(
checkpoints, experimental, hasLowParticipation))
proc init*(
T: type ForkChoice, epochRef: EpochRef, blck: BlockRef,
hasLowParticipation = false): T =
experimental = false, hasLowParticipation = false): T =
## Initialize a fork choice context for a finalized state - in the finalized
## state, the justified and finalized checkpoints are the same, so only one
## is used here
@ -68,8 +69,9 @@ proc init*(
FinalityCheckpoints(
justified: checkpoint,
finalized: checkpoint),
hasLowParticipation),
experimental, hasLowParticipation),
checkpoints: Checkpoints(
experimental: experimental,
justified: BalanceCheckpoint(
checkpoint: checkpoint,
balances: epochRef.effective_balances),
@ -116,7 +118,7 @@ proc update_justified(
epochRef = dag.getEpochRef(blck, epoch, false).valueOr:
# Shouldn't happen for justified data unless out of sync with ChainDAG
warn "Skipping justified checkpoint update, no EpochRef - report bug",
blck, epoch, best = self.best_justified.epoch, error
blck, epoch, error
return
justified = Checkpoint(root: blck.root, epoch: epochRef.epoch)
@ -144,17 +146,21 @@ proc update_checkpoints(
## Update checkpoints in store if necessary
# Update justified checkpoint
if checkpoints.justified.epoch > self.justified.checkpoint.epoch:
if not self.experimental:
if checkpoints.justified.epoch > self.best_justified.epoch:
self.best_justified = checkpoints.justified
if ? should_update_justified_checkpoint(self, dag, checkpoints.justified):
? self.update_justified(dag, checkpoints.justified)
else:
? self.update_justified(dag, checkpoints.justified)
# Update finalized checkpoint
if checkpoints.finalized.epoch > self.finalized.epoch:
trace "Updating finalized",
store = self.finalized, state = checkpoints.finalized
self.finalized = checkpoints.finalized
if not self.experimental:
if checkpoints.justified != self.justified.checkpoint:
? self.update_justified(dag, checkpoints.justified)
@ -185,6 +191,7 @@ proc on_tick(
# Update store.justified_checkpoint if a better checkpoint on the
# store.finalized_checkpoint chain
if not self.checkpoints.experimental:
let
best_justified_epoch = self.checkpoints.best_justified.epoch
store_justified_epoch = self.checkpoints.justified.checkpoint.epoch
@ -307,11 +314,11 @@ func process_equivocation*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/phase0/fork-choice.md#on_block
func process_block*(self: var ForkChoiceBackend,
block_root: Eth2Digest,
bid: BlockId,
parent_root: Eth2Digest,
checkpoints: FinalityCheckpoints,
unrealized = none(FinalityCheckpoints)): FcResult[void] =
self.proto_array.onBlock(block_root, parent_root, checkpoints, unrealized)
self.proto_array.onBlock(bid, parent_root, checkpoints, unrealized)
proc process_block*(self: var ForkChoice,
dag: ChainDAGRef,
@ -358,14 +365,14 @@ proc process_block*(self: var ForkChoice,
blck = shortLog(blckRef), checkpoints = epochRef.checkpoints, unrealized
? update_checkpoints(self.checkpoints, dag, unrealized)
? process_block(
self.backend, blckRef.root, blck.parent_root, unrealized)
self.backend, blckRef.bid, blck.parent_root, unrealized)
else:
? process_block(
self.backend, blckRef.root, blck.parent_root,
self.backend, blckRef.bid, blck.parent_root,
epochRef.checkpoints, some unrealized) # Realized in `on_tick`
else:
? process_block(
self.backend, blckRef.root, blck.parent_root, epochRef.checkpoints)
self.backend, blckRef.bid, blck.parent_root, epochRef.checkpoints)
ok()
@ -424,13 +431,16 @@ func get_safe_beacon_block_root*(self: ForkChoice): Eth2Digest =
self.checkpoints.justified.checkpoint.root
func prune*(
self: var ForkChoiceBackend, finalized_root: Eth2Digest
self: var ForkChoiceBackend, checkpoints: FinalityCheckpoints
): FcResult[void] =
## Prune blocks preceding the finalized root as they are now unneeded.
self.proto_array.prune(finalized_root)
self.proto_array.prune(checkpoints)
func prune*(self: var ForkChoice): FcResult[void] =
self.backend.prune(self.checkpoints.finalized.root)
self.backend.prune(
FinalityCheckpoints(
justified: self.checkpoints.justified.checkpoint,
finalized: self.checkpoints.finalized))
func mark_root_invalid*(self: var ForkChoice, root: Eth2Digest) =
try:

View File

@ -88,6 +88,7 @@ type
## Subtracted from logical index to get the physical index
ProtoArray* = object
experimental*: bool
hasLowParticipation*: bool
currentEpoch*: Epoch
checkpoints*: FinalityCheckpoints
@ -98,7 +99,7 @@ type
previousProposerBoostScore*: uint64
ProtoNode* = object
root*: Eth2Digest
bid*: BlockId
parent*: Option[Index]
checkpoints*: FinalityCheckpoints
weight*: int64
@ -111,6 +112,7 @@ type
balances*: seq[Gwei]
Checkpoints* = object
experimental*: bool
time*: BeaconTime
justified*: BalanceCheckpoint
finalized*: Checkpoint

View File

@ -81,17 +81,21 @@ func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
parentIdx: Index,
childIdx: Index): FcResult[void]
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool
func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool]
func nodeIsViableForHead(
self: ProtoArray, node: ProtoNode, nodeIdx: Index): bool
func nodeLeadsToViableHead(
self: ProtoArray, node: ProtoNode, nodeIdx: Index): FcResult[bool]
# ProtoArray routines
# ----------------------------------------------------------------------
func init*(
T: type ProtoArray, checkpoints: FinalityCheckpoints,
hasLowParticipation: bool): T =
experimental, hasLowParticipation: bool): T =
let node = ProtoNode(
root: checkpoints.finalized.root,
bid: BlockId(
slot: checkpoints.finalized.epoch.start_slot,
root: checkpoints.finalized.root),
parent: none(int),
checkpoints: checkpoints,
weight: 0,
@ -99,10 +103,11 @@ func init*(
bestChild: none(int),
bestDescendant: none(int))
T(hasLowParticipation: hasLowParticipation,
T(experimental: experimental,
hasLowParticipation: hasLowParticipation,
checkpoints: checkpoints,
nodes: ProtoNodes(buf: @[node], offset: 0),
indices: {node.root: 0}.toTable())
indices: {node.bid.root: 0}.toTable())
iterator realizePendingCheckpoints*(
self: var ProtoArray, resetTipTracking = true): FinalityCheckpoints =
@ -111,7 +116,7 @@ iterator realizePendingCheckpoints*(
let physicalIdx = idx - self.nodes.offset
if unrealized != self.nodes.buf[physicalIdx].checkpoints:
trace "Pulling up chain tip",
blck = self.nodes.buf[physicalIdx].root,
blck = self.nodes.buf[physicalIdx].bid.root,
checkpoints = self.nodes.buf[physicalIdx].checkpoints,
unrealized
self.nodes.buf[physicalIdx].checkpoints = unrealized
@ -173,7 +178,7 @@ func applyScoreChanges*(self: var ProtoArray,
self.checkpoints = checkpoints
# If previous epoch is justified, pull up all current tips to previous epoch
if self.hasLowParticipation and self.isPreviousEpochJustified:
if self.experimental and self.isPreviousEpochJustified:
for realized in self.realizePendingCheckpoints(resetTipTracking = false):
discard
@ -187,7 +192,7 @@ func applyScoreChanges*(self: var ProtoArray,
# Iterate backwards through all the indices in `self.nodes`
for nodePhysicalIdx in countdown(self.nodes.len - 1, 0):
if node.root.isZero:
if node.bid.root.isZero:
continue
var nodeDelta = deltas[nodePhysicalIdx]
@ -195,7 +200,7 @@ func applyScoreChanges*(self: var ProtoArray,
# If we find the node for which the proposer boost was previously applied,
# decrease the delta by the previous score amount.
if (not self.previousProposerBoostRoot.isZero) and
self.previousProposerBoostRoot == node.root:
self.previousProposerBoostRoot == node.bid.root:
if nodeDelta < 0 and
nodeDelta - low(Delta) < self.previousProposerBoostScore.int64:
return err ForkChoiceError(
@ -207,7 +212,7 @@ func applyScoreChanges*(self: var ProtoArray,
# the delta by the new score amount.
#
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_latest_attesting_balance
if (not proposerBoostRoot.isZero) and proposerBoostRoot == node.root:
if (not proposerBoostRoot.isZero) and proposerBoostRoot == node.bid.root:
proposerBoostScore = calculateProposerBoost(newBalances)
if nodeDelta >= 0 and
high(Delta) - nodeDelta < proposerBoostScore.int64:
@ -265,7 +270,7 @@ func applyScoreChanges*(self: var ProtoArray,
self.previousProposerBoostScore = proposerBoostScore
for nodePhysicalIdx in countdown(self.nodes.len - 1, 0):
if node.root.isZero:
if node.bid.root.isZero:
continue
if node.parent.isSome():
@ -281,7 +286,7 @@ func applyScoreChanges*(self: var ProtoArray,
ok()
func onBlock*(self: var ProtoArray,
root: Eth2Digest,
bid: BlockId,
parent: Eth2Digest,
checkpoints: FinalityCheckpoints,
unrealized = none(FinalityCheckpoints)): FcResult[void] =
@ -294,7 +299,7 @@ func onBlock*(self: var ProtoArray,
# Note: if parent is an "Option" type, we can run out of stack space.
# If the block is already known, ignore it
if root in self.indices:
if bid.root in self.indices:
return ok()
var parentIdx: Index
@ -303,13 +308,13 @@ func onBlock*(self: var ProtoArray,
do:
return err ForkChoiceError(
kind: fcUnknownParent,
childRoot: root,
childRoot: bid.root,
parentRoot: parent)
let nodeLogicalIdx = self.nodes.offset + self.nodes.buf.len
let node = ProtoNode(
root: root,
bid: bid,
parent: some(parentIdx),
checkpoints: checkpoints,
weight: 0,
@ -317,11 +322,11 @@ func onBlock*(self: var ProtoArray,
bestChild: none(int),
bestDescendant: none(int))
self.indices[node.root] = nodeLogicalIdx
self.indices[node.bid.root] = nodeLogicalIdx
self.nodes.add node
if unrealized.isSome:
self.currentEpochTips.del parentIdx
if unrealized.isSome:
self.currentEpochTips[nodeLogicalIdx] = unrealized.get
? self.maybeUpdateBestChildAndDescendant(parentIdx, nodeLogicalIdx)
@ -359,34 +364,38 @@ func findHead*(self: var ProtoArray,
index: bestDescendantIdx)
# Perform a sanity check to ensure the node can be head
if not self.nodeIsViableForHead(bestNode.get()):
if not self.nodeIsViableForHead(bestNode.get(), bestDescendantIdx):
return err ForkChoiceError(
kind: fcInvalidBestNode,
startRoot: justifiedRoot,
fkChoiceCheckpoints: self.checkpoints,
headRoot: justifiedNode.get().root,
headRoot: justifiedNode.get().bid.root,
headCheckpoints: justifiedNode.get().checkpoints)
head = bestNode.get().root
head = bestNode.get().bid.root
ok()
func prune*(self: var ProtoArray, finalizedRoot: Eth2Digest): FcResult[void] =
func prune*(
self: var ProtoArray,
checkpoints: FinalityCheckpoints): FcResult[void] =
## Update the tree with new finalization information.
## The tree is pruned if and only if:
## - The `finalizedRoot` and finalized epoch are different from current
## - `checkpoints.finalized.root` and finalized epoch
## are different from current
##
## Returns error if:
## - The finalized epoch is less than the current one
## - The finalized epoch matches the current one but the finalized root is different
## - The finalized epoch matches the current one but the f
## inalized root is different
## - Internal error due to invalid indices in `self`
var finalizedIdx: int
self.indices.withValue(finalizedRoot, value) do:
self.indices.withValue(checkpoints.finalized.root, value) do:
finalizedIdx = value[]
do:
return err ForkChoiceError(
kind: fcFinalizedNodeUnknown,
blockRoot: finalizedRoot)
blockRoot: checkpoints.finalized.root)
if finalizedIdx == self.nodes.offset:
# Nothing to do
@ -395,15 +404,14 @@ func prune*(self: var ProtoArray, finalizedRoot: Eth2Digest): FcResult[void] =
if finalizedIdx < self.nodes.offset:
return err ForkChoiceError(
kind: fcPruningFromOutdatedFinalizedRoot,
finalizedRoot: finalizedRoot)
finalizedRoot: checkpoints.finalized.root)
trace "Pruning blocks from fork choice",
finalizedRoot = shortLog(finalizedRoot)
trace "Pruning blocks from fork choice", checkpoints
let finalPhysicalIdx = finalizedIdx - self.nodes.offset
for nodeIdx in 0 ..< finalPhysicalIdx:
self.currentEpochTips.del nodeIdx
self.indices.del(self.nodes.buf[nodeIdx].root)
self.indices.del(self.nodes.buf[nodeIdx].bid.root)
# Drop all nodes prior to finalization.
# This is done in-place with `moveMem` to avoid costly reallocations.
@ -445,7 +453,8 @@ func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
kind: fcInvalidNodeIndex,
index: parentIdx)
let childLeadsToViableHead = ? self.nodeLeadsToViableHead(child.get())
let childLeadsToViableHead =
? self.nodeLeadsToViableHead(child.get(), childIdx)
let # Aliases to the 3 possible (bestChild, bestDescendant) tuples
changeToNone = (none(Index), none(Index))
@ -477,7 +486,7 @@ func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
index: bestChildIdx)
let bestChildLeadsToViableHead =
? self.nodeLeadsToViableHead(bestChild.get())
? self.nodeLeadsToViableHead(bestChild.get(), bestChildIdx)
if childLeadsToViableHead and not bestChildLeadsToViableHead:
# The child leads to a viable head, but the current best-child doesn't
@ -487,7 +496,7 @@ func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
noChange
elif child.get().weight == bestChild.get().weight:
# Tie-breaker of equal weights by root
if child.get().root.tiebreak(bestChild.get().root):
if child.get().bid.root.tiebreak(bestChild.get().bid.root):
changeToChild
else:
noChange
@ -511,7 +520,8 @@ func maybeUpdateBestChildAndDescendant(self: var ProtoArray,
ok()
func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
func nodeLeadsToViableHead(
self: ProtoArray, node: ProtoNode, nodeIdx: Index): FcResult[bool] =
## Indicates if the node itself or its best-descendant are viable
## for blockchain head
let bestDescendantIsViableForHead = block:
@ -522,13 +532,14 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
return err ForkChoiceError(
kind: fcInvalidBestDescendant,
index: bestDescendantIdx)
self.nodeIsViableForHead(bestDescendant.get())
self.nodeIsViableForHead(bestDescendant.get(), bestDescendantIdx)
else:
false
ok(bestDescendantIsViableForHead or self.nodeIsViableForHead(node))
ok(bestDescendantIsViableForHead or self.nodeIsViableForHead(node, nodeIdx))
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
func nodeIsViableForHead(
self: ProtoArray, node: ProtoNode, nodeIdx: Index): bool =
## This is the equivalent of `filter_block_tree` function in eth2 spec
## https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/phase0/fork-choice.md#filter_block_tree
@ -545,6 +556,35 @@ func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
node.checkpoints.finalized == self.checkpoints.finalized or
self.checkpoints.finalized.epoch == GENESIS_EPOCH
if self.experimental:
var correctJustified =
self.checkpoints.justified.epoch == GENESIS_EPOCH or
node.checkpoints.justified.epoch == self.checkpoints.justified.epoch
if not correctJustified and self.isPreviousEpochJustified and
node.bid.slot.epoch == self.currentEpoch:
let unrealized =
self.currentEpochTips.getOrDefault(nodeIdx, node.checkpoints)
correctJustified =
unrealized.justified.epoch >= self.checkpoints.justified.epoch and
node.checkpoints.justified.epoch + 2 >= self.currentEpoch
return
if not correctJustified:
false
elif self.checkpoints.finalized.epoch == GENESIS_EPOCH:
true
else:
let finalizedSlot = self.checkpoints.finalized.epoch.start_slot
var ancestor = some node
while ancestor.isSome and ancestor.unsafeGet.bid.slot > finalizedSlot:
if ancestor.unsafeGet.parent.isSome:
ancestor = self.nodes[ancestor.unsafeGet.parent.unsafeGet]
else:
ancestor.reset()
if ancestor.isSome:
ancestor.unsafeGet.bid.root == self.checkpoints.finalized.root
else:
false
## Any node that has a different finalized or justified epoch
## should not be viable for the head.
(
@ -582,7 +622,7 @@ func propagateInvalidity*(
# Helpers to dump internal state
type ProtoArrayItem* = object
root*: Eth2Digest
bid*: BlockId
parent*: Eth2Digest
checkpoints*: FinalityCheckpoints
unrealized*: Option[FinalityCheckpoints]
@ -597,13 +637,13 @@ func root(self: ProtoNodes, logicalIdx: Option[Index]): Eth2Digest =
let node = self[logicalIdx.unsafeGet]
if node.isNone:
return ZERO_HASH
node.unsafeGet.root
node.unsafeGet.bid.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:
if node.bid.root.isZero:
continue
let unrealized = block:
@ -614,7 +654,7 @@ iterator items*(self: ProtoArray): ProtoArrayItem =
none(FinalityCheckpoints)
yield ProtoArrayItem(
root: node.root,
bid: node.bid,
parent: self.nodes.root(node.parent),
checkpoints: node.checkpoints,
unrealized: unrealized,

View File

@ -142,8 +142,7 @@ proc loadChainDag(
db: BeaconChainDB,
eventBus: EventBus,
validatorMonitor: ref ValidatorMonitor,
networkGenesisValidatorsRoot: Opt[Eth2Digest],
shouldEnableTestFeatures: bool): ChainDAGRef =
networkGenesisValidatorsRoot: Opt[Eth2Digest]): ChainDAGRef =
info "Loading block DAG from database", path = config.databaseDir
var dag: ChainDAGRef
@ -170,10 +169,12 @@ proc loadChainDag(
jsonVersion: contextFork,
sszContext: dag.forkDigests[].atStateFork(contextFork)))
var extraFlags = {enableTestFeatures}
if config.deploymentPhase <= DeploymentPhase.Testnet:
extraFlags.incl experimental
if config.deploymentPhase <= DeploymentPhase.Devnet:
extraFlags.incl lowParticipation
let
extraFlags =
if shouldEnableTestFeatures: {enableTestFeatures, lowParticipation}
else: {enableTestFeatures}
chainDagFlags =
if config.strictVerification: {strictVerification}
else: {}
@ -548,8 +549,7 @@ proc init*(T: type BeaconNode,
dag = loadChainDag(
config, cfg, db, eventBus,
validatorMonitor, networkGenesisValidatorsRoot,
config.deploymentPhase <= DeploymentPhase.Testnet)
validatorMonitor, networkGenesisValidatorsRoot)
genesisTime = getStateField(dag.headState, genesis_time)
beaconClock = BeaconClock.init(genesisTime)
getBeaconTime = beaconClock.getBeaconTimeFn()

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-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).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -82,7 +82,7 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
)
# https://github.com/ethereum/beacon-APIs/pull/232
if node.config.debugForkChoice:
if node.config.debugForkChoice or experimental in node.dag.updateFlags:
router.api(MethodGet,
"/eth/v1/debug/fork_choice") do () -> RestApiResponse:
type
@ -109,17 +109,6 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
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:
@ -133,14 +122,14 @@ proc installDebugApiHandlers*(router: var RestRouter, node: BeaconNode) =
none(Checkpoint)
responses.add ForkChoiceResponse(
slot: slot,
block_root: item.root,
slot: item.bid.slot,
block_root: item.bid.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,
execution_optimistic: node.dag.is_optimistic(item.bid.root),
execution_payload_root: node.dag.loadExecutionBlockRoot(item.bid),
extra_data: some ForkChoiceResponseExtraData(
justified_root: item.checkpoints.justified.root,
finalized_root: item.checkpoints.finalized.root,

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# 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).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -46,7 +46,7 @@ type
justified_state_balances*: seq[Gwei]
expected_head*: Eth2Digest
of ProcessBlock:
root*: Eth2Digest
bid*: BlockId
parent_root*: Eth2Digest
blk_checkpoints*: FinalityCheckpoints
of ProcessAttestation:
@ -54,7 +54,7 @@ type
block_root*: Eth2Digest
target_epoch*: Epoch
of Prune: # ProtoArray specific
finalized_root*: Eth2Digest
prune_checkpoints*: FinalityCheckpoints
expected_len*: int
func apply(ctx: var ForkChoiceBackend, id: int, op: Operation) =
@ -79,11 +79,11 @@ func apply(ctx: var ForkChoiceBackend, id: int, op: Operation) =
debugEcho &" Detected an expected invalid head from justified checkpoint {op.checkpoints.justified}, finalized checkpoint {op.checkpoints.finalized}"
of ProcessBlock:
let r = ctx.process_block(
block_root = op.root,
bid = op.bid,
parent_root = op.parent_root,
checkpoints = op.blk_checkpoints)
doAssert r.isOk(), &"process_block (op #{id}) returned an error: {r.error}"
debugEcho " Processed block 0x", op.root, " with parent 0x", op.parent_root, " and justified checkpoint ", op.blk_checkpoints.justified
debugEcho " Processed block 0x", op.bid.root, " with parent 0x", op.parent_root, " and justified checkpoint ", op.blk_checkpoints.justified
of ProcessAttestation:
ctx.process_attestation(
validator_index = op.validator_index,
@ -91,11 +91,11 @@ func apply(ctx: var ForkChoiceBackend, id: int, op: Operation) =
target_epoch = op.target_epoch)
debugEcho " Processed att target 0x", op.block_root, " from validator ", op.validator_index, " for epoch ", op.target_epoch
of Prune:
let r = ctx.prune(op.finalized_root)
let r = ctx.prune(op.prune_checkpoints)
doAssert r.isOk(), &"prune (op #{id}) returned an error: {r.error}"
doAssert ctx.proto_array.nodes.len == op.expected_len,
&"prune (op #{id}): the resulting length ({ctx.proto_array.nodes.len}) was not expected ({op.expected_len})"
debugEcho " Maybe_pruned block preceding finalized block 0x", op.finalized_root
debugEcho " Maybe_pruned block preceding finalized block 0x", op.prune_checkpoints.finalized.root
func run*(ctx: var ForkChoiceBackend, ops: seq[Operation]) =
## Apply a sequence of fork-choice operations on a store

View File

@ -41,7 +41,9 @@ func setup_finality_01(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
# 3 <- just: 2, fin: 1
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(1),
bid: BlockId(
slot: Epoch(1).start_slot,
root: fakeHash(1)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(0)),
@ -49,7 +51,9 @@ func setup_finality_01(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(2),
bid: BlockId(
slot: Epoch(2).start_slot,
root: fakeHash(2)),
parent_root: fakeHash(1),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(1), epoch: Epoch(1)),
@ -57,7 +61,9 @@ func setup_finality_01(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(3),
bid: BlockId(
slot: Epoch(3).start_slot,
root: fakeHash(3)),
parent_root: fakeHash(2),
blk_Checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(2), epoch: Epoch(2)),

View File

@ -47,7 +47,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
# Left branch
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(1),
bid: BlockId(
slot: Slot(1),
root: fakeHash(1)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(0)),
@ -55,7 +57,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(3),
bid: BlockId(
slot: Epoch(2).start_slot,
root: fakeHash(3)),
parent_root: fakeHash(1),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(1), epoch: Epoch(1)),
@ -63,7 +67,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(5),
bid: BlockId(
slot: Epoch(2).start_slot + 2,
root: fakeHash(5)),
parent_root: fakeHash(3),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(1), epoch: Epoch(1)),
@ -71,7 +77,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(7),
bid: BlockId(
slot: Epoch(2).start_slot + 4,
root: fakeHash(7)),
parent_root: fakeHash(5),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(1), epoch: Epoch(1)),
@ -79,7 +87,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(9),
bid: BlockId(
slot: Epoch(3).start_slot,
root: fakeHash(9)),
parent_root: fakeHash(7),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(3), epoch: Epoch(2)),
@ -102,7 +112,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
# Right branch
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(2),
bid: BlockId(
slot: Slot(2),
root: fakeHash(2)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(0)),
@ -110,7 +122,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(4),
bid: BlockId(
slot: Epoch(1).start_slot + 1,
root: fakeHash(4)),
parent_root: fakeHash(2),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(0)),
@ -118,7 +132,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(6),
bid: BlockId(
slot: Epoch(2).start_slot + 3,
root: fakeHash(6)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(0)),
@ -126,7 +142,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(8),
bid: BlockId(
slot: Epoch(3).start_slot + 1,
root: fakeHash(8)),
parent_root: fakeHash(6),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(2), epoch: Epoch(1)),
@ -134,7 +152,9 @@ func setup_finality_02(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operati
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(10),
bid: BlockId(
slot: Epoch(5).start_slot + 1,
root: fakeHash(10)),
parent_root: fakeHash(8),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(4), epoch: Epoch(2)),

View File

@ -38,7 +38,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 2
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(2),
bid: BlockId(
slot: Epoch(3).start_slot + 2,
root: fakeHash(2)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -64,7 +66,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 2 1
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(1),
bid: BlockId(
slot: Epoch(3).start_slot + 1,
root: fakeHash(1)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -92,7 +96,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 3
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(3),
bid: BlockId(
slot: Epoch(3).start_slot + 3,
root: fakeHash(3)),
parent_root: fakeHash(1),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -122,7 +128,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 4 3
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(4),
bid: BlockId(
slot: Epoch(3).start_slot + 4,
root: fakeHash(4)),
parent_root: fakeHash(2),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -154,7 +162,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 5 <- justified epoch = 2
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(5),
bid: BlockId(
slot: Epoch(4).start_slot,
root: fakeHash(5)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -221,7 +231,9 @@ func setup_no_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]
# 6
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(6),
bid: BlockId(
slot: Epoch(4).start_slot + 1,
root: fakeHash(6)),
parent_root: fakeHash(5),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),

View File

@ -38,7 +38,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 2
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(2),
bid: BlockId(
slot: Epoch(1).start_slot + 2,
root: fakeHash(2)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -64,7 +66,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 2 1
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(1),
bid: BlockId(
slot: Epoch(1).start_slot + 1,
root: fakeHash(1)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -140,7 +144,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 3
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(3),
bid: BlockId(
slot: Epoch(1).start_slot + 3,
root: fakeHash(3)),
parent_root: fakeHash(1),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -226,7 +232,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 4
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(4),
bid: BlockId(
slot: Epoch(1).start_slot + 4,
root: fakeHash(4)),
parent_root: fakeHash(3),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -262,7 +270,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 5 <- justified epoch = 2
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(5),
bid: BlockId(
slot: Epoch(2).start_slot,
root: fakeHash(5)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -300,7 +310,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 5 6 <- justified epoch = 0
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(6),
bid: BlockId(
slot: Epoch(2).start_slot + 1,
root: fakeHash(6)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
@ -349,7 +361,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 9
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(7),
bid: BlockId(
slot: Epoch(2).start_slot + 2,
root: fakeHash(7)),
parent_root: fakeHash(5),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -357,7 +371,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(8),
bid: BlockId(
slot: Epoch(2).start_slot + 3,
root: fakeHash(8)),
parent_root: fakeHash(7),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -366,7 +382,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# Finalizes 5
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(9),
bid: BlockId(
slot: Epoch(2).start_slot + 4,
root: fakeHash(9)),
parent_root: fakeHash(8),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -481,7 +499,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 9 10
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(10),
bid: BlockId(
slot: Epoch(3).start_slot,
root: fakeHash(10)),
parent_root: fakeHash(8),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
@ -626,7 +646,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# - 6 is a discarded chain
result.ops.add Operation(
kind: Prune,
finalized_root: fakeHash(5),
prune_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
expected_len: 6)
# Prune shouldn't have changed the head
@ -651,7 +673,9 @@ func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
# 11
result.ops.add Operation(
kind: ProcessBlock,
root: fakeHash(11),
bid: BlockId(
slot: Epoch(3).start_slot + 1,
root: fakeHash(11)),
parent_root: fakeHash(9),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),