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 all 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
50 changes: 39 additions & 11 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,20 @@ export function getValidatorApi({
{
skipHeadChecksAndUpdate,
commonBlockBody,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {
skipHeadChecksAndUpdate?: boolean;
commonBlockBody?: CommonBlockBody;
} = {}
parentBlockRoot: inParentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> &
(
| {
skipHeadChecksAndUpdate: true;
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
| {
skipHeadChecksAndUpdate?: false | undefined;
commonBlockBody?: undefined;
parentBlockRoot?: undefined;
}
) = {}
): Promise<routes.validator.ProduceBlindedBlockRes> {
const version = config.getForkName(slot);
if (!isForkExecution(version)) {
Expand All @@ -344,6 +354,7 @@ export function getValidatorApi({
throw Error("Execution builder disabled");
}

let parentBlockRoot: Root;
if (skipHeadChecksAndUpdate !== true) {
notWhileSyncing();
await waitForSlot(slot); // Must never request for a future slot > currentSlot
Expand All @@ -352,14 +363,17 @@ export function getValidatorApi({
// forkChoice.updateTime() might have already been called by the onSlot clock
// handler, in which case this should just return.
chain.forkChoice.updateTime(slot);
chain.recomputeForkChoiceHead();
parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot);
} else {
parentBlockRoot = inParentBlockRoot;
}

let timer;
try {
timer = metrics?.blockProductionTime.startTimer();
const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlindedBlock({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
commonBlockBody,
Expand Down Expand Up @@ -393,14 +407,21 @@ export function getValidatorApi({
strictFeeRecipientCheck,
skipHeadChecksAndUpdate,
commonBlockBody,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {
skipHeadChecksAndUpdate?: boolean;
commonBlockBody?: CommonBlockBody;
} = {}
parentBlockRoot: inParentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> &
(
| {
skipHeadChecksAndUpdate: true;
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
| {skipHeadChecksAndUpdate?: false | undefined; commonBlockBody?: undefined; parentBlockRoot?: undefined}
) = {}
): Promise<routes.validator.ProduceBlockOrContentsRes & {shouldOverrideBuilder?: boolean}> {
const source = ProducedBlockSource.engine;
metrics?.blockProductionRequests.inc({source});

let parentBlockRoot: Root;
if (skipHeadChecksAndUpdate !== true) {
notWhileSyncing();
await waitForSlot(slot); // Must never request for a future slot > currentSlot
Expand All @@ -409,14 +430,17 @@ export function getValidatorApi({
// forkChoice.updateTime() might have already been called by the onSlot clock
// handler, in which case this should just return.
chain.forkChoice.updateTime(slot);
chain.recomputeForkChoiceHead();
parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot);
} else {
parentBlockRoot = inParentBlockRoot;
}

let timer;
try {
timer = metrics?.blockProductionTime.startTimer();
const {block, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder} = await chain.produceBlock({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
feeRecipient,
Expand Down Expand Up @@ -484,7 +508,6 @@ export function getValidatorApi({
// forkChoice.updateTime() might have already been called by the onSlot clock
// handler, in which case this should just return.
chain.forkChoice.updateTime(slot);
chain.recomputeForkChoiceHead();

const fork = config.getForkName(slot);
// set some sensible opts
Expand Down Expand Up @@ -529,8 +552,11 @@ export function getValidatorApi({
};

logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext);
const parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot);

const commonBlockBody = await chain.produceCommonBlockBody({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
});
Expand All @@ -555,6 +581,7 @@ export function getValidatorApi({
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
commonBlockBody,
parentBlockRoot,
})
: Promise.reject(new Error("Builder disabled"));

Expand All @@ -565,6 +592,7 @@ export function getValidatorApi({
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
commonBlockBody,
parentBlockRoot,
}).then((engineBlock) => {
// Once the engine returns a block, in the event of either:
// - suspected builder censorship
Expand Down
66 changes: 52 additions & 14 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 @@ -475,22 +483,19 @@ export class BeaconChain implements IBeaconChain {
}

async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody> {
const {slot} = blockAttributes;
const head = this.forkChoice.getHead();
const {slot, parentBlockRoot} = blockAttributes;
const state = await this.regen.getBlockSlotState(
head.blockRoot,
toHexString(parentBlockRoot),
slot,
{dontTransferCache: true},
RegenCaller.produceBlock
);
const parentBlockRoot = fromHexString(head.blockRoot);

// TODO: To avoid breaking changes for metric define this attribute
const blockType = BlockType.Full;

return produceCommonBlockBody.call(this, blockType, state, {
...blockAttributes,
parentBlockRoot,
parentSlot: slot - 1,
});
}
Expand All @@ -514,21 +519,26 @@ export class BeaconChain implements IBeaconChain {

async produceBlockWrapper<T extends BlockType>(
blockType: T,
{randaoReveal, graffiti, slot, feeRecipient, commonBlockBody}: BlockAttributes & {commonBlockBody?: CommonBlockBody}
{
randaoReveal,
graffiti,
slot,
feeRecipient,
commonBlockBody,
parentBlockRoot,
}: BlockAttributes & {commonBlockBody?: CommonBlockBody}
): Promise<{
block: AssembledBlockType<T>;
executionPayloadValue: Wei;
consensusBlockValue: Wei;
shouldOverrideBuilder?: boolean;
}> {
const head = this.forkChoice.getHead();
const state = await this.regen.getBlockSlotState(
head.blockRoot,
toHexString(parentBlockRoot),
slot,
{dontTransferCache: true},
RegenCaller.produceBlock
);
const parentBlockRoot = fromHexString(head.blockRoot);
const proposerIndex = state.epochCtx.getBeaconProposer(slot);
const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes();

Expand Down Expand Up @@ -648,10 +658,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
19 changes: 15 additions & 4 deletions packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ForkChoiceStore,
ExecutionStatus,
JustifiedBalancesGetter,
ForkChoiceOpts,
ForkChoiceOpts as RawForkChoiceOpts,
} from "@lodestar/fork-choice";
import {
CachedBeaconStateAllForks,
Expand All @@ -16,12 +16,16 @@ 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";
import {GENESIS_SLOT} from "../../constants/index.js";

export type {ForkChoiceOpts};
export type ForkChoiceOpts = RawForkChoiceOpts & {
// for testing only
forkchoiceConstructor?: typeof ForkChoice;
};

/**
* Fork Choice extended with a ChainEventEmitter
Expand All @@ -32,7 +36,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 All @@ -47,7 +52,11 @@ export function initializeForkChoice(

const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(state);

return new ForkChoice(
// forkchoiceConstructor is only used for some test cases
// production code use ForkChoice constructor directly
const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice;

return new forkchoiceConstructor(
config,

new ForkChoiceStore(
Expand All @@ -68,6 +77,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 +99,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
Loading
Loading