@lodestar/beacon-node 1.41.0-dev.9fa839a030 → 1.41.0-dev.a35cbde8b3

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 (104) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +121 -3
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lightclient/index.d.ts.map +1 -1
  5. package/lib/api/impl/lightclient/index.js +19 -2
  6. package/lib/api/impl/lightclient/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +101 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  11. package/lib/chain/blocks/importBlock.js +0 -2
  12. package/lib/chain/blocks/importBlock.js.map +1 -1
  13. package/lib/chain/blocks/index.d.ts.map +1 -1
  14. package/lib/chain/blocks/index.js +2 -1
  15. package/lib/chain/blocks/index.js.map +1 -1
  16. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  17. package/lib/chain/blocks/writeBlockInputToDb.js +3 -0
  18. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  19. package/lib/chain/chain.d.ts.map +1 -1
  20. package/lib/chain/chain.js +23 -5
  21. package/lib/chain/chain.js.map +1 -1
  22. package/lib/chain/emitter.d.ts +2 -2
  23. package/lib/chain/emitter.d.ts.map +1 -1
  24. package/lib/chain/lightClient/index.d.ts.map +1 -1
  25. package/lib/chain/lightClient/index.js +1 -1
  26. package/lib/chain/lightClient/index.js.map +1 -1
  27. package/lib/chain/options.d.ts.map +1 -1
  28. package/lib/chain/options.js.map +1 -1
  29. package/lib/chain/prepareNextSlot.js +3 -3
  30. package/lib/chain/prepareNextSlot.js.map +1 -1
  31. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +10 -2
  32. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  33. package/lib/chain/produceBlock/computeNewStateRoot.js +24 -2
  34. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  35. package/lib/chain/produceBlock/produceBlockBody.d.ts +22 -7
  36. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  37. package/lib/chain/produceBlock/produceBlockBody.js +102 -9
  38. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  39. package/lib/chain/validation/dataColumnSidecar.d.ts +2 -2
  40. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  41. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  42. package/lib/metrics/metrics/beacon.d.ts +1 -0
  43. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  44. package/lib/metrics/metrics/beacon.js +5 -0
  45. package/lib/metrics/metrics/beacon.js.map +1 -1
  46. package/lib/metrics/metrics/lodestar.d.ts +5 -0
  47. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  48. package/lib/metrics/metrics/lodestar.js +9 -0
  49. package/lib/metrics/metrics/lodestar.js.map +1 -1
  50. package/lib/metrics/metrics.d.ts.map +1 -1
  51. package/lib/metrics/metrics.js +8 -3
  52. package/lib/metrics/metrics.js.map +1 -1
  53. package/lib/network/gossip/interface.d.ts +3 -3
  54. package/lib/network/gossip/interface.d.ts.map +1 -1
  55. package/lib/network/gossip/topic.d.ts +66 -16
  56. package/lib/network/gossip/topic.d.ts.map +1 -1
  57. package/lib/network/gossip/topic.js +2 -2
  58. package/lib/network/gossip/topic.js.map +1 -1
  59. package/lib/network/interface.d.ts +3 -2
  60. package/lib/network/interface.d.ts.map +1 -1
  61. package/lib/network/network.d.ts +3 -2
  62. package/lib/network/network.d.ts.map +1 -1
  63. package/lib/network/network.js +10 -1
  64. package/lib/network/network.js.map +1 -1
  65. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  66. package/lib/network/processor/gossipHandlers.js +5 -1
  67. package/lib/network/processor/gossipHandlers.js.map +1 -1
  68. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.d.ts.map +1 -1
  69. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js +7 -1
  70. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js.map +1 -1
  71. package/lib/util/dataColumns.d.ts +11 -1
  72. package/lib/util/dataColumns.d.ts.map +1 -1
  73. package/lib/util/dataColumns.js +31 -0
  74. package/lib/util/dataColumns.js.map +1 -1
  75. package/lib/util/serializedCache.d.ts +5 -0
  76. package/lib/util/serializedCache.d.ts.map +1 -1
  77. package/lib/util/serializedCache.js +5 -0
  78. package/lib/util/serializedCache.js.map +1 -1
  79. package/package.json +15 -15
  80. package/src/api/impl/beacon/blocks/index.ts +145 -2
  81. package/src/api/impl/lightclient/index.ts +19 -2
  82. package/src/api/impl/validator/index.ts +124 -1
  83. package/src/chain/blocks/importBlock.ts +0 -3
  84. package/src/chain/blocks/index.ts +2 -1
  85. package/src/chain/blocks/writeBlockInputToDb.ts +3 -0
  86. package/src/chain/chain.ts +35 -6
  87. package/src/chain/emitter.ts +2 -2
  88. package/src/chain/lightClient/index.ts +4 -1
  89. package/src/chain/options.ts +1 -0
  90. package/src/chain/prepareNextSlot.ts +5 -5
  91. package/src/chain/produceBlock/computeNewStateRoot.ts +35 -3
  92. package/src/chain/produceBlock/produceBlockBody.ts +154 -11
  93. package/src/chain/validation/dataColumnSidecar.ts +2 -5
  94. package/src/metrics/metrics/beacon.ts +5 -0
  95. package/src/metrics/metrics/lodestar.ts +9 -0
  96. package/src/metrics/metrics.ts +8 -3
  97. package/src/network/gossip/interface.ts +3 -3
  98. package/src/network/gossip/topic.ts +2 -1
  99. package/src/network/interface.ts +4 -1
  100. package/src/network/network.ts +20 -4
  101. package/src/network/processor/gossipHandlers.ts +7 -1
  102. package/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +6 -1
  103. package/src/util/dataColumns.ts +41 -0
  104. package/src/util/serializedCache.ts +5 -0
@@ -1,11 +1,14 @@
1
1
  import {
2
2
  CachedBeaconStateAllForks,
3
+ CachedBeaconStateGloas,
3
4
  DataAvailabilityStatus,
4
5
  ExecutionPayloadStatus,
6
+ G2_POINT_AT_INFINITY,
5
7
  StateHashTreeRootSource,
6
8
  stateTransition,
7
9
  } from "@lodestar/state-transition";
8
- import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types";
10
+ import {processExecutionPayloadEnvelope} from "@lodestar/state-transition/block";
11
+ import {BeaconBlock, BlindedBeaconBlock, Gwei, Root, gloas} from "@lodestar/types";
9
12
  import {ZERO_HASH} from "../../constants/index.js";
10
13
  import {Metrics} from "../../metrics/index.js";
11
14
 
@@ -18,7 +21,7 @@ export function computeNewStateRoot(
18
21
  metrics: Metrics | null,
19
22
  state: CachedBeaconStateAllForks,
20
23
  block: BeaconBlock | BlindedBeaconBlock
21
- ): {newStateRoot: Root; proposerReward: Gwei} {
24
+ ): {newStateRoot: Root; proposerReward: Gwei; postState: CachedBeaconStateAllForks} {
22
25
  // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
23
26
  const blockEmptySig = {message: block, signature: ZERO_HASH};
24
27
 
@@ -51,5 +54,34 @@ export function computeNewStateRoot(
51
54
  const newStateRoot = postState.hashTreeRoot();
52
55
  hashTreeRootTimer?.();
53
56
 
54
- return {newStateRoot, proposerReward};
57
+ return {newStateRoot, proposerReward, postState};
58
+ }
59
+
60
+ /**
61
+ * Compute the state root after processing an execution payload envelope.
62
+ * Similar to `computeNewStateRoot` but for payload envelope processing.
63
+ *
64
+ * The `postBlockState` is mutated in place, callers must ensure it is not needed afterward.
65
+ */
66
+ export function computeEnvelopeStateRoot(
67
+ metrics: Metrics | null,
68
+ postBlockState: CachedBeaconStateGloas,
69
+ envelope: gloas.ExecutionPayloadEnvelope
70
+ ): Root {
71
+ const signedEnvelope: gloas.SignedExecutionPayloadEnvelope = {
72
+ message: envelope,
73
+ signature: G2_POINT_AT_INFINITY,
74
+ };
75
+
76
+ const processEnvelopeTimer = metrics?.blockPayload.executionPayloadEnvelopeProcessingTime.startTimer();
77
+ processExecutionPayloadEnvelope(postBlockState, signedEnvelope, false);
78
+ processEnvelopeTimer?.();
79
+
80
+ const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
81
+ source: StateHashTreeRootSource.computeEnvelopeStateRoot,
82
+ });
83
+ const stateRoot = postBlockState.hashTreeRoot();
84
+ hashTreeRootTimer?.();
85
+
86
+ return stateRoot;
55
87
  }
@@ -1,10 +1,12 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ProtoBlock, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
2
+ import {IForkChoice, ProtoBlock, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
3
  import {
4
+ BUILDER_INDEX_SELF_BUILD,
4
5
  ForkName,
5
6
  ForkPostBellatrix,
6
7
  ForkPostDeneb,
7
8
  ForkPostFulu,
9
+ ForkPostGloas,
8
10
  ForkPreGloas,
9
11
  ForkSeq,
10
12
  isForkPostAltair,
@@ -16,6 +18,8 @@ import {
16
18
  CachedBeaconStateBellatrix,
17
19
  CachedBeaconStateCapella,
18
20
  CachedBeaconStateExecutions,
21
+ CachedBeaconStateGloas,
22
+ G2_POINT_AT_INFINITY,
19
23
  computeTimeAtSlot,
20
24
  getExpectedWithdrawals,
21
25
  getRandaoMix,
@@ -42,6 +46,7 @@ import {
42
46
  deneb,
43
47
  electra,
44
48
  fulu,
49
+ gloas,
45
50
  } from "@lodestar/types";
46
51
  import {Logger, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
47
52
  import {ZERO_HASH_HEX} from "../../constants/index.js";
@@ -99,6 +104,20 @@ export type AssembledBodyType<T extends BlockType> = T extends BlockType.Full
99
104
  : BlindedBeaconBlockBody;
100
105
  export type AssembledBlockType<T extends BlockType> = T extends BlockType.Full ? BeaconBlock : BlindedBeaconBlock;
101
106
 
107
+ export type ProduceFullGloas = {
108
+ type: BlockType.Full;
109
+ fork: ForkPostGloas;
110
+ executionPayload: ExecutionPayload<ForkPostGloas>;
111
+ executionRequests: electra.ExecutionRequests;
112
+ blobsBundle: BlobsBundle<ForkPostGloas>;
113
+ cells: fulu.Cell[][];
114
+ /**
115
+ * Cached envelope state root computed during block production.
116
+ * This is the state root after running `processExecutionPayloadEnvelope` on the
117
+ * post-block state, and later used to construct the `ExecutionPayloadEnvelope`.
118
+ */
119
+ envelopeStateRoot: Root;
120
+ };
102
121
  export type ProduceFullFulu = {
103
122
  type: BlockType.Full;
104
123
  fork: ForkPostFulu;
@@ -131,6 +150,7 @@ export type ProduceBlinded = {
131
150
 
132
151
  /** The result of local block production, everything that's not the block itself */
133
152
  export type ProduceResult =
153
+ | ProduceFullGloas
134
154
  | ProduceFullFulu
135
155
  | ProduceFullDeneb
136
156
  | ProduceFullBellatrix
@@ -180,12 +200,112 @@ export async function produceBlockBody<T extends BlockType>(
180
200
  this.logger.verbose("Producing beacon block body", logMeta);
181
201
 
182
202
  if (isForkPostGloas(fork)) {
183
- // TODO GLOAS: Set body.signedExecutionPayloadBid and body.payloadAttestation
203
+ // TODO GLOAS: support non self-building here, the block type differentiation between
204
+ // full and blinded no longer makes sense in gloas, it might be a good idea to move
205
+ // this into a completely separate function and have pre/post gloas more separated
206
+ const gloasState = currentState as CachedBeaconStateGloas;
207
+ const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
208
+ const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
209
+ const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
210
+
211
+ const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer();
212
+
213
+ this.logger.verbose("Preparing execution payload from engine", {
214
+ slot: blockSlot,
215
+ parentBlockRoot: toRootHex(parentBlockRoot),
216
+ feeRecipient,
217
+ });
218
+
219
+ // Get execution payload from EL
220
+ const prepareRes = await prepareExecutionPayload(
221
+ this,
222
+ this.logger,
223
+ fork,
224
+ parentBlockRoot,
225
+ safeBlockHash,
226
+ finalizedBlockHash ?? ZERO_HASH_HEX,
227
+ gloasState,
228
+ feeRecipient
229
+ );
230
+
231
+ const {prepType, payloadId} = prepareRes;
232
+ Object.assign(logMeta, {executionPayloadPrepType: prepType});
233
+
234
+ if (prepType !== PayloadPreparationType.Cached) {
235
+ await sleep(PAYLOAD_GENERATION_TIME_MS);
236
+ }
237
+
238
+ this.logger.verbose("Fetching execution payload from engine", {slot: blockSlot, payloadId});
239
+ const payloadRes = await this.executionEngine.getPayload(fork, payloadId);
240
+
241
+ endExecutionPayload?.({step: BlockProductionStep.executionPayload});
242
+
243
+ const {executionPayload, blobsBundle, executionRequests} = payloadRes;
244
+ executionPayloadValue = payloadRes.executionPayloadValue;
245
+ shouldOverrideBuilder = payloadRes.shouldOverrideBuilder;
246
+
247
+ if (blobsBundle === undefined) {
248
+ throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`);
249
+ }
250
+ if (executionRequests === undefined) {
251
+ throw Error(`Missing executionRequests response from getPayload at fork=${fork}`);
252
+ }
253
+
254
+ const cells = blobsBundle.blobs.map((blob) => kzg.computeCells(blob));
255
+ if (this.opts.sanityCheckExecutionEngineBlobs) {
256
+ await validateCellsAndKzgCommitments(blobsBundle.commitments, blobsBundle.proofs, cells);
257
+ }
258
+
259
+ // Create self-build execution payload bid
260
+ const bid: gloas.ExecutionPayloadBid = {
261
+ parentBlockHash: gloasState.latestBlockHash,
262
+ parentBlockRoot: parentBlockRoot,
263
+ blockHash: executionPayload.blockHash,
264
+ prevRandao: getRandaoMix(gloasState, gloasState.epochCtx.epoch),
265
+ feeRecipient: executionPayload.feeRecipient,
266
+ gasLimit: BigInt(executionPayload.gasLimit),
267
+ builderIndex: BUILDER_INDEX_SELF_BUILD,
268
+ slot: blockSlot,
269
+ value: 0,
270
+ executionPayment: 0,
271
+ blobKzgCommitments: blobsBundle.commitments,
272
+ };
273
+ const signedBid: gloas.SignedExecutionPayloadBid = {
274
+ message: bid,
275
+ signature: G2_POINT_AT_INFINITY,
276
+ };
277
+
184
278
  const commonBlockBody = await commonBlockBodyPromise;
185
- blockBody = Object.assign({}, commonBlockBody) as AssembledBodyType<T>;
186
- executionPayloadValue = BigInt(0);
279
+ const gloasBody = Object.assign({}, commonBlockBody) as gloas.BeaconBlockBody;
280
+ gloasBody.signedExecutionPayloadBid = signedBid;
281
+ // TODO GLOAS: Get payload attestations from pool for previous slot
282
+ gloasBody.payloadAttestations = [];
283
+ blockBody = gloasBody as AssembledBodyType<T>;
284
+
285
+ // Store execution payload data required to construct execution payload envelope later
286
+ const gloasResult = produceResult as ProduceFullGloas;
287
+ gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
288
+ gloasResult.executionRequests = executionRequests;
289
+ gloasResult.blobsBundle = blobsBundle;
290
+ gloasResult.cells = cells;
291
+
292
+ const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime);
293
+ this.metrics?.blockPayload.payloadFetchedTime.observe({prepType}, fetchedTime);
294
+ this.logger.verbose("Produced block with self-build bid", {
295
+ slot: blockSlot,
296
+ executionPayloadValue,
297
+ prepType,
298
+ payloadId,
299
+ fetchedTime,
300
+ executionBlockHash: toRootHex(executionPayload.blockHash),
301
+ blobs: blobsBundle.commitments.length,
302
+ });
187
303
 
188
- // We don't deal with blinded blocks, execution engine, blobs and execution requests post-gloas
304
+ Object.assign(logMeta, {
305
+ transactions: executionPayload.transactions.length,
306
+ blobs: blobsBundle.commitments.length,
307
+ shouldOverrideBuilder,
308
+ });
189
309
  } else if (isForkPostBellatrix(fork)) {
190
310
  const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
191
311
  const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
@@ -480,10 +600,12 @@ export async function prepareExecutionPayload(
480
600
  parentBlockRoot: Root,
481
601
  safeBlockHash: RootHex,
482
602
  finalizedBlockHash: RootHex,
483
- state: CachedBeaconStateExecutions,
603
+ state: CachedBeaconStateExecutions | CachedBeaconStateGloas,
484
604
  suggestedFeeRecipient: string
485
605
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
486
- const parentHash = state.latestExecutionPayloadHeader.blockHash;
606
+ const parentHash = isForkPostGloas(fork)
607
+ ? (state as CachedBeaconStateGloas).latestBlockHash
608
+ : (state as CachedBeaconStateExecutions).latestExecutionPayloadHeader.blockHash;
487
609
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
488
610
  const prevRandao = getRandaoMix(state, state.epochCtx.epoch);
489
611
 
@@ -568,25 +690,46 @@ export function getPayloadAttributesForSSE(
568
690
  fork: ForkPostBellatrix,
569
691
  chain: {
570
692
  config: ChainForkConfig;
693
+ forkChoice: IForkChoice;
571
694
  },
572
695
  {
573
696
  prepareState,
574
697
  prepareSlot,
575
698
  parentBlockRoot,
576
699
  feeRecipient,
577
- }: {prepareState: CachedBeaconStateExecutions; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string}
700
+ }: {
701
+ prepareState: CachedBeaconStateExecutions | CachedBeaconStateGloas;
702
+ prepareSlot: Slot;
703
+ parentBlockRoot: Root;
704
+ feeRecipient: string;
705
+ }
578
706
  ): SSEPayloadAttributes {
579
- const parentHash = prepareState.latestExecutionPayloadHeader.blockHash;
707
+ const parentHash = isForkPostGloas(fork)
708
+ ? (prepareState as CachedBeaconStateGloas).latestBlockHash
709
+ : (prepareState as CachedBeaconStateExecutions).latestExecutionPayloadHeader.blockHash;
580
710
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
581
711
  prepareState,
582
712
  prepareSlot,
583
713
  parentBlockRoot,
584
714
  feeRecipient,
585
715
  });
716
+
717
+ let parentBlockNumber: number;
718
+ if (isForkPostGloas(fork)) {
719
+ // TODO GLOAS: revisit this after fork choice changes are merged
720
+ const parentBlock = chain.forkChoice.getBlock(parentBlockRoot);
721
+ if (parentBlock?.executionPayloadBlockHash == null) {
722
+ throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
723
+ }
724
+ parentBlockNumber = parentBlock.executionPayloadNumber;
725
+ } else {
726
+ parentBlockNumber = (prepareState as CachedBeaconStateExecutions).latestExecutionPayloadHeader.blockNumber;
727
+ }
728
+
586
729
  const ssePayloadAttributes: SSEPayloadAttributes = {
587
730
  proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot),
588
731
  proposalSlot: prepareSlot,
589
- parentBlockNumber: prepareState.latestExecutionPayloadHeader.blockNumber,
732
+ parentBlockNumber,
590
733
  parentBlockRoot,
591
734
  parentBlockHash: parentHash,
592
735
  payloadAttributes,
@@ -605,7 +748,7 @@ function preparePayloadAttributes(
605
748
  parentBlockRoot,
606
749
  feeRecipient,
607
750
  }: {
608
- prepareState: CachedBeaconStateExecutions;
751
+ prepareState: CachedBeaconStateExecutions | CachedBeaconStateGloas;
609
752
  prepareSlot: Slot;
610
753
  parentBlockRoot: Root;
611
754
  feeRecipient: string;
@@ -10,7 +10,7 @@ import {
10
10
  getBlockHeaderProposerSignatureSetByHeaderSlot,
11
11
  getBlockHeaderProposerSignatureSetByParentStateSlot,
12
12
  } from "@lodestar/state-transition";
13
- import {Root, Slot, SubnetID, fulu, ssz} from "@lodestar/types";
13
+ import {DataColumnSidecar, Root, Slot, SubnetID, fulu, ssz} from "@lodestar/types";
14
14
  import {byteArrayEquals, toRootHex, verifyMerkleBranch} from "@lodestar/utils";
15
15
  import {Metrics} from "../../metrics/metrics.js";
16
16
  import {kzg} from "../../util/kzg.js";
@@ -457,9 +457,6 @@ export async function validateBlockDataColumnSidecars(
457
457
  * SPEC FUNCTION
458
458
  * https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.4/specs/fulu/p2p-interface.md#compute_subnet_for_data_column_sidecar
459
459
  */
460
- export function computeSubnetForDataColumnSidecar(
461
- config: ChainConfig,
462
- columnSidecar: fulu.DataColumnSidecar
463
- ): SubnetID {
460
+ export function computeSubnetForDataColumnSidecar(config: ChainConfig, columnSidecar: DataColumnSidecar): SubnetID {
464
461
  return columnSidecar.index % config.DATA_COLUMN_SIDECAR_SUBNET_COUNT;
465
462
  }
@@ -144,6 +144,11 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
144
144
  help: "Time for preparing payload in advance",
145
145
  buckets: [0.1, 1, 3, 5, 10],
146
146
  }),
147
+ executionPayloadEnvelopeProcessingTime: register.histogram({
148
+ name: "beacon_block_payload_envelope_processing_seconds",
149
+ help: "Time to process execution payload envelope during block production",
150
+ buckets: [0.005, 0.01, 0.05, 0.1, 0.2, 0.5, 1],
151
+ }),
147
152
  payloadFetchedTime: register.histogram<{prepType: PayloadPreparationType}>({
148
153
  name: "beacon_block_payload_fetched_time",
149
154
  help: "Time to fetch the payload from EL",
@@ -827,6 +827,15 @@ export function createLodestarMetrics(
827
827
  help: "Total number of blobs retrieved from execution engine and published to gossip",
828
828
  }),
829
829
  },
830
+ // Gossip execution payload envelope
831
+ gossipExecutionPayloadEnvelope: {
832
+ elapsedTimeTillReceived: register.histogram<{source: OpSource}>({
833
+ name: "lodestar_gossip_execution_payload_envelope_elapsed_time_till_received",
834
+ help: "Time elapsed between slot time and the time execution payload envelope received",
835
+ labelNames: ["source"],
836
+ buckets: [0.5, 1, 2, 4, 6, 12],
837
+ }),
838
+ },
830
839
  recoverDataColumnSidecars: {
831
840
  recoverTime: register.histogram({
832
841
  name: "lodestar_recover_data_column_sidecar_recover_time_seconds",
@@ -19,11 +19,16 @@ export function createMetrics(opts: MetricsOptions, genesisTime: number, externa
19
19
  const lodestar = createLodestarMetrics(register, opts.metadata, genesisTime);
20
20
  const stateTransition = getMetrics(register);
21
21
 
22
- process.on("unhandledRejection", (_error) => {
22
+ const onUnhandledRejection = (_error: unknown): void => {
23
23
  lodestar.unhandledPromiseRejections.inc();
24
- });
24
+ };
25
+ process.on("unhandledRejection", onUnhandledRejection);
25
26
 
26
- const close = collectNodeJSMetrics(register);
27
+ const nodeJsMetricsClose = collectNodeJSMetrics(register);
28
+ const close = (): void => {
29
+ process.removeListener("unhandledRejection", onUnhandledRejection);
30
+ nodeJsMetricsClose();
31
+ };
27
32
 
28
33
  // Merge external registries
29
34
  for (const externalRegister of externalRegistries) {
@@ -4,6 +4,7 @@ import {PeerIdStr} from "@chainsafe/libp2p-gossipsub/types";
4
4
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
5
5
  import {
6
6
  AttesterSlashing,
7
+ DataColumnSidecar,
7
8
  LightClientFinalityUpdate,
8
9
  LightClientOptimisticUpdate,
9
10
  SignedAggregateAndProof,
@@ -14,7 +15,6 @@ import {
14
15
  altair,
15
16
  capella,
16
17
  deneb,
17
- fulu,
18
18
  gloas,
19
19
  phase0,
20
20
  } from "@lodestar/types";
@@ -98,7 +98,7 @@ export type GossipTypeMap = {
98
98
  [GossipType.blob_sidecar]: deneb.BlobSidecar;
99
99
  [GossipType.beacon_aggregate_and_proof]: SignedAggregateAndProof;
100
100
  [GossipType.beacon_attestation]: SingleAttestation;
101
- [GossipType.data_column_sidecar]: fulu.DataColumnSidecar;
101
+ [GossipType.data_column_sidecar]: DataColumnSidecar;
102
102
  [GossipType.voluntary_exit]: phase0.SignedVoluntaryExit;
103
103
  [GossipType.proposer_slashing]: phase0.ProposerSlashing;
104
104
  [GossipType.attester_slashing]: AttesterSlashing;
@@ -117,7 +117,7 @@ export type GossipFnByType = {
117
117
  [GossipType.blob_sidecar]: (blobSidecar: deneb.BlobSidecar) => Promise<void> | void;
118
118
  [GossipType.beacon_aggregate_and_proof]: (aggregateAndProof: SignedAggregateAndProof) => Promise<void> | void;
119
119
  [GossipType.beacon_attestation]: (attestation: SingleAttestation) => Promise<void> | void;
120
- [GossipType.data_column_sidecar]: (dataColumnSidecar: fulu.DataColumnSidecar) => Promise<void> | void;
120
+ [GossipType.data_column_sidecar]: (dataColumnSidecar: DataColumnSidecar) => Promise<void> | void;
121
121
  [GossipType.voluntary_exit]: (voluntaryExit: phase0.SignedVoluntaryExit) => Promise<void> | void;
122
122
  [GossipType.proposer_slashing]: (proposerSlashing: phase0.ProposerSlashing) => Promise<void> | void;
123
123
  [GossipType.attester_slashing]: (attesterSlashing: AttesterSlashing) => Promise<void> | void;
@@ -6,6 +6,7 @@ import {
6
6
  SYNC_COMMITTEE_SUBNET_COUNT,
7
7
  isForkPostAltair,
8
8
  isForkPostElectra,
9
+ isForkPostFulu,
9
10
  } from "@lodestar/params";
10
11
  import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types";
11
12
  import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js";
@@ -92,7 +93,7 @@ export function getGossipSSZType(topic: GossipTopic) {
92
93
  case GossipType.blob_sidecar:
93
94
  return ssz.deneb.BlobSidecar;
94
95
  case GossipType.data_column_sidecar:
95
- return ssz.fulu.DataColumnSidecar;
96
+ return isForkPostFulu(fork) ? sszTypesFor(fork).DataColumnSidecar : ssz.fulu.DataColumnSidecar;
96
97
  case GossipType.beacon_aggregate_and_proof:
97
98
  return sszTypesFor(fork).SignedAggregateAndProof;
98
99
  case GossipType.beacon_attestation:
@@ -19,6 +19,7 @@ import type {Datastore} from "interface-datastore";
19
19
  import {Libp2p as ILibp2p} from "libp2p";
20
20
  import {
21
21
  AttesterSlashing,
22
+ DataColumnSidecar,
22
23
  LightClientFinalityUpdate,
23
24
  LightClientOptimisticUpdate,
24
25
  SignedAggregateAndProof,
@@ -31,6 +32,7 @@ import {
31
32
  capella,
32
33
  deneb,
33
34
  fulu,
35
+ gloas,
34
36
  phase0,
35
37
  } from "@lodestar/types";
36
38
  import {BlockInputSource} from "../chain/blocks/blockInput/types.js";
@@ -86,7 +88,7 @@ export interface INetwork extends INetworkCorePublic {
86
88
  publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number>;
87
89
  publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise<number>;
88
90
  publishBeaconAttestation(attestation: SingleAttestation, subnet: SubnetID): Promise<number>;
89
- publishDataColumnSidecar(dataColumnSideCar: fulu.DataColumnSidecar): Promise<number>;
91
+ publishDataColumnSidecar(dataColumnSideCar: DataColumnSidecar): Promise<number>;
90
92
  publishVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): Promise<number>;
91
93
  publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number>;
92
94
  publishProposerSlashing(proposerSlashing: phase0.ProposerSlashing): Promise<number>;
@@ -95,6 +97,7 @@ export interface INetwork extends INetworkCorePublic {
95
97
  publishContributionAndProof(contributionAndProof: altair.SignedContributionAndProof): Promise<number>;
96
98
  publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number>;
97
99
  publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number>;
100
+ publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number>;
98
101
 
99
102
  // Debug
100
103
  dumpGossipQueue(gossipType: GossipType): Promise<PendingGossipsubMessage[]>;
@@ -10,6 +10,7 @@ import {ResponseIncoming} from "@lodestar/reqresp";
10
10
  import {computeEpochAtSlot} from "@lodestar/state-transition";
11
11
  import {
12
12
  AttesterSlashing,
13
+ DataColumnSidecar,
13
14
  LightClientBootstrap,
14
15
  LightClientFinalityUpdate,
15
16
  LightClientOptimisticUpdate,
@@ -24,6 +25,7 @@ import {
24
25
  capella,
25
26
  deneb,
26
27
  fulu,
28
+ gloas,
27
29
  phase0,
28
30
  } from "@lodestar/types";
29
31
  import {prettyPrintIndices, sleep} from "@lodestar/utils";
@@ -33,7 +35,7 @@ import {computeSubnetForDataColumnSidecar} from "../chain/validation/dataColumnS
33
35
  import {IBeaconDb} from "../db/interface.js";
34
36
  import {Metrics, RegistryMetricCreator} from "../metrics/index.js";
35
37
  import {IClock} from "../util/clock.js";
36
- import {CustodyConfig} from "../util/dataColumns.js";
38
+ import {CustodyConfig, isGloasDataColumnSidecar} from "../util/dataColumns.js";
37
39
  import {PeerIdStr, peerIdToString} from "../util/peerId.js";
38
40
  import {promiseAllMaybeAsync} from "../util/promises.js";
39
41
  import {BeaconBlocksByRootRequest, BlobSidecarsByRootRequest, DataColumnSidecarsByRootRequest} from "../util/types.js";
@@ -354,8 +356,11 @@ export class Network implements INetwork {
354
356
  });
355
357
  }
356
358
 
357
- async publishDataColumnSidecar(dataColumnSidecar: fulu.DataColumnSidecar): Promise<number> {
358
- const epoch = computeEpochAtSlot(dataColumnSidecar.signedBlockHeader.message.slot);
359
+ async publishDataColumnSidecar(dataColumnSidecar: DataColumnSidecar): Promise<number> {
360
+ const slot = isGloasDataColumnSidecar(dataColumnSidecar)
361
+ ? dataColumnSidecar.slot
362
+ : dataColumnSidecar.signedBlockHeader.message.slot;
363
+ const epoch = computeEpochAtSlot(slot);
359
364
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
360
365
 
361
366
  const subnet = computeSubnetForDataColumnSidecar(this.config, dataColumnSidecar);
@@ -489,6 +494,17 @@ export class Network implements INetwork {
489
494
  );
490
495
  }
491
496
 
497
+ async publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number> {
498
+ const epoch = computeEpochAtSlot(signedEnvelope.message.slot);
499
+ const boundary = this.config.getForkBoundaryAtEpoch(epoch);
500
+
501
+ return this.publishGossip<GossipType.execution_payload>(
502
+ {type: GossipType.execution_payload, boundary},
503
+ signedEnvelope,
504
+ {ignoreDuplicatePublishError: true}
505
+ );
506
+ }
507
+
492
508
  private async publishGossip<K extends GossipType>(
493
509
  topic: GossipTopicMap[K],
494
510
  object: GossipTypeMap[K],
@@ -765,7 +781,7 @@ export class Network implements INetwork {
765
781
  this.core.setTargetGroupCount(count);
766
782
  };
767
783
 
768
- private onPublishDataColumns = (sidecars: fulu.DataColumnSidecar[]): Promise<number[]> => {
784
+ private onPublishDataColumns = (sidecars: DataColumnSidecar[]): Promise<number[]> => {
769
785
  return promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishDataColumnSidecar(sidecar)));
770
786
  };
771
787
 
@@ -548,7 +548,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
548
548
  seenTimestampSec,
549
549
  }: GossipHandlerParamGeneric<GossipType.data_column_sidecar>) => {
550
550
  const {serializedData} = gossipData;
551
- const dataColumnSidecar = sszDeserialize(topic, serializedData);
551
+ // TODO GLOAS: handle gloas.DataColumnSidecar
552
+ const dataColumnSidecar = sszDeserialize(topic, serializedData) as fulu.DataColumnSidecar;
552
553
  const dataColumnSlot = dataColumnSidecar.signedBlockHeader.message.slot;
553
554
  const index = dataColumnSidecar.index;
554
555
 
@@ -821,11 +822,16 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
821
822
  [GossipType.execution_payload]: async ({
822
823
  gossipData,
823
824
  topic,
825
+ seenTimestampSec,
824
826
  }: GossipHandlerParamGeneric<GossipType.execution_payload>) => {
825
827
  const {serializedData} = gossipData;
826
828
  const executionPayloadEnvelope = sszDeserialize(topic, serializedData);
827
829
  await validateGossipExecutionPayloadEnvelope(chain, executionPayloadEnvelope);
828
830
 
831
+ const slot = executionPayloadEnvelope.message.slot;
832
+ const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
833
+ metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
834
+
829
835
  // TODO GLOAS: Handle valid envelope. Need an import flow that calls `processExecutionPayloadEnvelope` and fork choice
830
836
  },
831
837
  [GossipType.payload_attestation_message]: async ({
@@ -19,6 +19,7 @@ export async function* onLightClientUpdatesByRange(
19
19
  assertLightClientServer(chain.lightClientServer);
20
20
 
21
21
  const count = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, requestBody.count);
22
+ let started = false;
22
23
  for (let period = requestBody.startPeriod; period < requestBody.startPeriod + count; period++) {
23
24
  try {
24
25
  const update = await chain.lightClientServer.getUpdate(period);
@@ -29,9 +30,13 @@ export async function* onLightClientUpdatesByRange(
29
30
  data: type.serialize(update),
30
31
  boundary,
31
32
  };
33
+ started = true;
32
34
  } catch (e) {
33
35
  if ((e as LightClientServerError).type?.code === LightClientServerErrorCode.RESOURCE_UNAVAILABLE) {
34
- throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, (e as Error).message);
36
+ // Period not available, if we already started yielding, stop to
37
+ // preserve consecutive order. Otherwise skip leading gaps.
38
+ if (started) return;
39
+ continue;
35
40
  }
36
41
  throw new ResponseError(RespStatus.SERVER_ERROR, (e as Error).message);
37
42
  }
@@ -15,9 +15,12 @@ import {
15
15
  BeaconBlockBody,
16
16
  ColumnIndex,
17
17
  CustodyIndex,
18
+ DataColumnSidecar,
19
+ Root,
18
20
  SSZTypesFor,
19
21
  SignedBeaconBlock,
20
22
  SignedBeaconBlockHeader,
23
+ Slot,
21
24
  deneb,
22
25
  fulu,
23
26
  gloas,
@@ -277,6 +280,11 @@ export function getBlobKzgCommitments(
277
280
  return (signedBlock.message.body as BeaconBlockBody<ForkPostFulu & ForkPreGloas>).blobKzgCommitments;
278
281
  }
279
282
 
283
+ /** Type guard for `gloas.DataColumnSidecar` */
284
+ export function isGloasDataColumnSidecar(sidecar: DataColumnSidecar): sidecar is gloas.DataColumnSidecar {
285
+ return (sidecar as gloas.DataColumnSidecar).beaconBlockRoot !== undefined;
286
+ }
287
+
280
288
  /**
281
289
  * Given a signed block header and the commitments, inclusion proof, cells/proofs associated with
282
290
  * each blob in the block, assemble the sidecars which can be distributed to peers.
@@ -359,6 +367,39 @@ export function getDataColumnSidecarsFromColumnSidecar(
359
367
  );
360
368
  }
361
369
 
370
+ /**
371
+ * In Gloas, data column sidecars have a simplified structure with `slot` and `beaconBlockRoot`
372
+ * instead of `signedBlockHeader`, `kzgCommitments`, and `kzgCommitmentsInclusionProof`.
373
+ */
374
+ export function getDataColumnSidecarsForGloas(
375
+ slot: Slot,
376
+ beaconBlockRoot: Root,
377
+ cellsAndKzgProofs: {cells: Uint8Array[]; proofs: Uint8Array[]}[]
378
+ ): gloas.DataColumnSidecars {
379
+ // No need to create data column sidecars if there are no blobs
380
+ if (cellsAndKzgProofs.length === 0) {
381
+ return [];
382
+ }
383
+
384
+ const sidecars: gloas.DataColumnSidecars = [];
385
+ for (let columnIndex = 0; columnIndex < NUMBER_OF_COLUMNS; columnIndex++) {
386
+ const column: Uint8Array[] = [];
387
+ const kzgProofs: Uint8Array[] = [];
388
+ for (const {cells, proofs} of cellsAndKzgProofs) {
389
+ column.push(cells[columnIndex]);
390
+ kzgProofs.push(proofs[columnIndex]);
391
+ }
392
+ sidecars.push({
393
+ index: columnIndex,
394
+ column,
395
+ kzgProofs,
396
+ slot,
397
+ beaconBlockRoot,
398
+ });
399
+ }
400
+ return sidecars;
401
+ }
402
+
362
403
  /**
363
404
  * If we receive more than half of NUMBER_OF_COLUMNS (64) we should recover all remaining columns
364
405
  */
@@ -14,6 +14,11 @@ export class SerializedCache {
14
14
  this.map.set(obj, serialized);
15
15
  }
16
16
 
17
+ /**
18
+ * Replace the internal WeakMap to force GC of all cached entries.
19
+ * Must only be called after all DB writes that may read from this cache have completed,
20
+ * otherwise cached serialized bytes will be unavailable and data will be re-serialized unnecessarily.
21
+ */
17
22
  clear(): void {
18
23
  this.map = new WeakMap();
19
24
  }