UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

593 lines • 31.8 kB
import { getSafeExecutionBlockHash } from "@lodestar/fork-choice"; import { BUILDER_INDEX_SELF_BUILD, ForkSeq, isForkPostAltair, isForkPostBellatrix, isForkPostGloas, } from "@lodestar/params"; import { G2_POINT_AT_INFINITY, computeTimeAtSlot, isStatePostBellatrix, isStatePostCapella, isStatePostGloas, } from "@lodestar/state-transition"; import { ssz, } from "@lodestar/types"; import { byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex } from "@lodestar/utils"; import { ZERO_HASH_HEX } from "../../constants/index.js"; import { numToQuantity } from "../../execution/engine/utils.js"; import { getExpectedGasLimit, } from "../../execution/index.js"; import { fromGraffitiBytes } from "../../util/graffiti.js"; import { kzg } from "../../util/kzg.js"; import { validateBlobsAndKzgCommitments, validateCellsAndKzgCommitments } from "./validateBlobsAndKzgCommitments.js"; // Time to provide the EL to generate a payload from new payload id const PAYLOAD_GENERATION_TIME_MS = 500; export { PayloadPreparationType }; var PayloadPreparationType; (function (PayloadPreparationType) { PayloadPreparationType["Fresh"] = "Fresh"; PayloadPreparationType["Cached"] = "Cached"; PayloadPreparationType["Reorged"] = "Reorged"; PayloadPreparationType["Blinded"] = "Blinded"; })(PayloadPreparationType || (PayloadPreparationType = {})); export { BlockProductionStep }; /** * Block production steps tracked in metrics */ var BlockProductionStep; (function (BlockProductionStep) { BlockProductionStep["proposerSlashing"] = "proposerSlashing"; BlockProductionStep["attesterSlashings"] = "attesterSlashings"; BlockProductionStep["voluntaryExits"] = "voluntaryExits"; BlockProductionStep["blsToExecutionChanges"] = "blsToExecutionChanges"; BlockProductionStep["attestations"] = "attestations"; BlockProductionStep["syncAggregate"] = "syncAggregate"; BlockProductionStep["executionPayload"] = "executionPayload"; })(BlockProductionStep || (BlockProductionStep = {})); export { BlockType }; var BlockType; (function (BlockType) { BlockType["Full"] = "Full"; BlockType["Blinded"] = "Blinded"; })(BlockType || (BlockType = {})); export async function produceBlockBody(blockType, currentState, blockAttr) { const { slot: blockSlot, feeRecipient: requestedFeeRecipient, parentBlock, proposerIndex, proposerPubKey, commonBlockBodyPromise, } = blockAttr; let executionPayloadValue; let blockBody; const parentBlockRoot = fromHex(parentBlock.blockRoot); // even though shouldOverrideBuilder is relevant for the engine response, for simplicity of typing // we just return it undefined for the builder which anyway doesn't get consumed downstream let shouldOverrideBuilder; const fork = this.config.getForkName(blockSlot); const produceResult = { type: blockType, fork, }; const logMeta = { fork, blockType, slot: blockSlot, }; this.logger.verbose("Producing beacon block body", logMeta); if (isForkPostGloas(fork)) { if (!isStatePostGloas(currentState)) { throw new Error("Expected Gloas state for Gloas block production"); } // TODO GLOAS: support non self-building here, the block type differentiation between // full and blinded no longer makes sense in gloas, it might be a good idea to move // this into a completely separate function and have pre/post gloas more separated const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice); const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX; const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex); const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer(); this.logger.verbose("Preparing execution payload from engine", { slot: blockSlot, parentBlockRoot: toRootHex(parentBlockRoot), feeRecipient, }); // Get execution payload from EL let parentBlockHash; let parentExecutionRequests; // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below let stateAfterParentPayload = currentState; const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot)); if (isExtendingPayload) { parentBlockHash = currentState.latestExecutionPayloadBid.blockHash; parentExecutionRequests = await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot); stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests); } else { parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash; parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue(); } const prepareRes = await prepareExecutionPayload(this, this.logger, fork, parentBlockRoot, parentBlockHash, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, stateAfterParentPayload, feeRecipient); const { prepType, payloadId } = prepareRes; Object.assign(logMeta, { executionPayloadPrepType: prepType }); if (prepType !== PayloadPreparationType.Cached) { await sleep(PAYLOAD_GENERATION_TIME_MS); } this.logger.verbose("Fetching execution payload from engine", { slot: blockSlot, payloadId }); const payloadRes = await this.executionEngine.getPayload(fork, payloadId); endExecutionPayload?.({ step: BlockProductionStep.executionPayload }); const { executionPayload, blobsBundle, executionRequests } = payloadRes; executionPayloadValue = payloadRes.executionPayloadValue; shouldOverrideBuilder = payloadRes.shouldOverrideBuilder; if (blobsBundle === undefined) { throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`); } if (executionRequests === undefined) { throw Error(`Missing executionRequests response from getPayload at fork=${fork}`); } const cells = blobsBundle.blobs.map((blob) => kzg.computeCells(blob)); if (this.opts.sanityCheckExecutionEngineBlobs) { await validateCellsAndKzgCommitments(blobsBundle.commitments, blobsBundle.proofs, cells); } // Create self-build execution payload bid const bid = { parentBlockHash, parentBlockRoot, blockHash: executionPayload.blockHash, prevRandao: currentState.getRandaoMix(currentState.epoch), feeRecipient: executionPayload.feeRecipient, gasLimit: BigInt(executionPayload.gasLimit), builderIndex: BUILDER_INDEX_SELF_BUILD, slot: blockSlot, value: 0, executionPayment: 0, blobKzgCommitments: blobsBundle.commitments, executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests), }; const signedBid = { message: bid, signature: G2_POINT_AT_INFINITY, }; const commonBlockBody = await commonBlockBodyPromise; const gloasBody = Object.assign({}, commonBlockBody); gloasBody.signedExecutionPayloadBid = signedBid; gloasBody.payloadAttestations = this.payloadAttestationPool.getPayloadAttestationsForBlock(parentBlock.blockRoot, blockSlot - 1); gloasBody.parentExecutionRequests = parentExecutionRequests; // Drop voluntary exits that parent_execution_requests have invalidated (e.g. a withdrawal // request initiating an exit on the same validator). Op pool selected against the unapplied // state, so re-validate against the post-apply state to avoid producing an invalid block. if (isExtendingPayload && commonBlockBody.voluntaryExits.length > 0) { gloasBody.voluntaryExits = commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) => stateAfterParentPayload.isValidVoluntaryExit(signedVoluntaryExit, false)); } blockBody = gloasBody; // Store execution payload data required to construct execution payload envelope later const gloasResult = produceResult; gloasResult.executionPayload = executionPayload; gloasResult.executionRequests = executionRequests; gloasResult.blobsBundle = blobsBundle; gloasResult.cells = cells; gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot); const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); this.metrics?.blockPayload.payloadFetchedTime.observe({ prepType }, fetchedTime); this.logger.verbose("Produced block with self-build bid", { slot: blockSlot, executionPayloadValue, prepType, payloadId, fetchedTime, executionBlockHash: toRootHex(executionPayload.blockHash), blobs: blobsBundle.commitments.length, }); Object.assign(logMeta, { transactions: executionPayload.transactions.length, blobs: blobsBundle.commitments.length, shouldOverrideBuilder, }); } else if (isForkPostBellatrix(fork)) { if (!isStatePostBellatrix(currentState)) { throw new Error("Expected Bellatrix state for execution block production"); } const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice); const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX; const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex); const feeRecipientType = requestedFeeRecipient ? "requested" : this.beaconProposerCache.get(proposerIndex) ? "cached" : "default"; Object.assign(logMeta, { feeRecipientType, feeRecipient }); if (blockType === BlockType.Blinded) { if (!this.executionBuilder) throw Error("External builder not configured"); const executionBuilder = this.executionBuilder; const builderPromise = (async () => { const endExecutionPayloadHeader = this.metrics?.builderBlockProductionTimeSteps.startTimer(); // This path will not be used in the production, but is here just for merge mock // tests because merge-mock requires an fcU to be issued prior to fetch payload // header. if (executionBuilder.issueLocalFcUWithFeeRecipient !== undefined) { await prepareExecutionPayload(this, this.logger, fork, parentBlockRoot, currentState.latestExecutionPayloadHeader.blockHash, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, currentState, executionBuilder.issueLocalFcUWithFeeRecipient); } // For MeV boost integration, this is where the execution header will be // fetched from the payload id and a blinded block will be produced instead of // fullblock for the validator to sign this.logger.verbose("Fetching execution payload header from builder", { slot: blockSlot, proposerPubKey: toHex(proposerPubKey), }); const headerRes = await prepareExecutionPayloadHeader(this, fork, currentState, proposerPubKey); endExecutionPayloadHeader?.({ step: BlockProductionStep.executionPayload, }); return headerRes; })(); const [builderRes, commonBlockBody] = await Promise.all([builderPromise, commonBlockBodyPromise]); blockBody = Object.assign({}, commonBlockBody); blockBody.executionPayloadHeader = builderRes.header; executionPayloadValue = builderRes.executionPayloadValue; const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); const prepType = PayloadPreparationType.Blinded; this.metrics?.blockPayload.payloadFetchedTime.observe({ prepType }, fetchedTime); this.logger.verbose("Fetched execution payload header from builder", { slot: blockSlot, executionPayloadValue, prepType, fetchedTime, }); const targetGasLimit = executionBuilder.getValidatorRegistration(proposerPubKey)?.gasLimit; if (!targetGasLimit) { // This should only happen if cache was cleared due to restart of beacon node this.logger.warn("Failed to get validator registration, could not check header gas limit", { slot: blockSlot, proposerIndex, proposerPubKey: toPubkeyHex(proposerPubKey), }); } else { const headerGasLimit = builderRes.header.gasLimit; const parentGasLimit = currentState.latestExecutionPayloadHeader.gasLimit; const expectedGasLimit = getExpectedGasLimit(parentGasLimit, targetGasLimit); const lowerBound = Math.min(parentGasLimit, expectedGasLimit); const upperBound = Math.max(parentGasLimit, expectedGasLimit); if (headerGasLimit < lowerBound || headerGasLimit > upperBound) { throw Error(`Header gas limit ${headerGasLimit} is outside of acceptable range [${lowerBound}, ${upperBound}]`); } if (headerGasLimit !== expectedGasLimit) { this.logger.warn("Header gas limit does not match expected value", { slot: blockSlot, headerGasLimit, expectedGasLimit, parentGasLimit, targetGasLimit, }); } } if (ForkSeq[fork] >= ForkSeq.deneb) { const { blobKzgCommitments } = builderRes; if (blobKzgCommitments === undefined) { throw Error(`Invalid builder getHeader response for fork=${fork}, missing blobKzgCommitments`); } blockBody.blobKzgCommitments = blobKzgCommitments; Object.assign(logMeta, { blobs: blobKzgCommitments.length }); } if (ForkSeq[fork] >= ForkSeq.electra) { const { executionRequests } = builderRes; if (executionRequests === undefined) { throw Error(`Invalid builder getHeader response for fork=${fork}, missing executionRequests`); } blockBody.executionRequests = executionRequests; } } // blockType === BlockType.Full else { // enginePromise only supports pre-gloas const enginePromise = (async () => { const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer(); this.logger.verbose("Preparing execution payload from engine", { slot: blockSlot, parentBlockRoot: toRootHex(parentBlockRoot), feeRecipient, }); // https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/deneb/validator.md#constructing-the-beaconblockbody const prepareRes = await prepareExecutionPayload(this, this.logger, fork, parentBlockRoot, currentState.latestExecutionPayloadHeader.blockHash, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, currentState, feeRecipient); const { prepType, payloadId } = prepareRes; Object.assign(logMeta, { executionPayloadPrepType: prepType }); if (prepType !== PayloadPreparationType.Cached) { // Wait for 500ms to allow EL to add some txs to the payload // the pitfalls of this have been put forward here, but 500ms delay for block proposal // seems marginal even with unhealthy network // // See: https://discord.com/channels/595666850260713488/892088344438255616/1009882079632314469 await sleep(PAYLOAD_GENERATION_TIME_MS); } this.logger.verbose("Fetching execution payload from engine", { slot: blockSlot, payloadId }); const payloadRes = await this.executionEngine.getPayload(fork, payloadId); endExecutionPayload?.({ step: BlockProductionStep.executionPayload, }); return { ...prepareRes, ...payloadRes }; })().catch((e) => { this.metrics?.blockPayload.payloadFetchErrors.inc(); throw e; }); const [engineRes, commonBlockBody] = await Promise.all([enginePromise, commonBlockBodyPromise]); blockBody = Object.assign({}, commonBlockBody); { const { prepType, payloadId, executionPayload, blobsBundle, executionRequests } = engineRes; shouldOverrideBuilder = engineRes.shouldOverrideBuilder; blockBody.executionPayload = executionPayload; produceResult.executionPayload = executionPayload; executionPayloadValue = engineRes.executionPayloadValue; Object.assign(logMeta, { transactions: executionPayload.transactions.length, shouldOverrideBuilder }); const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); this.metrics?.blockPayload.payloadFetchedTime.observe({ prepType }, fetchedTime); this.logger.verbose("Fetched execution payload from engine", { slot: blockSlot, executionPayloadValue, prepType, payloadId, fetchedTime, executionHeadBlockHash: toRootHex(engineRes.executionPayload.blockHash), }); if (executionPayload.transactions.length === 0) { this.metrics?.blockPayload.emptyPayloads.inc({ prepType }); } if (ForkSeq[fork] >= ForkSeq.fulu) { if (blobsBundle === undefined) { throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`); } // NOTE: Even though the fulu.BlobsBundle type is superficially the same as deneb.BlobsBundle, it is NOT. // In fulu, proofs are _cell_ proofs, vs in deneb they are _blob_ proofs. const timer = this?.metrics?.peerDas.dataColumnSidecarComputationTime.startTimer(); const cells = blobsBundle.blobs.map((blob) => kzg.computeCells(blob)); timer?.(); if (this.opts.sanityCheckExecutionEngineBlobs) { const validationTimer = this.metrics?.peerDas.kzgVerificationDataColumnBatchTime.startTimer(); try { await validateCellsAndKzgCommitments(blobsBundle.commitments, blobsBundle.proofs, cells); } finally { validationTimer?.(); } } blockBody.blobKzgCommitments = blobsBundle.commitments; produceResult.blobsBundle = blobsBundle; produceResult.cells = cells; Object.assign(logMeta, { blobs: blobsBundle.commitments.length }); } else if (ForkSeq[fork] >= ForkSeq.deneb) { if (blobsBundle === undefined) { throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`); } if (this.opts.sanityCheckExecutionEngineBlobs) { await validateBlobsAndKzgCommitments(blobsBundle.commitments, blobsBundle.proofs, blobsBundle.blobs); } blockBody.blobKzgCommitments = blobsBundle.commitments; produceResult.blobsBundle = blobsBundle; Object.assign(logMeta, { blobs: blobsBundle.commitments.length }); } if (ForkSeq[fork] >= ForkSeq.electra) { if (executionRequests === undefined) { throw Error(`Missing executionRequests response from getPayload at fork=${fork}`); } blockBody.executionRequests = executionRequests; } } } } else { const commonBlockBody = await commonBlockBodyPromise; blockBody = Object.assign({}, commonBlockBody); executionPayloadValue = BigInt(0); } const { graffiti, attestations, deposits, voluntaryExits, attesterSlashings, proposerSlashings } = blockBody; Object.assign(logMeta, { graffiti: fromGraffitiBytes(graffiti), attestations: attestations.length, deposits: deposits.length, voluntaryExits: voluntaryExits.length, attesterSlashings: attesterSlashings.length, proposerSlashings: proposerSlashings.length, }); if (isForkPostAltair(fork)) { const { syncAggregate } = blockBody; Object.assign(logMeta, { syncAggregateParticipants: syncAggregate.syncCommitteeBits.getTrueBitIndexes().length, }); } if (ForkSeq[fork] >= ForkSeq.gloas) { const { blsToExecutionChanges, payloadAttestations } = blockBody; Object.assign(logMeta, { blsToExecutionChanges: blsToExecutionChanges.length, payloadAttestations: payloadAttestations.length, }); } else if (ForkSeq[fork] >= ForkSeq.capella) { const { blsToExecutionChanges, executionPayload } = blockBody; Object.assign(logMeta, { blsToExecutionChanges: blsToExecutionChanges.length, }); // withdrawals are only available in full body if (blockType === BlockType.Full) { Object.assign(logMeta, { withdrawals: executionPayload.withdrawals.length, }); } } Object.assign(logMeta, { executionPayloadValue }); this.logger.verbose("Produced beacon block body", logMeta); return { body: blockBody, produceResult, executionPayloadValue, shouldOverrideBuilder }; } /** * Produce ExecutionPayload for post-merge. */ export async function prepareExecutionPayload(chain, logger, fork, parentBlockRoot, parentBlockHash, safeBlockHash, finalizedBlockHash, /** * Post-gloas, when extending a full parent, callers must apply * parent execution payload first (see `withParentPayloadApplied`). */ state, suggestedFeeRecipient) { const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime); const prevRandao = state.getRandaoMix(state.epoch); const payloadIdCached = chain.executionEngine.payloadIdCache.get({ headBlockHash: toRootHex(parentBlockHash), finalizedBlockHash, timestamp: numToQuantity(timestamp), prevRandao: toHex(prevRandao), suggestedFeeRecipient, }); // prepareExecutionPayload will throw error via notifyForkchoiceUpdate if // the EL returns Syncing on this request to prepare a payload // TODO: Handle only this case, DO NOT put a generic try / catch that discards all errors let payloadId; let prepType; if (payloadIdCached) { payloadId = payloadIdCached; prepType = PayloadPreparationType.Cached; } else { // If there was a payload assigned to this timestamp, it would imply that there some sort // of payload reorg, i.e. head, fee recipient or any other fcu param changed if (chain.executionEngine.payloadIdCache.hasPayload({ timestamp: numToQuantity(timestamp) })) { prepType = PayloadPreparationType.Reorged; } else { prepType = PayloadPreparationType.Fresh; } const attributes = preparePayloadAttributes(fork, chain, { prepareState: state, prepareSlot: state.slot, parentBlockRoot, parentBlockHash, feeRecipient: suggestedFeeRecipient, }); payloadId = await chain.executionEngine.notifyForkchoiceUpdate(fork, toRootHex(parentBlockHash), safeBlockHash, finalizedBlockHash, attributes); logger.verbose("Prepared payload id from execution engine", { payloadId }); } // Should never happen, notifyForkchoiceUpdate() with payload attributes always // returns payloadId if (payloadId === null) { throw Error("notifyForkchoiceUpdate returned payloadId null"); } // We are only returning payloadId here because prepareExecutionPayload is also called from // prepareNextSlot, which is an advance call to execution engine to start building payload // Actual payload isn't produced till getPayload is called. return { payloadId, prepType }; } async function prepareExecutionPayloadHeader(chain, fork, state, proposerPubKey) { if (!chain.executionBuilder) { throw Error("executionBuilder required"); } const parentHash = state.latestExecutionPayloadHeader.blockHash; return chain.executionBuilder.getHeader(fork, state.slot, parentHash, proposerPubKey); } export function getPayloadAttributesForSSE(fork, chain, { prepareState, prepareSlot, parentBlockRoot, parentBlockHash, feeRecipient, }) { const payloadAttributes = preparePayloadAttributes(fork, chain, { prepareState, prepareSlot, parentBlockRoot, parentBlockHash, feeRecipient, }); let parentBlockNumber; if (isForkPostGloas(fork)) { const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(toRootHex(parentBlockRoot), toRootHex(parentBlockHash)); if (parentBlock?.executionPayloadBlockHash == null) { throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`); } parentBlockNumber = parentBlock.executionPayloadNumber; } else { parentBlockNumber = prepareState.payloadBlockNumber; } const ssePayloadAttributes = { proposerIndex: prepareState.getBeaconProposer(prepareSlot), proposalSlot: prepareSlot, parentBlockNumber, parentBlockRoot, parentBlockHash, payloadAttributes, }; return ssePayloadAttributes; } function preparePayloadAttributes(fork, chain, { prepareState, prepareSlot, parentBlockRoot, parentBlockHash, feeRecipient, }) { const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime); const prevRandao = prepareState.getRandaoMix(prepareState.epoch); const payloadAttributes = { timestamp, prevRandao, suggestedFeeRecipient: feeRecipient, }; if (ForkSeq[fork] >= ForkSeq.capella) { if (!isStatePostCapella(prepareState)) { throw new Error("Expected Capella state for withdrawals"); } if (isStatePostGloas(prepareState)) { const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash); if (isExtendingPayload) { // applyParentExecutionPayload sets latestBlockHash = parentBid.blockHash, so a mismatch // here means the caller did not apply parent payload to prepareState if (!byteArrayEquals(prepareState.latestBlockHash, prepareState.latestExecutionPayloadBid.blockHash)) { throw new Error("Expected state with parent execution payload applied for withdrawals"); } payloadAttributes.withdrawals = prepareState.getExpectedWithdrawals().expectedWithdrawals; } else { // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch // already deducted from CL balances but never credited on the EL (the envelope // was not delivered). The next payload must carry those same withdrawals to // restore CL/EL consistency, otherwise validators permanently lose that balance. payloadAttributes.withdrawals = prepareState.payloadExpectedWithdrawals; } } else { // withdrawals logic is now fork aware as it changes on electra fork post capella payloadAttributes.withdrawals = prepareState.getExpectedWithdrawals().expectedWithdrawals; } } if (ForkSeq[fork] >= ForkSeq.deneb) { payloadAttributes.parentBeaconBlockRoot = parentBlockRoot; } if (ForkSeq[fork] >= ForkSeq.gloas) { payloadAttributes.slotNumber = prepareSlot; } return payloadAttributes; } export async function produceCommonBlockBody(blockType, currentState, { randaoReveal, graffiti, slot, parentBlock }) { const stepsMetrics = blockType === BlockType.Full ? this.metrics?.executionBlockProductionTimeSteps : this.metrics?.builderBlockProductionTimeSteps; const fork = this.config.getForkName(slot); // TODO: // Iterate through the naive aggregation pool and ensure all the attestations from there // are included in the operation pool. // for (const attestation of db.attestationPool.getAll()) { // try { // opPool.insertAttestation(attestation); // } catch (e) { // // Don't stop block production if there's an error, just create a log. // logger.error("Attestation did not transfer to op pool", {}, e); // } // } const [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges] = this.opPool.getSlashingsAndExits(currentState, blockType, this.metrics); const endAttestations = stepsMetrics?.startTimer(); const attestations = this.aggregatedAttestationPool.getAttestationsForBlock(fork, this.forkChoice, this.shufflingCache, currentState); endAttestations?.({ step: BlockProductionStep.attestations, }); const blockBody = { randaoReveal, graffiti, // Eth1 data voting is no longer required since electra eth1Data: currentState.eth1Data, proposerSlashings, attesterSlashings, attestations, // Since electra, deposits are processed by the execution layer, // we no longer support handling deposits from earlier forks. deposits: [], voluntaryExits, }; if (ForkSeq[fork] >= ForkSeq.capella) { blockBody.blsToExecutionChanges = blsToExecutionChanges; } const endSyncAggregate = stepsMetrics?.startTimer(); if (ForkSeq[fork] >= ForkSeq.altair) { const parentBlockRoot = fromHex(parentBlock.blockRoot); const previousSlot = slot - 1; const syncAggregate = this.syncContributionAndProofPool.getAggregate(previousSlot, parentBlockRoot); this.metrics?.production.producedSyncAggregateParticipants.observe(syncAggregate.syncCommitteeBits.getTrueBitIndexes().length); blockBody.syncAggregate = syncAggregate; } endSyncAggregate?.({ step: BlockProductionStep.syncAggregate, }); return blockBody; } //# sourceMappingURL=produceBlockBody.js.map