@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
682 lines (597 loc) • 23.5 kB
text/typescript
import {
BYTES_PER_FIELD_ELEMENT,
BYTES_PER_LOGS_BLOOM,
CELLS_PER_EXT_BLOB,
CONSOLIDATION_REQUEST_TYPE,
DEPOSIT_REQUEST_TYPE,
FIELD_ELEMENTS_PER_BLOB,
ForkName,
ForkSeq,
WITHDRAWAL_REQUEST_TYPE,
} from "@lodestar/params";
import {
BlobsBundle,
ExecutionPayload,
ExecutionRequests,
Root,
Wei,
bellatrix,
capella,
deneb,
electra,
gloas,
ssz,
} from "@lodestar/types";
import {BlobAndProof} from "@lodestar/types/deneb";
import {BlobAndProofV2} from "@lodestar/types/fulu";
import {
ExecutionPayloadStatus,
ExecutionRequestType,
PayloadAttributes,
VersionedHashes,
isExecutionRequestType,
} from "./interface.js";
import {WithdrawalV1} from "./payloadIdCache.js";
import {
DATA,
QUANTITY,
bytesToData,
dataIntoBytes,
dataToBytes,
numToQuantity,
quantityToBigint,
quantityToNum,
} from "./utils.js";
export type EngineApiRpcParamTypes = {
/**
* 1. Object - Instance of ExecutionPayload
*/
engine_newPayloadV1: [ExecutionPayloadRpc];
engine_newPayloadV2: [ExecutionPayloadRpc];
engine_newPayloadV3: [ExecutionPayloadRpc, VersionedHashesRpc, DATA];
engine_newPayloadV4: [ExecutionPayloadRpc, VersionedHashesRpc, DATA, ExecutionRequestsRpc];
engine_newPayloadV5: [ExecutionPayloadRpc, VersionedHashesRpc, DATA, ExecutionRequestsRpc];
/**
* 1. Object - Payload validity status with respect to the consensus rules:
* - blockHash: DATA, 32 Bytes - block hash value of the payload
* - status: String: VALID|INVALID - result of the payload validation with respect to the proof-of-stake consensus rules
*/
engine_forkchoiceUpdatedV1: [
forkChoiceData: {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA},
payloadAttributes?: PayloadAttributesRpc,
];
engine_forkchoiceUpdatedV2: [
forkChoiceData: {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA},
payloadAttributes?: PayloadAttributesRpc,
];
engine_forkchoiceUpdatedV3: [
forkChoiceData: {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA},
payloadAttributes?: PayloadAttributesRpc,
];
engine_forkchoiceUpdatedV4: [
forkChoiceData: {headBlockHash: DATA; safeBlockHash: DATA; finalizedBlockHash: DATA},
payloadAttributes?: PayloadAttributesRpc,
];
/**
* 1. payloadId: QUANTITY, 64 Bits - Identifier of the payload building process
*/
engine_getPayloadV1: [QUANTITY];
engine_getPayloadV2: [QUANTITY];
engine_getPayloadV3: [QUANTITY];
engine_getPayloadV4: [QUANTITY];
engine_getPayloadV5: [QUANTITY];
engine_getPayloadV6: [QUANTITY];
/**
* 1. Array of DATA - Array of block_hash field values of the ExecutionPayload structure
* */
engine_getPayloadBodiesByHashV1: DATA[][];
/**
* 1. start: QUANTITY, 64 bits - Starting block number
* 2. count: QUANTITY, 64 bits - Number of blocks to return
*/
engine_getPayloadBodiesByRangeV1: [start: QUANTITY, count: QUANTITY];
/**
* Object - Instance of ClientVersion
*/
engine_getClientVersionV1: [ClientVersionRpc];
engine_getBlobsV1: [DATA[]];
engine_getBlobsV2: [DATA[]];
};
export type PayloadStatus = {
status: ExecutionPayloadStatus;
latestValidHash: DATA | null;
validationError: string | null;
};
export type EngineApiRpcReturnTypes = {
/**
* Object - Response object:
* - status: String - the result of the payload execution:
*/
engine_newPayloadV1: PayloadStatus;
engine_newPayloadV2: PayloadStatus;
engine_newPayloadV3: PayloadStatus;
engine_newPayloadV4: PayloadStatus;
engine_newPayloadV5: PayloadStatus;
engine_forkchoiceUpdatedV1: {
payloadStatus: PayloadStatus;
payloadId: QUANTITY | null;
};
engine_forkchoiceUpdatedV2: {
payloadStatus: PayloadStatus;
payloadId: QUANTITY | null;
};
engine_forkchoiceUpdatedV3: {
payloadStatus: PayloadStatus;
payloadId: QUANTITY | null;
};
engine_forkchoiceUpdatedV4: {
payloadStatus: PayloadStatus;
payloadId: QUANTITY | null;
};
/**
* payloadId | Error: QUANTITY, 64 Bits - Identifier of the payload building process
*/
engine_getPayloadV1: ExecutionPayloadRpc;
engine_getPayloadV2: ExecutionPayloadResponse;
engine_getPayloadV3: ExecutionPayloadResponse;
engine_getPayloadV4: ExecutionPayloadResponse;
engine_getPayloadV5: ExecutionPayloadResponse;
engine_getPayloadV6: ExecutionPayloadResponse;
engine_getPayloadBodiesByHashV1: (ExecutionPayloadBodyRpc | null)[];
engine_getPayloadBodiesByRangeV1: (ExecutionPayloadBodyRpc | null)[];
engine_getClientVersionV1: ClientVersionRpc[];
engine_getBlobsV1: (BlobAndProofRpc | null)[];
engine_getBlobsV2: BlobAndProofV2Rpc[] | null;
};
type ExecutionPayloadRpcWithValue = {
executionPayload: ExecutionPayloadRpc;
// even though CL tracks this as executionPayloadValue, EL returns this as blockValue
blockValue: QUANTITY;
blobsBundle?: BlobsBundleRpc;
executionRequests?: ExecutionRequestsRpc;
shouldOverrideBuilder?: boolean;
};
type ExecutionPayloadResponse = ExecutionPayloadRpcWithValue;
export type ExecutionPayloadBodyRpc = {
transactions: DATA[];
withdrawals: WithdrawalV1[] | null | undefined;
};
export type ExecutionPayloadBody = {
transactions: bellatrix.Transaction[];
withdrawals: capella.Withdrawals | null;
};
export type ExecutionPayloadRpc = {
parentHash: DATA; // 32 bytes
feeRecipient: DATA; // 20 bytes
stateRoot: DATA; // 32 bytes
receiptsRoot: DATA; // 32 bytes
logsBloom: DATA; // 256 bytes
prevRandao: DATA; // 32 bytes
blockNumber: QUANTITY;
gasLimit: QUANTITY;
gasUsed: QUANTITY;
timestamp: QUANTITY;
extraData: DATA; // 0 to 32 bytes
baseFeePerGas: QUANTITY;
blockHash: DATA; // 32 bytes
transactions: DATA[];
withdrawals?: WithdrawalRpc[]; // Capella hardfork
blobGasUsed?: QUANTITY; // DENEB
excessBlobGas?: QUANTITY; // DENEB
blockAccessList?: DATA; // GLOAS:EIP-7928
slotNumber?: QUANTITY; // GLOAS:EIP-7843
};
export type WithdrawalRpc = {
index: QUANTITY;
validatorIndex: QUANTITY;
address: DATA;
amount: QUANTITY;
};
/**
* ExecutionRequestsRpc only holds at most 3 elements and no repeated type:
* - ssz'ed DepositRequests
* - ssz'ed WithdrawalRequests
* - ssz'ed ConsolidationRequests
*/
export type ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[];
export type DepositRequestsRpc = DATA;
export type WithdrawalRequestsRpc = DATA;
export type ConsolidationRequestsRpc = DATA;
export type BlobAndProofRpc = {
blob: DATA;
proof: DATA;
};
export type BlobAndProofV2Rpc = {
blob: DATA;
proofs: DATA[];
};
const BLOB_BYTES = BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB;
const PROOF_BYTES = 48;
export const BLOB_AND_PROOF_V2_RPC_BYTES = BLOB_BYTES + PROOF_BYTES * CELLS_PER_EXT_BLOB;
export type VersionedHashesRpc = DATA[];
export type PayloadAttributesRpc = {
/** QUANTITY, 64 Bits - value for the timestamp field of the new payload */
timestamp: QUANTITY;
/** DATA, 32 Bytes - value for the prevRandao field of the new payload */
prevRandao: DATA;
/** DATA, 20 Bytes - suggested value for the coinbase field of the new payload */
suggestedFeeRecipient: DATA;
withdrawals?: WithdrawalRpc[];
/** DATA, 32 Bytes - value for the parentBeaconBlockRoot to be used for building block */
parentBeaconBlockRoot?: DATA;
/** QUANTITY, 64 Bits - value for the slot number field of the new payload (EIP-7843) */
slotNumber?: QUANTITY;
};
export type ClientVersionRpc = {
/** ClientCode */
code: string;
/** string, Human-readable name of the client */
name: string;
/** string, the version string of the current implementation */
version: string;
/** DATA, 4 bytes - first four bytes of the latest commit hash of this build */
commit: DATA;
};
export interface BlobsBundleRpc {
commitments: DATA[]; // each 48 bytes
blobs: DATA[]; // each 4096 * 32 = 131072 bytes
proofs: DATA[]; // some ELs could also provide proofs, each 48 bytes
}
export function serializeExecutionPayload(fork: ForkName, data: ExecutionPayload): ExecutionPayloadRpc {
const payload: ExecutionPayloadRpc = {
parentHash: bytesToData(data.parentHash),
feeRecipient: bytesToData(data.feeRecipient),
stateRoot: bytesToData(data.stateRoot),
receiptsRoot: bytesToData(data.receiptsRoot),
logsBloom: bytesToData(data.logsBloom),
prevRandao: bytesToData(data.prevRandao),
blockNumber: numToQuantity(data.blockNumber),
gasLimit: numToQuantity(data.gasLimit),
gasUsed: numToQuantity(data.gasUsed),
timestamp: numToQuantity(data.timestamp),
extraData: bytesToData(data.extraData),
baseFeePerGas: numToQuantity(data.baseFeePerGas),
blockHash: bytesToData(data.blockHash),
transactions: data.transactions.map((tran) => bytesToData(tran)),
};
// Capella adds withdrawals to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.capella) {
const {withdrawals} = data as capella.ExecutionPayload;
payload.withdrawals = withdrawals.map(serializeWithdrawal);
}
// DENEB adds blobGasUsed & excessBlobGas to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.deneb) {
const {blobGasUsed, excessBlobGas} = data as deneb.ExecutionPayload;
payload.blobGasUsed = numToQuantity(blobGasUsed);
payload.excessBlobGas = numToQuantity(excessBlobGas);
}
// No changes in Electra
if (ForkSeq[fork] >= ForkSeq.gloas) {
const {blockAccessList, slotNumber} = data as gloas.ExecutionPayload;
payload.blockAccessList = bytesToData(blockAccessList);
payload.slotNumber = numToQuantity(slotNumber);
}
return payload;
}
export function serializeVersionedHashes(vHashes: VersionedHashes): VersionedHashesRpc {
return vHashes.map(bytesToData);
}
export function hasPayloadValue(
response: ExecutionPayloadResponse | ExecutionPayloadRpc
): response is ExecutionPayloadRpcWithValue {
return (response as ExecutionPayloadRpcWithValue).blockValue !== undefined;
}
export function parseExecutionPayload(
fork: ForkName,
response: ExecutionPayloadResponse | ExecutionPayloadRpc
): {
executionPayload: ExecutionPayload;
executionPayloadValue: Wei;
blobsBundle?: BlobsBundle;
executionRequests?: ExecutionRequests;
shouldOverrideBuilder?: boolean;
} {
let data: ExecutionPayloadRpc;
let executionPayloadValue: Wei;
let blobsBundle: BlobsBundle | undefined;
let executionRequests: ExecutionRequests | undefined;
let shouldOverrideBuilder: boolean;
if (hasPayloadValue(response)) {
executionPayloadValue = quantityToBigint(response.blockValue);
data = response.executionPayload;
blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined;
executionRequests = response.executionRequests
? deserializeExecutionRequests(response.executionRequests)
: undefined;
shouldOverrideBuilder = response.shouldOverrideBuilder ?? false;
} else {
data = response;
// Just set it to zero as default
executionPayloadValue = BigInt(0);
blobsBundle = undefined;
executionRequests = undefined;
shouldOverrideBuilder = false;
}
const executionPayload = {
parentHash: dataToBytes(data.parentHash, 32),
feeRecipient: dataToBytes(data.feeRecipient, 20),
stateRoot: dataToBytes(data.stateRoot, 32),
receiptsRoot: dataToBytes(data.receiptsRoot, 32),
logsBloom: dataToBytes(data.logsBloom, BYTES_PER_LOGS_BLOOM),
prevRandao: dataToBytes(data.prevRandao, 32),
blockNumber: quantityToNum(data.blockNumber),
gasLimit: quantityToNum(data.gasLimit),
gasUsed: quantityToNum(data.gasUsed),
timestamp: quantityToNum(data.timestamp),
extraData: dataToBytes(data.extraData, null),
baseFeePerGas: quantityToBigint(data.baseFeePerGas),
blockHash: dataToBytes(data.blockHash, 32),
transactions: data.transactions.map((tran) => dataToBytes(tran, null)),
};
if (ForkSeq[fork] >= ForkSeq.capella) {
const {withdrawals} = data;
// Geth can also reply with null
if (withdrawals == null) {
throw Error(
`withdrawals missing for ${fork} >= capella executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
(executionPayload as capella.ExecutionPayload).withdrawals = withdrawals.map((w) => deserializeWithdrawal(w));
}
// DENEB adds excessBlobGas to the ExecutionPayload
if (ForkSeq[fork] >= ForkSeq.deneb) {
const {blobGasUsed, excessBlobGas} = data;
if (blobGasUsed == null) {
throw Error(
`blobGasUsed missing for ${fork} >= deneb executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
if (excessBlobGas == null) {
throw Error(
`excessBlobGas missing for ${fork} >= deneb executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
(executionPayload as deneb.ExecutionPayload).blobGasUsed = quantityToBigint(blobGasUsed);
(executionPayload as deneb.ExecutionPayload).excessBlobGas = quantityToBigint(excessBlobGas);
}
// No changes in Electra
if (ForkSeq[fork] >= ForkSeq.gloas) {
const {blockAccessList, slotNumber} = data;
if (blockAccessList == null) {
throw Error(
`blockAccessList missing for ${fork} >= gloas executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
if (slotNumber == null) {
throw Error(
`slotNumber missing for ${fork} >= gloas executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}`
);
}
(executionPayload as gloas.ExecutionPayload).blockAccessList = dataToBytes(blockAccessList, null);
(executionPayload as gloas.ExecutionPayload).slotNumber = quantityToNum(slotNumber);
}
return {executionPayload, executionPayloadValue, blobsBundle, executionRequests, shouldOverrideBuilder};
}
export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttributesRpc {
return {
timestamp: numToQuantity(data.timestamp),
prevRandao: bytesToData(data.prevRandao),
suggestedFeeRecipient: data.suggestedFeeRecipient,
withdrawals: data.withdrawals?.map(serializeWithdrawal),
parentBeaconBlockRoot: data.parentBeaconBlockRoot ? bytesToData(data.parentBeaconBlockRoot) : undefined,
slotNumber: data.slotNumber !== undefined ? numToQuantity(data.slotNumber) : undefined,
};
}
export function serializeBeaconBlockRoot(data: Root): DATA {
return bytesToData(data);
}
export function deserializePayloadAttributes(data: PayloadAttributesRpc): PayloadAttributes {
return {
timestamp: quantityToNum(data.timestamp),
prevRandao: dataToBytes(data.prevRandao, 32),
// DATA is anyway a hex string, so we can just track it as a hex string to
// avoid any conversions
suggestedFeeRecipient: data.suggestedFeeRecipient,
withdrawals: data.withdrawals?.map((withdrawal) => deserializeWithdrawal(withdrawal)),
parentBeaconBlockRoot: data.parentBeaconBlockRoot ? dataToBytes(data.parentBeaconBlockRoot, 32) : undefined,
slotNumber: data.slotNumber !== undefined ? quantityToNum(data.slotNumber) : undefined,
};
}
export function parseBlobsBundle(data: BlobsBundleRpc): BlobsBundle {
return {
// As of Nov 17th 2022 according to Dan's tests Geth returns null if no blobs in block
commitments: (data.commitments ?? []).map((kzg) => dataToBytes(kzg, 48)),
blobs: (data.blobs ?? []).map((blob) => dataToBytes(blob, BLOB_BYTES)),
proofs: (data.proofs ?? []).map((kzg) => dataToBytes(kzg, PROOF_BYTES)),
};
}
export function serializeBlobsBundle(data: BlobsBundle): BlobsBundleRpc {
return {
commitments: data.commitments.map((kzg) => bytesToData(kzg)),
blobs: data.blobs.map((blob) => bytesToData(blob)),
proofs: data.proofs.map((proof) => bytesToData(proof)),
};
}
export function serializeWithdrawal(withdrawal: capella.Withdrawal): WithdrawalRpc {
return {
index: numToQuantity(withdrawal.index),
validatorIndex: numToQuantity(withdrawal.validatorIndex),
address: bytesToData(withdrawal.address),
// Both CL and EL now deal in Gwei, just little-endian to big-endian conversion required
amount: numToQuantity(withdrawal.amount),
};
}
export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdrawal {
return {
index: quantityToNum(serialized.index),
validatorIndex: quantityToNum(serialized.validatorIndex),
address: dataToBytes(serialized.address, 20),
// Both CL and EL now deal in Gwei, just big-endian to little-endian conversion required
amount: quantityToBigint(serialized.amount),
} as capella.Withdrawal;
}
/**
* Prepend a single-byte requestType to requestsBytes
*/
function prefixRequests(requestsBytes: Uint8Array, requestType: ExecutionRequestType): Uint8Array {
const prefixedRequests = new Uint8Array(1 + requestsBytes.length);
prefixedRequests[0] = requestType;
prefixedRequests.set(requestsBytes, 1);
return prefixedRequests;
}
function serializeDepositRequests(depositRequests: electra.DepositRequests): DepositRequestsRpc {
const requestsBytes = ssz.electra.DepositRequests.serialize(depositRequests);
return bytesToData(prefixRequests(requestsBytes, DEPOSIT_REQUEST_TYPE));
}
function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.DepositRequests {
return ssz.electra.DepositRequests.deserialize(dataToBytes(serialized, null));
}
function serializeWithdrawalRequests(withdrawalRequests: electra.WithdrawalRequests): WithdrawalRequestsRpc {
const requestsBytes = ssz.electra.WithdrawalRequests.serialize(withdrawalRequests);
return bytesToData(prefixRequests(requestsBytes, WITHDRAWAL_REQUEST_TYPE));
}
function deserializeWithdrawalRequests(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests {
return ssz.electra.WithdrawalRequests.deserialize(dataToBytes(serialized, null));
}
function serializeConsolidationRequests(
consolidationRequests: electra.ConsolidationRequests
): ConsolidationRequestsRpc {
const requestsBytes = ssz.electra.ConsolidationRequests.serialize(consolidationRequests);
return bytesToData(prefixRequests(requestsBytes, CONSOLIDATION_REQUEST_TYPE));
}
function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): electra.ConsolidationRequests {
return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null));
}
/**
* This is identical to get_execution_requests_list in
* https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list
*/
export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc {
const {deposits, withdrawals, consolidations} = executionRequests;
const result = [];
if (deposits.length !== 0) {
result.push(serializeDepositRequests(deposits));
}
if (withdrawals.length !== 0) {
result.push(serializeWithdrawalRequests(withdrawals));
}
if (consolidations.length !== 0) {
result.push(serializeConsolidationRequests(consolidations));
}
return result;
}
export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests {
const result: ExecutionRequests = {
deposits: [],
withdrawals: [],
consolidations: [],
};
if (serialized.length === 0) {
return result;
}
let prevRequestType: ExecutionRequestType | undefined;
for (let prefixedRequests of serialized) {
// Slice out 0x so it is easier to extract request type
if (prefixedRequests.startsWith("0x")) {
prefixedRequests = prefixedRequests.slice(2);
}
const currentRequestType = parseInt(prefixedRequests.substring(0, 2), 16);
if (!isExecutionRequestType(currentRequestType)) {
throw Error(`Invalid request type currentRequestType=${prefixedRequests.substring(0, 2)}`);
}
const requests = prefixedRequests.slice(2);
if (requests.length === 0) {
throw Error(
`Request with empty data must be excluded from execution requests currentRequestType=${currentRequestType}`
);
}
if (prevRequestType !== undefined && prevRequestType >= currentRequestType) {
throw Error(
`Current request type must be larger than previous request type prevRequestType=${prevRequestType} currentRequestType=${currentRequestType}`
);
}
switch (currentRequestType) {
case DEPOSIT_REQUEST_TYPE: {
result.deposits = deserializeDepositRequests(requests);
break;
}
case WITHDRAWAL_REQUEST_TYPE: {
result.withdrawals = deserializeWithdrawalRequests(requests);
break;
}
case CONSOLIDATION_REQUEST_TYPE: {
result.consolidations = deserializeConsolidationRequests(requests);
break;
}
}
prevRequestType = currentRequestType;
}
return result;
}
export function deserializeExecutionPayloadBody(data: ExecutionPayloadBodyRpc | null): ExecutionPayloadBody | null {
return data
? {
transactions: data.transactions.map((tran) => dataToBytes(tran, null)),
withdrawals: data.withdrawals ? data.withdrawals.map(deserializeWithdrawal) : null,
}
: null;
}
export function serializeExecutionPayloadBody(data: ExecutionPayloadBody | null): ExecutionPayloadBodyRpc | null {
return data
? {
transactions: data.transactions.map((tran) => bytesToData(tran)),
withdrawals: data.withdrawals ? data.withdrawals.map(serializeWithdrawal) : null,
}
: null;
}
export function deserializeBlobAndProofs(data: BlobAndProofRpc | null): BlobAndProof | null {
return data
? {
blob: dataToBytes(data.blob, BLOB_BYTES),
proof: dataToBytes(data.proof, PROOF_BYTES),
}
: null;
}
export function deserializeBlobAndProofsV2(data: BlobAndProofV2Rpc): BlobAndProofV2 {
return {
blob: dataToBytes(data.blob, BLOB_BYTES),
proofs: data.proofs.map((proof) => dataToBytes(proof, PROOF_BYTES)),
};
}
/**
* The same to deserializeBlobAndProofsV2 but using preallocated buffers since BlobAndProofV2Rpc is fixed size
*/
export function deserializeBlobAndProofsV2IntoBytes(data: BlobAndProofV2Rpc, buffer: Uint8Array): BlobAndProofV2 {
if (buffer.length !== BLOB_AND_PROOF_V2_RPC_BYTES) {
throw Error(
`Invalid buffer length ${buffer.length}, expected ${BLOB_AND_PROOF_V2_RPC_BYTES} to hold BlobAndProofV2Rpc`
);
}
// https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#blobandproofv2
// proofs MUST contain exactly CELLS_PER_EXT_BLOB cell proofs.
if (data.proofs.length !== CELLS_PER_EXT_BLOB) {
throw Error(`Invalid proofs length ${data.proofs.length}, expected ${CELLS_PER_EXT_BLOB}`);
}
const blob = dataIntoBytes(data.blob, buffer.subarray(0, BLOB_BYTES));
const proofs: Uint8Array[] = [];
for (let i = 0; i < CELLS_PER_EXT_BLOB; i++) {
const proof = dataIntoBytes(
data.proofs[i],
buffer.subarray(BLOB_BYTES + i * PROOF_BYTES, BLOB_BYTES + (i + 1) * PROOF_BYTES)
);
if (proof.length !== PROOF_BYTES) {
throw Error(`Invalid proof length ${proof.length}, expected ${PROOF_BYTES}`);
}
proofs.push(proof);
}
return {
blob,
proofs,
};
}
export function assertReqSizeLimit(blockHashesReqCount: number, count: number): void {
if (blockHashesReqCount > count) {
throw new Error(`Requested blocks must not be > ${count}`);
}
return;
}