Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: proposer boost reorg #6298

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6473e0b
Add presets
ensi321 Dec 15, 2023
87a0b6d
Add timeliness to protoBlock
ensi321 Jan 3, 2024
f0581bf
Rename proposerBoostScore to committeeFraction
ensi321 Jan 4, 2024
2da8dfc
Implement getProposerHead
ensi321 Jan 4, 2024
1d0d2f7
Add proposerBoostReorgEnabled flag
ensi321 Jan 4, 2024
c9d7582
Add updateAndGetHead
ensi321 Jan 9, 2024
16c056b
Add predictProposerHead()
ensi321 Jan 9, 2024
5d4fc9b
Refactor forkChoice. Add comment
ensi321 Jan 9, 2024
7d0095c
Predict proposer head if proposing next slot
ensi321 Jan 9, 2024
7ea9e02
Add todo
ensi321 Jan 9, 2024
b9aee5c
Lint
ensi321 Jan 9, 2024
90cb85a
Add logger to forkChoice
ensi321 Jan 15, 2024
a031b40
newHead calculation varies depending if we are proposer
ensi321 Jan 15, 2024
bd1fd42
Add timeliness to tests
ensi321 Jan 15, 2024
01609e8
Merge branch 'unstable' into proposer-boost-reorg
ensi321 Jan 15, 2024
a2b06a2
Lint
ensi321 Jan 15, 2024
2124d27
Fix getProposerHead
ensi321 Jan 16, 2024
d341d02
Add test
ensi321 Jan 16, 2024
b1b1eaa
Lint
ensi321 Jan 16, 2024
d6a73e7
Fix
ensi321 Jan 16, 2024
d273339
Lint
ensi321 Jan 17, 2024
3037e54
Merge branch 'unstable' into proposer-boost-reorg
ensi321 Jan 17, 2024
a7b9d44
Fix consensusBlockValue unit in debug log
ensi321 Jan 22, 2024
a50c276
lint
ensi321 Jan 22, 2024
b8ec80f
Fix typo
ensi321 Jan 24, 2024
8d44cfd
Merge branch 'unstable' into proposer-boost-reorg
ensi321 Jan 30, 2024
feb6de7
Fix naming conflict
ensi321 Jan 30, 2024
5d0990d
Revert changes in `importBlock` as per suggestion
ensi321 Jan 30, 2024
9a1d577
Merge branch 'unstable' into proposer-boost-reorg
ensi321 Mar 5, 2024
439f7e9
Update test
ensi321 Mar 6, 2024
b805fd2
fix test
ensi321 Mar 7, 2024
0ee4dff
Disable reorg by default
ensi321 Mar 18, 2024
e542037
updatedHeadRoot
ensi321 Mar 18, 2024
a7bd0f8
Split recomputeForkChoiceHead into 3 fns
ensi321 Mar 18, 2024
5e75849
Improve metrics
ensi321 Mar 18, 2024
04f83a2
Avoid setting forkchoice.head in getProposerHead
ensi321 Mar 18, 2024
944f234
Use the correct commonBlockBody to produce block
ensi321 Mar 18, 2024
1b233c3
Lint
ensi321 Mar 18, 2024
e55fa43
Fix unit test
ensi321 Mar 18, 2024
39b1113
Fix unit test
ensi321 Mar 19, 2024
7d02297
Remove extra update forkchoice head
ensi321 Mar 19, 2024
a5c4bce
fix: refactor and e2e test
twoeths Mar 21, 2024
852220c
chore: address PR comment
twoeths Mar 22, 2024
3fef364
Update packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts
ensi321 Mar 22, 2024
07a11d5
Merge pull request #3 from tuyennhv/proposer-boost-reorg-e2e-test
ensi321 Mar 22, 2024
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
1 change: 1 addition & 0 deletions packages/api/src/beacon/routes/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const protoNodeSszType = new ContainerType(
parentRoot: stringType,
stateRoot: stringType,
targetRoot: stringType,
timeliness: ssz.Boolean,
justifiedEpoch: ssz.Epoch,
justifiedRoot: stringType,
finalizedEpoch: ssz.Epoch,
Expand Down
1 change: 1 addition & 0 deletions packages/api/test/unit/beacon/testData/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const testData: GenericServerTestCases<Api> = {
weight: 1,
bestChild: "1",
bestDescendant: "1",
timeliness: false,
},
],
},
Expand Down
3 changes: 3 additions & 0 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,10 +529,13 @@ export function getValidatorApi({
};

logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext);
const proposerHead = chain.getProposerHead(slot);
ensi321 marked this conversation as resolved.
Show resolved Hide resolved

const commonBlockBody = await chain.produceCommonBlockBody({
slot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
proposerHead,
});
logger.debug("Produced common block body", loggerContext);

Expand Down
48 changes: 42 additions & 6 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
bellatrix,
isBlindedBeaconBlock,
} from "@lodestar/types";
import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
import {ProcessShutdownCallback} from "@lodestar/validator";
import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils";
import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
Expand All @@ -43,7 +43,14 @@ import {ensureDir, writeIfNotExist} from "../util/file.js";
import {isOptimisticBlock} from "../util/forkChoice.js";
import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
import {ChainEventEmitter, ChainEvent} from "./emitter.js";
import {IBeaconChain, ProposerPreparationData, BlockHash, StateGetOpts, CommonBlockBody} from "./interface.js";
import {
IBeaconChain,
ProposerPreparationData,
BlockHash,
StateGetOpts,
CommonBlockBody,
FindHeadFnName,
} from "./interface.js";
import {IChainOptions} from "./options.js";
import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js";
import {initializeForkChoice} from "./forkChoice/index.js";
Expand Down Expand Up @@ -252,7 +259,8 @@ export class BeaconChain implements IBeaconChain {
clock.currentSlot,
cachedState,
opts,
this.justifiedBalancesGetter.bind(this)
this.justifiedBalancesGetter.bind(this),
logger
);
const regen = new QueuedStateRegenerator({
config,
Expand Down Expand Up @@ -476,7 +484,7 @@ export class BeaconChain implements IBeaconChain {

async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody> {
const {slot} = blockAttributes;
const head = this.forkChoice.getHead();
const head = blockAttributes.proposerHead ?? this.forkChoice.getHead();
Copy link
Contributor

@twoeths twoeths Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to make change in chain.produceBlockWrapper() to use use proposerHead inside BlockAttributes to build block

const state = await this.regen.getBlockSlotState(
head.blockRoot,
slot,
Expand Down Expand Up @@ -648,10 +656,38 @@ export class BeaconChain implements IBeaconChain {

recomputeForkChoiceHead(): ProtoBlock {
this.metrics?.forkChoice.requests.inc();
const timer = this.metrics?.forkChoice.findHead.startTimer();
const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.recomputeForkChoiceHead});

try {
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead});
} catch (e) {
this.metrics?.forkChoice.errors.inc();
throw e;
} finally {
timer?.();
}
}

predictProposerHead(slot: Slot): ProtoBlock {
this.metrics?.forkChoice.requests.inc();
const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.predictProposerHead});

try {
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot});
} catch (e) {
this.metrics?.forkChoice.errors.inc();
throw e;
} finally {
timer?.();
}
}

getProposerHead(slot: Slot): ProtoBlock {
this.metrics?.forkChoice.requests.inc();
const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.getProposerHead});

try {
return this.forkChoice.updateHead();
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetProposerHead, slot});
} catch (e) {
this.metrics?.forkChoice.errors.inc();
throw e;
Expand Down
6 changes: 5 additions & 1 deletion packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isMergeTransitionComplete,
} from "@lodestar/state-transition";

import {Logger} from "@lodestar/utils";
import {computeAnchorCheckpoint} from "../initState.js";
import {ChainEventEmitter} from "../emitter.js";
import {ChainEvent} from "../emitter.js";
Expand All @@ -32,7 +33,8 @@ export function initializeForkChoice(
currentSlot: Slot,
state: CachedBeaconStateAllForks,
opts: ForkChoiceOpts,
justifiedBalancesGetter: JustifiedBalancesGetter
justifiedBalancesGetter: JustifiedBalancesGetter,
logger?: Logger
): ForkChoice {
const {blockHeader, checkpoint} = computeAnchorCheckpoint(config, state);
const finalizedCheckpoint = {...checkpoint};
Expand Down Expand Up @@ -68,6 +70,7 @@ export function initializeForkChoice(
parentRoot: toHexString(blockHeader.parentRoot),
stateRoot: toHexString(blockHeader.stateRoot),
blockRoot: toHexString(checkpoint.root),
timeliness: true, // Optimisitcally assume is timely

justifiedEpoch: justifiedCheckpoint.epoch,
justifiedRoot: toHexString(justifiedCheckpoint.root),
Expand All @@ -89,6 +92,7 @@ export function initializeForkChoice(
currentSlot
),

logger,
opts
);
}
12 changes: 12 additions & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export type StateGetOpts = {
allowRegen: boolean;
};

export enum FindHeadFnName {
recomputeForkChoiceHead = "recomputeForkChoiceHead",
predictProposerHead = "predictProposerHead",
getProposerHead = "getProposerHead",
}

/**
* The IBeaconChain service deals with processing incoming blocks, advancing a state transition
* and applying the fork choice rule to update the chain head
Expand Down Expand Up @@ -180,6 +186,12 @@ export interface IBeaconChain {

recomputeForkChoiceHead(): ProtoBlock;

/** When proposerBoostReorg is enabled, this is called at slot n-1 to predict the head block to build on if we are proposing at slot n */
predictProposerHead(slot: Slot): ProtoBlock;

/** When proposerBoostReorg is enabled and we are proposing a block, this is called to determine which head block to build on */
getProposerHead(slot: Slot): ProtoBlock;

waitForBlock(slot: Slot, root: RootHex): Promise<boolean>;

updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise<void>;
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const defaultChainOptions: IChainOptions = {
blsVerifyAllMultiThread: false,
disableBlsBatchVerify: false,
proposerBoostEnabled: true,
proposerBoostReorgEnabled: false,
computeUnrealized: true,
safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY,
suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
Expand Down
29 changes: 26 additions & 3 deletions packages/beacon-node/src/chain/prepareNextSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
computeEpochAtSlot,
isExecutionStateType,
computeTimeAtSlot,
CachedBeaconStateExecutions,
StateHashTreeRootSource,
} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
Expand Down Expand Up @@ -143,7 +144,29 @@ export class PrepareNextSlotScheduler {
if (isExecutionStateType(prepareState)) {
const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot);
const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
let updatedPrepareState = prepareState;
let updatedHeadRoot = headRoot;

if (feeRecipient) {
// If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not
const {slot: proposerHeadSlot, blockRoot: proposerHeadRoot} = this.chain.predictProposerHead(clockSlot);

// If we predict we can reorg, update prepareState with proposer head block
if (proposerHeadRoot !== headRoot || proposerHeadSlot !== headSlot) {
this.logger.verbose("Weak head detected. May build on this block instead:", {
ensi321 marked this conversation as resolved.
Show resolved Hide resolved
proposerHeadSlot,
proposerHeadRoot,
});
this.metrics?.weakHeadDetected.inc();
updatedPrepareState = (await this.chain.regen.getBlockSlotState(
proposerHeadRoot,
prepareSlot,
{dontTransferCache: !isEpochTransition},
RegenCaller.precomputeEpoch
)) as CachedBeaconStateExecutions;
updatedHeadRoot = proposerHeadRoot;
}

// Update the builder status, if enabled shoot an api call to check status
this.chain.updateBuilderStatus(clockSlot);
if (this.chain.executionBuilder?.status) {
Expand All @@ -166,10 +189,10 @@ export class PrepareNextSlotScheduler {
this.chain,
this.logger,
fork as ForkExecution, // State is of execution type
fromHex(headRoot),
fromHex(updatedHeadRoot),
safeBlockHash,
finalizedBlockHash,
prepareState,
updatedPrepareState,
feeRecipient
);
this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
Expand All @@ -182,7 +205,7 @@ export class PrepareNextSlotScheduler {
// If emitPayloadAttributes is true emit a SSE payloadAttributes event
if (this.chain.opts.emitPayloadAttributes === true) {
const data = await getPayloadAttributesForSSE(fork as ForkExecution, this.chain, {
prepareState,
prepareState: updatedPrepareState,
prepareSlot,
parentBlockRoot: fromHex(headRoot),
// The likely consumers of this API are builders and will anyway ignore the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {ChainForkConfig} from "@lodestar/config";
import {ForkSeq, ForkExecution, isForkExecution} from "@lodestar/params";
import {toHex, sleep, Logger} from "@lodestar/utils";

import {ProtoBlock} from "@lodestar/fork-choice";
import type {BeaconChain} from "../chain.js";
import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from "../../execution/index.js";
import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
Expand Down Expand Up @@ -65,6 +66,7 @@ export type BlockAttributes = {
graffiti: Bytes32;
slot: Slot;
feeRecipient?: string;
proposerHead?: ProtoBlock;
};

export enum BlockType {
Expand Down
8 changes: 7 additions & 1 deletion packages/beacon-node/src/metrics/metrics/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
// Non-spec'ed

forkChoice: {
findHead: register.histogram({
findHead: register.histogram<{entrypoint: string}>({
name: "beacon_fork_choice_find_head_seconds",
help: "Time taken to find head in seconds",
buckets: [0.1, 1, 10],
labelNames: ["entrypoint"],
}),
requests: register.gauge({
name: "beacon_fork_choice_requests_total",
Expand Down Expand Up @@ -198,5 +199,10 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
name: "beacon_clock_epoch",
help: "Current clock epoch",
}),

weakHeadDetected: register.gauge({
name: "beacon_weak_head_detected",
help: "Detected current head block is weak. May reorg it out when proposing next slot. See proposer boost reorg for more",
}),
};
}
2 changes: 2 additions & 0 deletions packages/beacon-node/test/mocks/mockedBeaconChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ vi.mock("../../src/chain/chain.js", async (importActual) => {
beaconProposerCache: new BeaconProposerCache(),
shufflingCache: new ShufflingCache(),
produceCommonBlockBody: vi.fn(),
getProposerHead: vi.fn(),
produceBlock: vi.fn(),
produceBlindedBlock: vi.fn(),
getCanonicalBlockAtSlot: vi.fn(),
recomputeForkChoiceHead: vi.fn(),
predictProposerHead: vi.fn(),
getHeadStateAtCurrentEpoch: vi.fn(),
getHeadState: vi.fn(),
updateBuilderStatus: vi.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ describe(`getAttestationsForBlock vc=${vc}`, () => {
unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root),
executionPayloadBlockHash: null,
executionStatus: ExecutionStatus.PreMerge,

timeliness: false,
},
originalState.slot
);
Expand All @@ -87,6 +89,7 @@ describe(`getAttestationsForBlock vc=${vc}`, () => {
unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root),
executionPayloadBlockHash: null,
executionStatus: ExecutionStatus.PreMerge,
timeliness: false,
},
slot
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe("produceBlockBody", () => {
chain = new BeaconChain(
{
proposerBoostEnabled: true,
proposerBoostReorgEnabled: false,
computeUnrealized: false,
safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY,
disableArchiveOnCheckpoint: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe.skip("verify+import blocks - range sync perf test", () => {
const chain = new BeaconChain(
{
proposerBoostEnabled: true,
proposerBoostReorgEnabled: false,
computeUnrealized: false,
safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY,
disableArchiveOnCheckpoint: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ describe("PrepareNextSlot scheduler", () => {
chainStub.emitter.on(routes.events.EventType.payloadAttributes, spy);
getForkStub.mockReturnValue(ForkName.bellatrix);
chainStub.recomputeForkChoiceHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
chainStub.predictProposerHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
forkChoiceStub.getJustifiedBlock.mockReturnValue({} as ProtoBlock);
forkChoiceStub.getFinalizedBlock.mockReturnValue({} as ProtoBlock);
updateBuilderStatus.mockReturnValue(void 0);
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/test/utils/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,7 @@ export const zeroProtoBlock: ProtoBlock = {
unrealizedFinalizedEpoch: 0,
unrealizedFinalizedRoot: ZERO_HASH_HEX,

timeliness: false,

...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge},
};
2 changes: 2 additions & 0 deletions packages/beacon-node/test/utils/typeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export function generateProtoBlock(overrides: Partial<ProtoBlock> = {}): ProtoBl
unrealizedFinalizedEpoch: 0,
unrealizedFinalizedRoot: ZERO_HASH_HEX,

timeliness: false,

...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge},

...overrides,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): {
unrealizedFinalizedEpoch: 0,
unrealizedFinalizedRoot: ZERO_HASH_HEX,

timeliness: false,

...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge},
};

Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/options/beaconNodeOptions/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ChainArgs = {
// as this is defined as part of BeaconPaths
// "chain.persistInvalidSszObjectsDir": string;
"chain.proposerBoostEnabled"?: boolean;
"chain.proposerBoostReorgEnabled"?: boolean;
"chain.disableImportExecutionFcU"?: boolean;
"chain.preaggregateSlotDistance"?: number;
"chain.attDataCacheSlotDistance"?: number;
Expand Down Expand Up @@ -40,6 +41,7 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
persistInvalidSszObjectsDir: undefined as any,
proposerBoostEnabled: args["chain.proposerBoostEnabled"],
proposerBoostReorgEnabled: args["chain.proposerBoostReorgEnabled"],
disableImportExecutionFcU: args["chain.disableImportExecutionFcU"],
preaggregateSlotDistance: args["chain.preaggregateSlotDistance"],
attDataCacheSlotDistance: args["chain.attDataCacheSlotDistance"],
Expand Down Expand Up @@ -122,6 +124,14 @@ Will double processing times. Use only for debugging purposes.",
group: "chain",
},

"chain.proposerBoostReorgEnabled": {
hidden: true,
type: "boolean",
description: "Enable proposer boost reorg to reorg out a late block",
defaultDescription: String(defaultOptions.chain.proposerBoostReorgEnabled),
group: "chain",
},

"chain.disableImportExecutionFcU": {
hidden: true,
type: "boolean",
Expand Down
Loading
Loading