@lodestar/beacon-node 1.48.0-dev.0dbea0de1b → 1.48.0-dev.262a9c1d16

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 (139) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +0 -7
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/utils.js +14 -0
  6. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  7. package/lib/api/impl/config/index.d.ts.map +1 -1
  8. package/lib/api/impl/config/index.js +4 -1
  9. package/lib/api/impl/config/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +39 -39
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  14. package/lib/chain/archiveStore/archiveStore.js +4 -1
  15. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  16. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
  17. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
  19. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  20. package/lib/chain/blocks/importBlock.d.ts +5 -1
  21. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  22. package/lib/chain/blocks/importBlock.js +12 -18
  23. package/lib/chain/blocks/importBlock.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts +8 -4
  25. package/lib/chain/blocks/index.d.ts.map +1 -1
  26. package/lib/chain/blocks/index.js +80 -15
  27. package/lib/chain/blocks/index.js.map +1 -1
  28. package/lib/chain/blocks/types.d.ts +8 -0
  29. package/lib/chain/blocks/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/types.js.map +1 -1
  31. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
  32. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  33. package/lib/chain/blocks/utils/chainSegment.js +26 -11
  34. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  35. package/lib/chain/chain.d.ts +7 -2
  36. package/lib/chain/chain.d.ts.map +1 -1
  37. package/lib/chain/chain.js +69 -40
  38. package/lib/chain/chain.js.map +1 -1
  39. package/lib/chain/emitter.d.ts +1 -1
  40. package/lib/chain/emitter.js +1 -1
  41. package/lib/chain/errors/blockError.d.ts +3 -5
  42. package/lib/chain/errors/blockError.d.ts.map +1 -1
  43. package/lib/chain/errors/blockError.js +2 -2
  44. package/lib/chain/errors/blockError.js.map +1 -1
  45. package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
  46. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  47. package/lib/chain/errors/executionPayloadBid.js +1 -0
  48. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  49. package/lib/chain/errors/executionPayloadEnvelope.d.ts +0 -11
  50. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  51. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  52. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  53. package/lib/chain/errors/gossipValidation.d.ts +2 -1
  54. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  55. package/lib/chain/errors/gossipValidation.js +1 -0
  56. package/lib/chain/errors/gossipValidation.js.map +1 -1
  57. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  58. package/lib/chain/forkChoice/index.js +2 -0
  59. package/lib/chain/forkChoice/index.js.map +1 -1
  60. package/lib/chain/interface.d.ts +2 -2
  61. package/lib/chain/interface.d.ts.map +1 -1
  62. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  63. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  64. package/lib/chain/prepareNextSlot.js +13 -13
  65. package/lib/chain/prepareNextSlot.js.map +1 -1
  66. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  67. package/lib/chain/produceBlock/produceBlockBody.js +46 -64
  68. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  69. package/lib/chain/validation/block.d.ts.map +1 -1
  70. package/lib/chain/validation/block.js +9 -50
  71. package/lib/chain/validation/block.js.map +1 -1
  72. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  73. package/lib/chain/validation/executionPayloadBid.js +16 -0
  74. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  75. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  76. package/lib/chain/validation/executionPayloadEnvelope.js +0 -29
  77. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  78. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  79. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  80. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  81. package/lib/chain/validatorMonitor.d.ts +7 -1
  82. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  83. package/lib/chain/validatorMonitor.js +22 -5
  84. package/lib/chain/validatorMonitor.js.map +1 -1
  85. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  86. package/lib/execution/builder/validateBid.js +3 -0
  87. package/lib/execution/builder/validateBid.js.map +1 -1
  88. package/lib/execution/engine/mock.d.ts.map +1 -1
  89. package/lib/execution/engine/mock.js +4 -1
  90. package/lib/execution/engine/mock.js.map +1 -1
  91. package/lib/metrics/metrics/lodestar.d.ts +4 -3
  92. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  93. package/lib/metrics/metrics/lodestar.js +5 -5
  94. package/lib/metrics/metrics/lodestar.js.map +1 -1
  95. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  96. package/lib/network/processor/gossipHandlers.js +382 -278
  97. package/lib/network/processor/gossipHandlers.js.map +1 -1
  98. package/lib/network/processor/index.d.ts.map +1 -1
  99. package/lib/network/processor/index.js +22 -16
  100. package/lib/network/processor/index.js.map +1 -1
  101. package/lib/network/reqresp/rateLimit.js +2 -1
  102. package/lib/network/reqresp/rateLimit.js.map +1 -1
  103. package/lib/sync/range/range.d.ts.map +1 -1
  104. package/lib/sync/range/range.js +24 -1
  105. package/lib/sync/range/range.js.map +1 -1
  106. package/package.json +15 -15
  107. package/src/api/impl/beacon/blocks/index.ts +0 -10
  108. package/src/api/impl/beacon/state/utils.ts +15 -0
  109. package/src/api/impl/config/index.ts +4 -1
  110. package/src/api/impl/validator/index.ts +44 -43
  111. package/src/chain/archiveStore/archiveStore.ts +6 -0
  112. package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
  113. package/src/chain/blocks/importBlock.ts +25 -20
  114. package/src/chain/blocks/index.ts +107 -21
  115. package/src/chain/blocks/types.ts +9 -0
  116. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  117. package/src/chain/chain.ts +78 -50
  118. package/src/chain/emitter.ts +1 -1
  119. package/src/chain/errors/blockError.ts +3 -3
  120. package/src/chain/errors/executionPayloadBid.ts +2 -0
  121. package/src/chain/errors/executionPayloadEnvelope.ts +0 -9
  122. package/src/chain/errors/gossipValidation.ts +1 -0
  123. package/src/chain/forkChoice/index.ts +2 -0
  124. package/src/chain/interface.ts +2 -2
  125. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  126. package/src/chain/prepareNextSlot.ts +16 -16
  127. package/src/chain/produceBlock/produceBlockBody.ts +50 -71
  128. package/src/chain/validation/block.ts +9 -66
  129. package/src/chain/validation/executionPayloadBid.ts +18 -0
  130. package/src/chain/validation/executionPayloadEnvelope.ts +0 -37
  131. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  132. package/src/chain/validatorMonitor.ts +24 -7
  133. package/src/execution/builder/validateBid.ts +4 -0
  134. package/src/execution/engine/mock.ts +4 -1
  135. package/src/metrics/metrics/lodestar.ts +6 -5
  136. package/src/network/processor/gossipHandlers.ts +489 -344
  137. package/src/network/processor/index.ts +24 -17
  138. package/src/network/reqresp/rateLimit.ts +2 -1
  139. package/src/sync/range/range.ts +23 -1
@@ -1,22 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ExecutionStatus} from "@lodestar/fork-choice";
3
- import {
4
- ForkName,
5
- MAX_ATTESTATIONS_ELECTRA,
6
- MAX_ATTESTER_SLASHINGS_ELECTRA,
7
- MAX_BLS_TO_EXECUTION_CHANGES,
8
- MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
9
- MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
10
- MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
11
- MAX_PAYLOAD_ATTESTATIONS,
12
- MAX_PROPOSER_SLASHINGS,
13
- MAX_VOLUNTARY_EXITS,
14
- MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
15
- MIN_SEED_LOOKAHEAD,
16
- isForkPostBellatrix,
17
- isForkPostDeneb,
18
- isForkPostGloas,
19
- } from "@lodestar/params";
3
+ import {ForkName, MIN_SEED_LOOKAHEAD, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
20
4
  import {
21
5
  computeEpochAtSlot,
22
6
  computeStartSlotAtEpoch,
@@ -173,7 +157,8 @@ export async function validateGossipBlock(
173
157
  }
174
158
 
175
159
  if (isForkPostGloas(fork)) {
176
- const bid = (block as gloas.BeaconBlock).body.signedExecutionPayloadBid.message;
160
+ const body = (block as gloas.BeaconBlock).body;
161
+ const bid = body.signedExecutionPayloadBid.message;
177
162
 
178
163
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer
179
164
  // -- i.e. validate that len(bid.blob_kzg_commitments) <= max_blobs_per_block
@@ -196,54 +181,12 @@ export async function validateGossipBlock(
196
181
  });
197
182
  }
198
183
 
199
- // [REJECT] The counts of `block.body.parent_execution_requests` are within
200
- // their respective limits -- i.e. validate that
201
- // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
202
- // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
203
- // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
204
- // and
205
- // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
206
- // [REJECT] The counts of the block body operations are within their respective
207
- // limits -- i.e. validate that
208
- // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
209
- // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
210
- // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
211
- // `len(block.body.deposits) == 0`,
212
- // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
213
- // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
214
- // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
215
- const body = (block as gloas.BeaconBlock).body;
216
- const requests = body.parentExecutionRequests;
217
- const countLimits: [string, number, number][] = [
218
- ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
219
- [
220
- "parentExecutionRequests.consolidations",
221
- requests.consolidations.length,
222
- MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
223
- ],
224
- [
225
- "parentExecutionRequests.builderDeposits",
226
- requests.builderDeposits.length,
227
- MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
228
- ],
229
- ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
230
- ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
231
- ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
232
- ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
233
- ["deposits", body.deposits.length, 0],
234
- ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
235
- ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
236
- ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
237
- ];
238
- for (const [name, count, limit] of countLimits) {
239
- if (count > limit) {
240
- throw new BlockGossipError(GossipAction.REJECT, {
241
- code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
242
- name,
243
- count,
244
- limit,
245
- });
246
- }
184
+ // [REJECT] The block contains no deposits.
185
+ if (body.deposits.length !== 0) {
186
+ throw new BlockGossipError(GossipAction.REJECT, {
187
+ code: BlockErrorCode.NON_ZERO_DEPOSITS,
188
+ count: body.deposits.length,
189
+ });
247
190
  }
248
191
 
249
192
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
@@ -113,6 +113,15 @@ export async function validateApiExecutionPayloadBid(
113
113
  });
114
114
  }
115
115
 
116
+ // [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
117
+ // `bid.block_hash != bid.parent_block_hash`.
118
+ if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
119
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
120
+ code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
121
+ blockHash: toRootHex(bid.blockHash),
122
+ });
123
+ }
124
+
116
125
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
117
126
  // consensus layer -- i.e. validate that
118
127
  // `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
@@ -266,6 +275,15 @@ async function validateExecutionPayloadBid(
266
275
  });
267
276
  }
268
277
 
278
+ // [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
279
+ // `bid.block_hash != bid.parent_block_hash`.
280
+ if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
281
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
282
+ code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
283
+ blockHash: toRootHex(bid.blockHash),
284
+ });
285
+ }
286
+
269
287
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
270
288
  // consensus layer -- i.e. validate that
271
289
  // `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
@@ -1,11 +1,4 @@
1
1
  import {PayloadStatus} from "@lodestar/fork-choice";
2
- import {
3
- MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
4
- MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
5
- MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
6
- MAX_WITHDRAWALS_PER_PAYLOAD,
7
- MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
8
- } from "@lodestar/params";
9
2
  import {
10
3
  computeStartSlotAtEpoch,
11
4
  getExecutionPayloadEnvelopeSignatureSet,
@@ -122,36 +115,6 @@ async function validateExecutionPayloadEnvelope(
122
115
  });
123
116
  }
124
117
 
125
- // [REJECT] The counts of `execution_requests` are within their respective limits.
126
- // New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
127
- // are enforced here in gossip validation.
128
- const {executionRequests} = envelope;
129
- const requestCountLimits: [string, number, number][] = [
130
- ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
131
- ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
132
- ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
133
- ["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
134
- ];
135
- for (const [name, count, limit] of requestCountLimits) {
136
- if (count > limit) {
137
- throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
138
- code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
139
- name,
140
- count,
141
- limit,
142
- });
143
- }
144
- }
145
-
146
- // [REJECT] The number of withdrawals is within the limit.
147
- if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
148
- throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
149
- code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
150
- count: payload.withdrawals.length,
151
- limit: MAX_WITHDRAWALS_PER_PAYLOAD,
152
- });
153
- }
154
-
155
118
  // Get the block state to verify the builder's signature.
156
119
  const blockState = await chain.regen
157
120
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
@@ -7,7 +7,6 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
7
7
  import {toRootHex} from "@lodestar/utils";
8
8
  import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
9
9
  import {IBeaconChain} from "../index.js";
10
- import {RegenCaller} from "../regen/index.js";
11
10
 
12
11
  export type PayloadAttestationValidationResult = {
13
12
  attDataRootHex: RootHex;
@@ -82,15 +81,15 @@ async function validatePayloadAttestationMessage(
82
81
  // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
83
82
  // it is possible that the block didn't pass the validation
84
83
 
85
- // Use the referenced block's branch state for the PTC committee check
86
- const state = await chain.regen
87
- .getBlockSlotState(block, data.slot, {dontTransferCache: true}, RegenCaller.validateGossipPayloadAttestationMessage)
88
- .catch(() => {
89
- throw new PayloadAttestationError(GossipAction.IGNORE, {
90
- code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
91
- blockRoot: toRootHex(data.beaconBlockRoot),
92
- });
84
+ // block.slot === data.slot is enforced above, so use the block's post-state directly to avoid
85
+ // getting through regen queue
86
+ const state = chain.regen.getStateSync(block.stateRoot);
87
+ if (state == null) {
88
+ throw new PayloadAttestationError(GossipAction.IGNORE, {
89
+ code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
90
+ blockRoot: toRootHex(data.beaconBlockRoot),
93
91
  });
92
+ }
94
93
 
95
94
  if (!isStatePostGloas(state)) {
96
95
  throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
3
3
  import {
4
4
  IBeaconStateView,
5
5
  ParticipationFlags,
@@ -1117,7 +1117,11 @@ function isCanonical(rootCache: RootHexCache, block: AttestationBlockInclusion):
1117
1117
 
1118
1118
  /** Returns true if root at slot is the same at slot - 1 == there was no new block at slot */
1119
1119
  function isMissedSlot(rootCache: RootHexCache, slot: Slot): boolean {
1120
- return slot > 0 && rootCache.getBlockRootAtSlot(slot) === rootCache.getBlockRootAtSlot(slot - 1);
1120
+ if (slot <= 0) {
1121
+ return false;
1122
+ }
1123
+ const root = rootCache.getBlockRootAtSlot(slot);
1124
+ return root !== null && root === rootCache.getBlockRootAtSlot(slot - 1);
1121
1125
  }
1122
1126
 
1123
1127
  function renderBlockProposalSummary(
@@ -1131,7 +1135,11 @@ function renderBlockProposalSummary(
1131
1135
  return "not_submitted";
1132
1136
  }
1133
1137
 
1134
- if (rootCache.getBlockRootAtSlot(proposalSlot) === proposal.blockRoot) {
1138
+ const canonicalRoot = rootCache.getBlockRootAtSlot(proposalSlot);
1139
+ if (canonicalRoot === null) {
1140
+ return "unknown";
1141
+ }
1142
+ if (canonicalRoot === proposal.blockRoot) {
1135
1143
  // Canonical state includes our block
1136
1144
  return "canonical";
1137
1145
  }
@@ -1157,14 +1165,23 @@ function renderBlockProposalSummary(
1157
1165
  * In normal network conditions the same root is read multiple times, specially the target.
1158
1166
  */
1159
1167
  export class RootHexCache {
1160
- private readonly blockRootSlotCache = new Map<Slot, RootHex>();
1168
+ private readonly blockRootSlotCache = new Map<Slot, RootHex | null>();
1161
1169
 
1162
1170
  constructor(private readonly state: IBeaconStateView) {}
1163
1171
 
1164
- getBlockRootAtSlot(slot: Slot): RootHex {
1172
+ /**
1173
+ * Block root at `slot`, null if the state does not cover it: slots at or after the state's own slot (an inclusion
1174
+ * in a block the head state does not know yet, e.g. an orphaned block, or the slot after the last attestation of
1175
+ * the epoch) or slots older than `SLOTS_PER_HISTORICAL_ROOT`. Callers treat null as unknown instead of throwing
1176
+ * and aborting the summaries of all remaining validators.
1177
+ */
1178
+ getBlockRootAtSlot(slot: Slot): RootHex | null {
1165
1179
  let root = this.blockRootSlotCache.get(slot);
1166
- if (!root) {
1167
- root = toRootHex(this.state.getBlockRootAtSlot(slot));
1180
+ if (root === undefined) {
1181
+ root =
1182
+ slot < this.state.slot && slot >= this.state.slot - SLOTS_PER_HISTORICAL_ROOT
1183
+ ? toRootHex(this.state.getBlockRootAtSlot(slot))
1184
+ : null;
1168
1185
  this.blockRootSlotCache.set(slot, root);
1169
1186
  }
1170
1187
  return root;
@@ -56,6 +56,10 @@ export async function validateBuilderApiExecutionPayloadBid(
56
56
  );
57
57
  }
58
58
 
59
+ if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
60
+ throw Error(`Bid block hash ${toRootHex(bid.blockHash)} must not equal its parent block hash`);
61
+ }
62
+
59
63
  const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
60
64
  const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
61
65
  if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
@@ -296,7 +296,10 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
296
296
 
297
297
  // 5. Client software MUST update its forkchoice state if payloads referenced by forkchoiceState.headBlockHash and
298
298
  // forkchoiceState.finalizedBlockHash are VALID.
299
- if (!this.validBlocks.has(finalizedBlockHash)) {
299
+ //
300
+ // A zero finalizedBlockHash means "no finalized execution block" (e.g. the finalized checkpoint has no execution
301
+ // payload, pre-merge/pre-TTD). Real EL clients accept this, so the mock must not treat it as an unknown block.
302
+ if (finalizedBlockHash !== ZERO_HASH_HEX && !this.validBlocks.has(finalizedBlockHash)) {
300
303
  throw Error(`Unknown finalizedBlockHash ${finalizedBlockHash}`);
301
304
  }
302
305
  this.headBlockHash = headBlockHash;
@@ -2,6 +2,7 @@
2
2
  import {NotReorgedReason} from "@lodestar/fork-choice";
3
3
  import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
4
4
  import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
5
+ import type {LateCanonicalBlockReason} from "../../chain/archiveStore/utils/archiveBlocks.js";
5
6
  import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
6
7
  import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
7
8
  import {
@@ -1047,6 +1048,11 @@ export function createLodestarMetrics(
1047
1048
  name: "lodestar_import_block_set_head_after_cutoff_total",
1048
1049
  help: "Total times an imported block is set as head after ATTESTATION_DUE_BPS of the slot",
1049
1050
  }),
1051
+ lateCanonicalBlock: register.counter<{reason: LateCanonicalBlockReason}>({
1052
+ name: "lodestar_import_block_late_canonical_total",
1053
+ help: "Total finalized-canonical blocks this node imported after the attestation cutoff; reason distinguishes this node's processing lag (slow_import) from late reception (late_receive)",
1054
+ labelNames: ["reason"],
1055
+ }),
1050
1056
  bySource: register.gauge<{source: BlockInputSource}>({
1051
1057
  name: "lodestar_import_block_by_source_total",
1052
1058
  help: "Total number of imported blocks by source",
@@ -1352,11 +1358,6 @@ export function createLodestarMetrics(
1352
1358
  help: "Total number of InsertOutcome as a result of adding an execution payload bid from gossip to the pool",
1353
1359
  labelNames: ["insertOutcome"],
1354
1360
  }),
1355
- apiInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
1356
- name: "lodestar_oppool_execution_payload_bid_pool_api_insert_outcome_total",
1357
- help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
1358
- labelNames: ["insertOutcome"],
1359
- }),
1360
1361
  apiValidationTime: register.histogram({
1361
1362
  name: "lodestar_api_execution_payload_bid_validation_time_seconds",
1362
1363
  help: "Time elapsed for signed execution payload bid validation - api path",