adjust checkpoint tracking for devnets (#4039)

Track checkpoints more defensively on devnets with low participation.
This commit is contained in:
Etan Kissling 2022-08-29 09:26:01 +02:00 committed by GitHub
parent b60456fdf3
commit 994339c7ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 28 deletions

View File

@ -97,7 +97,9 @@ proc init*(T: type AttestationPool, dag: ChainDAGRef,
## holding a zero_root. ## holding a zero_root.
let finalizedEpochRef = dag.getFinalizedEpochRef() let finalizedEpochRef = dag.getFinalizedEpochRef()
var forkChoice = ForkChoice.init(finalizedEpochRef, dag.finalizedHead.blck) var forkChoice = ForkChoice.init(
finalizedEpochRef, dag.finalizedHead.blck,
lowParticipation in dag.updateFlags)
# Feed fork choice with unfinalized history - during startup, block pool only # Feed fork choice with unfinalized history - during startup, block pool only
# keeps track of a single history so we just need to follow it # keeps track of a single history so we just need to follow it

View File

@ -775,8 +775,9 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
lcDataConfig = default(LightClientDataConfig)): ChainDAGRef = lcDataConfig = default(LightClientDataConfig)): ChainDAGRef =
cfg.checkForkConsistency() cfg.checkForkConsistency()
doAssert updateFlags - {strictVerification, enableTestFeatures} == {}, doAssert updateFlags - {
"Other flags not supported in ChainDAG" strictVerification, enableTestFeatures, lowParticipation
} == {}, "Other flags not supported in ChainDAG"
# TODO we require that the db contains both a head and a tail block - # TODO we require that the db contains both a head and a tail block -
# asserting here doesn't seem like the right way to go about it however.. # asserting here doesn't seem like the right way to go about it however..
@ -803,7 +804,9 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
# The only allowed flag right now is strictVerification, as the others all # The only allowed flag right now is strictVerification, as the others all
# allow skipping some validation. # allow skipping some validation.
updateFlags: {strictVerification, enableTestFeatures} * updateFlags, updateFlags: updateFlags * {
strictVerification, enableTestFeatures, lowParticipation
},
cfg: cfg, cfg: cfg,
vanityLogs: vanityLogs, vanityLogs: vanityLogs,

View File

@ -41,5 +41,7 @@ type
## should skip calculating that last state root. ## should skip calculating that last state root.
enableTestFeatures ##\ enableTestFeatures ##\
## Whether to enable extra features for testing. ## Whether to enable extra features for testing.
lowParticipation ##\
## Whether the network is prone to low participation.
UpdateFlags* = set[UpdateFlag] UpdateFlags* = set[UpdateFlag]

View File

@ -51,10 +51,14 @@ func compute_deltas(
logScope: topics = "fork_choice" logScope: topics = "fork_choice"
func init*(T: type ForkChoiceBackend, checkpoints: FinalityCheckpoints): T = func init*(
T(proto_array: ProtoArray.init(checkpoints)) T: type ForkChoiceBackend, checkpoints: FinalityCheckpoints,
hasLowParticipation = false): T =
T(proto_array: ProtoArray.init(checkpoints, hasLowParticipation))
proc init*(T: type ForkChoice, epochRef: EpochRef, blck: BlockRef): T = proc init*(
T: type ForkChoice, epochRef: EpochRef, blck: BlockRef,
hasLowParticipation = false): T =
## Initialize a fork choice context for a finalized state - in the finalized ## Initialize a fork choice context for a finalized state - in the finalized
## state, the justified and finalized checkpoints are the same, so only one ## state, the justified and finalized checkpoints are the same, so only one
## is used here ## is used here
@ -66,7 +70,8 @@ proc init*(T: type ForkChoice, epochRef: EpochRef, blck: BlockRef): T =
backend: ForkChoiceBackend.init( backend: ForkChoiceBackend.init(
FinalityCheckpoints( FinalityCheckpoints(
justified: checkpoint, justified: checkpoint,
finalized: checkpoint)), finalized: checkpoint),
hasLowParticipation),
checkpoints: Checkpoints( checkpoints: Checkpoints(
justified: BalanceCheckpoint( justified: BalanceCheckpoint(
checkpoint: checkpoint, checkpoint: checkpoint,
@ -369,6 +374,7 @@ proc process_block*(self: var ForkChoice,
func find_head*( func find_head*(
self: var ForkChoiceBackend, self: var ForkChoiceBackend,
current_epoch: Epoch,
checkpoints: FinalityCheckpoints, checkpoints: FinalityCheckpoints,
justified_state_balances: seq[Gwei], justified_state_balances: seq[Gwei],
proposer_boost_root: Eth2Digest proposer_boost_root: Eth2Digest
@ -387,7 +393,8 @@ func find_head*(
# Apply score changes # Apply score changes
? self.proto_array.applyScoreChanges( ? self.proto_array.applyScoreChanges(
deltas, checkpoints, justified_state_balances, proposer_boost_root) deltas, current_epoch, checkpoints,
justified_state_balances, proposer_boost_root)
self.balances = justified_state_balances self.balances = justified_state_balances
@ -407,6 +414,7 @@ proc get_head*(self: var ForkChoice,
? self.update_time(dag, wallTime) ? self.update_time(dag, wallTime)
self.backend.find_head( self.backend.find_head(
self.checkpoints.time.slotOrZero.epoch,
FinalityCheckpoints( FinalityCheckpoints(
justified: self.checkpoints.justified.checkpoint, justified: self.checkpoints.justified.checkpoint,
finalized: self.checkpoints.finalized), finalized: self.checkpoints.finalized),

View File

@ -92,6 +92,8 @@ type
## to get the physical index ## to get the physical index
ProtoArray* = object ProtoArray* = object
hasLowParticipation*: bool
currentEpoch*: Epoch
checkpoints*: FinalityCheckpoints checkpoints*: FinalityCheckpoints
nodes*: ProtoNodes nodes*: ProtoNodes
indices*: Table[Eth2Digest, Index] indices*: Table[Eth2Digest, Index]

View File

@ -74,6 +74,9 @@ func len*(nodes: ProtoNodes): int =
func add(nodes: var ProtoNodes, node: ProtoNode) = func add(nodes: var ProtoNodes, node: ProtoNode) =
nodes.buf.add node nodes.buf.add node
func isPreviousEpochJustified(self: ProtoArray): bool =
self.checkpoints.justified.epoch + 1 == self.currentEpoch
# Forward declarations # Forward declarations
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ -87,7 +90,9 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool]
# ProtoArray routines # ProtoArray routines
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T = func init*(
T: type ProtoArray, checkpoints: FinalityCheckpoints,
hasLowParticipation: bool): T =
let node = ProtoNode( let node = ProtoNode(
root: checkpoints.finalized.root, root: checkpoints.finalized.root,
parent: none(int), parent: none(int),
@ -96,10 +101,29 @@ func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T =
bestChild: none(int), bestChild: none(int),
bestDescendant: none(int)) bestDescendant: none(int))
T(checkpoints: checkpoints, T(hasLowParticipation: hasLowParticipation,
checkpoints: checkpoints,
nodes: ProtoNodes(buf: @[node], offset: 0), nodes: ProtoNodes(buf: @[node], offset: 0),
indices: {node.root: 0}.toTable()) indices: {node.root: 0}.toTable())
iterator realizePendingCheckpoints*(
self: var ProtoArray, resetTipTracking = true): FinalityCheckpoints =
# Pull-up chain tips from previous epoch
for idx, unrealized in self.currentEpochTips.pairs():
let physicalIdx = idx - self.nodes.offset
if unrealized != self.nodes.buf[physicalIdx].checkpoints:
trace "Pulling up chain tip",
blck = self.nodes.buf[physicalIdx].root,
checkpoints = self.nodes.buf[physicalIdx].checkpoints,
unrealized
self.nodes.buf[physicalIdx].checkpoints = unrealized
yield unrealized
# Reset tip tracking for new epoch
if resetTipTracking:
self.currentEpochTips.clear()
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#configuration # https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#configuration
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_latest_attesting_balance # https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_latest_attesting_balance
const PROPOSER_SCORE_BOOST* = 40 const PROPOSER_SCORE_BOOST* = 40
@ -124,6 +148,7 @@ func calculateProposerBoost(validatorBalances: openArray[Gwei]): int64 =
func applyScoreChanges*(self: var ProtoArray, func applyScoreChanges*(self: var ProtoArray,
deltas: var openArray[Delta], deltas: var openArray[Delta],
currentEpoch: Epoch,
checkpoints: FinalityCheckpoints, checkpoints: FinalityCheckpoints,
newBalances: openArray[Gwei], newBalances: openArray[Gwei],
proposerBoostRoot: Eth2Digest): FcResult[void] = proposerBoostRoot: Eth2Digest): FcResult[void] =
@ -148,8 +173,14 @@ func applyScoreChanges*(self: var ProtoArray,
deltasLen: deltas.len, deltasLen: deltas.len,
indicesLen: self.indices.len) indicesLen: self.indices.len)
self.currentEpoch = currentEpoch
self.checkpoints = checkpoints self.checkpoints = checkpoints
# If previous epoch is justified, pull up all current tips to previous epoch
if self.hasLowParticipation and self.isPreviousEpochJustified:
for realized in self.realizePendingCheckpoints(resetTipTracking = false):
discard
## Alias ## Alias
# This cannot raise the IndexError exception, how to tell compiler? # This cannot raise the IndexError exception, how to tell compiler?
template node: untyped {.dirty.} = template node: untyped {.dirty.} =
@ -300,21 +331,6 @@ func onBlock*(self: var ProtoArray,
ok() ok()
iterator realizePendingCheckpoints*(self: var ProtoArray): FinalityCheckpoints =
# Pull-up chain tips from previous epoch
for idx, unrealized in self.currentEpochTips.pairs():
let physicalIdx = idx - self.nodes.offset
trace "Pulling up chain tip",
blck = self.nodes.buf[physicalIdx].root,
checkpoints = self.nodes.buf[physicalIdx].checkpoints,
unrealized
self.nodes.buf[physicalIdx].checkpoints = unrealized
yield unrealized
# Reset tip tracking for new epoch
self.currentEpochTips.clear()
func findHead*(self: var ProtoArray, func findHead*(self: var ProtoArray,
head: var Eth2Digest, head: var Eth2Digest,
justifiedRoot: Eth2Digest): FcResult[void] = justifiedRoot: Eth2Digest): FcResult[void] =
@ -518,7 +534,14 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool = func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
## This is the equivalent of `filter_block_tree` function in eth2 spec ## This is the equivalent of `filter_block_tree` function in eth2 spec
## https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#filter_block_tree ## https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#filter_block_tree
## if self.hasLowParticipation:
if node.checkpoints.justified.epoch < self.checkpoints.justified.epoch:
return false
if self.isPreviousEpochJustified:
return true
return node.checkpoints.finalized == self.checkpoints.finalized or
self.checkpoints.finalized.epoch == GENESIS_EPOCH
## Any node that has a different finalized or justified epoch ## Any node that has a different finalized or justified epoch
## should not be viable for the head. ## should not be viable for the head.
( (

View File

@ -154,7 +154,7 @@ proc loadChainDag(
let let
extraFlags = extraFlags =
if shouldEnableTestFeatures: {enableTestFeatures} if shouldEnableTestFeatures: {enableTestFeatures, lowParticipation}
else: {enableTestFeatures} else: {enableTestFeatures}
chainDagFlags = chainDagFlags =
if config.strictVerification: {strictVerification} if config.strictVerification: {strictVerification}

View File

@ -65,6 +65,7 @@ func apply(ctx: var ForkChoiceBackend, id: int, op: Operation) =
case op.kind case op.kind
of FindHead, InvalidFindHead: of FindHead, InvalidFindHead:
let r = ctx.find_head( let r = ctx.find_head(
GENESIS_EPOCH,
op.checkpoints, op.checkpoints,
op.justified_state_balances, op.justified_state_balances,
# Don't use proposer boosting # Don't use proposer boosting