@lodestar/beacon-node 1.44.0-dev.552cdce8d0 → 1.44.0-dev.5a822af737

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 (220) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +50 -29
  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 +1 -26
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/config/constants.d.ts +1 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +2 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/debug/index.d.ts.map +1 -1
  12. package/lib/api/impl/debug/index.js +69 -12
  13. package/lib/api/impl/debug/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  15. package/lib/api/impl/lodestar/index.js +28 -0
  16. package/lib/api/impl/lodestar/index.js.map +1 -1
  17. package/lib/api/impl/validator/index.d.ts.map +1 -1
  18. package/lib/api/impl/validator/index.js +121 -69
  19. package/lib/api/impl/validator/index.js.map +1 -1
  20. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  21. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js +0 -4
  23. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +5 -2
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  28. package/lib/chain/blocks/importExecutionPayload.js +5 -3
  29. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  30. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  31. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  32. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  33. package/lib/chain/chain.d.ts +1 -1
  34. package/lib/chain/chain.d.ts.map +1 -1
  35. package/lib/chain/chain.js +10 -2
  36. package/lib/chain/chain.js.map +1 -1
  37. package/lib/chain/emitter.d.ts +2 -1
  38. package/lib/chain/emitter.d.ts.map +1 -1
  39. package/lib/chain/emitter.js.map +1 -1
  40. package/lib/chain/errors/blockError.d.ts +0 -7
  41. package/lib/chain/errors/blockError.d.ts.map +1 -1
  42. package/lib/chain/errors/blockError.js +0 -3
  43. package/lib/chain/errors/blockError.js.map +1 -1
  44. package/lib/chain/errors/executionPayloadBid.d.ts +17 -1
  45. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  46. package/lib/chain/errors/executionPayloadBid.js +3 -0
  47. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  48. package/lib/chain/errors/payloadAttestation.d.ts +6 -0
  49. package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
  50. package/lib/chain/errors/payloadAttestation.js +1 -0
  51. package/lib/chain/errors/payloadAttestation.js.map +1 -1
  52. package/lib/chain/forkChoice/index.d.ts +4 -4
  53. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  54. package/lib/chain/forkChoice/index.js +24 -11
  55. package/lib/chain/forkChoice/index.js.map +1 -1
  56. package/lib/chain/opPools/executionPayloadBidPool.d.ts +4 -4
  57. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  58. package/lib/chain/opPools/executionPayloadBidPool.js +6 -4
  59. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  60. package/lib/chain/options.d.ts.map +1 -1
  61. package/lib/chain/options.js +1 -0
  62. package/lib/chain/options.js.map +1 -1
  63. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  64. package/lib/chain/prepareNextSlot.js +2 -1
  65. package/lib/chain/prepareNextSlot.js.map +1 -1
  66. package/lib/chain/produceBlock/produceBlockBody.d.ts +7 -1
  67. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  68. package/lib/chain/produceBlock/produceBlockBody.js +108 -19
  69. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  70. package/lib/chain/regen/interface.d.ts +2 -1
  71. package/lib/chain/regen/interface.d.ts.map +1 -1
  72. package/lib/chain/regen/interface.js +2 -0
  73. package/lib/chain/regen/interface.js.map +1 -1
  74. package/lib/chain/regen/queued.d.ts +0 -1
  75. package/lib/chain/regen/queued.d.ts.map +1 -1
  76. package/lib/chain/regen/queued.js +0 -4
  77. package/lib/chain/regen/queued.js.map +1 -1
  78. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
  79. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  80. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
  81. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  82. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
  83. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  84. package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
  85. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  86. package/lib/chain/stateCache/types.d.ts +0 -2
  87. package/lib/chain/stateCache/types.d.ts.map +1 -1
  88. package/lib/chain/stateCache/types.js.map +1 -1
  89. package/lib/chain/validation/block.d.ts +5 -1
  90. package/lib/chain/validation/block.d.ts.map +1 -1
  91. package/lib/chain/validation/block.js +4 -14
  92. package/lib/chain/validation/block.js.map +1 -1
  93. package/lib/chain/validation/executionPayloadBid.d.ts +7 -3
  94. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  95. package/lib/chain/validation/executionPayloadBid.js +70 -16
  96. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  97. package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
  98. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  99. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  100. package/lib/chain/validation/payloadAttestationMessage.js +24 -4
  101. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  102. package/lib/chain/validatorMonitor.d.ts +1 -0
  103. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  104. package/lib/chain/validatorMonitor.js +16 -0
  105. package/lib/chain/validatorMonitor.js.map +1 -1
  106. package/lib/execution/builder/index.d.ts +1 -2
  107. package/lib/execution/builder/index.d.ts.map +1 -1
  108. package/lib/execution/builder/index.js +0 -1
  109. package/lib/execution/builder/index.js.map +1 -1
  110. package/lib/execution/engine/interface.d.ts +1 -0
  111. package/lib/execution/engine/interface.d.ts.map +1 -1
  112. package/lib/execution/engine/types.d.ts +2 -0
  113. package/lib/execution/engine/types.d.ts.map +1 -1
  114. package/lib/execution/engine/types.js +2 -0
  115. package/lib/execution/engine/types.js.map +1 -1
  116. package/lib/metrics/metrics/lodestar.d.ts +5 -1
  117. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  118. package/lib/metrics/metrics/lodestar.js +14 -3
  119. package/lib/metrics/metrics/lodestar.js.map +1 -1
  120. package/lib/network/gossip/topic.d.ts +2 -2
  121. package/lib/network/interface.d.ts +1 -0
  122. package/lib/network/interface.d.ts.map +1 -1
  123. package/lib/network/network.d.ts +1 -0
  124. package/lib/network/network.d.ts.map +1 -1
  125. package/lib/network/network.js +5 -0
  126. package/lib/network/network.js.map +1 -1
  127. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  128. package/lib/network/processor/gossipHandlers.js +21 -6
  129. package/lib/network/processor/gossipHandlers.js.map +1 -1
  130. package/lib/network/processor/index.d.ts +2 -2
  131. package/lib/network/processor/index.d.ts.map +1 -1
  132. package/lib/network/processor/index.js +25 -23
  133. package/lib/network/processor/index.js.map +1 -1
  134. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  135. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
  136. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  137. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  138. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +13 -3
  139. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  140. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
  141. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  142. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
  143. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  144. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
  145. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  146. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts +2 -1
  147. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  148. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +15 -1
  149. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  150. package/lib/network/reqresp/handlers/index.js +4 -4
  151. package/lib/network/reqresp/handlers/index.js.map +1 -1
  152. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  153. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +22 -3
  154. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  155. package/lib/sync/types.d.ts +9 -1
  156. package/lib/sync/types.d.ts.map +1 -1
  157. package/lib/sync/types.js +9 -2
  158. package/lib/sync/types.js.map +1 -1
  159. package/lib/sync/unknownBlock.d.ts.map +1 -1
  160. package/lib/sync/unknownBlock.js +64 -30
  161. package/lib/sync/unknownBlock.js.map +1 -1
  162. package/lib/util/dataColumns.d.ts.map +1 -1
  163. package/lib/util/dataColumns.js +16 -11
  164. package/lib/util/dataColumns.js.map +1 -1
  165. package/lib/util/sszBytes.js +1 -1
  166. package/package.json +15 -17
  167. package/src/api/impl/beacon/blocks/index.ts +60 -38
  168. package/src/api/impl/beacon/pool/index.ts +4 -42
  169. package/src/api/impl/config/constants.ts +2 -0
  170. package/src/api/impl/debug/index.ts +73 -12
  171. package/src/api/impl/lodestar/index.ts +30 -0
  172. package/src/api/impl/validator/index.ts +141 -82
  173. package/src/chain/archiveStore/archiveStore.ts +0 -5
  174. package/src/chain/blocks/importBlock.ts +10 -2
  175. package/src/chain/blocks/importExecutionPayload.ts +8 -2
  176. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -4
  177. package/src/chain/chain.ts +12 -1
  178. package/src/chain/emitter.ts +3 -2
  179. package/src/chain/errors/blockError.ts +0 -4
  180. package/src/chain/errors/executionPayloadBid.ts +13 -1
  181. package/src/chain/errors/payloadAttestation.ts +2 -0
  182. package/src/chain/forkChoice/index.ts +27 -4
  183. package/src/chain/opPools/executionPayloadBidPool.ts +10 -9
  184. package/src/chain/options.ts +1 -0
  185. package/src/chain/prepareNextSlot.ts +2 -1
  186. package/src/chain/produceBlock/produceBlockBody.ts +159 -26
  187. package/src/chain/regen/interface.ts +2 -1
  188. package/src/chain/regen/queued.ts +0 -5
  189. package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
  190. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  191. package/src/chain/stateCache/types.ts +0 -2
  192. package/src/chain/validation/block.ts +12 -16
  193. package/src/chain/validation/executionPayloadBid.ts +78 -19
  194. package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
  195. package/src/chain/validation/payloadAttestationMessage.ts +26 -4
  196. package/src/chain/validatorMonitor.ts +18 -0
  197. package/src/execution/builder/index.ts +1 -4
  198. package/src/execution/engine/interface.ts +1 -0
  199. package/src/execution/engine/types.ts +4 -0
  200. package/src/metrics/metrics/lodestar.ts +15 -3
  201. package/src/network/interface.ts +1 -0
  202. package/src/network/network.ts +11 -0
  203. package/src/network/processor/gossipHandlers.ts +26 -6
  204. package/src/network/processor/index.ts +27 -27
  205. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
  206. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -3
  207. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
  208. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
  209. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +20 -1
  210. package/src/network/reqresp/handlers/index.ts +4 -4
  211. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +21 -3
  212. package/src/sync/types.ts +11 -2
  213. package/src/sync/unknownBlock.ts +70 -31
  214. package/src/util/dataColumns.ts +17 -12
  215. package/src/util/sszBytes.ts +1 -1
  216. package/lib/execution/builder/utils.d.ts +0 -5
  217. package/lib/execution/builder/utils.d.ts.map +0 -1
  218. package/lib/execution/builder/utils.js +0 -17
  219. package/lib/execution/builder/utils.js.map +0 -1
  220. package/src/execution/builder/utils.ts +0 -19
@@ -4,9 +4,10 @@ import {
4
4
  createSingleSignatureSetFromComponents,
5
5
  getExecutionPayloadBidSigningRoot,
6
6
  isActiveBuilder,
7
+ isGasLimitTargetCompatible,
7
8
  isStatePostGloas,
8
9
  } from "@lodestar/state-transition";
9
- import {gloas} from "@lodestar/types";
10
+ import {ValidatorIndex, gloas} from "@lodestar/types";
10
11
  import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils";
11
12
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
12
13
  import {ExecutionPayloadBidError, ExecutionPayloadBidErrorCode, GossipAction} from "../errors/index.js";
@@ -16,28 +17,24 @@ import {RegenCaller} from "../regen/index.js";
16
17
  export async function validateApiExecutionPayloadBid(
17
18
  chain: IBeaconChain,
18
19
  signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
19
- ): Promise<void> {
20
+ ): Promise<{proposerIndex: ValidatorIndex}> {
20
21
  return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
21
22
  }
22
23
 
23
24
  export async function validateGossipExecutionPayloadBid(
24
25
  chain: IBeaconChain,
25
26
  signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
26
- ): Promise<void> {
27
+ ): Promise<{proposerIndex: ValidatorIndex}> {
27
28
  return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
28
29
  }
29
30
 
30
31
  async function validateExecutionPayloadBid(
31
32
  chain: IBeaconChain,
32
33
  signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
33
- ): Promise<void> {
34
+ ): Promise<{proposerIndex: ValidatorIndex}> {
34
35
  const bid = signedExecutionPayloadBid.message;
35
36
  const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
36
37
  const parentBlockHashHex = toRootHex(bid.parentBlockHash);
37
- const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipExecutionPayloadBid);
38
- if (!isStatePostGloas(state)) {
39
- throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
40
- }
41
38
 
42
39
  // [IGNORE] `bid.slot` is the current slot or the next slot.
43
40
  const currentSlot = chain.clock.currentSlot;
@@ -60,6 +57,17 @@ async function validateExecutionPayloadBid(
60
57
  });
61
58
  }
62
59
 
60
+ // [REJECT] The bid is for a higher slot than its parent block -- i.e.
61
+ // validate that `bid.slot` is greater than the slot of the block with root
62
+ // `bid.parent_block_root`.
63
+ if (bid.slot <= parentBlock.slot) {
64
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
65
+ code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT,
66
+ parentSlot: parentBlock.slot,
67
+ slot: bid.slot,
68
+ });
69
+ }
70
+
63
71
  // [IGNORE] A `SignedProposerPreferences` matching `bid.slot` and the bid's branch has been
64
72
  // seen — i.e. `proposal_slot == bid.slot` AND `dependent_root ==
65
73
  // get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`.
@@ -99,9 +107,31 @@ async function validateExecutionPayloadBid(
99
107
  });
100
108
  }
101
109
 
110
+ // Use the bid's parent branch state for builder checks
111
+ const state = await chain.regen
112
+ .getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateGossipExecutionPayloadBid)
113
+ .catch(() => {
114
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
115
+ code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
116
+ parentBlockRoot: parentBlockRootHex,
117
+ });
118
+ });
119
+
120
+ if (!isStatePostGloas(state)) {
121
+ throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
122
+ }
123
+
102
124
  // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
103
125
  // `is_active_builder(state, bid.builder_index)` returns `True`.
104
- const builder = state.getBuilder(bid.builderIndex);
126
+ let builder: gloas.Builder;
127
+ try {
128
+ builder = state.getBuilder(bid.builderIndex);
129
+ } catch {
130
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
131
+ code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
132
+ builderIndex: bid.builderIndex,
133
+ });
134
+ }
105
135
  if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
106
136
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
107
137
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
@@ -128,14 +158,33 @@ async function validateExecutionPayloadBid(
128
158
  });
129
159
  }
130
160
 
131
- // [REJECT] `bid.gas_limit == proposer_preferences.gas_limit`.
132
- const bidGasLimit = Number(bid.gasLimit);
133
- if (bidGasLimit !== proposerPreferences.message.gasLimit) {
134
- throw new ExecutionPayloadBidError(GossipAction.REJECT, {
161
+ // [IGNORE] `bid.parent_block_hash` is the block hash of a known execution payload in fork
162
+ // choice. Looks up the variant of `bid.parent_block_root` whose payload hash matches
163
+ // `bid.parent_block_hash` — works for both FULL parents (FULL variant carries the delivered
164
+ // payload's hash) and EMPTY parents (EMPTY/PENDING variants carry the inherited parent
165
+ // payload's hash, since the new block doesn't have its own payload). Variant carries the
166
+ // executed payload's gas_limit, which we use as `parent_gas_limit` below.
167
+ const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(parentBlockRootHex, parentBlockHashHex);
168
+ if (parentPayloadVariant === null || parentPayloadVariant.executionPayloadBlockHash === null) {
169
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
170
+ code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH,
171
+ parentBlockHash: parentBlockHashHex,
172
+ });
173
+ }
174
+
175
+ // [IGNORE] `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`,
176
+ // where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
177
+ // `target_gas_limit` is `proposer_preferences.target_gas_limit`.
178
+ const bidGasLimit = bid.gasLimit;
179
+ const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
180
+ const targetGasLimit = proposerPreferences.message.targetGasLimit;
181
+ if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
182
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
135
183
  code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_GAS_LIMIT_MISMATCH,
136
184
  builderIndex: bid.builderIndex,
137
185
  bidGasLimit,
138
- expectedGasLimit: proposerPreferences.message.gasLimit,
186
+ parentGasLimit,
187
+ targetGasLimit,
139
188
  });
140
189
  }
141
190
 
@@ -166,11 +215,11 @@ async function validateExecutionPayloadBid(
166
215
  // [IGNORE] this bid is the highest value bid seen for the tuple
167
216
  // `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
168
217
  const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
169
- if (bestBid !== null && bestBid.value >= bid.value) {
218
+ if (bestBid !== null && bestBid.message.value >= bid.value) {
170
219
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
171
220
  code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
172
221
  bidValue: bid.value,
173
- currentHighestBid: bestBid.value,
222
+ currentHighestBid: bestBid.message.value,
174
223
  });
175
224
  }
176
225
  // [IGNORE] `bid.value` is less or equal than the builder's excess balance --
@@ -183,9 +232,17 @@ async function validateExecutionPayloadBid(
183
232
  });
184
233
  }
185
234
 
186
- // [IGNORE] `bid.parent_block_hash` is the block hash of a known execution
187
- // payload in fork choice.
188
- // TODO GLOAS: implement this
235
+ // [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
236
+ // `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
237
+ const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
238
+ if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
239
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
240
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
241
+ builderIndex: bid.builderIndex,
242
+ bidPrevRandao: toHex(bid.prevRandao),
243
+ expectedPrevRandao: toHex(randaoMix),
244
+ });
245
+ }
189
246
 
190
247
  // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
191
248
  const signatureSet = createSingleSignatureSetFromComponents(
@@ -204,4 +261,6 @@ async function validateExecutionPayloadBid(
204
261
 
205
262
  // Valid
206
263
  chain.seenExecutionPayloadBids.add(bid.slot, bid.builderIndex);
264
+
265
+ return {proposerIndex: proposerPreferences.message.validatorIndex};
207
266
  }
@@ -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, {
@@ -8,6 +8,7 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
8
8
  import {toRootHex} from "@lodestar/utils";
9
9
  import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
10
10
  import {IBeaconChain} from "../index.js";
11
+ import {RegenCaller} from "../regen/index.js";
11
12
 
12
13
  export type PayloadAttestationValidationResult = {
13
14
  attDataRootHex: RootHex;
@@ -61,22 +62,43 @@ async function validatePayloadAttestationMessage(
61
62
  // [IGNORE] The message's block `data.beacon_block_root` has been seen (via
62
63
  // gossip or non-gossip sources) (a client MAY queue attestation for processing
63
64
  // once the block is retrieved. Note a client might want to request payload after).
64
- if (!chain.forkChoice.hasBlock(data.beaconBlockRoot)) {
65
+ const block = chain.forkChoice.getBlockDefaultStatus(data.beaconBlockRoot);
66
+ if (!block) {
65
67
  throw new PayloadAttestationError(GossipAction.IGNORE, {
66
68
  code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
67
69
  blockRoot: toRootHex(data.beaconBlockRoot),
68
70
  });
69
71
  }
70
72
 
71
- const state = chain.getHeadState();
72
- if (!isStatePostGloas(state)) {
73
- throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
73
+ // [IGNORE] The block referenced by `data.beacon_block_root` is at slot `data.slot`,
74
+ // i.e. the block has `block.slot == data.slot`.
75
+ if (block.slot !== data.slot) {
76
+ throw new PayloadAttestationError(GossipAction.IGNORE, {
77
+ code: PayloadAttestationErrorCode.INVALID_BLOCK_SLOT,
78
+ blockRoot: toRootHex(data.beaconBlockRoot),
79
+ blockSlot: block.slot,
80
+ slot: data.slot,
81
+ });
74
82
  }
75
83
 
76
84
  // [REJECT] The message's block `data.beacon_block_root` passes validation.
77
85
  // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
78
86
  // it is possible that the block didn't pass the validation
79
87
 
88
+ // Use the referenced block's branch state for the PTC committee check
89
+ const state = await chain.regen
90
+ .getBlockSlotState(block, data.slot, {dontTransferCache: true}, RegenCaller.validateGossipPayloadAttestationMessage)
91
+ .catch(() => {
92
+ throw new PayloadAttestationError(GossipAction.IGNORE, {
93
+ code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
94
+ blockRoot: toRootHex(data.beaconBlockRoot),
95
+ });
96
+ });
97
+
98
+ if (!isStatePostGloas(state)) {
99
+ throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
100
+ }
101
+
80
102
  // [REJECT] The message's validator index is within the payload committee in
81
103
  // `get_ptc(state, data.slot)`. The `state` is the head state corresponding to
82
104
  // processing the block up to the current slot as determined by the fork choice.
@@ -66,6 +66,7 @@ export type ValidatorMonitor = {
66
66
  delaySec: Seconds,
67
67
  envelope: gloas.SignedExecutionPayloadEnvelope
68
68
  ): void;
69
+ registerExecutionPayloadBid(src: OpSource, proposerIndex: ValidatorIndex, bid: gloas.ExecutionPayloadBid): void;
69
70
  registerImportedBlock(block: BeaconBlock, data: {proposerBalanceDelta: number}): void;
70
71
  onPoolSubmitUnaggregatedAttestation(
71
72
  seenTimestampSec: number,
@@ -459,6 +460,23 @@ export function createValidatorMonitor(
459
460
  // TODO GLOAS: implement execution payload envelope monitoring
460
461
  },
461
462
 
463
+ registerExecutionPayloadBid(src, proposerIndex, bid) {
464
+ if (!validators.has(proposerIndex)) {
465
+ return;
466
+ }
467
+ log("Received an execution payload bid for monitored proposer", {
468
+ slot: bid.slot,
469
+ proposerIndex,
470
+ src,
471
+ builderIndex: bid.builderIndex,
472
+ gasLimit: bid.gasLimit,
473
+ value: bid.value.toString(),
474
+ parentBlockRoot: toRootHex(bid.parentBlockRoot),
475
+ parentBlockHash: toRootHex(bid.parentBlockHash),
476
+ blockHash: toRootHex(bid.blockHash),
477
+ });
478
+ },
479
+
462
480
  registerImportedBlock(block, {proposerBalanceDelta}) {
463
481
  const validator = validators.get(block.proposerIndex);
464
482
  if (validator) {
@@ -1,11 +1,8 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {Logger} from "@lodestar/logger";
3
3
  import {Metrics} from "../../metrics/metrics.js";
4
- import {IExecutionBuilder} from "./interface.js";
5
-
6
- export {getExpectedGasLimit} from "./utils.js";
7
-
8
4
  import {ExecutionBuilderHttp, ExecutionBuilderHttpOpts, defaultExecutionBuilderHttpOpts} from "./http.js";
5
+ import {IExecutionBuilder} from "./interface.js";
9
6
 
10
7
  export {ExecutionBuilderHttp, defaultExecutionBuilderHttpOpts};
11
8
 
@@ -88,6 +88,7 @@ export type PayloadAttributes = {
88
88
  withdrawals?: capella.Withdrawal[];
89
89
  parentBeaconBlockRoot?: Uint8Array;
90
90
  slotNumber?: number; // EIP-7843
91
+ targetGasLimit?: number; // GLOAS (PayloadAttributesV4, execution-apis#796)
91
92
  };
92
93
 
93
94
  export type VersionedHashes = Uint8Array[];
@@ -245,6 +245,8 @@ export type PayloadAttributesRpc = {
245
245
  parentBeaconBlockRoot?: DATA;
246
246
  /** QUANTITY, 64 Bits - value for the slot number field of the new payload (EIP-7843) */
247
247
  slotNumber?: QUANTITY;
248
+ /** QUANTITY, 64 Bits - target value for the gasLimit field of the new payload (GLOAS, execution-apis#796) */
249
+ targetGasLimit?: QUANTITY;
248
250
  };
249
251
 
250
252
  export type ClientVersionRpc = {
@@ -425,6 +427,7 @@ export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttr
425
427
  withdrawals: data.withdrawals?.map(serializeWithdrawal),
426
428
  parentBeaconBlockRoot: data.parentBeaconBlockRoot ? bytesToData(data.parentBeaconBlockRoot) : undefined,
427
429
  slotNumber: data.slotNumber !== undefined ? numToQuantity(data.slotNumber) : undefined,
430
+ targetGasLimit: data.targetGasLimit !== undefined ? numToQuantity(data.targetGasLimit) : undefined,
428
431
  };
429
432
  }
430
433
 
@@ -442,6 +445,7 @@ export function deserializePayloadAttributes(data: PayloadAttributesRpc): Payloa
442
445
  withdrawals: data.withdrawals?.map((withdrawal) => deserializeWithdrawal(withdrawal)),
443
446
  parentBeaconBlockRoot: data.parentBeaconBlockRoot ? dataToBytes(data.parentBeaconBlockRoot, 32) : undefined,
444
447
  slotNumber: data.slotNumber !== undefined ? quantityToNum(data.slotNumber) : undefined,
448
+ targetGasLimit: data.targetGasLimit !== undefined ? quantityToNum(data.targetGasLimit) : undefined,
445
449
  };
446
450
  }
447
451
 
@@ -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",
@@ -987,10 +998,11 @@ export function createLodestarMetrics(
987
998
  }),
988
999
  },
989
1000
  importPayload: {
990
- bySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
991
- name: "lodestar_import_payload_by_source_total",
992
- help: "Total number of imported execution payload envelopes by source",
1001
+ elapsedTimeTillImported: register.histogram<{source: PayloadEnvelopeInputSource}>({
1002
+ name: "lodestar_import_payload_elapsed_time_till_imported_seconds",
1003
+ help: "Time elapsed between slot time and the time execution payload envelope is imported (added to fork choice)",
993
1004
  labelNames: ["source"],
1005
+ buckets: [1, 2, 3, 6, 9, 12],
994
1006
  }),
995
1007
  columnsBySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
996
1008
  name: "lodestar_import_payload_columns_by_source_total",
@@ -113,6 +113,7 @@ export interface INetwork extends INetworkCorePublic {
113
113
  publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number>;
114
114
  publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number>;
115
115
  publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number>;
116
+ publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number>;
116
117
  publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number>;
117
118
  publishProposerPreferences(signedProposerPreferences: gloas.SignedProposerPreferences): Promise<number>;
118
119
 
@@ -515,6 +515,17 @@ export class Network implements INetwork {
515
515
  );
516
516
  }
517
517
 
518
+ async publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number> {
519
+ const epoch = computeEpochAtSlot(signedBid.message.slot);
520
+ const boundary = this.config.getForkBoundaryAtEpoch(epoch);
521
+
522
+ return this.publishGossip<GossipType.execution_payload_bid>(
523
+ {type: GossipType.execution_payload_bid, boundary},
524
+ signedBid,
525
+ {ignoreDuplicatePublishError: true}
526
+ );
527
+ }
528
+
518
529
  async publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number> {
519
530
  const epoch = computeEpochAtSlot(payloadAttestationMessage.data.slot);
520
531
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
@@ -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
 
@@ -1117,7 +1124,16 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1117
1124
  }
1118
1125
 
1119
1126
  const slot = envelope.payload.slotNumber;
1120
- const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
1127
+ const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
1128
+
1129
+ logger.debug("Received gossip payload envelope", {
1130
+ currentSlot: chain.clock.currentSlot,
1131
+ peerId: peerIdStr,
1132
+ slot,
1133
+ blockRoot: toRootHex(envelope.beaconBlockRoot),
1134
+ delaySec,
1135
+ });
1136
+
1121
1137
  metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1122
1138
  chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
1123
1139
 
@@ -1205,8 +1221,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1205
1221
  }
1206
1222
  chain.forkChoice.notifyPtcMessages(
1207
1223
  toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1224
+ payloadAttestationMessage.data.slot,
1208
1225
  validationResult.validatorCommitteeIndices,
1209
- payloadAttestationMessage.data.payloadPresent
1226
+ payloadAttestationMessage.data.payloadPresent,
1227
+ payloadAttestationMessage.data.blobDataAvailable
1210
1228
  );
1211
1229
  },
1212
1230
  [GossipType.execution_payload_bid]: async ({
@@ -1215,16 +1233,18 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1215
1233
  }: GossipHandlerParamGeneric<GossipType.execution_payload_bid>) => {
1216
1234
  const {serializedData} = gossipData;
1217
1235
  const executionPayloadBid = sszDeserialize(topic, serializedData);
1218
- await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
1236
+ const {proposerIndex} = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
1219
1237
 
1220
1238
  // Handle valid payload bid by storing in a bid pool
1221
1239
  try {
1222
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid.message);
1240
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
1223
1241
  metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1224
1242
  } catch (e) {
1225
1243
  logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
1226
1244
  }
1227
1245
 
1246
+ chain.validatorMonitor?.registerExecutionPayloadBid(OpSource.gossip, proposerIndex, executionPayloadBid.message);
1247
+
1228
1248
  chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
1229
1249
  version: config.getForkName(executionPayloadBid.message.slot),
1230
1250
  data: executionPayloadBid,
@@ -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,6 @@
1
1
  import {PeerId} from "@libp2p/interface";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
- import {GENESIS_SLOT, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
+ import {GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params";
4
4
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
5
5
  import {computeEpochAtSlot} from "@lodestar/state-transition";
6
6
  import {deneb, phase0} from "@lodestar/types";
@@ -29,13 +29,20 @@ export async function* onBeaconBlocksByRange(
29
29
  // starts above it to avoid duplicate yields. See archiveBlocks.ts for the migration logic.
30
30
  const archiveMaxSlot = finalizedSlot;
31
31
 
32
- const forkName = chain.config.getForkName(startSlot);
33
- if (isForkPostFulu(forkName) && startSlot < chain.earliestAvailableSlot) {
34
- chain.logger.verbose("Peer did not respect earliestAvailableSlot for BeaconBlocksByRange", {
32
+ // endSlot is exclusive, so highest served slot is endSlot - 1.
33
+ // Throw only when the entire requested range is below earliestAvailableSlot.
34
+ if (endSlot - 1 < chain.earliestAvailableSlot) {
35
+ chain.logger.verbose("Peer requested range before earliestAvailableSlot for BeaconBlocksByRange", {
35
36
  peer: prettyPrintPeerId(peerId),
36
37
  client: peerClient,
38
+ startSlot,
39
+ count,
40
+ earliestAvailableSlot: chain.earliestAvailableSlot,
37
41
  });
38
- return;
42
+ throw new ResponseError(
43
+ RespStatus.RESOURCE_UNAVAILABLE,
44
+ `Requested range is before earliestAvailableSlot startSlot=${startSlot} count=${count} earliestAvailableSlot=${chain.earliestAvailableSlot}`
45
+ );
39
46
  }
40
47
 
41
48
  // Finalized range of blocks