UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

115 lines 6.46 kB
import { PayloadStatus } from "@lodestar/fork-choice"; import { computeStartSlotAtEpoch, getExecutionPayloadEnvelopeSignatureSet, isStatePostGloas, } from "@lodestar/state-transition"; import { ssz } from "@lodestar/types"; import { byteArrayEquals, toRootHex } from "@lodestar/utils"; import { ExecutionPayloadEnvelopeError, ExecutionPayloadEnvelopeErrorCode, GossipAction } from "../errors/index.js"; import { RegenCaller } from "../regen/index.js"; export async function validateApiExecutionPayloadEnvelope(chain, executionPayloadEnvelope) { return validateExecutionPayloadEnvelope(chain, executionPayloadEnvelope); } export async function validateGossipExecutionPayloadEnvelope(chain, executionPayloadEnvelope) { return validateExecutionPayloadEnvelope(chain, executionPayloadEnvelope); } async function validateExecutionPayloadEnvelope(chain, executionPayloadEnvelope) { const envelope = executionPayloadEnvelope.message; const { payload } = envelope; const blockRootHex = toRootHex(envelope.beaconBlockRoot); // [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via // gossip or non-gossip sources) (a client MAY queue payload for processing once // the block is retrieved). // TODO GLOAS: Need to review this, we should queue the envelope for later // processing if the block is not yet known, otherwise we would ignore it here const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot); if (block === null) { throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, { code: ExecutionPayloadEnvelopeErrorCode.BLOCK_ROOT_UNKNOWN, blockRoot: blockRootHex, }); } // [IGNORE] The node has not seen another valid // `SignedExecutionPayloadEnvelope` for this block root from this builder. const envelopeBlock = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.FULL); const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex); if (envelopeBlock || payloadInput?.hasPayloadEnvelope()) { throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, { code: ExecutionPayloadEnvelopeErrorCode.ENVELOPE_ALREADY_KNOWN, blockRoot: blockRootHex, slot: payload.slotNumber, }); } if (!payloadInput) { // PayloadEnvelopeInput should have been created during block import throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, { code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING, blockRoot: blockRootHex, }); } // [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot -- i.e. validate that `payload.slotNumber >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)` const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint(); const finalizedSlot = computeStartSlotAtEpoch(finalizedCheckpoint.epoch); if (payload.slotNumber < finalizedSlot) { throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, { code: ExecutionPayloadEnvelopeErrorCode.BELONG_TO_FINALIZED_BLOCK, envelopeSlot: payload.slotNumber, finalizedSlot, }); } // [REJECT] `block` passes validation. // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice, // it is possible that the block didn't pass the validation // [REJECT] `block.slot` equals `payload.slotNumber`. if (block.slot !== payload.slotNumber) { throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, { code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH, envelopeSlot: payload.slotNumber, blockSlot: block.slot, }); } // [REJECT] `envelope.builder_index == bid.builder_index` if (envelope.builderIndex !== payloadInput.getBuilderIndex()) { throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, { code: ExecutionPayloadEnvelopeErrorCode.BUILDER_INDEX_MISMATCH, envelopeBuilderIndex: envelope.builderIndex, bidBuilderIndex: payloadInput.getBuilderIndex(), }); } // [REJECT] `payload.block_hash == bid.block_hash` if (toRootHex(payload.blockHash) !== payloadInput.getBlockHashHex()) { throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, { code: ExecutionPayloadEnvelopeErrorCode.BLOCK_HASH_MISMATCH, envelopeBlockHash: toRootHex(payload.blockHash), bidBlockHash: payloadInput.getBlockHashHex(), }); } // [REJECT] `hash_tree_root(envelope.execution_requests) == bid.execution_requests_root` const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests); if (!byteArrayEquals(requestsRoot, payloadInput.getBid().executionRequestsRoot)) { throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, { code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH, envelopeRequestsRoot: toRootHex(requestsRoot), bidRequestsRoot: toRootHex(payloadInput.getBid().executionRequestsRoot), }); } // Get the block state to verify the builder's signature. const blockState = await chain.regen .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope) .catch(() => { throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, { code: ExecutionPayloadEnvelopeErrorCode.UNKNOWN_BLOCK_STATE, blockRoot: blockRootHex, slot: payload.slotNumber, }); }); if (!isStatePostGloas(blockState)) { throw new Error(`Expected gloas+ state for execution payload envelope validation, got fork=${blockState.forkName}`); } // [REJECT] `signed_execution_payload_envelope.signature` is valid as verified // by `verify_execution_payload_envelope_signature`. const signatureSet = getExecutionPayloadEnvelopeSignatureSet(chain.config, chain.pubkeyCache, blockState, executionPayloadEnvelope, payloadInput.proposerIndex); if (!(await chain.bls.verifySignatureSets([signatureSet], { verifyOnMainThread: true }))) { throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, { code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE, }); } } //# sourceMappingURL=executionPayloadEnvelope.js.map