Skip to content

Commit

Permalink
Update the beacon block production for phase0
Browse files Browse the repository at this point in the history
  • Loading branch information
nazarhussain committed Jan 9, 2024
1 parent ea49409 commit 9cb7edf
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 75 deletions.
27 changes: 24 additions & 3 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,11 @@ export function getValidatorApi({
// as of now fee recipient checks can not be performed because builder does not return bid recipient
{
skipHeadChecksAndUpdate,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {skipHeadChecksAndUpdate?: boolean} = {}
phase0BlockBody,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {
skipHeadChecksAndUpdate?: boolean;
phase0BlockBody?: phase0.BeaconBlockBody;
} = {}
): Promise<routes.validator.ProduceBlindedBlockRes> {
const version = config.getForkName(slot);
if (!isForkExecution(version)) {
Expand Down Expand Up @@ -323,6 +327,7 @@ export function getValidatorApi({
slot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
phase0BlockBody,
});

metrics?.blockProductionSuccess.inc({source});
Expand Down Expand Up @@ -352,7 +357,11 @@ export function getValidatorApi({
feeRecipient,
strictFeeRecipientCheck,
skipHeadChecksAndUpdate,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {skipHeadChecksAndUpdate?: boolean} = {}
phase0BlockBody,
}: Omit<routes.validator.ExtraProduceBlockOps, "builderSelection"> & {
skipHeadChecksAndUpdate?: boolean;
phase0BlockBody?: phase0.BeaconBlockBody;
} = {}
): Promise<routes.validator.ProduceBlockOrContentsRes> {
const source = ProducedBlockSource.engine;
metrics?.blockProductionRequests.inc({source});
Expand All @@ -376,6 +385,7 @@ export function getValidatorApi({
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
feeRecipient,
phase0BlockBody,
});
const version = config.getForkName(block.slot);
if (strictFeeRecipientCheck && feeRecipient && isForkExecution(version)) {
Expand Down Expand Up @@ -455,22 +465,32 @@ export function getValidatorApi({
chain.executionBuilder !== undefined &&
builderSelection !== routes.validator.BuilderSelection.ExecutionOnly;

logger.verbose("Assembling block with produceEngineOrBuilderBlock ", {
const loggerContext = {
fork,
builderSelection,
slot,
isBuilderEnabled,
strictFeeRecipientCheck,
// winston logger doesn't like bigint
builderBoostFactor: `${builderBoostFactor}`,
};

logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext);
const beaconBlockBodyPhase0 = await chain.produceBlockBodyPhase0({
slot,
randaoReveal,
graffiti: toGraffitiBuffer(graffiti || ""),
});
logger.debug("Produced phase 0 block body", loggerContext);

// Start calls for building execution and builder blocks
const blindedBlockPromise = isBuilderEnabled
? // can't do fee recipient checks as builder bid doesn't return feeRecipient as of now
produceBuilderBlindedBlock(slot, randaoReveal, graffiti, {
feeRecipient,
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
phase0BlockBody: beaconBlockBodyPhase0,
}).catch((e) => {
logger.error("produceBuilderBlindedBlock failed to produce block", {slot}, e);
return null;
Expand All @@ -493,6 +513,7 @@ export function getValidatorApi({
strictFeeRecipientCheck,
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
phase0BlockBody: beaconBlockBodyPhase0,
}).catch((e) => {
logger.error("produceEngineFullBlockOrContents failed to produce block", {slot}, e);
return null;
Expand Down
35 changes: 30 additions & 5 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
import {BeaconProposerCache} from "./beaconProposerCache.js";
import {CheckpointBalancesCache} from "./balancesCache.js";
import {AssembledBlockType, BlobsResultType, BlockType} from "./produceBlock/index.js";
import {BlockAttributes, produceBlockBody} from "./produceBlock/produceBlockBody.js";
import {BlockAttributes, produceBlockBody, produceBlockBodyPhase0} from "./produceBlock/produceBlockBody.js";
import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
import {BlockInput} from "./blocks/types.js";
import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
Expand Down Expand Up @@ -463,28 +463,52 @@ export class BeaconChain implements IBeaconChain {
return {block: data, executionOptimistic: isOptimisticBlock(block)};
}
// If block is not found in hot db, try cold db since there could be an archive cycle happening
// TODO: Add a lock to the archiver to have determinstic behaviour on where are blocks
// TODO: Add a lock to the archiver to have deterministic behavior on where are blocks
}

const data = await this.db.blockArchive.getByRoot(fromHexString(root));
return data && {block: data, executionOptimistic: false};
}

async produceBlockBodyPhase0(
blockAttributes: Pick<BlockAttributes, "slot" | "graffiti" | "randaoReveal">
): Promise<phase0.BeaconBlockBody> {
const {slot} = blockAttributes;
const head = this.forkChoice.getHead();
const state = await this.regen.getBlockSlotState(
head.blockRoot,
slot,
{dontTransferCache: true},
RegenCaller.produceBlock
);

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

return produceBlockBodyPhase0.call(this, blockType, state, blockAttributes);
}

produceBlock(
blockAttributes: BlockAttributes
blockAttributes: BlockAttributes & {phase0BlockBody?: phase0.BeaconBlockBody}
): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
return this.produceBlockWrapper<BlockType.Full>(BlockType.Full, blockAttributes);
}

produceBlindedBlock(
blockAttributes: BlockAttributes
blockAttributes: BlockAttributes & {phase0BlockBody?: phase0.BeaconBlockBody}
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
return this.produceBlockWrapper<BlockType.Blinded>(BlockType.Blinded, blockAttributes);
}

async produceBlockWrapper<T extends BlockType>(
blockType: T,
{randaoReveal, graffiti, slot, feeRecipient}: BlockAttributes
{
randaoReveal,
graffiti,
slot,
feeRecipient,
phase0BlockBody,
}: BlockAttributes & {phase0BlockBody?: phase0.BeaconBlockBody}
): Promise<{block: AssembledBlockType<T>; executionPayloadValue: Wei; consensusBlockValue: Gwei}> {
const head = this.forkChoice.getHead();
const state = await this.regen.getBlockSlotState(
Expand All @@ -506,6 +530,7 @@ export class BeaconChain implements IBeaconChain {
parentBlockRoot,
proposerIndex,
proposerPubKey,
phase0BlockBody,
});

// The hashtree root computed here for debug log will get cached and hence won't introduce additional delays
Expand Down
5 changes: 3 additions & 2 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,12 @@ export interface IBeaconChain {

getContents(beaconBlock: deneb.BeaconBlock): deneb.Contents;

produceBlockBodyPhase0(blockAttributes: BlockAttributes): Promise<phase0.BeaconBlockBody>;
produceBlock(
blockAttributes: BlockAttributes
blockAttributes: BlockAttributes & {phase0BlockBody?: phase0.BeaconBlockBody}
): Promise<{block: allForks.BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}>;
produceBlindedBlock(
blockAttributes: BlockAttributes
blockAttributes: BlockAttributes & {phase0BlockBody?: phase0.BeaconBlockBody}
): Promise<{block: allForks.BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Gwei}>;

/** Process a block until complete */
Expand Down
50 changes: 29 additions & 21 deletions packages/beacon-node/src/chain/opPools/opPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ export class OpPool {
});
}

getBlsToExecutionChanges(
state: CachedBeaconStateAllForks,
blockType: BlockType,
metrics: Metrics | null
): capella.SignedBLSToExecutionChange[] {
const stepsMetrics =
blockType === BlockType.Full
? metrics?.executionBlockProductionTimeSteps
: metrics?.builderBlockProductionTimeSteps;

const endBlsToExecutionChanges = stepsMetrics?.startTimer();
const blsToExecutionChanges: capella.SignedBLSToExecutionChange[] = [];
for (const blsToExecutionChange of this.blsToExecutionChanges.values()) {
if (isValidBlsToExecutionChangeForBlockInclusion(state, blsToExecutionChange.data)) {
blsToExecutionChanges.push(blsToExecutionChange.data);
if (blsToExecutionChanges.length >= MAX_BLS_TO_EXECUTION_CHANGES) {
break;
}
}
}
endBlsToExecutionChanges?.({
step: BlockProductionStep.blsToExecutionChanges,
});

return blsToExecutionChanges;
}

/**
* Get proposer and attester slashings and voluntary exits and bls to execution change for inclusion in a block.
*
Expand All @@ -172,12 +199,7 @@ export class OpPool {
state: CachedBeaconStateAllForks,
blockType: BlockType,
metrics: Metrics | null
): [
phase0.AttesterSlashing[],
phase0.ProposerSlashing[],
phase0.SignedVoluntaryExit[],
capella.SignedBLSToExecutionChange[],
] {
): [phase0.AttesterSlashing[], phase0.ProposerSlashing[], phase0.SignedVoluntaryExit[]] {
const {config} = state;
const stateEpoch = computeEpochAtSlot(state.slot);
const stateFork = config.getForkName(state.slot);
Expand Down Expand Up @@ -261,21 +283,7 @@ export class OpPool {
step: BlockProductionStep.voluntaryExits,
});

const endBlsToExecutionChanges = stepsMetrics?.startTimer();
const blsToExecutionChanges: capella.SignedBLSToExecutionChange[] = [];
for (const blsToExecutionChange of this.blsToExecutionChanges.values()) {
if (isValidBlsToExecutionChangeForBlockInclusion(state, blsToExecutionChange.data)) {
blsToExecutionChanges.push(blsToExecutionChange.data);
if (blsToExecutionChanges.length >= MAX_BLS_TO_EXECUTION_CHANGES) {
break;
}
}
}
endBlsToExecutionChanges?.({
step: BlockProductionStep.blsToExecutionChanges,
});

return [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges];
return [attesterSlashings, proposerSlashings, voluntaryExits];
}

/** For beacon pool API */
Expand Down
103 changes: 61 additions & 42 deletions packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,23 @@ export async function produceBlockBody<T extends BlockType>(
this: BeaconChain,
blockType: T,
currentState: CachedBeaconStateAllForks,
{
randaoReveal,
graffiti,
slot: blockSlot,
feeRecipient: requestedFeeRecipient,
parentSlot,
parentBlockRoot,
proposerIndex,
proposerPubKey,
}: BlockAttributes & {
blockAttr: BlockAttributes & {
parentSlot: Slot;
parentBlockRoot: Root;
proposerIndex: ValidatorIndex;
proposerPubKey: BLSPubkey;
phase0BlockBody?: phase0.BeaconBlockBody;
}
): Promise<{body: AssembledBodyType<T>; blobs: BlobsResult; executionPayloadValue: Wei}> {
const {
slot: blockSlot,
feeRecipient: requestedFeeRecipient,
parentSlot,
parentBlockRoot,
proposerIndex,
proposerPubKey,
phase0BlockBody,
} = blockAttr;
// Type-safe for blobs variable. Translate 'null' value into 'preDeneb' enum
// TODO: Not ideal, but better than just using null.
// TODO: Does not guarantee that preDeneb enum goes with a preDeneb block
Expand All @@ -123,6 +124,10 @@ export async function produceBlockBody<T extends BlockType>(
slot: blockSlot,
};
this.logger.verbose("Producing beacon block body", logMeta);
const stepsMetrics =
blockType === BlockType.Full
? this.metrics?.executionBlockProductionTimeSteps
: this.metrics?.builderBlockProductionTimeSteps;

// TODO:
// Iterate through the naive aggregation pool and ensure all the attestations from there
Expand All @@ -135,38 +140,9 @@ export async function produceBlockBody<T extends BlockType>(
// logger.error("Attestation did not transfer to op pool", {}, e);
// }
// }

const stepsMetrics =
blockType === BlockType.Full
? this.metrics?.executionBlockProductionTimeSteps
: this.metrics?.builderBlockProductionTimeSteps;

const [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges] =
this.opPool.getSlashingsAndExits(currentState, blockType, this.metrics);

const endAttestations = stepsMetrics?.startTimer();
const attestations = this.aggregatedAttestationPool.getAttestationsForBlock(this.forkChoice, currentState);
endAttestations?.({
step: BlockProductionStep.attestations,
});

const endEth1DataAndDeposits = stepsMetrics?.startTimer();
const {eth1Data, deposits} = await this.eth1.getEth1DataAndDeposits(currentState);
endEth1DataAndDeposits?.({
step: BlockProductionStep.eth1DataAndDeposits,
});

const blockBody: phase0.BeaconBlockBody = {
randaoReveal,
graffiti,
eth1Data,
proposerSlashings,
attesterSlashings,
attestations,
deposits,
voluntaryExits,
};

const blockBody = phase0BlockBody ?? (await produceBlockBodyPhase0.call(this, blockType, currentState, blockAttr));
const blsToExecutionChanges = this.opPool.getBlsToExecutionChanges(currentState, blockType, this.metrics);
const {attestations, deposits, voluntaryExits, attesterSlashings, proposerSlashings} = blockBody;
const blockEpoch = computeEpochAtSlot(blockSlot);

const endSyncAggregate = stepsMetrics?.startTimer();
Expand Down Expand Up @@ -607,3 +583,46 @@ function preparePayloadAttributes(
}

/** process_sync_committee_contributions is implemented in syncCommitteeContribution.getSyncAggregate */

export async function produceBlockBodyPhase0<T extends BlockType>(
this: BeaconChain,
blockType: T,
currentState: CachedBeaconStateAllForks,
{randaoReveal, graffiti}: Pick<BlockAttributes, "randaoReveal" | "graffiti">
): Promise<phase0.BeaconBlockBody> {
const stepsMetrics =
blockType === BlockType.Full
? this.metrics?.executionBlockProductionTimeSteps
: this.metrics?.builderBlockProductionTimeSteps;

const [attesterSlashings, proposerSlashings, voluntaryExits] = this.opPool.getSlashingsAndExits(
currentState,
blockType,
this.metrics
);

const endAttestations = stepsMetrics?.startTimer();
const attestations = this.aggregatedAttestationPool.getAttestationsForBlock(this.forkChoice, currentState);
endAttestations?.({
step: BlockProductionStep.attestations,
});

const endEth1DataAndDeposits = stepsMetrics?.startTimer();
const {eth1Data, deposits} = await this.eth1.getEth1DataAndDeposits(currentState);
endEth1DataAndDeposits?.({
step: BlockProductionStep.eth1DataAndDeposits,
});

const blockBody: phase0.BeaconBlockBody = {
randaoReveal,
graffiti,
eth1Data,
proposerSlashings,
attesterSlashings,
attestations,
deposits,
voluntaryExits,
};

return blockBody;
}
1 change: 1 addition & 0 deletions packages/beacon-node/test/__mocks__/mockedBeaconChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ vi.mock("../../src/chain/index.js", async (requireActual) => {
// @ts-expect-error
beaconProposerCache: new BeaconProposerCache(),
shufflingCache: new ShufflingCache(),
produceBlockBodyPhase0: vi.fn(),
produceBlock: vi.fn(),
produceBlindedBlock: vi.fn(),
getCanonicalBlockAtSlot: vi.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe("api/validator - produceBlockV2", function () {
const headSlot = 0;
forkChoiceStub.getHead.mockReturnValue(generateProtoBlock({slot: headSlot}));

opPoolStub.getSlashingsAndExits.mockReturnValue([[], [], [], []]);
opPoolStub.getSlashingsAndExits.mockReturnValue([[], [], []]);
aggregatedAttestationPoolStub.getAttestationsForBlock.mockReturnValue([]);
eth1Stub.getEth1DataAndDeposits.mockResolvedValue({eth1Data: ssz.phase0.Eth1Data.defaultValue(), deposits: []});
forkChoiceStub.getJustifiedBlock.mockReturnValue({} as ProtoBlock);
Expand Down
Loading

0 comments on commit 9cb7edf

Please sign in to comment.