diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index e6f9cfee526b..e95896faa129 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -58,6 +58,13 @@ export enum ClientCode { XX = "XX", // unknown } +// Represents request type in ExecutionRequests defined in EIP-7685 +export enum RequestType { + DEPOSIT_REQUEST_TYPE = 0, // 0x00 + WITHDRAWAL_REQUEST_TYPE = 1, // 0x01 + CONSOLIDATION_REQUEST_TYPE = 2, // 0x02 +} + export type ExecutePayloadResponse = | { status: ExecutionPayloadStatus.SYNCING | ExecutionPayloadStatus.ACCEPTED; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 52ddb8548629..33677ab2b11a 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -15,9 +15,11 @@ import { DATA, QUANTITY, quantityToBigint, + numberToHex, } from "../../eth1/provider/utils.js"; -import {ExecutionPayloadStatus, BlobsBundle, PayloadAttributes, VersionedHashes} from "./interface.js"; +import {ExecutionPayloadStatus, BlobsBundle, PayloadAttributes, VersionedHashes, RequestType} from "./interface.js"; import {WithdrawalV1} from "./payloadIdCache.js"; +import {fromHexString} from "@chainsafe/ssz"; export type EngineApiRpcParamTypes = { /** @@ -160,12 +162,12 @@ export type WithdrawalRpc = { }; /** - * ExecutionRequestsRpc only holds 3 elements in the following order: + * 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 ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[]; export type DepositRequestsRpc = DATA; export type WithdrawalRequestsRpc = DATA; @@ -394,8 +396,19 @@ export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdr } as capella.Withdrawal; } +function prefixRequests(requestsBytes: Uint8Array, requestType: RequestType): Uint8Array { + const prefix = fromHexString(numberToHex(requestType)); + + const prefixedRequests = new Uint8Array(prefix.length + requestsBytes.length); + prefixedRequests.set(prefix, 0); + prefixedRequests.set(requestsBytes, prefix.length); + + return prefixedRequests; +} + function serializeDepositRequests(depositRequests: electra.DepositRequests): DepositRequestsRpc { - return bytesToData(ssz.electra.DepositRequests.serialize(depositRequests)); + const requestsBytes = ssz.electra.DepositRequests.serialize(depositRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.DEPOSIT_REQUEST_TYPE)); } function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.DepositRequests { @@ -403,17 +416,19 @@ function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.Dep } function serializeWithdrawalRequests(withdrawalRequests: electra.WithdrawalRequests): WithdrawalRequestsRpc { - return bytesToData(ssz.electra.WithdrawalRequests.serialize(withdrawalRequests)); + const requestsBytes = ssz.electra.WithdrawalRequests.serialize(withdrawalRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.WITHDRAWAL_REQUEST_TYPE)); } -function deserializeWithdrawalRequest(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { +function deserializeWithdrawalRequests(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { return ssz.electra.WithdrawalRequests.deserialize(dataToBytes(serialized, null)); } function serializeConsolidationRequests( consolidationRequests: electra.ConsolidationRequests ): ConsolidationRequestsRpc { - return bytesToData(ssz.electra.ConsolidationRequests.serialize(consolidationRequests)); + const requestsBytes = ssz.electra.ConsolidationRequests.serialize(consolidationRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.CONSOLIDATION_REQUEST_TYPE)); } function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): electra.ConsolidationRequests { @@ -426,22 +441,64 @@ function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): */ export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc { const {deposits, withdrawals, consolidations} = executionRequests; + const result = []; - return [ - serializeDepositRequests(deposits), - serializeWithdrawalRequests(withdrawals), - serializeConsolidationRequests(consolidations), - ]; + 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 [deposits, withdrawals, consolidations] = serialized; - - return { - deposits: deserializeDepositRequests(deposits), - withdrawals: deserializeWithdrawalRequest(withdrawals), - consolidations: deserializeConsolidationRequests(consolidations), + const result: ExecutionRequests = { + deposits: [], + withdrawals: [], + consolidations: [], }; + + if (serialized.length === 0) { + return result; + } + + let prevRequestType: RequestType | undefined; + + for (const prefixedRequests of serialized) { + const currentRequestType = RequestType[prefixedRequests[0] as keyof typeof RequestType]; + const requests = prefixedRequests.slice(1); + + if (prevRequestType !== undefined && prevRequestType >= currentRequestType) { + throw Error( + `Current request type must be larger than previous request type prevRequestType=${prevRequestType} currentRequestType=${currentRequestType}` + ); + } + + switch (currentRequestType) { + case RequestType.DEPOSIT_REQUEST_TYPE: { + result.deposits = deserializeDepositRequests(requests); + break; + } + case RequestType.WITHDRAWAL_REQUEST_TYPE: { + result.withdrawals = deserializeWithdrawalRequests(requests); + break; + } + case RequestType.CONSOLIDATION_REQUEST_TYPE: { + result.consolidations = deserializeConsolidationRequests(requests); + break; + } + } + prevRequestType = currentRequestType; + } + + return result; } export function deserializeExecutionPayloadBody(data: ExecutionPayloadBodyRpc | null): ExecutionPayloadBody | null {