Skip to content

Commit b45ea37

Browse files
authored
chore: add should_override_forkchoice_update spec test check (#8020)
**Motivation** We are not running `should_override_forkchoice_update` spec tests from ethereum/consensus-specs#3034 as checks are missing. **Description** - add `should_override_forkchoice_update` spec test check - pass `secFromSlot` to `shouldOverrideForkChoiceUpdate` as it's required now - add [`proposal_slot == current_slot and proposing_on_time`](https://github.com/ethereum/consensus-specs/blob/18387696969c0bb34e96164434a3a36edca296c9/specs/bellatrix/fork-choice.md?plain=1#L145) to `shouldOverrideForkChoiceUpdate` to pass spec tests
1 parent 2f2eaf1 commit b45ea37

File tree

6 files changed

+63
-24
lines changed

6 files changed

+63
-24
lines changed

packages/beacon-node/src/chain/blocks/importBlock.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,19 @@ export async function importBlock(
331331
try {
332332
const proposerIndex = postState.epochCtx.getBeaconProposer(blockSlot + 1);
333333
const feeRecipient = this.beaconProposerCache.get(proposerIndex);
334+
const {currentSlot} = this.clock;
334335

335336
if (feeRecipient) {
336337
// We would set this to true if
337338
// 1) This is a gossip block
338339
// 2) We are proposer of next slot
339340
// 3) Proposer boost reorg related flag is turned on (this is checked inside the function)
340341
// 4) Block meets the criteria of being re-orged out (this is also checked inside the function)
341-
const result = this.forkChoice.shouldOverrideForkChoiceUpdate(this.clock.currentSlot, blockSummary.blockRoot);
342+
const result = this.forkChoice.shouldOverrideForkChoiceUpdate(
343+
blockSummary.blockRoot,
344+
this.clock.secFromSlot(currentSlot),
345+
currentSlot
346+
);
342347
shouldOverrideFcu = result.shouldOverrideFcu;
343348
if (!result.shouldOverrideFcu) {
344349
notOverrideFcuReason = result.reason;

packages/beacon-node/src/chain/chain.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,9 +839,10 @@ export class BeaconChain implements IBeaconChain {
839839
predictProposerHead(slot: Slot): ProtoBlock {
840840
this.metrics?.forkChoice.requests.inc();
841841
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.predictProposerHead});
842+
const secFromSlot = this.clock.secFromSlot(slot);
842843

843844
try {
844-
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}).head;
845+
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, secFromSlot, slot}).head;
845846
} catch (e) {
846847
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetPredictedProposerHead});
847848
throw e;

packages/beacon-node/test/spec/presets/fork_choice.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,21 @@ const forkChoiceTest =
324324
`Invalid proposer head at step ${i}`
325325
);
326326
}
327+
if (step.checks.should_override_forkchoice_update) {
328+
const currentSlot = Math.floor(tickTime / config.SECONDS_PER_SLOT);
329+
const result = chain.forkChoice.shouldOverrideForkChoiceUpdate(
330+
head.blockRoot,
331+
tickTime % config.SECONDS_PER_SLOT,
332+
currentSlot
333+
);
334+
if (result.shouldOverrideFcu === false) {
335+
logger.debug(`Not reorged reason ${result.reason} at step ${i}`);
336+
}
337+
expect({result: result.shouldOverrideFcu, validator_is_connected: true}).toEqualWithMessage(
338+
step.checks.should_override_forkchoice_update,
339+
`Invalid should override fcu result at step ${i}`
340+
);
341+
}
327342
}
328343

329344
// None of the above
@@ -487,6 +502,10 @@ type Checks = {
487502
finalized_checkpoint?: SpecTestCheckpoint;
488503
proposer_boost_root?: RootHex;
489504
get_proposer_head?: string;
505+
should_override_forkchoice_update?: {
506+
validator_is_connected: boolean;
507+
result: boolean;
508+
};
490509
};
491510
};
492511

packages/fork-choice/src/forkChoice/forkChoice.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export enum UpdateHeadOpt {
7171
export type UpdateAndGetHeadOpt =
7272
| {mode: UpdateHeadOpt.GetCanonicialHead}
7373
| {mode: UpdateHeadOpt.GetProposerHead; secFromSlot: number; slot: Slot}
74-
| {mode: UpdateHeadOpt.GetPredictedProposerHead; slot: Slot};
74+
| {mode: UpdateHeadOpt.GetPredictedProposerHead; secFromSlot: number; slot: Slot};
7575

7676
/**
7777
* Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice":
@@ -208,7 +208,7 @@ export class ForkChoice implements IForkChoice {
208208
const canonicialHeadBlock = mode === UpdateHeadOpt.GetPredictedProposerHead ? this.getHead() : this.updateHead();
209209
switch (mode) {
210210
case UpdateHeadOpt.GetPredictedProposerHead:
211-
return {head: this.predictProposerHead(canonicialHeadBlock, opt.slot)};
211+
return {head: this.predictProposerHead(canonicialHeadBlock, opt.secFromSlot, opt.slot)};
212212
case UpdateHeadOpt.GetProposerHead: {
213213
const {
214214
proposerHead: head,
@@ -229,7 +229,11 @@ export class ForkChoice implements IForkChoice {
229229
// Return true if the given block passes all criteria to be re-orged out
230230
// Return false otherwise.
231231
// Note when proposer boost reorg is disabled, it always returns false
232-
shouldOverrideForkChoiceUpdate(currentSlot: Slot, blockRoot: RootHex): ShouldOverrideForkChoiceUpdateResult {
232+
shouldOverrideForkChoiceUpdate(
233+
blockRoot: RootHex,
234+
secFromSlot: number,
235+
currentSlot: Slot
236+
): ShouldOverrideForkChoiceUpdateResult {
233237
const headBlock = this.getBlockHex(blockRoot);
234238
if (headBlock === null) {
235239
// should not happen beacause this block just got imported. Fall back to no-reorg.
@@ -261,7 +265,8 @@ export class ForkChoice implements IForkChoice {
261265
return {shouldOverrideFcu: false, reason: prelimNotReorgedReason ?? NotReorgedReason.Unknown};
262266
}
263267

264-
const currentTimeOk = headBlock.slot === currentSlot;
268+
const currentTimeOk =
269+
headBlock.slot === currentSlot || (proposalSlot === currentSlot && this.isProposingOnTime(secFromSlot));
265270
if (!currentTimeOk) {
266271
return {shouldOverrideFcu: false, reason: NotReorgedReason.ReorgMoreThanOneSlot};
267272
}
@@ -287,7 +292,7 @@ export class ForkChoice implements IForkChoice {
287292
* By calling this function, we assume we are the proposer of next slot
288293
*
289294
*/
290-
predictProposerHead(headBlock: ProtoBlock, currentSlot?: Slot): ProtoBlock {
295+
predictProposerHead(headBlock: ProtoBlock, secFromSlot: number, currentSlot: Slot): ProtoBlock {
291296
// Skip re-org attempt if proposer boost (reorg) are disabled
292297
if (!this.opts?.proposerBoost || !this.opts?.proposerBoostReorg) {
293298
this.logger?.verbose("No proposer boot reorg prediction since the related flags are disabled");
@@ -300,10 +305,8 @@ export class ForkChoice implements IForkChoice {
300305
return headBlock;
301306
}
302307

303-
currentSlot = currentSlot ?? this.fcStore.currentSlot;
304-
305308
const blockRoot = headBlock.blockRoot;
306-
const result = this.shouldOverrideForkChoiceUpdate(currentSlot, blockRoot);
309+
const result = this.shouldOverrideForkChoiceUpdate(blockRoot, secFromSlot, currentSlot);
307310

308311
if (result.shouldOverrideFcu) {
309312
this.logger?.verbose("Current head is weak. Predicting next block to be built on parent of head.", {
@@ -351,10 +354,8 @@ export class ForkChoice implements IForkChoice {
351354
return {proposerHead, isHeadTimely, notReorgedReason: prelimNotReorgedReason};
352355
}
353356

354-
// https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/phase0/fork-choice.md#is_proposing_on_time
355-
const proposerReorgCutoff = this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT / 2;
356-
const isProposingOnTime = secFromSlot <= proposerReorgCutoff;
357-
if (!isProposingOnTime) {
357+
// Only re-org if we are proposing on-time
358+
if (!this.isProposingOnTime(secFromSlot)) {
358359
return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.NotProposingOnTime};
359360
}
360361

@@ -1177,6 +1178,14 @@ export class ForkChoice implements IForkChoice {
11771178
return this.fcStore.currentSlot === block.slot && isBeforeAttestingInterval;
11781179
}
11791180

1181+
/**
1182+
* https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/fork-choice.md#is_proposing_on_time
1183+
*/
1184+
private isProposingOnTime(secFromSlot: number): boolean {
1185+
const proposerReorgCutoff = this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT / 2;
1186+
return secFromSlot <= proposerReorgCutoff;
1187+
}
1188+
11801189
private getPreMergeExecStatus(executionStatus: MaybeValidExecutionStatus): ExecutionStatus.PreMerge {
11811190
if (executionStatus !== ExecutionStatus.PreMerge)
11821191
throw Error(`Invalid pre-merge execution status: expected: ${ExecutionStatus.PreMerge}, got ${executionStatus}`);

packages/fork-choice/src/forkChoice/interface.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ export interface IForkChoice {
117117
* Primarily being called during block import when proposerBoostReorg is enabled
118118
* fcu call in `importBlock()` will be suppressed if this returns true
119119
*/
120-
shouldOverrideForkChoiceUpdate(slot: Slot, blockRoot: RootHex): ShouldOverrideForkChoiceUpdateResult;
120+
shouldOverrideForkChoiceUpdate(
121+
blockRoot: RootHex,
122+
secFromSlot: number,
123+
currentSlot: Slot
124+
): ShouldOverrideForkChoiceUpdateResult;
121125
/**
122126
* Retrieves all possible chain heads (leaves of fork choice tree).
123127
*/

packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,14 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => {
174174
expectReorg: false,
175175
expectedNotReorgedReason: NotReorgedReason.ParentBlockDistanceMoreThanOneSlot,
176176
},
177-
{
178-
id: "No reorg if head block slot and current slot mismatch",
179-
parentBlock: {...baseParentHeadBlock},
180-
headBlock: {...baseHeadBlock},
181-
expectReorg: false,
182-
currentSlot: headSlot + 1,
183-
expectedNotReorgedReason: NotReorgedReason.ReorgMoreThanOneSlot,
184-
},
177+
// {
178+
// id: "No reorg if head block slot and current slot mismatch",
179+
// parentBlock: {...baseParentHeadBlock},
180+
// headBlock: {...baseHeadBlock},
181+
// expectReorg: false,
182+
// currentSlot: headSlot + 1,
183+
// expectedNotReorgedReason: NotReorgedReason.ReorgMoreThanOneSlot,
184+
// },
185185
];
186186

187187
beforeEach(() => {
@@ -200,13 +200,14 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => {
200200
protoArr.onBlock(parentBlock, parentBlock.slot);
201201
protoArr.onBlock(headBlock, headBlock.slot);
202202

203+
const secFromSlot = 0;
203204
const currentSlot = blockSeenSlot ?? headBlock.slot;
204205
const forkChoice = new ForkChoice(config, fcStore, protoArr, {
205206
proposerBoost: true,
206207
proposerBoostReorg: true,
207208
});
208209

209-
const result = forkChoice.shouldOverrideForkChoiceUpdate(currentSlot, headBlock.blockRoot);
210+
const result = forkChoice.shouldOverrideForkChoiceUpdate(headBlock.blockRoot, secFromSlot, currentSlot);
210211

211212
expect(result.shouldOverrideFcu).toBe(expectReorg);
212213

0 commit comments

Comments
 (0)