Skip to content

Commit 994339c

Browse files
authored
adjust checkpoint tracking for devnets (#4039)
Track checkpoints more defensively on devnets with low participation.
1 parent b60456f commit 994339c

File tree

8 files changed

+70
-29
lines changed

8 files changed

+70
-29
lines changed

beacon_chain/consensus_object_pools/attestation_pool.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ proc init*(T: type AttestationPool, dag: ChainDAGRef,
9797
## holding a zero_root.
9898
let finalizedEpochRef = dag.getFinalizedEpochRef()
9999

100-
var forkChoice = ForkChoice.init(finalizedEpochRef, dag.finalizedHead.blck)
100+
var forkChoice = ForkChoice.init(
101+
finalizedEpochRef, dag.finalizedHead.blck,
102+
lowParticipation in dag.updateFlags)
101103

102104
# Feed fork choice with unfinalized history - during startup, block pool only
103105
# keeps track of a single history so we just need to follow it

beacon_chain/consensus_object_pools/blockchain_dag.nim

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,9 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
775775
lcDataConfig = default(LightClientDataConfig)): ChainDAGRef =
776776
cfg.checkForkConsistency()
777777

778-
doAssert updateFlags - {strictVerification, enableTestFeatures} == {},
779-
"Other flags not supported in ChainDAG"
778+
doAssert updateFlags - {
779+
strictVerification, enableTestFeatures, lowParticipation
780+
} == {}, "Other flags not supported in ChainDAG"
780781

781782
# TODO we require that the db contains both a head and a tail block -
782783
# 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,
803804

804805
# The only allowed flag right now is strictVerification, as the others all
805806
# allow skipping some validation.
806-
updateFlags: {strictVerification, enableTestFeatures} * updateFlags,
807+
updateFlags: updateFlags * {
808+
strictVerification, enableTestFeatures, lowParticipation
809+
},
807810
cfg: cfg,
808811

809812
vanityLogs: vanityLogs,

beacon_chain/extras.nim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,7 @@ type
4141
## should skip calculating that last state root.
4242
enableTestFeatures ##\
4343
## Whether to enable extra features for testing.
44+
lowParticipation ##\
45+
## Whether the network is prone to low participation.
4446

4547
UpdateFlags* = set[UpdateFlag]

beacon_chain/fork_choice/fork_choice.nim

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ func compute_deltas(
5151

5252
logScope: topics = "fork_choice"
5353

54-
func init*(T: type ForkChoiceBackend, checkpoints: FinalityCheckpoints): T =
55-
T(proto_array: ProtoArray.init(checkpoints))
56-
57-
proc init*(T: type ForkChoice, epochRef: EpochRef, blck: BlockRef): T =
54+
func init*(
55+
T: type ForkChoiceBackend, checkpoints: FinalityCheckpoints,
56+
hasLowParticipation = false): T =
57+
T(proto_array: ProtoArray.init(checkpoints, hasLowParticipation))
58+
59+
proc init*(
60+
T: type ForkChoice, epochRef: EpochRef, blck: BlockRef,
61+
hasLowParticipation = false): T =
5862
## Initialize a fork choice context for a finalized state - in the finalized
5963
## state, the justified and finalized checkpoints are the same, so only one
6064
## is used here
@@ -66,7 +70,8 @@ proc init*(T: type ForkChoice, epochRef: EpochRef, blck: BlockRef): T =
6670
backend: ForkChoiceBackend.init(
6771
FinalityCheckpoints(
6872
justified: checkpoint,
69-
finalized: checkpoint)),
73+
finalized: checkpoint),
74+
hasLowParticipation),
7075
checkpoints: Checkpoints(
7176
justified: BalanceCheckpoint(
7277
checkpoint: checkpoint,
@@ -369,6 +374,7 @@ proc process_block*(self: var ForkChoice,
369374

370375
func find_head*(
371376
self: var ForkChoiceBackend,
377+
current_epoch: Epoch,
372378
checkpoints: FinalityCheckpoints,
373379
justified_state_balances: seq[Gwei],
374380
proposer_boost_root: Eth2Digest
@@ -387,7 +393,8 @@ func find_head*(
387393

388394
# Apply score changes
389395
? self.proto_array.applyScoreChanges(
390-
deltas, checkpoints, justified_state_balances, proposer_boost_root)
396+
deltas, current_epoch, checkpoints,
397+
justified_state_balances, proposer_boost_root)
391398

392399
self.balances = justified_state_balances
393400

@@ -407,6 +414,7 @@ proc get_head*(self: var ForkChoice,
407414
? self.update_time(dag, wallTime)
408415

409416
self.backend.find_head(
417+
self.checkpoints.time.slotOrZero.epoch,
410418
FinalityCheckpoints(
411419
justified: self.checkpoints.justified.checkpoint,
412420
finalized: self.checkpoints.finalized),

beacon_chain/fork_choice/fork_choice_types.nim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ type
9292
## to get the physical index
9393

9494
ProtoArray* = object
95+
hasLowParticipation*: bool
96+
currentEpoch*: Epoch
9597
checkpoints*: FinalityCheckpoints
9698
nodes*: ProtoNodes
9799
indices*: Table[Eth2Digest, Index]

beacon_chain/fork_choice/proto_array.nim

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ func len*(nodes: ProtoNodes): int =
7474
func add(nodes: var ProtoNodes, node: ProtoNode) =
7575
nodes.buf.add node
7676

77+
func isPreviousEpochJustified(self: ProtoArray): bool =
78+
self.checkpoints.justified.epoch + 1 == self.currentEpoch
79+
7780
# Forward declarations
7881
# ----------------------------------------------------------------------
7982

@@ -87,7 +90,9 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool]
8790
# ProtoArray routines
8891
# ----------------------------------------------------------------------
8992

90-
func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T =
93+
func init*(
94+
T: type ProtoArray, checkpoints: FinalityCheckpoints,
95+
hasLowParticipation: bool): T =
9196
let node = ProtoNode(
9297
root: checkpoints.finalized.root,
9398
parent: none(int),
@@ -96,10 +101,29 @@ func init*(T: type ProtoArray, checkpoints: FinalityCheckpoints): T =
96101
bestChild: none(int),
97102
bestDescendant: none(int))
98103

99-
T(checkpoints: checkpoints,
104+
T(hasLowParticipation: hasLowParticipation,
105+
checkpoints: checkpoints,
100106
nodes: ProtoNodes(buf: @[node], offset: 0),
101107
indices: {node.root: 0}.toTable())
102108

109+
iterator realizePendingCheckpoints*(
110+
self: var ProtoArray, resetTipTracking = true): FinalityCheckpoints =
111+
# Pull-up chain tips from previous epoch
112+
for idx, unrealized in self.currentEpochTips.pairs():
113+
let physicalIdx = idx - self.nodes.offset
114+
if unrealized != self.nodes.buf[physicalIdx].checkpoints:
115+
trace "Pulling up chain tip",
116+
blck = self.nodes.buf[physicalIdx].root,
117+
checkpoints = self.nodes.buf[physicalIdx].checkpoints,
118+
unrealized
119+
self.nodes.buf[physicalIdx].checkpoints = unrealized
120+
121+
yield unrealized
122+
123+
# Reset tip tracking for new epoch
124+
if resetTipTracking:
125+
self.currentEpochTips.clear()
126+
103127
# https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#configuration
104128
# https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#get_latest_attesting_balance
105129
const PROPOSER_SCORE_BOOST* = 40
@@ -124,6 +148,7 @@ func calculateProposerBoost(validatorBalances: openArray[Gwei]): int64 =
124148

125149
func applyScoreChanges*(self: var ProtoArray,
126150
deltas: var openArray[Delta],
151+
currentEpoch: Epoch,
127152
checkpoints: FinalityCheckpoints,
128153
newBalances: openArray[Gwei],
129154
proposerBoostRoot: Eth2Digest): FcResult[void] =
@@ -148,8 +173,14 @@ func applyScoreChanges*(self: var ProtoArray,
148173
deltasLen: deltas.len,
149174
indicesLen: self.indices.len)
150175

176+
self.currentEpoch = currentEpoch
151177
self.checkpoints = checkpoints
152178

179+
# If previous epoch is justified, pull up all current tips to previous epoch
180+
if self.hasLowParticipation and self.isPreviousEpochJustified:
181+
for realized in self.realizePendingCheckpoints(resetTipTracking = false):
182+
discard
183+
153184
## Alias
154185
# This cannot raise the IndexError exception, how to tell compiler?
155186
template node: untyped {.dirty.} =
@@ -300,21 +331,6 @@ func onBlock*(self: var ProtoArray,
300331

301332
ok()
302333

303-
iterator realizePendingCheckpoints*(self: var ProtoArray): FinalityCheckpoints =
304-
# Pull-up chain tips from previous epoch
305-
for idx, unrealized in self.currentEpochTips.pairs():
306-
let physicalIdx = idx - self.nodes.offset
307-
trace "Pulling up chain tip",
308-
blck = self.nodes.buf[physicalIdx].root,
309-
checkpoints = self.nodes.buf[physicalIdx].checkpoints,
310-
unrealized
311-
self.nodes.buf[physicalIdx].checkpoints = unrealized
312-
313-
yield unrealized
314-
315-
# Reset tip tracking for new epoch
316-
self.currentEpochTips.clear()
317-
318334
func findHead*(self: var ProtoArray,
319335
head: var Eth2Digest,
320336
justifiedRoot: Eth2Digest): FcResult[void] =
@@ -518,7 +534,14 @@ func nodeLeadsToViableHead(self: ProtoArray, node: ProtoNode): FcResult[bool] =
518534
func nodeIsViableForHead(self: ProtoArray, node: ProtoNode): bool =
519535
## This is the equivalent of `filter_block_tree` function in eth2 spec
520536
## https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#filter_block_tree
521-
##
537+
if self.hasLowParticipation:
538+
if node.checkpoints.justified.epoch < self.checkpoints.justified.epoch:
539+
return false
540+
if self.isPreviousEpochJustified:
541+
return true
542+
return node.checkpoints.finalized == self.checkpoints.finalized or
543+
self.checkpoints.finalized.epoch == GENESIS_EPOCH
544+
522545
## Any node that has a different finalized or justified epoch
523546
## should not be viable for the head.
524547
(

beacon_chain/nimbus_beacon_node.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ proc loadChainDag(
154154

155155
let
156156
extraFlags =
157-
if shouldEnableTestFeatures: {enableTestFeatures}
157+
if shouldEnableTestFeatures: {enableTestFeatures, lowParticipation}
158158
else: {enableTestFeatures}
159159
chainDagFlags =
160160
if config.strictVerification: {strictVerification}

tests/fork_choice/interpreter.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func apply(ctx: var ForkChoiceBackend, id: int, op: Operation) =
6565
case op.kind
6666
of FindHead, InvalidFindHead:
6767
let r = ctx.find_head(
68+
GENESIS_EPOCH,
6869
op.checkpoints,
6970
op.justified_state_balances,
7071
# Don't use proposer boosting

0 commit comments

Comments
 (0)