Skip to content

Commit

Permalink
fix: compute and get head once when producing block (#6617)
Browse files Browse the repository at this point in the history
* fix: compute and get head once when producing block

* fix: unit test type

* chore: skip n-historical state e2e test
  • Loading branch information
twoeths authored Apr 5, 2024
1 parent e202592 commit 898cd90
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 23 deletions.
49 changes: 38 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.recomputeForkChoiceHead().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.recomputeForkChoiceHead().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,7 @@ 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 parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot);

const fork = config.getForkName(slot);
// set some sensible opts
Expand Down Expand Up @@ -531,6 +555,7 @@ export function getValidatorApi({
logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext);
const commonBlockBody = await chain.produceCommonBlockBody({
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
});
Expand All @@ -555,6 +580,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 +591,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
20 changes: 11 additions & 9 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,22 +516,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 @@ -555,21 +552,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
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export type BlockAttributes = {
randaoReveal: BLSSignature;
graffiti: Bytes32;
slot: Slot;
parentBlockRoot: Root;
feeRecipient?: string;
};

Expand Down Expand Up @@ -95,7 +96,6 @@ export async function produceBlockBody<T extends BlockType>(
currentState: CachedBeaconStateAllForks,
blockAttr: BlockAttributes & {
parentSlot: Slot;
parentBlockRoot: Root;
proposerIndex: ValidatorIndex;
proposerPubKey: BLSPubkey;
commonBlockBody?: CommonBlockBody;
Expand Down Expand Up @@ -580,7 +580,6 @@ export async function produceCommonBlockBody<T extends BlockType>(
parentBlockRoot,
}: BlockAttributes & {
parentSlot: Slot;
parentBlockRoot: Root;
}
): Promise<CommonBlockBody> {
const stepsMetrics =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ describe(
// chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted
numEpochsPersisted: 4,
// chain is NOT finalized end of test
skip: true,
},
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {fromHexString} from "@chainsafe/ssz";
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {describe, it, expect, beforeEach, afterEach, vi} from "vitest";
import {ssz} from "@lodestar/types";
import {ProtoBlock} from "@lodestar/fork-choice";
Expand Down Expand Up @@ -43,9 +43,13 @@ describe("api/validator - produceBlockV2", function () {
// Set the node's state to way back from current slot
const slot = 100000;
const randaoReveal = fullBlock.body.randaoReveal;
const parentBlockRoot = fullBlock.parentRoot;
const graffiti = "a".repeat(32);
const feeRecipient = "0xcccccccccccccccccccccccccccccccccccccccc";

modules.chain.recomputeForkChoiceHead.mockReturnValue(
generateProtoBlock({blockRoot: toHexString(parentBlockRoot)})
);
modules.chain.produceBlock.mockResolvedValue({
block: fullBlock,
executionPayloadValue,
Expand All @@ -58,6 +62,7 @@ describe("api/validator - produceBlockV2", function () {
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
slot,
parentBlockRoot,
feeRecipient,
});

Expand All @@ -68,6 +73,7 @@ describe("api/validator - produceBlockV2", function () {
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
slot,
parentBlockRoot,
feeRecipient: undefined,
});
});
Expand All @@ -83,6 +89,7 @@ describe("api/validator - produceBlockV2", function () {
const headSlot = 0;
modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: headSlot}));

modules.chain.recomputeForkChoiceHead.mockReturnValue(generateProtoBlock({slot: headSlot}));
modules.chain["opPool"].getSlashingsAndExits.mockReturnValue([[], [], [], []]);
modules.chain["aggregatedAttestationPool"].getAttestationsForBlock.mockReturnValue([]);
modules.chain["eth1"].getEth1DataAndDeposits.mockResolvedValue({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {describe, it, expect, beforeEach, afterEach, vi} from "vitest";
import {toHexString} from "@chainsafe/ssz";
import {ssz} from "@lodestar/types";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {routes} from "@lodestar/api";
import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {ProtoBlock} from "@lodestar/fork-choice";
import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js";
import {SyncState} from "../../../../../src/sync/interface.js";
import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js";
Expand Down Expand Up @@ -82,6 +84,9 @@ describe("api/validator - produceBlockV3", function () {

vi.spyOn(modules.chain.clock, "currentSlot", "get").mockReturnValue(currentSlot);
vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced);
modules.chain.recomputeForkChoiceHead.mockReturnValue({
blockRoot: toHexString(fullBlock.parentRoot),
} as ProtoBlock);

if (enginePayloadValue !== null) {
const commonBlockBody: CommonBlockBody = {
Expand Down

0 comments on commit 898cd90

Please sign in to comment.