import { getSafeExecutionBlockHash } from "@lodestar/fork-choice";
import { BUILDER_INDEX_SELF_BUILD, ForkSeq, isForkPostAltair, isForkPostBellatrix, isForkPostGloas, } from "@lodestar/params";
import { G2_POINT_AT_INFINITY, computeTimeAtSlot, getExpectedWithdrawals, getRandaoMix, } from "@lodestar/state-transition";
import { 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)) {
        // 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 gloasState = currentState;
        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
        const prepareRes = await prepareExecutionPayload(this, this.logger, fork, parentBlockRoot, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, gloasState, 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: gloasState.latestBlockHash,
            parentBlockRoot: parentBlockRoot,
            blockHash: executionPayload.blockHash,
            prevRandao: getRandaoMix(gloasState, gloasState.epochCtx.epoch),
            feeRecipient: executionPayload.feeRecipient,
            gasLimit: BigInt(executionPayload.gasLimit),
            builderIndex: BUILDER_INDEX_SELF_BUILD,
            slot: blockSlot,
            value: 0,
            executionPayment: 0,
            blobKzgCommitments: blobsBundle.commitments,
        };
        const signedBid = {
            message: bid,
            signature: G2_POINT_AT_INFINITY,
        };
        const commonBlockBody = await commonBlockBodyPromise;
        const gloasBody = Object.assign({}, commonBlockBody);
        gloasBody.signedExecutionPayloadBid = signedBid;
        // TODO GLOAS: Get payload attestations from pool for previous slot
        gloasBody.payloadAttestations = [];
        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;
        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)) {
        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, 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/dev/specs/deneb/validator.md#constructing-the-beaconblockbody
                const prepareRes = await prepareExecutionPayload(this, this.logger, fork, parentBlockRoot, 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, safeBlockHash, finalizedBlockHash, state, suggestedFeeRecipient) {
    const parentHash = isForkPostGloas(fork)
        ? state.latestBlockHash
        : state.latestExecutionPayloadHeader.blockHash;
    const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
    const prevRandao = getRandaoMix(state, state.epochCtx.epoch);
    const payloadIdCached = chain.executionEngine.payloadIdCache.get({
        headBlockHash: toRootHex(parentHash),
        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,
            feeRecipient: suggestedFeeRecipient,
        });
        payloadId = await chain.executionEngine.notifyForkchoiceUpdate(fork, toRootHex(parentHash), 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, feeRecipient, }) {
    const parentHash = isForkPostGloas(fork)
        ? prepareState.latestBlockHash
        : prepareState.latestExecutionPayloadHeader.blockHash;
    const payloadAttributes = preparePayloadAttributes(fork, chain, {
        prepareState,
        prepareSlot,
        parentBlockRoot,
        feeRecipient,
    });
    let parentBlockNumber;
    if (isForkPostGloas(fork)) {
        const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(toRootHex(parentBlockRoot), toRootHex(parentHash));
        if (parentBlock?.executionPayloadBlockHash == null) {
            throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
        }
        parentBlockNumber = parentBlock.executionPayloadNumber;
    }
    else {
        parentBlockNumber = prepareState.latestExecutionPayloadHeader.blockNumber;
    }
    const ssePayloadAttributes = {
        proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot),
        proposalSlot: prepareSlot,
        parentBlockNumber,
        parentBlockRoot,
        parentBlockHash: parentHash,
        payloadAttributes,
    };
    return ssePayloadAttributes;
}
function preparePayloadAttributes(fork, chain, { prepareState, prepareSlot, parentBlockRoot, feeRecipient, }) {
    const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
    const prevRandao = getRandaoMix(prepareState, prepareState.epochCtx.epoch);
    const payloadAttributes = {
        timestamp,
        prevRandao,
        suggestedFeeRecipient: feeRecipient,
    };
    if (ForkSeq[fork] >= ForkSeq.capella) {
        // withdrawals logic is now fork aware as it changes on electra fork post capella
        payloadAttributes.withdrawals = getExpectedWithdrawals(ForkSeq[fork], prepareState).expectedWithdrawals;
    }
    if (ForkSeq[fork] >= ForkSeq.deneb) {
        payloadAttributes.parentBeaconBlockRoot = parentBlockRoot;
    }
    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