@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
80 lines • 5.28 kB
JavaScript
import { computeTimeAtSlot, getExecutionPayloadEnvelopeSignatureSet, } from "@lodestar/state-transition";
import { ssz } from "@lodestar/types";
import { byteArrayEquals, toHex, toRootHex } from "@lodestar/utils";
/**
* 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, state, envelope, opts) {
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, state, pubkeyCache, signedEnvelope, proposerIndex, bls) {
const signatureSet = getExecutionPayloadEnvelopeSignatureSet(config, pubkeyCache, state, signedEnvelope, proposerIndex);
return bls.verifySignatureSets([signatureSet]);
}
//# sourceMappingURL=verifyExecutionPayloadEnvelope.js.map