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.
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
# 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 =
cfg.checkForkConsistency()
doAssert updateFlags - {strictVerification, enableTestFeatures} == {},
"Other flags not supported in ChainDAG"
doAssert updateFlags - {
strictVerification, enableTestFeatures, lowParticipation
} == {}, "Other flags not supported in ChainDAG"
# 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..
@ -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
# allow skipping some validation.
updateFlags: {strictVerification, enableTestFeatures} * updateFlags,
updateFlags: updateFlags * {
strictVerification, enableTestFeatures, lowParticipation
},
cfg: cfg,
vanityLogs: vanityLogs,

View File

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

View File

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

View File

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

View File

@ -74,6 +74,9 @@ func len*(nodes: ProtoNodes): int =
func add(nodes: var ProtoNodes, node: ProtoNode) =
nodes.buf.add node
func isPreviousEpochJustified(self: ProtoArray): bool =
self.checkpoints.justified.epoch + 1 == self.currentEpoch
# Forward declarations
# ----------------------------------------------------------------------
@ -87,7 +90,9 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool]
# ProtoArray routines
# ----------------------------------------------------------------------
func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T =
func init*(
T: type ProtoArray, checkpoints: FinalityCheckpoints,
hasLowParticipation: bool): T =
let node = ProtoNode(
root: checkpoints.finalized.root,
parent: none(int),
@ -96,10 +101,29 @@ func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T =
bestChild: none(int),
bestDescendant: none(int))
T(checkpoints: checkpoints,
T(hasLowParticipation: hasLowParticipation,
checkpoints: checkpoints,
nodes: ProtoNodes(buf: @[node], offset: 0),
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.1.10/specs/phase0/fork-choice.md#get_latest_attesting_balance
const PROPOSER_SCORE_BOOST* = 40
@ -124,6 +148,7 @@ func calculateProposerBoost(validatorBalances: openArray[Gwei]): int64 =
func applyScoreChanges*(self: var ProtoArray,
deltas: var openArray[Delta],
currentEpoch: Epoch,
checkpoints: FinalityCheckpoints,
newBalances: openArray[Gwei],
proposerBoostRoot: Eth2Digest): FcResult[void] =
@ -148,8 +173,14 @@ func applyScoreChanges*(self: var ProtoArray,
deltasLen: deltas.len,
indicesLen: self.indices.len)
self.currentEpoch = currentEpoch
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
# This cannot raise the IndexError exception, how to tell compiler?
template node: untyped {.dirty.} =
@ -300,21 +331,6 @@ func onBlock*(self: var ProtoArray,
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,
head: var Eth2Digest,
justifiedRoot: Eth2Digest): FcResult[void] =
@ -518,7 +534,14 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
## 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
##
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
## should not be viable for the head.
(

View File

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

View File

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