@lodestar/beacon-node 1.48.0-dev.fd70e5fc15 → 1.48.0-dev.ff9749be2c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +4 -4
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  5. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  6. package/lib/chain/archiveStore/archiveStore.js +1 -0
  7. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  8. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
  10. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  11. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
  13. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  14. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
  15. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/worker.js +4 -1
  18. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  19. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  20. package/lib/chain/archiveStore/utils/archiveBlocks.js +31 -34
  21. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  22. package/lib/chain/blocks/index.d.ts.map +1 -1
  23. package/lib/chain/blocks/index.js +3 -2
  24. package/lib/chain/blocks/index.js.map +1 -1
  25. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  26. package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
  27. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  28. package/lib/chain/chain.d.ts +4 -3
  29. package/lib/chain/chain.d.ts.map +1 -1
  30. package/lib/chain/chain.js +60 -35
  31. package/lib/chain/chain.js.map +1 -1
  32. package/lib/chain/errors/blockError.d.ts +14 -7
  33. package/lib/chain/errors/blockError.d.ts.map +1 -1
  34. package/lib/chain/errors/blockError.js +0 -2
  35. package/lib/chain/errors/blockError.js.map +1 -1
  36. package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
  37. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  38. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  39. package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -0
  40. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  41. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  42. package/lib/chain/interface.d.ts +2 -2
  43. package/lib/chain/interface.d.ts.map +1 -1
  44. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  45. package/lib/chain/prepareNextSlot.js +16 -14
  46. package/lib/chain/prepareNextSlot.js.map +1 -1
  47. package/lib/chain/produceBlock/produceBlockBody.d.ts +4 -2
  48. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  49. package/lib/chain/produceBlock/produceBlockBody.js +11 -8
  50. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  51. package/lib/chain/validation/block.d.ts.map +1 -1
  52. package/lib/chain/validation/block.js +24 -11
  53. package/lib/chain/validation/block.js.map +1 -1
  54. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  55. package/lib/chain/validation/dataColumnSidecar.js +38 -13
  56. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  57. package/lib/chain/validation/executionPayloadEnvelope.js +10 -0
  58. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  59. package/lib/db/beacon.d.ts +14 -5
  60. package/lib/db/beacon.d.ts.map +1 -1
  61. package/lib/db/beacon.js +17 -3
  62. package/lib/db/beacon.js.map +1 -1
  63. package/lib/db/dataColumnStore.d.ts +39 -0
  64. package/lib/db/dataColumnStore.d.ts.map +1 -0
  65. package/lib/db/dataColumnStore.js +81 -0
  66. package/lib/db/dataColumnStore.js.map +1 -0
  67. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  68. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  69. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  70. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  71. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  72. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  73. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  74. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  75. package/lib/db/flatFileStore/errors.d.ts +82 -0
  76. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  77. package/lib/db/flatFileStore/errors.js +32 -0
  78. package/lib/db/flatFileStore/errors.js.map +1 -0
  79. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  80. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  81. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  82. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  83. package/lib/db/flatFileStore/index.d.ts +4 -0
  84. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  85. package/lib/db/flatFileStore/index.js +2 -0
  86. package/lib/db/flatFileStore/index.js.map +1 -0
  87. package/lib/db/flatFileStore/interface.d.ts +25 -0
  88. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  89. package/lib/db/flatFileStore/interface.js +2 -0
  90. package/lib/db/flatFileStore/interface.js.map +1 -0
  91. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  92. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  93. package/lib/db/flatFileStore/metrics.js +25 -0
  94. package/lib/db/flatFileStore/metrics.js.map +1 -0
  95. package/lib/db/flatFileStore/path.d.ts +9 -0
  96. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  97. package/lib/db/flatFileStore/path.js +21 -0
  98. package/lib/db/flatFileStore/path.js.map +1 -0
  99. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  100. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  101. package/lib/db/flatFileStore/slotIndex.js +46 -0
  102. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  103. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  104. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  105. package/lib/db/flatFileStore/snappy.js +12 -0
  106. package/lib/db/flatFileStore/snappy.js.map +1 -0
  107. package/lib/db/index.d.ts +1 -0
  108. package/lib/db/index.d.ts.map +1 -1
  109. package/lib/db/interface.d.ts +8 -1
  110. package/lib/db/interface.d.ts.map +1 -1
  111. package/lib/db/repositories/blockArchive.d.ts +8 -1
  112. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  113. package/lib/db/repositories/blockArchive.js +65 -28
  114. package/lib/db/repositories/blockArchive.js.map +1 -1
  115. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  116. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  117. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  118. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  119. package/lib/metrics/metrics/lodestar.d.ts +18 -0
  120. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  121. package/lib/metrics/metrics/lodestar.js +55 -1
  122. package/lib/metrics/metrics/lodestar.js.map +1 -1
  123. package/lib/monitoring/clientStats.js +1 -1
  124. package/lib/monitoring/clientStats.js.map +1 -1
  125. package/lib/network/gossip/encoding.d.ts.map +1 -1
  126. package/lib/network/gossip/encoding.js +0 -7
  127. package/lib/network/gossip/encoding.js.map +1 -1
  128. package/lib/network/gossip/topic.d.ts +2 -1
  129. package/lib/network/gossip/topic.d.ts.map +1 -1
  130. package/lib/network/gossip/topic.js +4 -20
  131. package/lib/network/gossip/topic.js.map +1 -1
  132. package/lib/network/libp2p/index.d.ts +9 -1
  133. package/lib/network/libp2p/index.d.ts.map +1 -1
  134. package/lib/network/libp2p/index.js +23 -0
  135. package/lib/network/libp2p/index.js.map +1 -1
  136. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  137. package/lib/network/processor/gossipHandlers.js +6 -7
  138. package/lib/network/processor/gossipHandlers.js.map +1 -1
  139. package/lib/network/processor/index.d.ts +49 -3
  140. package/lib/network/processor/index.d.ts.map +1 -1
  141. package/lib/network/processor/index.js +232 -98
  142. package/lib/network/processor/index.js.map +1 -1
  143. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  144. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  145. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  146. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  147. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  148. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  149. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  150. package/lib/network/reqresp/protocols.js +6 -22
  151. package/lib/network/reqresp/protocols.js.map +1 -1
  152. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  153. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  154. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  155. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  156. package/lib/node/nodejs.d.ts +2 -1
  157. package/lib/node/nodejs.d.ts.map +1 -1
  158. package/lib/node/nodejs.js +4 -2
  159. package/lib/node/nodejs.js.map +1 -1
  160. package/lib/util/sszBytes.d.ts +6 -0
  161. package/lib/util/sszBytes.d.ts.map +1 -1
  162. package/lib/util/sszBytes.js +1 -1
  163. package/lib/util/sszBytes.js.map +1 -1
  164. package/package.json +18 -19
  165. package/src/api/impl/beacon/blocks/index.ts +8 -4
  166. package/src/chain/archiveStore/archiveStore.ts +6 -1
  167. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  168. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  169. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  170. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  171. package/src/chain/archiveStore/utils/archiveBlocks.ts +34 -39
  172. package/src/chain/blocks/index.ts +13 -2
  173. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  174. package/src/chain/chain.ts +75 -34
  175. package/src/chain/errors/blockError.ts +19 -10
  176. package/src/chain/errors/dataColumnSidecarError.ts +24 -8
  177. package/src/chain/errors/executionPayloadEnvelope.ts +14 -2
  178. package/src/chain/interface.ts +2 -2
  179. package/src/chain/prepareNextSlot.ts +21 -17
  180. package/src/chain/produceBlock/produceBlockBody.ts +12 -8
  181. package/src/chain/validation/block.ts +24 -12
  182. package/src/chain/validation/dataColumnSidecar.ts +47 -14
  183. package/src/chain/validation/executionPayloadEnvelope.ts +10 -0
  184. package/src/db/beacon.ts +33 -6
  185. package/src/db/dataColumnStore.ts +113 -0
  186. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  187. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  188. package/src/db/flatFileStore/errors.ts +53 -0
  189. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  190. package/src/db/flatFileStore/index.ts +3 -0
  191. package/src/db/flatFileStore/interface.ts +21 -0
  192. package/src/db/flatFileStore/metrics.ts +48 -0
  193. package/src/db/flatFileStore/path.ts +32 -0
  194. package/src/db/flatFileStore/slotIndex.ts +56 -0
  195. package/src/db/flatFileStore/snappy.ts +14 -0
  196. package/src/db/index.ts +1 -0
  197. package/src/db/interface.ts +9 -1
  198. package/src/db/repositories/blockArchive.ts +89 -30
  199. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  200. package/src/metrics/metrics/lodestar.ts +57 -1
  201. package/src/monitoring/clientStats.ts +1 -1
  202. package/src/network/gossip/encoding.ts +0 -7
  203. package/src/network/gossip/topic.ts +3 -25
  204. package/src/network/libp2p/index.ts +28 -1
  205. package/src/network/processor/gossipHandlers.ts +11 -11
  206. package/src/network/processor/index.ts +263 -148
  207. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  208. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  209. package/src/network/reqresp/protocols.ts +7 -24
  210. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  211. package/src/node/nodejs.ts +6 -1
  212. package/src/util/sszBytes.ts +1 -1
@@ -209,8 +209,19 @@ export async function processBlocks(
209
209
  const blockSlot = signedBlock.message.slot;
210
210
  const {state} = err.type;
211
211
  const forkTypes = this.config.getForkTypes(blockSlot);
212
- this.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `${blockSlot}_invalid_signature`);
213
- this.persistInvalidSszBytes("BeaconState", state.serialize(), `${state.slot}_invalid_signature`);
212
+ const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
213
+ this.persistInvalidSszValue(
214
+ forkTypes.SignedBeaconBlock,
215
+ signedBlock,
216
+ `${blockSlot}_invalid_signature`,
217
+ blockRootHex
218
+ );
219
+ this.persistInvalidSszBytes(
220
+ "BeaconState",
221
+ state.serialize(),
222
+ toRootHex(state.hashTreeRoot()),
223
+ `${state.slot}_invalid_signature`
224
+ );
214
225
  } else if (err.type.code === BlockErrorCode.INVALID_STATE_ROOT) {
215
226
  const {signedBlock} = err;
216
227
  const blockSlot = signedBlock.message.slot;
@@ -1,6 +1,6 @@
1
- import {ForkPostDeneb, isForkPostDeneb} from "@lodestar/params";
1
+ import {ForkPostDeneb, ForkPostFulu, isForkPostDeneb} from "@lodestar/params";
2
2
  import {SignedBeaconBlock} from "@lodestar/types";
3
- import {fromHex, toRootHex} from "@lodestar/utils";
3
+ import {toRootHex} from "@lodestar/utils";
4
4
  import {getBlobKzgCommitments} from "../../util/dataColumns.js";
5
5
  import {BeaconChain} from "../chain.js";
6
6
  import {IBlockInput, IDataColumnsInput, isBlockInputBlobs, isBlockInputColumns} from "./blockInput/index.js";
@@ -76,7 +76,6 @@ async function writeBlockAndBlobsToDb(this: BeaconChain, blockInput: IBlockInput
76
76
  */
77
77
  export async function writeDataColumnsToDb(this: BeaconChain, blockInput: IDataColumnsInput): Promise<void> {
78
78
  const {slot, blockRootHex} = blockInput;
79
- const blockRoot = fromHex(blockRootHex);
80
79
 
81
80
  if (!blockInput.hasComputedAllData()) {
82
81
  // Supernodes may only have a subset of the data columns by the time the block begins to be imported
@@ -89,25 +88,19 @@ export async function writeDataColumnsToDb(this: BeaconChain, blockInput: IDataC
89
88
 
90
89
  const {custodyColumns} = this.custodyConfig;
91
90
  const dataColumnSidecars = blockInput.getCustodyColumns();
91
+ const dataColumnSidecarType = this.config.getForkTypes<ForkPostFulu>(slot).DataColumnSidecar;
92
92
 
93
- const binaryPuts: {key: number; value: Uint8Array}[] = [];
94
- const nonbinaryPuts = [];
93
+ const binaryColumns: {index: number; data: Uint8Array}[] = [];
95
94
  for (const dataColumnSidecar of dataColumnSidecars) {
96
- // skip reserializing column if we already have it
97
95
  const serialized = this.serializedCache.get(dataColumnSidecar);
98
- if (serialized) {
99
- binaryPuts.push({key: dataColumnSidecar.index, value: serialized});
100
- } else {
101
- nonbinaryPuts.push(dataColumnSidecar);
102
- }
96
+ binaryColumns.push({
97
+ index: dataColumnSidecar.index,
98
+ data: serialized ?? dataColumnSidecarType.serialize(dataColumnSidecar),
99
+ });
103
100
  }
101
+ await this.db.dataColumns.putManyBinary({slot, blockRoot: blockRootHex}, binaryColumns);
104
102
 
105
- await Promise.all([
106
- this.db.dataColumnSidecar.putManyBinary(blockRoot, binaryPuts),
107
- this.db.dataColumnSidecar.putMany(blockRoot, nonbinaryPuts),
108
- ]);
109
-
110
- this.logger.debug("Persisted dataColumnSidecars to hot DB", {
103
+ this.logger.debug("Persisted dataColumnSidecars", {
111
104
  slot,
112
105
  root: blockRootHex,
113
106
  dataColumnSidecars: dataColumnSidecars.length,
@@ -268,6 +268,7 @@ export class BeaconChain implements IBeaconChain {
268
268
  db,
269
269
  dbName,
270
270
  dataDir,
271
+ dataColumnDir,
271
272
  logger,
272
273
  processShutdownCallback,
273
274
  clock,
@@ -285,6 +286,7 @@ export class BeaconChain implements IBeaconChain {
285
286
  db: IBeaconDb;
286
287
  dbName: string;
287
288
  dataDir: string;
289
+ dataColumnDir?: string;
288
290
  logger: Logger;
289
291
  processShutdownCallback: ProcessShutdownCallback;
290
292
  /** Used for testing to supply fake clock */
@@ -505,7 +507,12 @@ export class BeaconChain implements IBeaconChain {
505
507
 
506
508
  this.archiveStore = new ArchiveStore(
507
509
  {db, chain: this, logger: logger as LoggerNode, metrics},
508
- {...opts, dbName, anchorState: {finalizedCheckpoint: anchorState.finalizedCheckpoint}},
510
+ {
511
+ ...opts,
512
+ dbName,
513
+ dataColumnDir: dataColumnDir ?? path.join(dataDir, "data_columns"),
514
+ anchorState: {finalizedCheckpoint: anchorState.finalizedCheckpoint},
515
+ },
509
516
  signal
510
517
  );
511
518
 
@@ -969,12 +976,15 @@ export class BeaconChain implements IBeaconChain {
969
976
 
970
977
  async getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<DataColumnSidecar[]> {
971
978
  const fork = this.config.getForkName(blockSlot);
979
+ const sidecarsByIndex = new Map<number, DataColumnSidecar>();
972
980
 
973
981
  if (isForkPostGloas(fork)) {
974
982
  // After gloas, columns are tracked in PayloadEnvelopeInput
975
983
  const payloadInput = this.seenPayloadEnvelopeInputCache.get(blockRootHex);
976
984
  if (payloadInput) {
977
- return payloadInput.getAllColumns();
985
+ for (const sidecar of payloadInput.getAllColumns()) {
986
+ sidecarsByIndex.set(sidecar.index, sidecar);
987
+ }
978
988
  }
979
989
  } else {
980
990
  // Before gloas, columns are tracked in BlockInput
@@ -983,16 +993,17 @@ export class BeaconChain implements IBeaconChain {
983
993
  if (!isBlockInputColumns(blockInput)) {
984
994
  throw new Error(`Expected block input to have columns: slot=${blockSlot} root=${blockRootHex}`);
985
995
  }
986
- return blockInput.getAllColumns();
996
+ for (const sidecar of blockInput.getAllColumns()) {
997
+ sidecarsByIndex.set(sidecar.index, sidecar);
998
+ }
987
999
  }
988
1000
  }
989
1001
 
990
- const sidecarsUnfinalized = await this.db.dataColumnSidecar.values(fromHex(blockRootHex));
991
- if (sidecarsUnfinalized.length > 0) {
992
- return sidecarsUnfinalized;
1002
+ for (const sidecar of await this.db.dataColumns.getAll({slot: blockSlot, blockRoot: blockRootHex})) {
1003
+ if (!sidecarsByIndex.has(sidecar.index)) sidecarsByIndex.set(sidecar.index, sidecar);
993
1004
  }
994
- const sidecarsFinalized = await this.db.dataColumnSidecarArchive.values(blockSlot);
995
- return sidecarsFinalized;
1005
+
1006
+ return [...sidecarsByIndex.values()].sort((a, b) => a.index - b.index);
996
1007
  }
997
1008
 
998
1009
  async getSerializedDataColumnSidecars(
@@ -1001,22 +1012,25 @@ export class BeaconChain implements IBeaconChain {
1001
1012
  indices: number[]
1002
1013
  ): Promise<(Uint8Array | undefined)[]> {
1003
1014
  const fork = this.config.getForkName(blockSlot);
1015
+ const dataColumnSidecars: (Uint8Array | undefined)[] = indices.map(() => undefined);
1004
1016
 
1005
1017
  if (isForkPostGloas(fork)) {
1006
1018
  // After gloas, columns are tracked in PayloadEnvelopeInput
1007
1019
  const payloadInput = this.seenPayloadEnvelopeInputCache.get(blockRootHex);
1008
1020
  if (payloadInput) {
1009
- return indices.map((index) => {
1021
+ for (let i = 0; i < indices.length; i++) {
1022
+ const index = indices[i];
1010
1023
  const sidecar = payloadInput.getColumn(index);
1011
1024
  if (!sidecar) {
1012
- return undefined;
1025
+ continue;
1013
1026
  }
1014
1027
  const serialized = this.serializedCache.get(sidecar);
1015
1028
  if (serialized) {
1016
- return serialized;
1029
+ dataColumnSidecars[i] = serialized;
1030
+ } else {
1031
+ dataColumnSidecars[i] = sszTypesFor(fork as ForkPostGloas).DataColumnSidecar.serialize(sidecar);
1017
1032
  }
1018
- return sszTypesFor(fork as ForkPostGloas).DataColumnSidecar.serialize(sidecar);
1019
- });
1033
+ }
1020
1034
  }
1021
1035
  } else {
1022
1036
  // Before gloas, columns are tracked in BlockInput
@@ -1025,26 +1039,42 @@ export class BeaconChain implements IBeaconChain {
1025
1039
  if (!isBlockInputColumns(blockInput)) {
1026
1040
  throw new Error(`Expected block input to have columns: slot=${blockSlot} root=${blockRootHex}`);
1027
1041
  }
1028
- return indices.map((index) => {
1042
+ for (let i = 0; i < indices.length; i++) {
1043
+ const index = indices[i];
1029
1044
  const sidecar = blockInput.getColumn(index);
1030
1045
  if (!sidecar) {
1031
- return undefined;
1046
+ continue;
1032
1047
  }
1033
1048
  const serialized = this.serializedCache.get(sidecar);
1034
1049
  if (serialized) {
1035
- return serialized;
1050
+ dataColumnSidecars[i] = serialized;
1051
+ } else {
1052
+ dataColumnSidecars[i] = sszTypesFor(blockInput.forkName as ForkPostFulu).DataColumnSidecar.serialize(
1053
+ sidecar
1054
+ );
1036
1055
  }
1037
- return sszTypesFor(blockInput.forkName as ForkPostFulu).DataColumnSidecar.serialize(sidecar);
1038
- });
1056
+ }
1039
1057
  }
1040
1058
  }
1041
1059
 
1042
- const sidecarsUnfinalized = await this.db.dataColumnSidecar.getManyBinary(fromHex(blockRootHex), indices);
1043
- if (sidecarsUnfinalized.some((sidecar) => sidecar != null)) {
1044
- return sidecarsUnfinalized;
1060
+ const missingPositions = dataColumnSidecars
1061
+ .map((sidecar, position) => (sidecar === undefined ? position : -1))
1062
+ .filter((position) => position !== -1);
1063
+
1064
+ if (missingPositions.length > 0) {
1065
+ const persistedSidecars = await this.db.dataColumns.getManyBinary(
1066
+ {slot: blockSlot, blockRoot: blockRootHex},
1067
+ missingPositions.map((position) => indices[position])
1068
+ );
1069
+ for (let i = 0; i < missingPositions.length; i++) {
1070
+ const sidecar = persistedSidecars[i];
1071
+ if (sidecar !== undefined) {
1072
+ dataColumnSidecars[missingPositions[i]] = sidecar;
1073
+ }
1074
+ }
1045
1075
  }
1046
- const sidecarsFinalized = await this.db.dataColumnSidecarArchive.getManyBinary(blockSlot, indices);
1047
- return sidecarsFinalized;
1076
+
1077
+ return dataColumnSidecars;
1048
1078
  }
1049
1079
 
1050
1080
  async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody> {
@@ -1387,10 +1417,15 @@ export class BeaconChain implements IBeaconChain {
1387
1417
  const slot = data.slot;
1388
1418
  if (isBlindedBeaconBlock(data)) {
1389
1419
  const sszType = this.config.getPostBellatrixForkTypes(slot).BlindedBeaconBlock;
1390
- void this.persistSszObject("BlindedBeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix);
1420
+ void this.persistSszObject(
1421
+ "BlindedBeaconBlock",
1422
+ sszType.serialize(data),
1423
+ toRootHex(sszType.hashTreeRoot(data)),
1424
+ suffix
1425
+ );
1391
1426
  } else {
1392
1427
  const sszType = this.config.getForkTypes(slot).BeaconBlock;
1393
- void this.persistSszObject("BeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix);
1428
+ void this.persistSszObject("BeaconBlock", sszType.serialize(data), toRootHex(sszType.hashTreeRoot(data)), suffix);
1394
1429
  }
1395
1430
  }
1396
1431
 
@@ -1411,33 +1446,39 @@ export class BeaconChain implements IBeaconChain {
1411
1446
  this.persistSszObject(
1412
1447
  `SignedBeaconBlock_slot_${blockSlot}`,
1413
1448
  blockType.serialize(block),
1414
- blockType.hashTreeRoot(block),
1449
+ toRootHex(this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message)),
1415
1450
  `${logStr}_block`
1416
1451
  ),
1417
1452
  this.persistSszObject(
1418
1453
  `preState_slot_${preState.slot}_BeaconState`,
1419
1454
  preState.serialize(),
1420
- preState.hashTreeRoot(),
1455
+ toRootHex(preState.hashTreeRoot()),
1421
1456
  `${logStr}_pre_state`
1422
1457
  ),
1423
1458
  this.persistSszObject(
1424
1459
  `postState_slot_${postState.slot}_BeaconState`,
1425
1460
  postState.serialize(),
1426
- postState.hashTreeRoot(),
1461
+ toRootHex(postStateRoot),
1427
1462
  `${logStr}_post_state`
1428
1463
  ),
1429
1464
  ]);
1430
1465
  }
1431
1466
 
1432
- persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string): void {
1467
+ persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string, rootHex?: RootHex): void {
1433
1468
  if (this.opts.persistInvalidSszObjects) {
1434
- void this.persistSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix);
1469
+ void this.persistSszObject(
1470
+ type.typeName,
1471
+ type.serialize(sszObject),
1472
+ // in SignedBeaconBlock case, we want to use BeaconBlock root instead
1473
+ rootHex ?? toRootHex(type.hashTreeRoot(sszObject)),
1474
+ suffix
1475
+ );
1435
1476
  }
1436
1477
  }
1437
1478
 
1438
- persistInvalidSszBytes(typeName: string, sszBytes: Uint8Array, suffix?: string): void {
1479
+ persistInvalidSszBytes(typeName: string, sszBytes: Uint8Array, rootHex: RootHex, suffix?: string): void {
1439
1480
  if (this.opts.persistInvalidSszObjects) {
1440
- void this.persistSszObject(typeName, sszBytes, sszBytes, suffix);
1481
+ void this.persistSszObject(typeName, sszBytes, rootHex, suffix);
1441
1482
  }
1442
1483
  }
1443
1484
 
@@ -1568,14 +1609,14 @@ export class BeaconChain implements IBeaconChain {
1568
1609
  return {state: blockState, stateId: "block_state_any_epoch", shouldWarn: true};
1569
1610
  }
1570
1611
 
1571
- private async persistSszObject(prefix: string, bytes: Uint8Array, root: Uint8Array, logStr?: string): Promise<void> {
1612
+ private async persistSszObject(prefix: string, bytes: Uint8Array, rootHex: RootHex, logStr?: string): Promise<void> {
1572
1613
  const now = new Date();
1573
1614
  // yyyy-MM-dd
1574
1615
  const dateStr = now.toISOString().split("T")[0];
1575
1616
 
1576
1617
  // by default store to lodestar_archive of current dir
1577
1618
  const dirpath = path.join(this.opts.persistInvalidSszObjectsDir ?? "invalid_ssz_objects", dateStr);
1578
- const filepath = path.join(dirpath, `${prefix}_${toRootHex(root)}.ssz`);
1619
+ const filepath = path.join(dirpath, `${prefix}_${rootHex}.ssz`);
1579
1620
 
1580
1621
  await ensureDir(dirpath);
1581
1622
 
@@ -78,8 +78,6 @@ export enum BlockErrorCode {
78
78
  TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
79
79
  /** Bid parent block root does not match block parent root */
80
80
  BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
81
- /** A block contains deposits where none are allowed */
82
- NON_ZERO_DEPOSITS = "BLOCK_ERROR_NON_ZERO_DEPOSITS",
83
81
  /** The parent block's execution payload has been verified as invalid */
84
82
  PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
85
83
  /**
@@ -120,9 +118,9 @@ export type BlockErrorType =
120
118
  | {code: BlockErrorCode.ALREADY_KNOWN; root: RootHex}
121
119
  | {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex; root: RootHex}
122
120
  | {code: BlockErrorCode.BLOCK_SLOT_LIMIT_REACHED}
123
- | {code: BlockErrorCode.INCORRECT_PROPOSER; proposerIndex: ValidatorIndex}
124
- | {code: BlockErrorCode.PROPOSAL_SIGNATURE_INVALID; blockSlot: Slot}
125
- | {code: BlockErrorCode.UNKNOWN_PROPOSER; proposerIndex: ValidatorIndex}
121
+ | {code: BlockErrorCode.INCORRECT_PROPOSER; slot: Slot; root: RootHex; proposerIndex: ValidatorIndex}
122
+ | {code: BlockErrorCode.PROPOSAL_SIGNATURE_INVALID; slot: Slot; root: RootHex}
123
+ | {code: BlockErrorCode.UNKNOWN_PROPOSER; slot: Slot; root: RootHex; proposerIndex: ValidatorIndex}
126
124
  | {code: BlockErrorCode.INVALID_SIGNATURE; state: IBeaconStateView}
127
125
  | {
128
126
  code: BlockErrorCode.INVALID_STATE_ROOT;
@@ -132,7 +130,7 @@ export type BlockErrorType =
132
130
  postState: IBeaconStateView;
133
131
  }
134
132
  | {code: BlockErrorCode.NOT_FINALIZED_DESCENDANT; parentRoot: RootHex}
135
- | {code: BlockErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
133
+ | {code: BlockErrorCode.NOT_LATER_THAN_PARENT; slot: Slot; root: RootHex; parentSlot: Slot}
136
134
  | {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}
137
135
  | {code: BlockErrorCode.NON_LINEAR_SLOTS}
138
136
  | {code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH; envelopeBlockRoot: RootHex; blockRoot: RootHex}
@@ -140,7 +138,7 @@ export type BlockErrorType =
140
138
  | {code: BlockErrorCode.BEACON_CHAIN_ERROR; error: Error}
141
139
  | {code: BlockErrorCode.KNOWN_BAD_BLOCK}
142
140
  | {code: BlockErrorCode.BLACKLISTED_BLOCK}
143
- | {code: BlockErrorCode.INCORRECT_TIMESTAMP; timestamp: number; expectedTimestamp: number}
141
+ | {code: BlockErrorCode.INCORRECT_TIMESTAMP; slot: Slot; root: RootHex; timestamp: number; expectedTimestamp: number}
144
142
  | {code: BlockErrorCode.TOO_MUCH_GAS_USED; gasUsed: number; gasLimit: number}
145
143
  | {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex}
146
144
  | {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}
@@ -151,9 +149,20 @@ export type BlockErrorType =
151
149
  errorMessage: string;
152
150
  }
153
151
  | {code: BlockErrorCode.DATA_UNAVAILABLE}
154
- | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
155
- | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
156
- | {code: BlockErrorCode.NON_ZERO_DEPOSITS; count: number}
152
+ | {
153
+ code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS;
154
+ slot: Slot;
155
+ root: RootHex;
156
+ blobKzgCommitmentsLen: number;
157
+ commitmentLimit: number;
158
+ }
159
+ | {
160
+ code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH;
161
+ slot: Slot;
162
+ root: RootHex;
163
+ bidParentRoot: RootHex;
164
+ blockParentRoot: RootHex;
165
+ }
157
166
  | {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
158
167
  | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
159
168
  | {code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS; parentBlockHash: RootHex; expectedBlockHash: RootHex};
@@ -47,10 +47,12 @@ export enum DataColumnSidecarErrorCode {
47
47
  }
48
48
 
49
49
  export type DataColumnSidecarErrorType =
50
- | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; columnIndex: number}
51
- | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; columnIndex: number}
50
+ | {code: DataColumnSidecarErrorCode.INVALID_INDEX; slot: Slot; root: RootHex; columnIndex: number}
51
+ | {code: DataColumnSidecarErrorCode.NO_COMMITMENTS; slot: Slot; root: RootHex; columnIndex: number}
52
52
  | {
53
53
  code: DataColumnSidecarErrorCode.MISMATCHED_LENGTHS;
54
+ slot: Slot;
55
+ root: RootHex;
54
56
  columnLength: number;
55
57
  commitmentsLength: number;
56
58
  proofsLength: number;
@@ -61,10 +63,17 @@ export type DataColumnSidecarErrorType =
61
63
  columnIndex: number;
62
64
  fork: ForkName;
63
65
  }
64
- | {code: DataColumnSidecarErrorCode.INVALID_SUBNET; columnIndex: number; gossipSubnet: SubnetID}
66
+ | {
67
+ code: DataColumnSidecarErrorCode.INVALID_SUBNET;
68
+ slot: Slot;
69
+ root: RootHex;
70
+ columnIndex: number;
71
+ gossipSubnet: SubnetID;
72
+ }
65
73
  | {
66
74
  code: DataColumnSidecarErrorCode.TOO_MANY_KZG_COMMITMENTS;
67
75
  slot: number;
76
+ root: RootHex;
68
77
  columnIndex: number;
69
78
  count: number;
70
79
  limit: number;
@@ -85,12 +94,12 @@ export type DataColumnSidecarErrorType =
85
94
  | {
86
95
  code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID;
87
96
  slot: Slot;
88
- blockRoot: RootHex;
97
+ root: RootHex;
89
98
  index: number;
90
99
  }
91
- | {code: DataColumnSidecarErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
92
- | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; columnIndex: number}
93
- | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; columnIndex: number}
100
+ | {code: DataColumnSidecarErrorCode.NOT_LATER_THAN_PARENT; slot: Slot; root: RootHex; parentSlot: Slot}
101
+ | {code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID; slot: Slot; root: RootHex; columnIndex: number}
102
+ | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF; slot: Slot; root: RootHex; columnIndex: number}
94
103
  | {code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_COUNT; slot: number; expected: number; actual: number}
95
104
  | {
96
105
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK;
@@ -106,6 +115,7 @@ export type DataColumnSidecarErrorType =
106
115
  }
107
116
  | {
108
117
  code: DataColumnSidecarErrorCode.INCORRECT_SIDECAR_SLOT;
118
+ root: RootHex;
109
119
  columnIndex: number;
110
120
  expected: Slot;
111
121
  actual: Slot;
@@ -127,7 +137,13 @@ export type DataColumnSidecarErrorType =
127
137
  actual: number;
128
138
  }
129
139
  | {code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF_BATCH; slot: number; reason: string}
130
- | {code: DataColumnSidecarErrorCode.INCORRECT_PROPOSER; actualProposerIndex: number; expectedProposerIndex: number}
140
+ | {
141
+ code: DataColumnSidecarErrorCode.INCORRECT_PROPOSER;
142
+ slot: Slot;
143
+ root: RootHex;
144
+ actualProposerIndex: number;
145
+ expectedProposerIndex: number;
146
+ }
131
147
  | {code: DataColumnSidecarErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; slot: Slot; blockRoot: RootHex};
132
148
 
133
149
  export class DataColumnSidecarGossipError extends GossipActionError<DataColumnSidecarErrorType> {}
@@ -26,23 +26,35 @@ export type ExecutionPayloadEnvelopeErrorType =
26
26
  slot: Slot;
27
27
  }
28
28
  | {code: ExecutionPayloadEnvelopeErrorCode.INVALID_BLOCK; blockRoot: RootHex}
29
- | {code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH; envelopeSlot: Slot; blockSlot: Slot}
29
+ | {
30
+ code: ExecutionPayloadEnvelopeErrorCode.SLOT_MISMATCH;
31
+ slot: Slot;
32
+ root: RootHex;
33
+ envelopeSlot: Slot;
34
+ blockSlot: Slot;
35
+ }
30
36
  | {
31
37
  code: ExecutionPayloadEnvelopeErrorCode.BUILDER_INDEX_MISMATCH;
38
+ slot: Slot;
39
+ root: RootHex;
32
40
  envelopeBuilderIndex: BuilderIndex;
33
41
  bidBuilderIndex: BuilderIndex | null;
34
42
  }
35
43
  | {
36
44
  code: ExecutionPayloadEnvelopeErrorCode.BLOCK_HASH_MISMATCH;
45
+ slot: Slot;
46
+ root: RootHex;
37
47
  envelopeBlockHash: RootHex;
38
48
  bidBlockHash: RootHex | null;
39
49
  }
40
50
  | {
41
51
  code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH;
52
+ slot: Slot;
53
+ root: RootHex;
42
54
  envelopeRequestsRoot: RootHex;
43
55
  bidRequestsRoot: RootHex;
44
56
  }
45
- | {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
57
+ | {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE; slot: Slot; root: RootHex}
46
58
  | {code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; blockRoot: RootHex};
47
59
 
48
60
  export class ExecutionPayloadEnvelopeError extends GossipActionError<ExecutionPayloadEnvelopeErrorType> {}
@@ -290,8 +290,8 @@ export interface IBeaconChain {
290
290
  postState: IBeaconStateView,
291
291
  block: SignedBeaconBlock
292
292
  ): Promise<void>;
293
- persistInvalidSszValue<T>(type: Type<T>, sszObject: T | Uint8Array, suffix?: string): void;
294
- persistInvalidSszBytes(type: string, sszBytes: Uint8Array, suffix?: string): void;
293
+ persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string, rootHex?: RootHex): void;
294
+ persistInvalidSszBytes(type: string, sszBytes: Uint8Array, rootHex: RootHex, suffix?: string): void;
295
295
  regenStateForAttestationVerification(
296
296
  attEpoch: Epoch,
297
297
  shufflingDependentRoot: RootHex,
@@ -25,6 +25,7 @@ import {Bytes32, Slot, ValidatorIndex} from "@lodestar/types";
25
25
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
26
26
  import {GENESIS_SLOT} from "../constants/constants.js";
27
27
  import {BuilderStatus} from "../execution/builder/http.js";
28
+ import {PayloadAttributes} from "../execution/index.js";
28
29
  import {Metrics} from "../metrics/index.js";
29
30
  import {ClockEvent} from "../util/clock.js";
30
31
  import {isQueueErrorAborted} from "../util/queue/index.js";
@@ -243,6 +244,24 @@ export class PrepareNextSlotScheduler {
243
244
  parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
244
245
  }
245
246
 
247
+ let payloadAttributes: PayloadAttributes | undefined;
248
+ // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
249
+ // every slot. Without the flag, only emit the event if we are proposing in the next slot.
250
+ if (
251
+ (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
252
+ this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
253
+ ) {
254
+ const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
255
+ prepareState: stateAfterParentPayload,
256
+ prepareSlot,
257
+ parentBlockRoot: fromHex(updatedHead.blockRoot),
258
+ parentBlockHash,
259
+ feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
260
+ });
261
+ this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
262
+ payloadAttributes = data.payloadAttributes;
263
+ }
264
+
246
265
  if (feeRecipient) {
247
266
  const preparationTime =
248
267
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -263,7 +282,8 @@ export class PrepareNextSlotScheduler {
263
282
  safeBlockHash,
264
283
  finalizedBlockHash,
265
284
  stateAfterParentPayload,
266
- feeRecipient
285
+ feeRecipient,
286
+ payloadAttributes
267
287
  );
268
288
  this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
269
289
  prepareSlot,
@@ -273,22 +293,6 @@ export class PrepareNextSlotScheduler {
273
293
  }
274
294
 
275
295
  this.computeStateHashTreeRoot(preparedState, isEpochTransition);
276
-
277
- // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
278
- // every slot. Without the flag, only emit the event if we are proposing in the next slot.
279
- if (
280
- (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
281
- this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
282
- ) {
283
- const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
284
- prepareState: stateAfterParentPayload,
285
- prepareSlot,
286
- parentBlockRoot: fromHex(updatedHead.blockRoot),
287
- parentBlockHash,
288
- feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
289
- });
290
- this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
291
- }
292
296
  } else {
293
297
  // Pre-bellatrix only reaches here at an epoch transition to precompute the next epoch state
294
298
  const preparedState = await this.chain.regen.getBlockSlotState(
@@ -735,7 +735,9 @@ export async function prepareExecutionPayload(
735
735
  * parent execution payload first (see `withParentPayloadApplied`).
736
736
  */
737
737
  state: IBeaconStateViewBellatrix,
738
- suggestedFeeRecipient: string
738
+ suggestedFeeRecipient: string,
739
+ /** Attributes already computed for the same state and fee recipient, e.g. for the SSE event */
740
+ payloadAttributes?: PayloadAttributes
739
741
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
740
742
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
741
743
  const prevRandao = state.getRandaoMix(state.epoch);
@@ -766,13 +768,15 @@ export async function prepareExecutionPayload(
766
768
  prepType = PayloadPreparationType.Fresh;
767
769
  }
768
770
 
769
- const attributes: PayloadAttributes = preparePayloadAttributes(fork, chain, {
770
- prepareState: state,
771
- prepareSlot: state.slot,
772
- parentBlockRoot,
773
- parentBlockHash,
774
- feeRecipient: suggestedFeeRecipient,
775
- });
771
+ const attributes: PayloadAttributes =
772
+ payloadAttributes ??
773
+ preparePayloadAttributes(fork, chain, {
774
+ prepareState: state,
775
+ prepareSlot: state.slot,
776
+ parentBlockRoot,
777
+ parentBlockHash,
778
+ feeRecipient: suggestedFeeRecipient,
779
+ });
776
780
 
777
781
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
778
782
  fork,