UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

138 lines (129 loc) 5.74 kB
import {BeaconConfig} from "@lodestar/config"; import { type IBeaconStateViewGloas, type PubkeyCache, computeTimeAtSlot, getExecutionPayloadEnvelopeSignatureSet, } from "@lodestar/state-transition"; import {gloas, ssz} from "@lodestar/types"; import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils"; import {IBlsVerifier} from "../bls/index.js"; export type VerifyExecutionPayloadEnvelopeOpts = { verifyExecutionRequestsRoot?: boolean; }; /** * Verify execution payload envelope fields against the post-block state. * * Signature verification and the execution engine call (`verify_and_notify_new_payload`) are * performed outside this function, see `verifyExecutionPayloadEnvelopeSignature` and * `importExecutionPayload` which run both in parallel with this check. * * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope */ export function verifyExecutionPayloadEnvelope( config: BeaconConfig, state: IBeaconStateViewGloas, envelope: gloas.ExecutionPayloadEnvelope, opts?: VerifyExecutionPayloadEnvelopeOpts ): void { const {verifyExecutionRequestsRoot = true} = opts ?? {}; const payload = envelope.payload; // Verify consistency with the beacon block. // Compute header root on a clone of latestBlockHeader to avoid mutating state. const headerValue = ssz.phase0.BeaconBlockHeader.clone(state.latestBlockHeader); if (byteArrayEquals(headerValue.stateRoot, ssz.Root.defaultValue())) { headerValue.stateRoot = state.hashTreeRoot(); } const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(headerValue); if (!byteArrayEquals(envelope.beaconBlockRoot, headerRoot)) { throw new Error( `Envelope's block is not the latest block header envelope=${toRootHex(envelope.beaconBlockRoot)} latestBlockHeader=${toRootHex(headerRoot)}` ); } if (!byteArrayEquals(envelope.parentBeaconBlockRoot, state.latestBlockHeader.parentRoot)) { throw new Error( `Envelope's parent_beacon_block_root mismatch envelope=${toRootHex(envelope.parentBeaconBlockRoot)} state=${toRootHex(state.latestBlockHeader.parentRoot)}` ); } // Verify consistency with the committed bid const bid = state.latestExecutionPayloadBid; if (envelope.builderIndex !== bid.builderIndex) { throw new Error( `Builder index mismatch between envelope and committed bid envelope=${envelope.builderIndex} bid=${bid.builderIndex}` ); } if (!byteArrayEquals(bid.prevRandao, payload.prevRandao)) { throw new Error( `Prev randao mismatch between bid and payload bid=${toHex(bid.prevRandao)} payload=${toHex(payload.prevRandao)}` ); } if (Number(bid.gasLimit) !== payload.gasLimit) { throw new Error( `Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${Number(bid.gasLimit)}` ); } if (!byteArrayEquals(bid.blockHash, payload.blockHash)) { throw new Error( `Block hash mismatch between payload and bid payload=${toRootHex(payload.blockHash)} bid=${toRootHex(bid.blockHash)}` ); } // Verify execution_requests_root matches bid commitment. // Can be skipped if already verified during gossip validation. if (verifyExecutionRequestsRoot) { const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests); if (!byteArrayEquals(requestsRoot, bid.executionRequestsRoot)) { throw new Error( `Execution requests root mismatch envelope=${toRootHex(requestsRoot)} bid=${toRootHex(bid.executionRequestsRoot)}` ); } } // should not use state.slot, it does not work for skipped slot checkpoint sync const blockSlot = state.latestBlockHeader.slot; if (payload.slotNumber !== blockSlot) { throw new Error( `Slot mismatch between payload and latest block header payload=${payload.slotNumber} latestBlockHeader=${blockSlot}` ); } if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) { throw new Error( `Parent hash mismatch between payload and state payload=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}` ); } const expectedTimestamp = computeTimeAtSlot(config, blockSlot, state.genesisTime); if (payload.timestamp !== expectedTimestamp) { throw new Error( `Timestamp mismatch between payload and state payload=${payload.timestamp} state=${expectedTimestamp}` ); } // Verify consistency with expected withdrawals const payloadWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(payload.withdrawals); const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals); if (!byteArrayEquals(payloadWithdrawalsRoot, expectedWithdrawalsRoot)) { throw new Error( `Withdrawals mismatch between payload and expected payload=${toRootHex(payloadWithdrawalsRoot)} expected=${toRootHex(expectedWithdrawalsRoot)}` ); } // Execution engine verification (verify_and_notify_new_payload) is done externally by the caller } /** * Verify the BLS signature of an execution payload envelope. * * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/fork-choice.md#new-verify_execution_payload_envelope_signature */ export async function verifyExecutionPayloadEnvelopeSignature( config: BeaconConfig, state: IBeaconStateViewGloas, pubkeyCache: PubkeyCache, signedEnvelope: gloas.SignedExecutionPayloadEnvelope, proposerIndex: number, bls: IBlsVerifier ): Promise<boolean> { const signatureSet = getExecutionPayloadEnvelopeSignatureSet( config, pubkeyCache, state, signedEnvelope, proposerIndex ); return bls.verifySignatureSets([signatureSet]); }