Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 65 additions & 27 deletions packages/beacon-node/test/spec/presets/fast_confirmation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {expect} from "vitest";
import {toHexString} from "@chainsafe/ssz";
import {createBeaconConfig} from "@lodestar/config";
import {getConfig} from "@lodestar/config/test-utils";
import {CheckpointWithHex, ExecutionStatus, ForkChoice} from "@lodestar/fork-choice";
import {CheckpointWithHex, ExecutionStatus, ForkChoice, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
import {testLogger} from "@lodestar/logger/test-utils";
import {
ACTIVE_PRESET,
Expand Down Expand Up @@ -71,7 +71,7 @@ import {ClockStopped} from "../../mocks/clock.js";
import {getMockedBeaconDb} from "../../mocks/mockedBeaconDb.js";
import {assertCorrectProgressiveBalances} from "../config.js";
import {ethereumConsensusSpecsTests} from "../specTestVersioning.js";
import {specTestIterator} from "../utils/specTestIterator.js";
import {defaultSkipOpts, specTestIterator} from "../utils/specTestIterator.js";
import {RunnerType, TestRunnerFn} from "../utils/types.js";

const ANCHOR_STATE_FILE_NAME = "anchor_state";
Expand Down Expand Up @@ -556,21 +556,42 @@ const fastConfirmationTest =
);
}

// TODO: Expose this value to to spec tests
// if (step.checks.previous_epoch_observed_justified_checkpoint) {
// }
const fcrStore = chain.forkChoice.getFastConfirmationStore();

// TODO: Expose this value to to spec tests
// if (step.checks.current_epoch_observed_justified_checkpoint) {
// }
if (step.checks.previous_epoch_observed_justified_checkpoint) {
expect(toSpecTestCheckpoint(fcrStore.previousEpochObservedJustifiedCheckpoint)).toEqualWithMessage(
step.checks.previous_epoch_observed_justified_checkpoint,
`Invalid previous epoch observed justified checkpoint at step ${i}`
);
}

// TODO: Expose this value to to spec tests
// if (step.checks.previous_slot_head) {
// }
if (step.checks.current_epoch_observed_justified_checkpoint) {
expect(toSpecTestCheckpoint(fcrStore.currentEpochObservedJustifiedCheckpoint)).toEqualWithMessage(
step.checks.current_epoch_observed_justified_checkpoint,
`Invalid current epoch observed justified checkpoint at step ${i}`
);
}

// TODO: Expose this value to to spec tests
// if (step.checks.current_slot_head) {
// }
if (step.checks.previous_epoch_greatest_unrealized_checkpoint) {
expect(toSpecTestCheckpoint(fcrStore.previousEpochGreatestUnrealizedCheckpoint)).toEqualWithMessage(
step.checks.previous_epoch_greatest_unrealized_checkpoint,
`Invalid previous epoch greatest unrealized checkpoint at step ${i}`
);
}

if (step.checks.previous_slot_head) {
expect(fcrStore.previousSlotHead).toEqualWithMessage(
step.checks.previous_slot_head,
`Invalid previous slot head at step ${i}`
);
}

if (step.checks.current_slot_head) {
expect(fcrStore.currentSlotHead).toEqualWithMessage(
step.checks.current_slot_head,
`Invalid current slot head at step ${i}`
);
}

if (step.checks.confirmed_root) {
expect(confirmedRoot).toEqualWithMessage(
Expand All @@ -579,6 +600,13 @@ const fastConfirmationTest =
);
}

if (step.checks.safe_execution_block_hash !== undefined) {
expect(getSafeExecutionBlockHash(chain.forkChoice)).toEqualWithMessage(
step.checks.safe_execution_block_hash,
`Invalid safe execution block hash at step ${i}`
);
}

if (step.checks.should_override_forkchoice_update) {
const currentSlot = Math.floor(tickTime / (config.SLOT_DURATION_MS / 1000));
const result = chain.forkChoice.shouldOverrideForkChoiceUpdate(
Expand Down Expand Up @@ -687,17 +715,20 @@ const fastConfirmationTest =
name.includes("voting_source_beyond_two_epoch") ||
name.includes("justified_update_always_if_better") ||
name.includes("justified_update_not_realized_finality") ||
// TODO: lodestar's fast-confirmation rule (FCR) needs a broader overhaul. These two
// is_one_confirmed cases (new in v1.7.0-alpha.12, present in electra + fulu) currently
// fail. Unskip once the FCR is reworked.
name.includes("is_one_confirmed_fails_large_validator_slashed") ||
// These vectors carry stub deposit signatures (bls_setting=2) and expect the deposit to
// be applied. Passing them requires skipping deposit signature verification inside epoch
// processing, which Lodestar does not support. Unskip if upstream signs deposits for
// real, or if full bls_setting=2 support is ever added.
name.includes("is_one_confirmed_fails_recently_activated_validator_voting_in_empty_slot") ||
// This case (new in v1.7.0-alpha.13, consensus-specs #5449) deposits a validator, but the
// vectors are generated with bls_setting=2 so the deposit carries a stub signature that
// only the pyspec BLS stub accepts. Lodestar verifies the deposit proof of possession
// inside processPendingDeposits, so the validator is never onboarded and the state root
// diverges once the pending deposit is applied.
name.includes("is_one_confirmed_passes_with_new_validator_activated_in_head_state"),
name.includes("is_one_confirmed_passes_with_new_validator_activated_in_head_state") ||
// These vectors run `on_fast_confirmation` twice in one slot (stale GU test) or skip an
// epoch-boundary run (consecutive slots test), so a client running the handler once per
// slot cannot reproduce the expected FCR-store variables. Fixed upstream, unskip when
// the next spec-tests release (> v1.7.0-alpha.13) is picked up:
// - https://github.com/ethereum/consensus-specs/pull/5499
// - https://github.com/ethereum/consensus-specs/pull/5498
name.includes("fcr_no_restart_if_head_gu_is_stale") ||
name.includes("is_one_confirmed_passes_with_empty_slot_and_attester_in_two_consecutive_slots_2"),
},
};
};
Expand Down Expand Up @@ -785,9 +816,12 @@ type Checks = {

previous_epoch_observed_justified_checkpoint?: SpecTestCheckpoint;
current_epoch_observed_justified_checkpoint?: SpecTestCheckpoint;
previous_epoch_greatest_unrealized_checkpoint?: SpecTestCheckpoint;
previous_slot_head?: string;
current_slot_head?: string;
confirmed_root?: string;
/** Expected response of `get_safe_execution_block_hash()`. New in Bellatrix. */
safe_execution_block_hash?: string;

// Custom attributes
get_proposer_head?: string;
Expand Down Expand Up @@ -842,6 +876,10 @@ function isCheck(step: Step): step is Checks {
return typeof (step as Checks).checks === "object";
}

specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), {
fast_confirmation: {type: RunnerType.default, fn: fastConfirmationTest({onlyPredefinedResponses: false})},
});
specTestIterator(
path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET),
{
fast_confirmation: {type: RunnerType.default, fn: fastConfirmationTest({onlyPredefinedResponses: false})},
},
defaultSkipOpts
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type ForkChoiceStateGetter = (
opts: {stateRoot: RootHex; checkpoint?: never} | {stateRoot?: never; checkpoint: CheckpointWithHex}
) => IBeaconStateView | null;

type IFastConfirmationSpecStore = {
export type IFastConfirmationSpecStore = {
confirmedRoot: RootHex;
previousEpochObservedJustifiedCheckpoint: CheckpointWithHex;
currentEpochObservedJustifiedCheckpoint: CheckpointWithHex;
Expand Down
12 changes: 12 additions & 0 deletions packages/fork-choice/src/forkChoice/forkChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
FastConfirmationRule,
FastConfirmationSteps,
type IFastConfirmationRule,
type IFastConfirmationSpecStore,
} from "./fastConfirmation/fastConfirmationRule.ts";
import {
AncestorResult,
Expand Down Expand Up @@ -228,6 +229,17 @@ export class ForkChoice implements IForkChoice {
return this.fastConfirmationRule?.getConfirmedRoot() ?? this.fcStore.justified.checkpoint.rootHex;
}

getFastConfirmationStore(): IFastConfirmationSpecStore {
return {
confirmedRoot: this.getConfirmedRoot(),
previousEpochObservedJustifiedCheckpoint: this.fcStore.previousEpochObservedJustifiedCheckpoint,
currentEpochObservedJustifiedCheckpoint: this.fcStore.currentEpochObservedJustifiedCheckpoint,
previousEpochGreatestUnrealizedCheckpoint: this.fcStore.previousEpochGreatestUnrealizedCheckpoint,
previousSlotHead: this.fcStore.previousSlotHead,
currentSlotHead: this.fcStore.currentSlotHead,
};
}

getConfirmedBlock(): ProtoBlock | null {
return this.getBlockHexDefaultStatus(this.getConfirmedRoot());
}
Expand Down
3 changes: 3 additions & 0 deletions packages/fork-choice/src/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ProtoBlock,
ProtoNode,
} from "../protoArray/interface.js";
import {IFastConfirmationSpecStore} from "./fastConfirmation/types.js";
import {UpdateAndGetHeadOpt} from "./forkChoice.js";
import {CheckpointWithHex} from "./store.js";

Expand Down Expand Up @@ -100,6 +101,8 @@ export interface IForkChoice {
getHead(): ProtoBlock;
getConfirmedRoot(): RootHex;
getConfirmedBlock(): ProtoBlock | null;
/** Snapshot of the spec `FastConfirmationStore` fields, mirroring `get_fast_confirmation_store` */
getFastConfirmationStore(): IFastConfirmationSpecStore;
/** Resume the fast confirmation rule; restarts from the finalized root on the next slot tick */
resumeFastConfirmation(): void;
/** Pause the fast confirmation rule (e.g. while syncing); pins the confirmed root to the finalized root */
Expand Down
Loading