@lodestar/beacon-node 1.45.0 → 1.46.0-dev.18a1510f34

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 (268) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +186 -57
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/config/constants.d.ts +1 -0
  5. package/lib/api/impl/config/constants.d.ts.map +1 -1
  6. package/lib/api/impl/config/constants.js +2 -1
  7. package/lib/api/impl/config/constants.js.map +1 -1
  8. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  9. package/lib/api/impl/lodestar/index.js +2 -3
  10. package/lib/api/impl/lodestar/index.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +173 -89
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/api/rest/base.d.ts.map +1 -1
  15. package/lib/api/rest/base.js +6 -3
  16. package/lib/api/rest/base.js.map +1 -1
  17. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/archiveStore.js +17 -3
  19. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  20. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +0 -1
  22. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  23. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  24. package/lib/chain/blocks/importBlock.js +2 -17
  25. package/lib/chain/blocks/importBlock.js.map +1 -1
  26. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  27. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  28. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  29. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  30. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  31. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  32. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  33. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  34. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  35. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  36. package/lib/chain/bls/multithread/index.js +0 -1
  37. package/lib/chain/bls/multithread/index.js.map +1 -1
  38. package/lib/chain/chain.d.ts.map +1 -1
  39. package/lib/chain/chain.js +44 -2
  40. package/lib/chain/chain.js.map +1 -1
  41. package/lib/chain/errors/blockError.d.ts +48 -6
  42. package/lib/chain/errors/blockError.d.ts.map +1 -1
  43. package/lib/chain/errors/blockError.js +25 -4
  44. package/lib/chain/errors/blockError.js.map +1 -1
  45. package/lib/chain/errors/executionPayloadBid.d.ts +11 -4
  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 +11 -0
  50. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  51. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  52. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  53. package/lib/chain/errors/proposerPreferences.d.ts +14 -1
  54. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  55. package/lib/chain/errors/proposerPreferences.js +2 -0
  56. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  57. package/lib/chain/lightClient/index.d.ts.map +1 -1
  58. package/lib/chain/lightClient/index.js +11 -6
  59. package/lib/chain/lightClient/index.js.map +1 -1
  60. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  61. package/lib/chain/lightClient/proofs.js +28 -2
  62. package/lib/chain/lightClient/proofs.js.map +1 -1
  63. package/lib/chain/lightClient/types.d.ts +3 -1
  64. package/lib/chain/lightClient/types.d.ts.map +1 -1
  65. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  66. package/lib/chain/opPools/aggregatedAttestationPool.js +2 -1
  67. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  68. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
  69. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  70. package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
  71. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  72. package/lib/chain/prepareNextSlot.js +1 -1
  73. package/lib/chain/prepareNextSlot.js.map +1 -1
  74. package/lib/chain/produceBlock/produceBlockBody.js +4 -4
  75. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  76. package/lib/chain/seenCache/seenExecutionPayloadBids.d.ts +6 -5
  77. package/lib/chain/seenCache/seenExecutionPayloadBids.d.ts.map +1 -1
  78. package/lib/chain/seenCache/seenExecutionPayloadBids.js +16 -8
  79. package/lib/chain/seenCache/seenExecutionPayloadBids.js.map +1 -1
  80. package/lib/chain/validation/aggregateAndProof.js +10 -6
  81. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  82. package/lib/chain/validation/attestation.d.ts.map +1 -1
  83. package/lib/chain/validation/attestation.js +10 -6
  84. package/lib/chain/validation/attestation.js.map +1 -1
  85. package/lib/chain/validation/blobSidecar.js +1 -1
  86. package/lib/chain/validation/block.d.ts.map +1 -1
  87. package/lib/chain/validation/block.js +53 -4
  88. package/lib/chain/validation/block.js.map +1 -1
  89. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  90. package/lib/chain/validation/executionPayloadBid.js +100 -26
  91. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  92. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  93. package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
  94. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  95. package/lib/chain/validation/proposerPreferences.d.ts +9 -2
  96. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  97. package/lib/chain/validation/proposerPreferences.js +65 -13
  98. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  99. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  100. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  101. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  102. package/lib/chain/validatorMonitor.js +1 -1
  103. package/lib/chain/validatorMonitor.js.map +1 -1
  104. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  105. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  106. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  107. package/lib/execution/engine/interface.d.ts +1 -1
  108. package/lib/execution/engine/types.js +1 -1
  109. package/lib/execution/engine/types.js.map +1 -1
  110. package/lib/metrics/nodeJsMetrics.js +1 -1
  111. package/lib/metrics/nodeJsMetrics.js.map +1 -1
  112. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  113. package/lib/network/core/networkCoreWorkerHandler.js +2 -7
  114. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  115. package/lib/network/discv5/index.d.ts.map +1 -1
  116. package/lib/network/discv5/index.js +1 -4
  117. package/lib/network/discv5/index.js.map +1 -1
  118. package/lib/network/gossip/encoding.d.ts +2 -2
  119. package/lib/network/gossip/encoding.d.ts.map +1 -1
  120. package/lib/network/gossip/encoding.js +15 -9
  121. package/lib/network/gossip/encoding.js.map +1 -1
  122. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  123. package/lib/network/gossip/gossipsub.js +2 -2
  124. package/lib/network/gossip/gossipsub.js.map +1 -1
  125. package/lib/network/gossip/topic.d.ts +79 -236
  126. package/lib/network/gossip/topic.d.ts.map +1 -1
  127. package/lib/network/gossip/topic.js +24 -1
  128. package/lib/network/gossip/topic.js.map +1 -1
  129. package/lib/network/interface.d.ts +4 -1
  130. package/lib/network/interface.d.ts.map +1 -1
  131. package/lib/network/libp2p/index.d.ts.map +1 -1
  132. package/lib/network/libp2p/index.js +2 -9
  133. package/lib/network/libp2p/index.js.map +1 -1
  134. package/lib/network/network.d.ts +4 -1
  135. package/lib/network/network.d.ts.map +1 -1
  136. package/lib/network/network.js +19 -11
  137. package/lib/network/network.js.map +1 -1
  138. package/lib/network/peers/datastore.d.ts +1 -1
  139. package/lib/network/peers/datastore.d.ts.map +1 -1
  140. package/lib/network/peers/datastore.js +1 -1
  141. package/lib/network/peers/datastore.js.map +1 -1
  142. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  143. package/lib/network/processor/gossipHandlers.js +12 -6
  144. package/lib/network/processor/gossipHandlers.js.map +1 -1
  145. package/lib/network/processor/index.d.ts.map +1 -1
  146. package/lib/network/processor/index.js +2 -1
  147. package/lib/network/processor/index.js.map +1 -1
  148. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  149. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  150. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  151. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  152. package/lib/network/reqresp/handlers/index.js +20 -11
  153. package/lib/network/reqresp/handlers/index.js.map +1 -1
  154. package/lib/network/reqresp/handlers/lightClientFinalityUpdate.js +1 -1
  155. package/lib/network/reqresp/handlers/lightClientFinalityUpdate.js.map +1 -1
  156. package/lib/network/reqresp/handlers/lightClientOptimisticUpdate.js +1 -1
  157. package/lib/network/reqresp/handlers/lightClientOptimisticUpdate.js.map +1 -1
  158. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.d.ts.map +1 -1
  159. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js +6 -2
  160. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js.map +1 -1
  161. package/lib/network/util.d.ts +1 -0
  162. package/lib/network/util.d.ts.map +1 -1
  163. package/lib/network/util.js +4 -0
  164. package/lib/network/util.js.map +1 -1
  165. package/lib/sync/constants.d.ts +6 -5
  166. package/lib/sync/constants.d.ts.map +1 -1
  167. package/lib/sync/constants.js +6 -5
  168. package/lib/sync/constants.js.map +1 -1
  169. package/lib/sync/range/batch.d.ts +10 -1
  170. package/lib/sync/range/batch.d.ts.map +1 -1
  171. package/lib/sync/range/batch.js +28 -10
  172. package/lib/sync/range/batch.js.map +1 -1
  173. package/lib/sync/range/chain.d.ts.map +1 -1
  174. package/lib/sync/range/chain.js +30 -15
  175. package/lib/sync/range/chain.js.map +1 -1
  176. package/lib/sync/range/utils/batches.d.ts +18 -0
  177. package/lib/sync/range/utils/batches.d.ts.map +1 -1
  178. package/lib/sync/range/utils/batches.js +39 -0
  179. package/lib/sync/range/utils/batches.js.map +1 -1
  180. package/lib/sync/sync.d.ts.map +1 -1
  181. package/lib/sync/sync.js +7 -0
  182. package/lib/sync/sync.js.map +1 -1
  183. package/lib/sync/unknownBlock.d.ts.map +1 -1
  184. package/lib/sync/unknownBlock.js +7 -2
  185. package/lib/sync/unknownBlock.js.map +1 -1
  186. package/lib/util/execution.js +1 -1
  187. package/lib/util/execution.js.map +1 -1
  188. package/lib/util/profile.d.ts +3 -0
  189. package/lib/util/profile.d.ts.map +1 -1
  190. package/lib/util/profile.js +1 -27
  191. package/lib/util/profile.js.map +1 -1
  192. package/lib/util/sszBytes.js +1 -1
  193. package/package.json +29 -38
  194. package/src/api/impl/beacon/blocks/index.ts +221 -63
  195. package/src/api/impl/config/constants.ts +2 -0
  196. package/src/api/impl/lodestar/index.ts +2 -3
  197. package/src/api/impl/validator/index.ts +207 -99
  198. package/src/api/rest/base.ts +8 -3
  199. package/src/chain/archiveStore/archiveStore.ts +20 -3
  200. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +0 -1
  201. package/src/chain/blocks/importBlock.ts +0 -17
  202. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  203. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  204. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  205. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -3
  206. package/src/chain/bls/multithread/index.ts +0 -1
  207. package/src/chain/chain.ts +61 -2
  208. package/src/chain/errors/blockError.ts +44 -7
  209. package/src/chain/errors/executionPayloadBid.ts +12 -4
  210. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  211. package/src/chain/errors/proposerPreferences.ts +16 -1
  212. package/src/chain/lightClient/index.ts +12 -7
  213. package/src/chain/lightClient/proofs.ts +36 -1
  214. package/src/chain/lightClient/types.ts +3 -1
  215. package/src/chain/opPools/aggregatedAttestationPool.ts +4 -2
  216. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  217. package/src/chain/prepareNextSlot.ts +1 -1
  218. package/src/chain/produceBlock/produceBlockBody.ts +5 -5
  219. package/src/chain/seenCache/seenExecutionPayloadBids.ts +22 -9
  220. package/src/chain/validation/aggregateAndProof.ts +12 -6
  221. package/src/chain/validation/attestation.ts +12 -6
  222. package/src/chain/validation/blobSidecar.ts +1 -1
  223. package/src/chain/validation/block.ts +69 -4
  224. package/src/chain/validation/executionPayloadBid.ts +119 -28
  225. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  226. package/src/chain/validation/proposerPreferences.ts +75 -13
  227. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  228. package/src/chain/validatorMonitor.ts +1 -1
  229. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  230. package/src/execution/engine/interface.ts +1 -1
  231. package/src/execution/engine/types.ts +1 -1
  232. package/src/metrics/nodeJsMetrics.ts +1 -1
  233. package/src/network/core/networkCoreWorkerHandler.ts +2 -6
  234. package/src/network/discv5/index.ts +1 -4
  235. package/src/network/gossip/encoding.ts +13 -7
  236. package/src/network/gossip/gossipsub.ts +7 -2
  237. package/src/network/gossip/topic.ts +30 -0
  238. package/src/network/interface.ts +3 -1
  239. package/src/network/libp2p/index.ts +2 -10
  240. package/src/network/network.ts +24 -15
  241. package/src/network/peers/datastore.ts +1 -1
  242. package/src/network/processor/gossipHandlers.ts +12 -6
  243. package/src/network/processor/index.ts +2 -1
  244. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  245. package/src/network/reqresp/handlers/index.ts +21 -12
  246. package/src/network/reqresp/handlers/lightClientFinalityUpdate.ts +1 -1
  247. package/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts +1 -1
  248. package/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +6 -2
  249. package/src/network/util.ts +5 -0
  250. package/src/sync/constants.ts +6 -5
  251. package/src/sync/range/batch.ts +31 -10
  252. package/src/sync/range/chain.ts +36 -16
  253. package/src/sync/range/utils/batches.ts +44 -0
  254. package/src/sync/sync.ts +10 -0
  255. package/src/sync/unknownBlock.ts +8 -2
  256. package/src/util/execution.ts +1 -1
  257. package/src/util/profile.ts +1 -31
  258. package/src/util/sszBytes.ts +1 -1
  259. package/lib/bun-wrappers/prometheus-gc-stats.d.ts +0 -2
  260. package/lib/bun-wrappers/prometheus-gc-stats.d.ts.map +0 -1
  261. package/lib/bun-wrappers/prometheus-gc-stats.js +0 -8
  262. package/lib/bun-wrappers/prometheus-gc-stats.js.map +0 -1
  263. package/lib/network/peers/datastore_bun.d.ts +0 -3
  264. package/lib/network/peers/datastore_bun.d.ts.map +0 -1
  265. package/lib/network/peers/datastore_bun.js +0 -3
  266. package/lib/network/peers/datastore_bun.js.map +0 -1
  267. package/src/bun-wrappers/prometheus-gc-stats.ts +0 -7
  268. package/src/network/peers/datastore_bun.ts +0 -3
@@ -1,6 +1,21 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ExecutionStatus} from "@lodestar/fork-choice";
3
- import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
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
+ isForkPostBellatrix,
16
+ isForkPostDeneb,
17
+ isForkPostGloas,
18
+ } from "@lodestar/params";
4
19
  import {
5
20
  computeEpochAtSlot,
6
21
  computeStartSlotAtEpoch,
@@ -88,8 +103,8 @@ export async function validateGossipBlock(
88
103
  // 2. The parent is unknown to us, we probably want to download it since it might actually
89
104
  // descend from the finalized root.
90
105
  // (Non-Lighthouse): Since we prune all blocks non-descendant from finalized checking the `db.block` database won't be useful to guard
91
- // against known bad fork blocks, so we throw PARENT_UNKNOWN for cases (1) and (2)
92
- throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
106
+ // against known bad fork blocks, so we throw PARENT_BLOCK_UNKNOWN for cases (1) and (2)
107
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
93
108
  }
94
109
 
95
110
  // [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
@@ -164,6 +179,56 @@ export async function validateGossipBlock(
164
179
  });
165
180
  }
166
181
 
182
+ // [REJECT] The counts of `block.body.parent_execution_requests` are within
183
+ // their respective limits -- i.e. validate that
184
+ // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
185
+ // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
186
+ // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
187
+ // and
188
+ // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
189
+ // [REJECT] The counts of the block body operations are within their respective
190
+ // limits -- i.e. validate that
191
+ // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
192
+ // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
193
+ // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
194
+ // `len(block.body.deposits) == 0`,
195
+ // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
196
+ // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
197
+ // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
198
+ const body = (block as gloas.BeaconBlock).body;
199
+ const requests = body.parentExecutionRequests;
200
+ const countLimits: [string, number, number][] = [
201
+ ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
202
+ [
203
+ "parentExecutionRequests.consolidations",
204
+ requests.consolidations.length,
205
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
206
+ ],
207
+ [
208
+ "parentExecutionRequests.builderDeposits",
209
+ requests.builderDeposits.length,
210
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
211
+ ],
212
+ ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
213
+ ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
214
+ ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
215
+ ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
216
+ ["deposits", body.deposits.length, 0],
217
+ ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
218
+ ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
219
+ ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
220
+ ];
221
+ for (const [name, count, limit] of countLimits) {
222
+ if (count > limit) {
223
+ throw new BlockGossipError(GossipAction.REJECT, {
224
+ code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
225
+ name,
226
+ count,
227
+ limit,
228
+ });
229
+ }
230
+ }
231
+
167
232
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
168
233
  // This requires execution engine integration to verify the parent block hash
169
234
  }
@@ -176,7 +241,7 @@ export async function validateGossipBlock(
176
241
  const blockState = await chain.regen
177
242
  .getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
178
243
  .catch(() => {
179
- throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
244
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
180
245
  });
181
246
 
182
247
  // in forky condition, make sure to populate ShufflingCache with regened state
@@ -1,4 +1,5 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
+ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
3
  import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
3
4
  import {
4
5
  computeEpochAtSlot,
@@ -8,13 +9,73 @@ import {
8
9
  isGasLimitTargetCompatible,
9
10
  isStatePostGloas,
10
11
  } from "@lodestar/state-transition";
11
- import {ValidatorIndex, gloas} from "@lodestar/types";
12
+ import {RootHex, Slot, ValidatorIndex, gloas} from "@lodestar/types";
12
13
  import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils";
13
14
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
14
15
  import {ExecutionPayloadBidError, ExecutionPayloadBidErrorCode, GossipAction} from "../errors/index.js";
15
16
  import {IBeaconChain} from "../index.js";
16
17
  import {RegenCaller} from "../regen/index.js";
17
18
 
19
+ /**
20
+ * Relative increment over the current highest bid required to forward a bid, in basis points.
21
+ * With 3%, laddering from 1 gwei to 1 ETH takes ~256 bids given the floor and cap below, see
22
+ * https://github.com/ethereum/consensus-specs/pull/4792#issuecomment-3714553549.
23
+ */
24
+ const BID_INCREMENT_BPS = 300;
25
+ /**
26
+ * Minimum absolute increment (0.0001 ETH). Covers low bid values where the relative increment
27
+ * is tiny and provides weak spam protection.
28
+ */
29
+ const BID_INCREMENT_FLOOR_GWEI = 100_000;
30
+ /**
31
+ * Maximum absolute increment (0.01 ETH). Bounds the barrier for legitimate competition on
32
+ * high value blocks where the relative increment would suppress closely competing bids.
33
+ */
34
+ const BID_INCREMENT_CAP_GWEI = 10_000_000;
35
+
36
+ /**
37
+ * Return the minimum value a new bid must have to be forwarded given the current highest bid.
38
+ * Division before multiplication to stay within safe integer range for max gwei values.
39
+ */
40
+ function getMinBidValue(currentHighestBid: number): number {
41
+ const relativeIncrement = Math.floor(currentHighestBid / 10_000) * BID_INCREMENT_BPS;
42
+ const increment = Math.min(Math.max(BID_INCREMENT_FLOOR_GWEI, relativeIncrement), BID_INCREMENT_CAP_GWEI);
43
+ return currentHighestBid + increment;
44
+ }
45
+
46
+ /**
47
+ * Check whether a bid builds on one of the paths compatible with the local head branch.
48
+ *
49
+ * The direct parent path is always allowed for proposer-boost reorgs. Otherwise the bid
50
+ * must build on the local head's full or empty payload variant, as selected for its slot.
51
+ */
52
+ function isBidCompatibleWithHead(
53
+ forkChoice: IForkChoice,
54
+ head: ProtoBlock,
55
+ bidSlot: Slot,
56
+ bidParentBlockRoot: RootHex,
57
+ bidParentBlockHash: RootHex
58
+ ): boolean {
59
+ const buildsOnParentBlock = bidParentBlockRoot === head.parentRoot;
60
+ const buildsOnParentPayload = bidParentBlockHash === head.parentBlockHash;
61
+
62
+ if (buildsOnParentBlock && buildsOnParentPayload) {
63
+ return true;
64
+ }
65
+
66
+ if (bidParentBlockRoot !== head.blockRoot) {
67
+ return false;
68
+ }
69
+
70
+ const buildsOnHeadPayload = bidParentBlockHash === head.executionPayloadBlockHash;
71
+
72
+ if (forkChoice.shouldBuildOnFull(head, bidSlot)) {
73
+ return buildsOnHeadPayload;
74
+ }
75
+
76
+ return buildsOnParentPayload;
77
+ }
78
+
18
79
  export async function validateApiExecutionPayloadBid(
19
80
  chain: IBeaconChain,
20
81
  signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
@@ -34,8 +95,8 @@ async function validateExecutionPayloadBid(
34
95
  signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid
35
96
  ): Promise<{proposerIndex: ValidatorIndex}> {
36
97
  const bid = signedExecutionPayloadBid.message;
37
- const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
38
- const parentBlockHashHex = toRootHex(bid.parentBlockHash);
98
+ const bidParentBlockRoot = toRootHex(bid.parentBlockRoot);
99
+ const bidParentBlockHash = toRootHex(bid.parentBlockHash);
39
100
 
40
101
  // [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
41
102
  if (
@@ -49,14 +110,40 @@ async function validateExecutionPayloadBid(
49
110
  });
50
111
  }
51
112
 
113
+ // [IGNORE] The bid is compatible with the current head branch.
114
+ const head = chain.forkChoice.getHead();
115
+ if (!isBidCompatibleWithHead(chain.forkChoice, head, bid.slot, bidParentBlockRoot, bidParentBlockHash)) {
116
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
117
+ code: ExecutionPayloadBidErrorCode.INCOMPATIBLE_WITH_HEAD,
118
+ slot: bid.slot,
119
+ parentBlockRoot: bidParentBlockRoot,
120
+ parentBlockHash: bidParentBlockHash,
121
+ headBlockRoot: head.blockRoot,
122
+ });
123
+ }
124
+
125
+ // [IGNORE] this is the first signed bid seen with a valid signature from the given builder for
126
+ // the tuple `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
127
+ // Entries are only added after signature verification, so known tuples can be dropped before
128
+ // state regeneration and the other expensive validation steps.
129
+ if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex, bidParentBlockHash, bidParentBlockRoot)) {
130
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
131
+ code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN,
132
+ builderIndex: bid.builderIndex,
133
+ slot: bid.slot,
134
+ parentBlockRoot: bidParentBlockRoot,
135
+ parentBlockHash: bidParentBlockHash,
136
+ });
137
+ }
138
+
52
139
  // [IGNORE] `bid.parent_block_root` is the hash tree root of a known beacon block in fork choice.
53
140
  // Moved earlier than the spec ordering so we can derive the proposer dependent root for the
54
141
  // proposer-preferences lookup below from a known fork-choice block.
55
- const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(parentBlockRootHex);
142
+ const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(bidParentBlockRoot);
56
143
  if (parentBlock === null) {
57
144
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
58
145
  code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
59
- parentBlockRoot: parentBlockRootHex,
146
+ parentBlockRoot: bidParentBlockRoot,
60
147
  });
61
148
  }
62
149
 
@@ -95,7 +182,7 @@ async function validateExecutionPayloadBid(
95
182
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
96
183
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES,
97
184
  slot: bid.slot,
98
- parentBlockRoot: parentBlockRootHex,
185
+ parentBlockRoot: bidParentBlockRoot,
99
186
  dependentRoot: "unknown",
100
187
  });
101
188
  }
@@ -105,7 +192,7 @@ async function validateExecutionPayloadBid(
105
192
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
106
193
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES,
107
194
  slot: bid.slot,
108
- parentBlockRoot: parentBlockRootHex,
195
+ parentBlockRoot: bidParentBlockRoot,
109
196
  dependentRoot: dependentRootHex,
110
197
  });
111
198
  }
@@ -116,7 +203,7 @@ async function validateExecutionPayloadBid(
116
203
  .catch(() => {
117
204
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
118
205
  code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
119
- parentBlockRoot: parentBlockRootHex,
206
+ parentBlockRoot: bidParentBlockRoot,
120
207
  });
121
208
  });
122
209
 
@@ -157,7 +244,7 @@ async function validateExecutionPayloadBid(
157
244
  }
158
245
 
159
246
  // [REJECT] `bid.execution_payment` is zero.
160
- if (bid.executionPayment !== 0) {
247
+ if (bid.executionPayment !== 0n) {
161
248
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
162
249
  code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
163
250
  builderIndex: bid.builderIndex,
@@ -165,9 +252,9 @@ async function validateExecutionPayloadBid(
165
252
  });
166
253
  }
167
254
 
168
- // [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`.
255
+ // [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
169
256
  if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
170
- throw new ExecutionPayloadBidError(GossipAction.REJECT, {
257
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
171
258
  code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH,
172
259
  builderIndex: bid.builderIndex,
173
260
  bidFeeRecipient: toHex(bid.feeRecipient),
@@ -181,11 +268,11 @@ async function validateExecutionPayloadBid(
181
268
  // payload's hash) and EMPTY parents (EMPTY/PENDING variants carry the inherited parent
182
269
  // payload's hash, since the new block doesn't have its own payload). Variant carries the
183
270
  // executed payload's gas_limit, which we use as `parent_gas_limit` below.
184
- const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(parentBlockRootHex, parentBlockHashHex);
271
+ const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(bidParentBlockRoot, bidParentBlockHash);
185
272
  if (parentPayloadVariant === null || parentPayloadVariant.executionPayloadBlockHash === null) {
186
273
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
187
274
  code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH,
188
- parentBlockHash: parentBlockHashHex,
275
+ parentBlockHash: bidParentBlockHash,
189
276
  });
190
277
  }
191
278
 
@@ -193,7 +280,7 @@ async function validateExecutionPayloadBid(
193
280
  // where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
194
281
  // `target_gas_limit` is `proposer_preferences.target_gas_limit`.
195
282
  const bidGasLimit = bid.gasLimit;
196
- const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
283
+ const parentGasLimit = BigInt(parentPayloadVariant.executionPayloadGasLimit);
197
284
  const targetGasLimit = proposerPreferences.message.targetGasLimit;
198
285
  if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
199
286
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
@@ -218,21 +305,13 @@ async function validateExecutionPayloadBid(
218
305
  });
219
306
  }
220
307
 
221
- // [IGNORE] this is the first signed bid seen with a valid signature from the given builder for this slot.
222
- if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex)) {
223
- throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
224
- code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN,
225
- builderIndex: bid.builderIndex,
226
- slot: bid.slot,
227
- parentBlockRoot: parentBlockRootHex,
228
- parentBlockHash: parentBlockHashHex,
229
- });
230
- }
231
-
232
308
  // [IGNORE] this bid is the highest value bid seen for the tuple
233
309
  // `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
234
- const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
235
- if (bestBid !== null && bestBid.message.value >= bid.value) {
310
+ // As a DoS prevention measure, the bid must also exceed the current highest bid by a minimum
311
+ // increment, see https://github.com/ethereum/consensus-specs/pull/4831. This prevents spam
312
+ // from builders submitting numerous bids with minimal value increments.
313
+ const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, bidParentBlockHash, bidParentBlockRoot);
314
+ if (bestBid !== null && bid.value < getMinBidValue(bestBid.message.value)) {
236
315
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
237
316
  code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
238
317
  bidValue: bid.value,
@@ -276,8 +355,20 @@ async function validateExecutionPayloadBid(
276
355
  });
277
356
  }
278
357
 
358
+ // Repeat the seen check after the awaited signature verification to prevent concurrent bids
359
+ // for the same builder and tuple from both passing validation.
360
+ if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex, bidParentBlockHash, bidParentBlockRoot)) {
361
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
362
+ code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN,
363
+ builderIndex: bid.builderIndex,
364
+ slot: bid.slot,
365
+ parentBlockRoot: bidParentBlockRoot,
366
+ parentBlockHash: bidParentBlockHash,
367
+ });
368
+ }
369
+
279
370
  // Valid
280
- chain.seenExecutionPayloadBids.add(bid.slot, bid.builderIndex);
371
+ chain.seenExecutionPayloadBids.add(bid.slot, bid.builderIndex, bidParentBlockHash, bidParentBlockRoot);
281
372
 
282
373
  return {proposerIndex: proposerPreferences.message.validatorIndex};
283
374
  }
@@ -1,4 +1,11 @@
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";
2
9
  import {
3
10
  computeStartSlotAtEpoch,
4
11
  getExecutionPayloadEnvelopeSignatureSet,
@@ -115,6 +122,36 @@ async function validateExecutionPayloadEnvelope(
115
122
  });
116
123
  }
117
124
 
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
+
118
155
  // Get the block state to verify the builder's signature.
119
156
  const blockState = await chain.regen
120
157
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
@@ -1,17 +1,19 @@
1
- import {SLOTS_PER_EPOCH} from "@lodestar/params";
1
+ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {GENESIS_EPOCH, GENESIS_SLOT, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
2
3
  import {
3
4
  computeEpochAtSlot,
5
+ computeStartSlotAtEpoch,
4
6
  createSingleSignatureSetFromComponents,
5
7
  getProposerPreferencesSigningRoot,
6
8
  } from "@lodestar/state-transition";
7
- import {ValidatorIndex, gloas} from "@lodestar/types";
9
+ import {Epoch, Slot, ValidatorIndex, gloas} from "@lodestar/types";
8
10
  import {toRootHex} from "@lodestar/utils";
9
11
  import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
10
12
  import {IBeaconChain} from "../index.js";
11
13
 
12
14
  /**
13
15
  * Validates a gossiped `SignedProposerPreferences` per
14
- * https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#proposer_preferences
16
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/p2p-interface.md#new-proposer_preferences
15
17
  */
16
18
  export async function validateGossipProposerPreferences(
17
19
  chain: IBeaconChain,
@@ -22,9 +24,10 @@ export async function validateGossipProposerPreferences(
22
24
  const dependentRootHex = toRootHex(dependentRoot);
23
25
  const proposalEpoch = computeEpochAtSlot(proposalSlot);
24
26
 
25
- // [IGNORE] `preferences.proposal_slot` is in the current or next epoch.
26
- const currentEpoch = chain.clock.currentEpoch;
27
- if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + 1) {
27
+ // [IGNORE] `preferences.proposal_slot` is within the proposer lookahead,
28
+ // allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
29
+ const currentEpoch = computeEpochAtSlot(chain.clock.currentSlotWithGossipDisparity);
30
+ if (proposalEpoch > currentEpoch + MIN_SEED_LOOKAHEAD) {
28
31
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
29
32
  code: ProposerPreferencesErrorCode.INVALID_EPOCH,
30
33
  proposalSlot,
@@ -34,31 +37,65 @@ export async function validateGossipProposerPreferences(
34
37
 
35
38
  // [IGNORE] `preferences.proposal_slot` has not already passed, i.e. `proposal_slot > current_slot`,
36
39
  // allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
37
- const currentSlot = chain.clock.currentSlotWithGossipDisparity;
38
- if (proposalSlot <= currentSlot) {
40
+ if (chain.clock.msFromSlot(proposalSlot) > chain.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
39
41
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
40
42
  code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED,
41
43
  proposalSlot,
42
- currentSlot,
44
+ currentSlot: chain.clock.currentSlot,
43
45
  });
44
46
  }
45
47
 
46
48
  // [IGNORE] The block with root `dependent_root` has been seen by the node.
49
+ const dependentBlock = chain.forkChoice.getBlockHexDefaultStatus(dependentRootHex);
50
+ if (dependentBlock === null) {
51
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
52
+ code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT,
53
+ proposalSlot,
54
+ dependentRoot: dependentRootHex,
55
+ });
56
+ }
57
+
58
+ const dependentEpoch = Math.max(GENESIS_EPOCH, proposalEpoch - MIN_SEED_LOOKAHEAD);
59
+ const dependentRootSlot = getDependentRootSlot(dependentEpoch);
60
+
61
+ // [REJECT] The slot of the block with root `preferences.dependent_root` is strictly less than
62
+ // `compute_start_slot_at_epoch(compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD)`.
63
+ if (dependentBlock.slot > dependentRootSlot) {
64
+ throw new ProposerPreferencesError(GossipAction.REJECT, {
65
+ code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT_SLOT,
66
+ dependentRoot: dependentRootHex,
67
+ dependentBlockSlot: dependentBlock.slot,
68
+ dependentRootSlot,
69
+ dependentEpoch,
70
+ });
71
+ }
72
+
73
+ // [IGNORE] `is_valid_dependent_root(store, preferences.dependent_root, epoch)` returns `True`,
74
+ // where `epoch` is `compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD`.
75
+ if (!isValidDependentRoot(chain.forkChoice, dependentBlock, dependentEpoch)) {
76
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
77
+ code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT,
78
+ dependentRoot: dependentRootHex,
79
+ dependentBlockSlot: dependentBlock.slot,
80
+ dependentEpoch,
81
+ });
82
+ }
83
+
47
84
  // Resolve the proposer lookahead for the message's branch via head state (fast path) or
48
85
  // the previous-root checkpoint state (populated by `processSlotsToNearestCheckpoint` for
49
- // any imported branch crossing into `proposalEpoch - 1`). The head-state path also handles
86
+ // any imported branch crossing into `dependentEpoch`). The head-state path also handles
50
87
  // narrow timing windows where the checkpoint state isn't yet populated.
51
88
  const headState = chain.getHeadState();
52
89
  let proposers: ValidatorIndex[] | null = null;
53
90
  if (headState.epoch === proposalEpoch && headState.currentDecisionRoot === dependentRootHex) {
54
91
  proposers = headState.currentProposers;
55
- } else if (headState.epoch === proposalEpoch - 1 && headState.nextDecisionRoot === dependentRootHex) {
92
+ } else if (headState.epoch === dependentEpoch && headState.nextDecisionRoot === dependentRootHex) {
56
93
  proposers = headState.nextProposers;
57
94
  } else {
58
95
  // Sync lookup only to not trigger disk reload from gossip input.
59
- const checkpointState = chain.regen.getCheckpointStateSync({epoch: proposalEpoch - 1, rootHex: dependentRootHex});
96
+ const checkpointState = chain.regen.getCheckpointStateSync({epoch: dependentEpoch, rootHex: dependentRootHex});
60
97
  if (checkpointState !== null) {
61
- // State is at `proposalEpoch - 1`, so proposers for `proposalSlot` (next epoch from
98
+ // State is at `dependentEpoch`, so proposers for `proposalSlot` (next epoch from
62
99
  // the state's perspective) live in `nextProposers`.
63
100
  proposers = checkpointState.nextProposers;
64
101
  }
@@ -118,3 +155,28 @@ export async function validateGossipProposerPreferences(
118
155
 
119
156
  chain.proposerPreferencesPool.add(signedProposerPreferences);
120
157
  }
158
+
159
+ /**
160
+ * Check whether `dependentBlock` is a viable shuffling-dependent root for `epoch`:
161
+ * it is before the epoch boundary and either has a direct child at or after
162
+ * the boundary, or is the current fork-choice head.
163
+ */
164
+ export function isValidDependentRoot(forkChoice: IForkChoice, dependentBlock: ProtoBlock, epoch: Epoch): boolean {
165
+ const epochStartSlot = computeStartSlotAtEpoch(epoch);
166
+ if (dependentBlock.slot > getDependentRootSlot(epoch)) {
167
+ return false;
168
+ }
169
+
170
+ for (const block of forkChoice.getBlockSummariesByParentRoot(dependentBlock.blockRoot)) {
171
+ if (block.slot >= epochStartSlot) {
172
+ return true;
173
+ }
174
+ }
175
+
176
+ return dependentBlock.blockRoot === forkChoice.getHeadRoot();
177
+ }
178
+
179
+ /** Return the slot used to select a dependent root for `epoch`, clamped to genesis. */
180
+ function getDependentRootSlot(epoch: Epoch): Slot {
181
+ return Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch) - 1);
182
+ }
@@ -1,7 +1,7 @@
1
1
  import {BeaconConfig} from "@lodestar/config";
2
- import {DOMAIN_AGGREGATE_AND_PROOF, ForkSeq} from "@lodestar/params";
2
+ import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
3
3
  import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
- import {Epoch, SignedAggregateAndProof, ValidatorIndex, ssz} from "@lodestar/types";
4
+ import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";
5
5
 
6
6
  export function getAggregateAndProofSigningRoot(
7
7
  config: BeaconConfig,
@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
14
14
  const slot = computeStartSlotAtEpoch(epoch);
15
15
  const fork = config.getForkName(slot);
16
16
  const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
17
- const sszType = ForkSeq[fork] >= ForkSeq.electra ? ssz.electra.AggregateAndProof : ssz.phase0.AggregateAndProof;
17
+ const sszType = config.getForkTypes(slot).AggregateAndProof;
18
18
  return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
19
19
  }
20
20
 
@@ -469,7 +469,7 @@ export function createValidatorMonitor(
469
469
  proposerIndex,
470
470
  src,
471
471
  builderIndex: bid.builderIndex,
472
- gasLimit: bid.gasLimit,
472
+ gasLimit: Number(bid.gasLimit),
473
473
  value: bid.value.toString(),
474
474
  parentBlockRoot: toRootHex(bid.parentBlockRoot),
475
475
  parentBlockHash: toRootHex(bid.parentBlockHash),
@@ -1,14 +1,16 @@
1
1
  import {ContainerType, VectorCompositeType} from "@chainsafe/ssz";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {DatabaseController, Repository} from "@lodestar/db";
4
+ import {CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS} from "@lodestar/params";
4
5
  import {ssz} from "@lodestar/types";
5
6
  import {SyncCommitteeWitness} from "../../chain/lightClient/types.js";
6
7
  import {Bucket, getBucketNameByValue} from "../buckets.js";
7
8
 
8
- // We add a 1-byte prefix where 0 means pre-electra and 1 means post-electra
9
+ // We add a 1-byte prefix where 0 means pre-Electra, 1 means post-Electra, and 2 means post-Gloas.
9
10
  enum PrefixByte {
10
11
  PRE_ELECTRA = 0,
11
12
  POST_ELECTRA = 1,
13
+ POST_GLOAS = 2,
12
14
  }
13
15
 
14
16
  export const NUM_WITNESS = 4;
@@ -35,6 +37,35 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
35
37
  // Overrides for multi-fork
36
38
  encodeValue(value: SyncCommitteeWitness): Uint8Array {
37
39
  const numWitness = value.witness.length;
40
+ const hasGloasBranches =
41
+ value.currentSyncCommitteeBranch !== undefined || value.nextSyncCommitteeBranch !== undefined;
42
+
43
+ if (hasGloasBranches) {
44
+ if (
45
+ value.currentSyncCommitteeBranch?.length !== CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS ||
46
+ value.nextSyncCommitteeBranch?.length !== NEXT_SYNC_COMMITTEE_DEPTH_GLOAS
47
+ ) {
48
+ throw Error(
49
+ `Invalid post-Gloas sync committee branch lengths current=${value.currentSyncCommitteeBranch?.length} next=${value.nextSyncCommitteeBranch?.length}`
50
+ );
51
+ }
52
+
53
+ const type = new ContainerType({
54
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
55
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
56
+ currentSyncCommitteeRoot: ssz.Root,
57
+ nextSyncCommitteeRoot: ssz.Root,
58
+ });
59
+
60
+ const valueBytes = type.serialize({
61
+ currentSyncCommitteeBranch: value.currentSyncCommitteeBranch,
62
+ nextSyncCommitteeBranch: value.nextSyncCommitteeBranch,
63
+ currentSyncCommitteeRoot: value.currentSyncCommitteeRoot,
64
+ nextSyncCommitteeRoot: value.nextSyncCommitteeRoot,
65
+ });
66
+
67
+ return prefixData(PrefixByte.POST_GLOAS, valueBytes);
68
+ }
38
69
 
39
70
  if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) {
40
71
  throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`);
@@ -51,19 +82,25 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
51
82
  // We need to differentiate between post-electra and pre-electra witness
52
83
  // such that we can deserialize correctly
53
84
  const isPostElectra = numWitness === NUM_WITNESS_ELECTRA;
54
- const prefixByte = new Uint8Array(1);
55
- prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA;
56
-
57
- const prefixedData = new Uint8Array(1 + valueBytes.length);
58
- prefixedData.set(prefixByte, 0);
59
- prefixedData.set(valueBytes, 1);
60
-
61
- return prefixedData;
85
+ return prefixData(isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA, valueBytes);
62
86
  }
63
87
 
64
88
  decodeValue(data: Uint8Array): SyncCommitteeWitness {
65
89
  // First byte is written
66
90
  const prefix = data.subarray(0, 1);
91
+ const isPostGloas = prefix[0] === PrefixByte.POST_GLOAS;
92
+
93
+ if (isPostGloas) {
94
+ const type = new ContainerType({
95
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
96
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
97
+ currentSyncCommitteeRoot: ssz.Root,
98
+ nextSyncCommitteeRoot: ssz.Root,
99
+ });
100
+
101
+ return {witness: [], ...type.deserialize(data.subarray(1))};
102
+ }
103
+
67
104
  const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA;
68
105
 
69
106
  const type = new ContainerType({
@@ -75,3 +112,14 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
75
112
  return type.deserialize(data.subarray(1));
76
113
  }
77
114
  }
115
+
116
+ function prefixData(prefix: PrefixByte, valueBytes: Uint8Array): Uint8Array {
117
+ const prefixByte = new Uint8Array(1);
118
+ prefixByte[0] = prefix;
119
+
120
+ const prefixedData = new Uint8Array(1 + valueBytes.length);
121
+ prefixedData.set(prefixByte, 0);
122
+ prefixedData.set(valueBytes, 1);
123
+
124
+ return prefixedData;
125
+ }