From 76ee6b33d21f6eb646d3f95d1efd22f033f1651d Mon Sep 17 00:00:00 2001
From: NC <17676176+ensi321@users.noreply.github.com>
Date: Wed, 4 Dec 2024 12:16:12 -0800
Subject: [PATCH] feat: exclude empty requests in execution requests list
 (#7196)

* initial commit

* Address comment

* Lint
---
 .../src/execution/engine/interface.ts         |  7 ++
 .../beacon-node/src/execution/engine/types.ts | 92 +++++++++++++++----
 2 files changed, 81 insertions(+), 18 deletions(-)

diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts
index c32cc1bc7215..f62bf50d8074 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 f35a63aa3d96..def5831b7609 100644
--- a/packages/beacon-node/src/execution/engine/types.ts
+++ b/packages/beacon-node/src/execution/engine/types.ts
@@ -17,7 +17,7 @@ import {
   quantityToBigint,
   quantityToNum,
 } from "../../eth1/provider/utils.js";
-import {BlobsBundle, ExecutionPayloadStatus, PayloadAttributes, VersionedHashes} from "./interface.js";
+import {BlobsBundle, ExecutionPayloadStatus, PayloadAttributes, RequestType, VersionedHashes} from "./interface.js";
 import {WithdrawalV1} from "./payloadIdCache.js";
 
 export type EngineApiRpcParamTypes = {
@@ -165,12 +165,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;
@@ -404,8 +404,20 @@ export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdr
   } as capella.Withdrawal;
 }
 
+/**
+ * Prepend a single-byte requestType to requestsBytes
+ */
+function prefixRequests(requestsBytes: Uint8Array, requestType: RequestType): Uint8Array {
+  const prefixedRequests = new Uint8Array(1 + requestsBytes.length);
+  prefixedRequests[0] = requestType;
+  prefixedRequests.set(requestsBytes, 1);
+
+  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 {
@@ -413,17 +425,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 {
@@ -436,22 +450,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 {