@lodestar/beacon-node 1.44.0-dev.4ac6f45830 → 1.44.0-dev.4b91ba8c2f

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 (127) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +23 -28
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +0 -25
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/config/constants.d.ts +4 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +5 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  12. package/lib/api/impl/lodestar/index.js +28 -0
  13. package/lib/api/impl/lodestar/index.js.map +1 -1
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +20 -27
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  18. package/lib/chain/blocks/importBlock.js +3 -2
  19. package/lib/chain/blocks/importBlock.js.map +1 -1
  20. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  21. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  22. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  23. package/lib/chain/chain.d.ts +2 -2
  24. package/lib/chain/chain.d.ts.map +1 -1
  25. package/lib/chain/chain.js +9 -2
  26. package/lib/chain/chain.js.map +1 -1
  27. package/lib/chain/emitter.d.ts +2 -1
  28. package/lib/chain/emitter.d.ts.map +1 -1
  29. package/lib/chain/emitter.js.map +1 -1
  30. package/lib/chain/errors/blockError.d.ts +0 -7
  31. package/lib/chain/errors/blockError.d.ts.map +1 -1
  32. package/lib/chain/errors/blockError.js +0 -3
  33. package/lib/chain/errors/blockError.js.map +1 -1
  34. package/lib/chain/errors/executionPayloadBid.d.ts +12 -0
  35. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  36. package/lib/chain/errors/executionPayloadBid.js +2 -0
  37. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  38. package/lib/chain/forkChoice/index.d.ts +4 -4
  39. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  40. package/lib/chain/forkChoice/index.js +10 -7
  41. package/lib/chain/forkChoice/index.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/interface.js.map +1 -1
  45. package/lib/chain/options.d.ts.map +1 -1
  46. package/lib/chain/options.js +1 -0
  47. package/lib/chain/options.js.map +1 -1
  48. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -2
  49. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  50. package/lib/chain/produceBlock/produceBlockBody.js +4 -4
  51. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  52. package/lib/chain/validation/block.d.ts +5 -1
  53. package/lib/chain/validation/block.d.ts.map +1 -1
  54. package/lib/chain/validation/block.js +4 -14
  55. package/lib/chain/validation/block.js.map +1 -1
  56. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  57. package/lib/chain/validation/executionPayloadBid.js +23 -1
  58. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  59. package/lib/chain/validation/executionPayloadEnvelope.js +1 -3
  60. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  61. package/lib/execution/engine/http.js +1 -1
  62. package/lib/execution/engine/http.js.map +1 -1
  63. package/lib/execution/engine/interface.d.ts +2 -2
  64. package/lib/execution/engine/interface.d.ts.map +1 -1
  65. package/lib/execution/engine/interface.js +6 -2
  66. package/lib/execution/engine/interface.js.map +1 -1
  67. package/lib/execution/engine/mock.d.ts.map +1 -1
  68. package/lib/execution/engine/mock.js +3 -5
  69. package/lib/execution/engine/mock.js.map +1 -1
  70. package/lib/execution/engine/types.d.ts +15 -7
  71. package/lib/execution/engine/types.d.ts.map +1 -1
  72. package/lib/execution/engine/types.js +47 -9
  73. package/lib/execution/engine/types.js.map +1 -1
  74. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  75. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  76. package/lib/metrics/metrics/lodestar.js +10 -0
  77. package/lib/metrics/metrics/lodestar.js.map +1 -1
  78. package/lib/network/gossip/topic.d.ts +30 -767
  79. package/lib/network/gossip/topic.d.ts.map +1 -1
  80. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  81. package/lib/network/processor/gossipHandlers.js +9 -2
  82. package/lib/network/processor/gossipHandlers.js.map +1 -1
  83. package/lib/network/processor/index.d.ts +2 -2
  84. package/lib/network/processor/index.d.ts.map +1 -1
  85. package/lib/network/processor/index.js +25 -23
  86. package/lib/network/processor/index.js.map +1 -1
  87. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  88. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +1 -2
  89. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  90. package/lib/sync/types.d.ts +9 -1
  91. package/lib/sync/types.d.ts.map +1 -1
  92. package/lib/sync/types.js +9 -2
  93. package/lib/sync/types.js.map +1 -1
  94. package/lib/sync/unknownBlock.d.ts.map +1 -1
  95. package/lib/sync/unknownBlock.js +64 -30
  96. package/lib/sync/unknownBlock.js.map +1 -1
  97. package/lib/util/sszBytes.js +1 -1
  98. package/package.json +15 -15
  99. package/src/api/impl/beacon/blocks/index.ts +27 -37
  100. package/src/api/impl/beacon/pool/index.ts +1 -41
  101. package/src/api/impl/config/constants.ts +8 -0
  102. package/src/api/impl/lodestar/index.ts +30 -0
  103. package/src/api/impl/validator/index.ts +29 -39
  104. package/src/chain/blocks/importBlock.ts +7 -5
  105. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -5
  106. package/src/chain/chain.ts +12 -4
  107. package/src/chain/emitter.ts +3 -2
  108. package/src/chain/errors/blockError.ts +0 -4
  109. package/src/chain/errors/executionPayloadBid.ts +14 -0
  110. package/src/chain/forkChoice/index.ts +13 -0
  111. package/src/chain/interface.ts +1 -2
  112. package/src/chain/options.ts +1 -0
  113. package/src/chain/produceBlock/produceBlockBody.ts +7 -7
  114. package/src/chain/validation/block.ts +12 -16
  115. package/src/chain/validation/executionPayloadBid.ts +25 -1
  116. package/src/chain/validation/executionPayloadEnvelope.ts +1 -3
  117. package/src/execution/engine/http.ts +1 -1
  118. package/src/execution/engine/interface.ts +12 -2
  119. package/src/execution/engine/mock.ts +6 -5
  120. package/src/execution/engine/types.ts +75 -14
  121. package/src/metrics/metrics/lodestar.ts +11 -0
  122. package/src/network/processor/gossipHandlers.ts +9 -2
  123. package/src/network/processor/index.ts +27 -27
  124. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +1 -2
  125. package/src/sync/types.ts +11 -2
  126. package/src/sync/unknownBlock.ts +70 -31
  127. package/src/util/sszBytes.ts +1 -1
@@ -108,7 +108,7 @@ export type ProduceFullGloas = {
108
108
  type: BlockType.Full;
109
109
  fork: ForkPostGloas;
110
110
  executionPayload: ExecutionPayload<ForkPostGloas>;
111
- executionRequests: electra.ExecutionRequests;
111
+ executionRequests: gloas.ExecutionRequests;
112
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
113
  cells: fulu.Cell[][];
114
114
  parentBlockRoot: Root;
@@ -228,7 +228,7 @@ export async function produceBlockBody<T extends BlockType>(
228
228
  );
229
229
  const parentExecutionRequests = isExtendingPayload
230
230
  ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
231
- : ssz.electra.ExecutionRequests.defaultValue();
231
+ : ssz.gloas.ExecutionRequests.defaultValue();
232
232
  executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;
233
233
 
234
234
  const commonBlockBody = await commonBlockBodyPromise;
@@ -272,7 +272,7 @@ export async function produceBlockBody<T extends BlockType>(
272
272
 
273
273
  // Get execution payload from EL
274
274
  let parentBlockHash: Bytes32;
275
- let parentExecutionRequests: electra.ExecutionRequests;
275
+ let parentExecutionRequests: gloas.ExecutionRequests;
276
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
277
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
278
278
  // Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
@@ -285,7 +285,7 @@ export async function produceBlockBody<T extends BlockType>(
285
285
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
286
286
  } else {
287
287
  parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
288
- parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
288
+ parentExecutionRequests = ssz.gloas.ExecutionRequests.defaultValue();
289
289
  }
290
290
  const prepareRes = await prepareExecutionPayload(
291
291
  this,
@@ -344,13 +344,13 @@ export async function produceBlockBody<T extends BlockType>(
344
344
  blockHash: executionPayload.blockHash,
345
345
  prevRandao: currentState.getRandaoMix(currentState.epoch),
346
346
  feeRecipient: executionPayload.feeRecipient,
347
- gasLimit: BigInt(executionPayload.gasLimit),
347
+ gasLimit: executionPayload.gasLimit,
348
348
  builderIndex: BUILDER_INDEX_SELF_BUILD,
349
349
  slot: blockSlot,
350
350
  value: 0,
351
351
  executionPayment: 0,
352
352
  blobKzgCommitments: blobsBundle.commitments,
353
- executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
353
+ executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests),
354
354
  };
355
355
  const signedBid: gloas.SignedExecutionPayloadBid = {
356
356
  message: bid,
@@ -375,7 +375,7 @@ export async function produceBlockBody<T extends BlockType>(
375
375
  // Store execution payload data required to construct execution payload envelope later
376
376
  const gloasResult = produceResult as ProduceFullGloas;
377
377
  gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
378
- gloasResult.executionRequests = executionRequests;
378
+ gloasResult.executionRequests = executionRequests as gloas.ExecutionRequests;
379
379
  gloasResult.blobsBundle = blobsBundle;
380
380
  gloasResult.cells = cells;
381
381
  gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
@@ -15,12 +15,17 @@ import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js
15
15
  import {IBeaconChain} from "../interface.js";
16
16
  import {RegenCaller} from "../regen/index.js";
17
17
 
18
+ export type GossipBlockValidationResult = {
19
+ /** Number of skipped slots between the block and its parent (blockSlot - parentSlot - 1) */
20
+ skippedSlots: number;
21
+ };
22
+
18
23
  export async function validateGossipBlock(
19
24
  config: ChainForkConfig,
20
25
  chain: IBeaconChain,
21
26
  signedBlock: SignedBeaconBlock,
22
27
  fork: ForkName
23
- ): Promise<void> {
28
+ ): Promise<GossipBlockValidationResult> {
24
29
  const block = signedBlock.message;
25
30
  const blockSlot = block.slot;
26
31
  const blockEpoch = computeEpochAtSlot(blockSlot);
@@ -109,21 +114,6 @@ export async function validateGossipBlock(
109
114
  }
110
115
  }
111
116
 
112
- // [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
113
- // This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
114
- // to do a bunch of epoch transitions, the longer the distance between the parent and block,
115
- // the more we have to do. epochTransitions are expensive ~750ms, so we must limit how many a
116
- // single bad block can trigger
117
- // Note: Ensure this check is done before calling chain.regen.getBlockSlotStat as this is the function that does various epoch transitions.
118
- // Note: This validation check is not part of the spec.
119
- if (chain.opts.maxSkipSlots != null && parentBlock.slot + chain.opts.maxSkipSlots < blockSlot) {
120
- throw new BlockGossipError(GossipAction.IGNORE, {
121
- code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS,
122
- parentSlot: parentBlock.slot,
123
- blockSlot,
124
- });
125
- }
126
-
127
117
  // [REJECT] The block is from a higher slot than its parent.
128
118
  if (parentBlock.slot >= blockSlot) {
129
119
  throw new BlockGossipError(GossipAction.REJECT, {
@@ -133,6 +123,10 @@ export async function validateGossipBlock(
133
123
  });
134
124
  }
135
125
 
126
+ // Number of skipped slots between block and parent (non-spec). Previously this gated blocks via
127
+ // maxSkipSlots; now the caller only observes it so legitimate post-skip blocks are no longer ignored.
128
+ const skippedSlots = blockSlot - parentBlock.slot - 1;
129
+
136
130
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
137
131
  if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
138
132
  const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
@@ -247,4 +241,6 @@ export async function validateGossipBlock(
247
241
  }
248
242
 
249
243
  chain.seenBlockProposers.add(blockSlot, proposerIndex);
244
+
245
+ return {skippedSlots};
250
246
  }
@@ -1,4 +1,5 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
+ import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
2
3
  import {
3
4
  computeEpochAtSlot,
4
5
  createSingleSignatureSetFromComponents,
@@ -139,6 +140,17 @@ async function validateExecutionPayloadBid(
139
140
  });
140
141
  }
141
142
 
143
+ // [REJECT] The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e.
144
+ // `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`.
145
+ if (builder.version !== PAYLOAD_BUILDER_VERSION) {
146
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
147
+ code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION,
148
+ builderIndex: bid.builderIndex,
149
+ version: builder.version,
150
+ expectedVersion: PAYLOAD_BUILDER_VERSION,
151
+ });
152
+ }
153
+
142
154
  // [REJECT] `bid.execution_payment` is zero.
143
155
  if (bid.executionPayment !== 0) {
144
156
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
@@ -175,7 +187,7 @@ async function validateExecutionPayloadBid(
175
187
  // [IGNORE] `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`,
176
188
  // where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
177
189
  // `target_gas_limit` is `proposer_preferences.target_gas_limit`.
178
- const bidGasLimit = Number(bid.gasLimit);
190
+ const bidGasLimit = bid.gasLimit;
179
191
  const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
180
192
  const targetGasLimit = proposerPreferences.message.targetGasLimit;
181
193
  if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
@@ -232,6 +244,18 @@ async function validateExecutionPayloadBid(
232
244
  });
233
245
  }
234
246
 
247
+ // [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
248
+ // `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
249
+ const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
250
+ if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
251
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
252
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
253
+ builderIndex: bid.builderIndex,
254
+ bidPrevRandao: toHex(bid.prevRandao),
255
+ expectedPrevRandao: toHex(randaoMix),
256
+ });
257
+ }
258
+
235
259
  // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
236
260
  const signatureSet = createSingleSignatureSetFromComponents(
237
261
  PublicKey.fromBytes(builder.pubkey),
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
35
35
  // [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
36
36
  // gossip or non-gossip sources) (a client MAY queue payload for processing once
37
37
  // the block is retrieved).
38
- // TODO GLOAS: Need to review this, we should queue the envelope for later
39
- // processing if the block is not yet known, otherwise we would ignore it here
40
38
  const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
41
39
  if (block === null) {
42
40
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
@@ -108,7 +106,7 @@ async function validateExecutionPayloadEnvelope(
108
106
  }
109
107
 
110
108
  // [REJECT] `hash_tree_root(envelope.execution_requests) == bid.execution_requests_root`
111
- const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
109
+ const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
112
110
  if (!byteArrayEquals(requestsRoot, payloadInput.getBid().executionRequestsRoot)) {
113
111
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
114
112
  code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH,
@@ -244,7 +244,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
244
244
  if (executionRequests === undefined) {
245
245
  throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
246
246
  }
247
- const serializedExecutionRequests = serializeExecutionRequests(executionRequests);
247
+ const serializedExecutionRequests = serializeExecutionRequests(fork, executionRequests);
248
248
  engineRequest = {
249
249
  method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
250
250
  params: [
@@ -1,5 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {
3
+ BUILDER_DEPOSIT_REQUEST_TYPE,
4
+ BUILDER_EXIT_REQUEST_TYPE,
3
5
  CONSOLIDATION_REQUEST_TYPE,
4
6
  DEPOSIT_REQUEST_TYPE,
5
7
  ForkName,
@@ -51,10 +53,18 @@ export enum ExecutionEngineState {
51
53
  export type ExecutionRequestType =
52
54
  | typeof DEPOSIT_REQUEST_TYPE
53
55
  | typeof WITHDRAWAL_REQUEST_TYPE
54
- | typeof CONSOLIDATION_REQUEST_TYPE;
56
+ | typeof CONSOLIDATION_REQUEST_TYPE
57
+ | typeof BUILDER_DEPOSIT_REQUEST_TYPE
58
+ | typeof BUILDER_EXIT_REQUEST_TYPE;
55
59
 
56
60
  export function isExecutionRequestType(type: number): type is ExecutionRequestType {
57
- return type === DEPOSIT_REQUEST_TYPE || type === WITHDRAWAL_REQUEST_TYPE || type === CONSOLIDATION_REQUEST_TYPE;
61
+ return (
62
+ type === DEPOSIT_REQUEST_TYPE ||
63
+ type === WITHDRAWAL_REQUEST_TYPE ||
64
+ type === CONSOLIDATION_REQUEST_TYPE ||
65
+ type === BUILDER_DEPOSIT_REQUEST_TYPE ||
66
+ type === BUILDER_EXIT_REQUEST_TYPE
67
+ );
58
68
  }
59
69
 
60
70
  export type ExecutePayloadResponse =
@@ -393,11 +393,12 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
393
393
  blobs,
394
394
  proofs,
395
395
  }),
396
- executionRequests: serializeExecutionRequests({
397
- deposits: ssz.electra.DepositRequests.defaultValue(),
398
- withdrawals: ssz.electra.WithdrawalRequests.defaultValue(),
399
- consolidations: ssz.electra.ConsolidationRequests.defaultValue(),
400
- }),
396
+ executionRequests: serializeExecutionRequests(
397
+ fork,
398
+ ForkSeq[fork] >= ForkSeq.gloas
399
+ ? ssz.gloas.ExecutionRequests.defaultValue()
400
+ : ssz.electra.ExecutionRequests.defaultValue()
401
+ ),
401
402
  });
402
403
 
403
404
  // IF the payload is deemed VALID and the build process has begun
@@ -1,4 +1,6 @@
1
1
  import {
2
+ BUILDER_DEPOSIT_REQUEST_TYPE,
3
+ BUILDER_EXIT_REQUEST_TYPE,
2
4
  BYTES_PER_FIELD_ELEMENT,
3
5
  BYTES_PER_LOGS_BLOOM,
4
6
  CELLS_PER_EXT_BLOB,
@@ -205,16 +207,27 @@ export type WithdrawalRpc = {
205
207
  };
206
208
 
207
209
  /**
208
- * ExecutionRequestsRpc only holds at most 3 elements and no repeated type:
209
- * - ssz'ed DepositRequests
210
- * - ssz'ed WithdrawalRequests
211
- * - ssz'ed ConsolidationRequests
210
+ * ExecutionRequestsRpc holds at most 5 elements and no repeated type, ordered by
211
+ * the single-byte request type prefix:
212
+ * - ssz'ed DepositRequests (0x00)
213
+ * - ssz'ed WithdrawalRequests (0x01)
214
+ * - ssz'ed ConsolidationRequests (0x02)
215
+ * - ssz'ed BuilderDepositRequests (0x03)
216
+ * - ssz'ed BuilderExitRequests (0x04)
212
217
  */
213
- export type ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[];
218
+ export type ExecutionRequestsRpc = (
219
+ | DepositRequestsRpc
220
+ | WithdrawalRequestsRpc
221
+ | ConsolidationRequestsRpc
222
+ | BuilderDepositRequestsRpc
223
+ | BuilderExitRequestsRpc
224
+ )[];
214
225
 
215
226
  export type DepositRequestsRpc = DATA;
216
227
  export type WithdrawalRequestsRpc = DATA;
217
228
  export type ConsolidationRequestsRpc = DATA;
229
+ export type BuilderDepositRequestsRpc = DATA;
230
+ export type BuilderExitRequestsRpc = DATA;
218
231
 
219
232
  export type BlobAndProofRpc = {
220
233
  blob: DATA;
@@ -339,7 +352,7 @@ export function parseExecutionPayload(
339
352
  data = response.executionPayload;
340
353
  blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined;
341
354
  executionRequests = response.executionRequests
342
- ? deserializeExecutionRequests(response.executionRequests)
355
+ ? deserializeExecutionRequests(fork, response.executionRequests)
343
356
  : undefined;
344
357
  shouldOverrideBuilder = response.shouldOverrideBuilder ?? false;
345
358
  } else {
@@ -526,13 +539,36 @@ function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc):
526
539
  return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null));
527
540
  }
528
541
 
542
+ function serializeBuilderDepositRequests(
543
+ builderDepositRequests: gloas.BuilderDepositRequests
544
+ ): BuilderDepositRequestsRpc {
545
+ const requestsBytes = ssz.gloas.BuilderDepositRequests.serialize(builderDepositRequests);
546
+ return bytesToData(prefixRequests(requestsBytes, BUILDER_DEPOSIT_REQUEST_TYPE));
547
+ }
548
+
549
+ function deserializeBuilderDepositRequests(serialized: BuilderDepositRequestsRpc): gloas.BuilderDepositRequests {
550
+ return ssz.gloas.BuilderDepositRequests.deserialize(dataToBytes(serialized, null));
551
+ }
552
+
553
+ function serializeBuilderExitRequests(builderExitRequests: gloas.BuilderExitRequests): BuilderExitRequestsRpc {
554
+ const requestsBytes = ssz.gloas.BuilderExitRequests.serialize(builderExitRequests);
555
+ return bytesToData(prefixRequests(requestsBytes, BUILDER_EXIT_REQUEST_TYPE));
556
+ }
557
+
558
+ function deserializeBuilderExitRequests(serialized: BuilderExitRequestsRpc): gloas.BuilderExitRequests {
559
+ return ssz.gloas.BuilderExitRequests.deserialize(dataToBytes(serialized, null));
560
+ }
561
+
529
562
  /**
530
563
  * This is identical to get_execution_requests_list in
531
564
  * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list
565
+ *
566
+ * Gloas extends the list with builder deposits (0x03) and builder exits (0x04) per
567
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#modified-get_execution_requests_list
532
568
  */
533
- export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc {
569
+ export function serializeExecutionRequests(fork: ForkName, executionRequests: ExecutionRequests): ExecutionRequestsRpc {
534
570
  const {deposits, withdrawals, consolidations} = executionRequests;
535
- const result = [];
571
+ const result: ExecutionRequestsRpc = [];
536
572
 
537
573
  if (deposits.length !== 0) {
538
574
  result.push(serializeDepositRequests(deposits));
@@ -546,15 +582,26 @@ export function serializeExecutionRequests(executionRequests: ExecutionRequests)
546
582
  result.push(serializeConsolidationRequests(consolidations));
547
583
  }
548
584
 
585
+ if (ForkSeq[fork] >= ForkSeq.gloas) {
586
+ const {builderDeposits, builderExits} = executionRequests as gloas.ExecutionRequests;
587
+
588
+ if (builderDeposits.length !== 0) {
589
+ result.push(serializeBuilderDepositRequests(builderDeposits));
590
+ }
591
+
592
+ if (builderExits.length !== 0) {
593
+ result.push(serializeBuilderExitRequests(builderExits));
594
+ }
595
+ }
596
+
549
597
  return result;
550
598
  }
551
599
 
552
- export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests {
553
- const result: ExecutionRequests = {
554
- deposits: [],
555
- withdrawals: [],
556
- consolidations: [],
557
- };
600
+ export function deserializeExecutionRequests(fork: ForkName, serialized: ExecutionRequestsRpc): ExecutionRequests {
601
+ const result: ExecutionRequests =
602
+ ForkSeq[fork] >= ForkSeq.gloas
603
+ ? {deposits: [], withdrawals: [], consolidations: [], builderDeposits: [], builderExits: []}
604
+ : {deposits: [], withdrawals: [], consolidations: []};
558
605
 
559
606
  if (serialized.length === 0) {
560
607
  return result;
@@ -601,6 +648,20 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc):
601
648
  result.consolidations = deserializeConsolidationRequests(requests);
602
649
  break;
603
650
  }
651
+ case BUILDER_DEPOSIT_REQUEST_TYPE: {
652
+ if (ForkSeq[fork] < ForkSeq.gloas) {
653
+ throw Error(`Builder deposit request is not supported pre-gloas fork=${fork}`);
654
+ }
655
+ (result as gloas.ExecutionRequests).builderDeposits = deserializeBuilderDepositRequests(requests);
656
+ break;
657
+ }
658
+ case BUILDER_EXIT_REQUEST_TYPE: {
659
+ if (ForkSeq[fork] < ForkSeq.gloas) {
660
+ throw Error(`Builder exit request is not supported pre-gloas fork=${fork}`);
661
+ }
662
+ (result as gloas.ExecutionRequests).builderExits = deserializeBuilderExitRequests(requests);
663
+ break;
664
+ }
604
665
  }
605
666
  prevRequestType = currentRequestType;
606
667
  }
@@ -610,6 +610,11 @@ export function createLodestarMetrics(
610
610
  help: "The origination source of one of the BlockInputSync triggers",
611
611
  labelNames: ["source"],
612
612
  }),
613
+ payloadSource: register.counter<{source: BlockInputSource}>({
614
+ name: "lodestar_payload_input_sync_source_total",
615
+ help: "Count of payload (execution payload envelope) sync triggers, labeled by their source",
616
+ labelNames: ["source"],
617
+ }),
613
618
  pendingBlocks: register.gauge({
614
619
  name: "lodestar_sync_unknown_block_pending_blocks_size",
615
620
  help: "Current size of UnknownBlockSync pending blocks cache",
@@ -861,6 +866,12 @@ export function createLodestarMetrics(
861
866
  labelNames: ["numBlobs"],
862
867
  }),
863
868
 
869
+ skippedSlots: register.histogram({
870
+ name: "lodestar_gossip_block_skipped_slots",
871
+ help: "Number of skipped slots between a gossip block and its parent (blockSlot - parentSlot - 1)",
872
+ buckets: [0, 1, 2, 4, 8, 16, 32],
873
+ }),
874
+
864
875
  processBlockErrors: register.gauge<{error: BlockErrorCode | "NOT_BLOCK_ERROR"}>({
865
876
  name: "lodestar_gossip_block_process_block_errors",
866
877
  help: "Count of errors, by error type, while processing blocks",
@@ -185,7 +185,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
185
185
  peerIdStr,
186
186
  });
187
187
  try {
188
- await validateGossipBlock(config, chain, signedBlock, fork);
188
+ const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
189
189
 
190
190
  if (isForkPostGloas(fork)) {
191
191
  chain.seenPayloadEnvelopeInputCache.add({
@@ -205,8 +205,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
205
205
 
206
206
  metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation);
207
207
  metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime);
208
+ metrics?.gossipBlock.skippedSlots.observe(skippedSlots);
208
209
 
209
- logger.debug("Validated gossip block", {...blockInputMeta, ...logCtx, recvToValidation, validationTime});
210
+ logger.debug("Validated gossip block", {
211
+ ...blockInputMeta,
212
+ ...logCtx,
213
+ recvToValidation,
214
+ validationTime,
215
+ skippedSlots,
216
+ });
210
217
 
211
218
  chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex});
212
219
 
@@ -186,9 +186,11 @@ export class NetworkProcessor {
186
186
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
187
187
  // to be stored in this Map and reprocessed once the block comes
188
188
  private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
189
+ private awaitingBlockMessageCount = 0;
189
190
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
190
191
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
191
192
  private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
193
+ private awaitingPayloadMessageCount = 0;
192
194
  private unknownBlocksBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
193
195
  private unknownEnvelopesBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
194
196
 
@@ -228,8 +230,8 @@ export class NetworkProcessor {
228
230
  metrics.gossipValidationQueue.keySize.set({topic}, this.gossipQueues[topic].keySize);
229
231
  metrics.gossipValidationQueue.concurrency.set({topic}, this.gossipTopicConcurrency[topic]);
230
232
  }
231
- metrics.awaitingBlockGossipMessages.countPerSlot.set(this.unknownBlockGossipsubMessagesCount);
232
- metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.unknownPayloadGossipsubMessagesCount);
233
+ metrics.awaitingBlockGossipMessages.countPerSlot.set(this.awaitingBlockMessageCount);
234
+ metrics.awaitingPayloadGossipMessages.countPerSlot.set(this.awaitingPayloadMessageCount);
233
235
  // specific metric for beacon_attestation topic
234
236
  metrics.gossipValidationQueue.keyAge.reset();
235
237
  for (const ageMs of this.gossipQueues.beacon_attestation.getDataAgeMs()) {
@@ -297,7 +299,7 @@ export class NetworkProcessor {
297
299
  return;
298
300
  }
299
301
  this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
300
- this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, peer, source});
302
+ this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
301
303
  }
302
304
 
303
305
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
@@ -497,7 +499,7 @@ export class NetworkProcessor {
497
499
  this.pushPendingGossipsubMessageToQueue(message);
498
500
  break;
499
501
  case PreprocessAction.AwaitBlock: {
500
- if (this.unknownBlockGossipsubMessagesCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
502
+ if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
501
503
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
502
504
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
503
505
  reason: ReprocessRejectReason.reached_limit,
@@ -509,10 +511,11 @@ export class NetworkProcessor {
509
511
  this.metrics?.awaitingBlockGossipMessages.queue.inc({topic: topicType});
510
512
  const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
511
513
  awaitingGossipsubMessages.add(message);
514
+ this.awaitingBlockMessageCount++;
512
515
  break;
513
516
  }
514
517
  case PreprocessAction.AwaitEnvelope: {
515
- if (this.unknownPayloadGossipsubMessagesCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
518
+ if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
516
519
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
517
520
  reason: ReprocessRejectReason.reached_limit,
518
521
  topic: topicType,
@@ -525,6 +528,7 @@ export class NetworkProcessor {
525
528
  preprocessResult.root
526
529
  );
527
530
  awaitingPayloadGossipsubMessages.add(message);
531
+ this.awaitingPayloadMessageCount++;
528
532
  break;
529
533
  }
530
534
  }
@@ -548,6 +552,12 @@ export class NetworkProcessor {
548
552
  return;
549
553
  }
550
554
 
555
+ // Atomically remove from map and update counter before async iteration to
556
+ // prevent double-decrement race with onClockSlot during yield points below
557
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
558
+ this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
559
+ }
560
+
551
561
  const nowSec = Date.now() / 1000;
552
562
  let count = 0;
553
563
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
@@ -567,8 +577,6 @@ export class NetworkProcessor {
567
577
  await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
568
578
  }
569
579
  }
570
-
571
- this.awaitingMessagesByBlockRoot.delete(rootHex);
572
580
  };
573
581
 
574
582
  private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
@@ -577,6 +585,12 @@ export class NetworkProcessor {
577
585
  return;
578
586
  }
579
587
 
588
+ // Atomically remove from map and update counter before async iteration to
589
+ // prevent double-decrement race with onClockSlot during yield points below
590
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
591
+ this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
592
+ }
593
+
580
594
  const nowSec = Date.now() / 1000;
581
595
  let count = 0;
582
596
  for (const message of waitingGossipsubMessages) {
@@ -593,8 +607,6 @@ export class NetworkProcessor {
593
607
  await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
594
608
  }
595
609
  }
596
-
597
- this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
598
610
  };
599
611
 
600
612
  private onClockSlot = (clockSlot: Slot): void => {
@@ -618,7 +630,9 @@ export class NetworkProcessor {
618
630
  );
619
631
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
620
632
  }
621
- this.awaitingMessagesByBlockRoot.delete(rootHex);
633
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
634
+ this.awaitingBlockMessageCount -= gossipMessages.size;
635
+ }
622
636
  }
623
637
  }
624
638
  this.unknownBlocksBySlot.delete(slot);
@@ -641,7 +655,9 @@ export class NetworkProcessor {
641
655
  );
642
656
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
643
657
  }
644
- this.awaitingMessagesByPayloadBlockRoot.delete(rootHex);
658
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
659
+ this.awaitingPayloadMessageCount -= gossipMessages.size;
660
+ }
645
661
  }
646
662
  }
647
663
  this.unknownEnvelopesBySlot.delete(slot);
@@ -784,20 +800,4 @@ export class NetworkProcessor {
784
800
 
785
801
  return null;
786
802
  }
787
-
788
- private get unknownBlockGossipsubMessagesCount(): number {
789
- let count = 0;
790
- for (const messages of this.awaitingMessagesByBlockRoot.values()) {
791
- count += messages.size;
792
- }
793
- return count;
794
- }
795
-
796
- private get unknownPayloadGossipsubMessagesCount(): number {
797
- let count = 0;
798
- for (const messages of this.awaitingMessagesByPayloadBlockRoot.values()) {
799
- count += messages.size;
800
- }
801
- return count;
802
- }
803
803
  }
@@ -1,6 +1,5 @@
1
1
  import {LogData} from "@lodestar/logger";
2
2
  import {ForkSeq} from "@lodestar/params";
3
- import {RespStatus, ResponseError} from "@lodestar/reqresp";
4
3
  import {ColumnIndex, Slot} from "@lodestar/types";
5
4
  import {prettyBytes, prettyPrintIndices, toRootHex} from "@lodestar/utils";
6
5
  import {IBeaconChain} from "../../../chain/interface.js";
@@ -76,7 +75,7 @@ export async function handleColumnSidecarUnavailability({
76
75
 
77
76
  export function validateRequestedDataColumns(chain: IBeaconChain, requestedColumns: ColumnIndex[]): ColumnIndex[] {
78
77
  if (requestedColumns.length === 0) {
79
- throw new ResponseError(RespStatus.INVALID_REQUEST, "dataColumnSidecar requested without column indices");
78
+ return [];
80
79
  }
81
80
 
82
81
  const {custodyColumns, custodyColumnsIndex} = chain.custodyConfig;
package/src/sync/types.ts CHANGED
@@ -19,7 +19,14 @@ export enum PendingBlockType {
19
19
  */
20
20
  INCOMPLETE_BLOCK_INPUT = "IncompleteBlockInput",
21
21
 
22
- UNKNOWN_DATA = "unknown_data",
22
+ /**
23
+ * Payload analog of UNKNOWN_BLOCK_ROOT: we have a beacon block root but not its execution payload envelope.
24
+ */
25
+ UNKNOWN_PAYLOAD_BLOCK_ROOT = "unknown_payload_block_root",
26
+ /**
27
+ * Payload analog of INCOMPLETE_BLOCK_INPUT: we have a partial payload input that did not complete in time.
28
+ */
29
+ INCOMPLETE_PAYLOAD_ENVELOPE = "incomplete_payload_envelope",
23
30
  }
24
31
 
25
32
  export enum PendingBlockInputStatus {
@@ -70,6 +77,8 @@ export type PendingPayloadInput = {
70
77
  export type PendingPayloadRootHex = {
71
78
  status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
72
79
  rootHex: RootHex;
80
+ // message slot hint, may be missing when resolving a parent payload
81
+ slot?: Slot;
73
82
  timeAddedSec: number;
74
83
  timeSyncedSec?: number;
75
84
  peerIdStrings: Set<string>;
@@ -125,5 +134,5 @@ export function getPayloadSyncCacheItemSlot(payload: PayloadSyncCacheItem): Slot
125
134
  return payload.envelope.message.payload.slotNumber;
126
135
  }
127
136
 
128
- return "unknown";
137
+ return payload.slot ?? "unknown";
129
138
  }