import {ssz, allForks, capella, eip4844} from "@lodestar/types"; import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; import {ForkSeq} from "@lodestar/params"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; import {getRandaoMix} from "../util/index.js"; import {isExecutionPayload, isMergeTransitionComplete} from "../util/execution.js"; import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js"; export function processExecutionPayload( fork: ForkSeq, state: CachedBeaconStateBellatrix | CachedBeaconStateCapella, payload: allForks.FullOrBlindedExecutionPayload, externalData: BlockExternalData ): void { // Verify consistency of the parent hash, block number, base fee per gas and gas limit // with respect to the previous execution payload header if (isMergeTransitionComplete(state)) { const {latestExecutionPayloadHeader} = state; if (!byteArrayEquals(payload.parentHash, latestExecutionPayloadHeader.blockHash)) { throw Error( `Invalid execution payload parentHash ${toHexString(payload.parentHash)} latest blockHash ${toHexString( latestExecutionPayloadHeader.blockHash )}` ); } } // Verify random const expectedRandom = getRandaoMix(state, state.epochCtx.epoch); if (!byteArrayEquals(payload.prevRandao, expectedRandom)) { throw Error( `Invalid execution payload random ${toHexString(payload.prevRandao)} expected=${toHexString(expectedRandom)}` ); } // Verify timestamp // // Note: inlined function in if statement // def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: // slots_since_genesis = slot - GENESIS_SLOT // return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) if (payload.timestamp !== state.genesisTime + state.slot * state.config.SECONDS_PER_SLOT) { throw Error(`Invalid timestamp ${payload.timestamp} genesisTime=${state.genesisTime} slot=${state.slot}`); } // Verify the execution payload is valid // // if executionEngine is null, executionEngine.onPayload MUST be called after running processBlock to get the // correct randao mix. Since executionEngine will be an async call in most cases it is called afterwards to keep // the state transition sync // // Equivalent to `assert executionEngine.notifyNewPayload(payload)` if (isExecutionPayload(payload)) { switch (externalData.executionPayloadStatus) { case ExecutionPayloadStatus.preMerge: throw Error("executionPayloadStatus preMerge"); case ExecutionPayloadStatus.invalid: throw Error("Invalid execution payload"); case ExecutionPayloadStatus.valid: break; // ok } } // For blinded or full payload -> return common header const transactionsRoot = isExecutionPayload(payload) ? ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions) : payload.transactionsRoot; const bellatrixPayloadFields: allForks.ExecutionPayloadHeader = { parentHash: payload.parentHash, feeRecipient: payload.feeRecipient, stateRoot: payload.stateRoot, receiptsRoot: payload.receiptsRoot, logsBloom: payload.logsBloom, prevRandao: payload.prevRandao, blockNumber: payload.blockNumber, gasLimit: payload.gasLimit, gasUsed: payload.gasUsed, timestamp: payload.timestamp, extraData: payload.extraData, baseFeePerGas: payload.baseFeePerGas, blockHash: payload.blockHash, transactionsRoot, }; if (fork >= ForkSeq.capella) { (bellatrixPayloadFields as capella.ExecutionPayloadHeader).withdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot( (payload as capella.ExecutionPayload).withdrawals ); } if (fork >= ForkSeq.eip4844) { // https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#process_execution_payload (bellatrixPayloadFields as eip4844.ExecutionPayloadHeader).excessDataGas = (payload as | eip4844.ExecutionPayloadHeader | eip4844.ExecutionPayload).excessDataGas; } // TODO EIP-4844: Types are not happy by default. Since it's a generic allForks type going through ViewDU // transformation then into allForks, probably some weird intersection incompatibility happens state.latestExecutionPayloadHeader = state.config .getExecutionForkTypes(state.slot) .ExecutionPayloadHeader.toViewDU(bellatrixPayloadFields) as typeof state.latestExecutionPayloadHeader; }