@lodestar/beacon-node 1.45.0-dev.c6b2b7f1ba → 1.45.0-dev.d16fa2b13a

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 (219) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +3 -2
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts +1 -1
  5. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  8. package/lib/api/impl/beacon/pool/index.js +6 -43
  9. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  10. package/lib/api/impl/beacon/state/index.d.ts +1 -1
  11. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  12. package/lib/api/impl/beacon/state/index.js +48 -5
  13. package/lib/api/impl/beacon/state/index.js.map +1 -1
  14. package/lib/api/impl/beacon/state/utils.d.ts +13 -3
  15. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  16. package/lib/api/impl/beacon/state/utils.js +48 -4
  17. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  18. package/lib/api/impl/debug/index.d.ts +1 -1
  19. package/lib/api/impl/debug/index.d.ts.map +1 -1
  20. package/lib/api/impl/debug/index.js +2 -2
  21. package/lib/api/impl/debug/index.js.map +1 -1
  22. package/lib/api/impl/lodestar/index.js +2 -2
  23. package/lib/api/impl/lodestar/index.js.map +1 -1
  24. package/lib/api/impl/proof/index.d.ts +1 -1
  25. package/lib/api/impl/proof/index.d.ts.map +1 -1
  26. package/lib/api/impl/proof/index.js +2 -2
  27. package/lib/api/impl/proof/index.js.map +1 -1
  28. package/lib/api/impl/utils.d.ts +23 -0
  29. package/lib/api/impl/utils.d.ts.map +1 -1
  30. package/lib/api/impl/utils.js +44 -1
  31. package/lib/api/impl/utils.js.map +1 -1
  32. package/lib/api/impl/validator/index.d.ts +0 -14
  33. package/lib/api/impl/validator/index.d.ts.map +1 -1
  34. package/lib/api/impl/validator/index.js +67 -62
  35. package/lib/api/impl/validator/index.js.map +1 -1
  36. package/lib/api/rest/base.d.ts.map +1 -1
  37. package/lib/api/rest/base.js +8 -0
  38. package/lib/api/rest/base.js.map +1 -1
  39. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  40. package/lib/chain/blocks/importBlock.js +2 -5
  41. package/lib/chain/blocks/importBlock.js.map +1 -1
  42. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  43. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  44. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  46. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  47. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  48. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  52. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  53. package/lib/chain/builderCircuitBreaker.js +59 -0
  54. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  55. package/lib/chain/chain.d.ts +3 -2
  56. package/lib/chain/chain.d.ts.map +1 -1
  57. package/lib/chain/chain.js +4 -3
  58. package/lib/chain/chain.js.map +1 -1
  59. package/lib/chain/errors/blockError.d.ts +48 -6
  60. package/lib/chain/errors/blockError.d.ts.map +1 -1
  61. package/lib/chain/errors/blockError.js +25 -4
  62. package/lib/chain/errors/blockError.js.map +1 -1
  63. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  64. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  65. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  66. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  67. package/lib/chain/interface.d.ts +3 -2
  68. package/lib/chain/interface.d.ts.map +1 -1
  69. package/lib/chain/interface.js.map +1 -1
  70. package/lib/chain/lightClient/index.d.ts.map +1 -1
  71. package/lib/chain/lightClient/index.js +11 -6
  72. package/lib/chain/lightClient/index.js.map +1 -1
  73. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  74. package/lib/chain/lightClient/proofs.js +28 -2
  75. package/lib/chain/lightClient/proofs.js.map +1 -1
  76. package/lib/chain/lightClient/types.d.ts +3 -1
  77. package/lib/chain/lightClient/types.d.ts.map +1 -1
  78. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  79. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  80. package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
  81. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  82. package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
  83. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  84. package/lib/chain/options.d.ts +5 -0
  85. package/lib/chain/options.d.ts.map +1 -1
  86. package/lib/chain/options.js +1 -0
  87. package/lib/chain/options.js.map +1 -1
  88. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  89. package/lib/chain/prepareNextSlot.js +12 -7
  90. package/lib/chain/prepareNextSlot.js.map +1 -1
  91. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  92. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  93. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  94. package/lib/chain/seenCache/index.d.ts +0 -1
  95. package/lib/chain/seenCache/index.d.ts.map +1 -1
  96. package/lib/chain/seenCache/index.js +0 -1
  97. package/lib/chain/seenCache/index.js.map +1 -1
  98. package/lib/chain/validation/block.d.ts.map +1 -1
  99. package/lib/chain/validation/block.js +55 -4
  100. package/lib/chain/validation/block.js.map +1 -1
  101. package/lib/chain/validation/executionPayloadBid.js +11 -10
  102. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  103. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  104. package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
  105. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  106. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  107. package/lib/chain/validation/proposerPreferences.js +14 -5
  108. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  109. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  110. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  111. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  112. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  113. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  114. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  115. package/lib/execution/builder/http.d.ts +18 -0
  116. package/lib/execution/builder/http.d.ts.map +1 -1
  117. package/lib/execution/builder/http.js +20 -11
  118. package/lib/execution/builder/http.js.map +1 -1
  119. package/lib/metrics/metrics/beacon.d.ts +6 -0
  120. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  121. package/lib/metrics/metrics/beacon.js +18 -0
  122. package/lib/metrics/metrics/beacon.js.map +1 -1
  123. package/lib/network/core/metrics.d.ts +65 -0
  124. package/lib/network/core/metrics.d.ts.map +1 -1
  125. package/lib/network/core/metrics.js +94 -0
  126. package/lib/network/core/metrics.js.map +1 -1
  127. package/lib/network/discv5/utils.d.ts.map +1 -1
  128. package/lib/network/discv5/utils.js +12 -3
  129. package/lib/network/discv5/utils.js.map +1 -1
  130. package/lib/network/gossip/encoding.d.ts +2 -2
  131. package/lib/network/gossip/encoding.d.ts.map +1 -1
  132. package/lib/network/gossip/encoding.js +22 -9
  133. package/lib/network/gossip/encoding.js.map +1 -1
  134. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  135. package/lib/network/gossip/gossipsub.js +4 -3
  136. package/lib/network/gossip/gossipsub.js.map +1 -1
  137. package/lib/network/gossip/topic.d.ts +831 -226
  138. package/lib/network/gossip/topic.d.ts.map +1 -1
  139. package/lib/network/gossip/topic.js +47 -4
  140. package/lib/network/gossip/topic.js.map +1 -1
  141. package/lib/network/peers/peerManager.d.ts.map +1 -1
  142. package/lib/network/peers/peerManager.js +10 -0
  143. package/lib/network/peers/peerManager.js.map +1 -1
  144. package/lib/network/peers/score/score.d.ts +3 -1
  145. package/lib/network/peers/score/score.d.ts.map +1 -1
  146. package/lib/network/peers/score/score.js +6 -1
  147. package/lib/network/peers/score/score.js.map +1 -1
  148. package/lib/network/peers/score/store.d.ts.map +1 -1
  149. package/lib/network/peers/score/store.js +6 -1
  150. package/lib/network/peers/score/store.js.map +1 -1
  151. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  152. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  153. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  154. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  155. package/lib/network/processor/gossipHandlers.js +18 -9
  156. package/lib/network/processor/gossipHandlers.js.map +1 -1
  157. package/lib/sync/unknownBlock.d.ts.map +1 -1
  158. package/lib/sync/unknownBlock.js +7 -2
  159. package/lib/sync/unknownBlock.js.map +1 -1
  160. package/lib/util/graffiti.d.ts +15 -0
  161. package/lib/util/graffiti.d.ts.map +1 -1
  162. package/lib/util/graffiti.js +55 -0
  163. package/lib/util/graffiti.js.map +1 -1
  164. package/package.json +19 -19
  165. package/src/api/impl/beacon/blocks/index.ts +3 -2
  166. package/src/api/impl/beacon/index.ts +1 -1
  167. package/src/api/impl/beacon/pool/index.ts +5 -53
  168. package/src/api/impl/beacon/state/index.ts +55 -3
  169. package/src/api/impl/beacon/state/utils.ts +68 -4
  170. package/src/api/impl/debug/index.ts +3 -2
  171. package/src/api/impl/lodestar/index.ts +2 -2
  172. package/src/api/impl/proof/index.ts +2 -2
  173. package/src/api/impl/utils.ts +51 -1
  174. package/src/api/impl/validator/index.ts +75 -67
  175. package/src/api/rest/base.ts +9 -0
  176. package/src/chain/blocks/importBlock.ts +2 -7
  177. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  178. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  179. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  180. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  181. package/src/chain/builderCircuitBreaker.ts +84 -0
  182. package/src/chain/chain.ts +7 -3
  183. package/src/chain/errors/blockError.ts +44 -7
  184. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  185. package/src/chain/interface.ts +2 -2
  186. package/src/chain/lightClient/index.ts +12 -7
  187. package/src/chain/lightClient/proofs.ts +36 -1
  188. package/src/chain/lightClient/types.ts +3 -1
  189. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  190. package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
  191. package/src/chain/options.ts +6 -0
  192. package/src/chain/prepareNextSlot.ts +11 -7
  193. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  194. package/src/chain/seenCache/index.ts +0 -1
  195. package/src/chain/validation/block.ts +72 -4
  196. package/src/chain/validation/executionPayloadBid.ts +14 -9
  197. package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
  198. package/src/chain/validation/proposerPreferences.ts +15 -5
  199. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  200. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  201. package/src/execution/builder/http.ts +30 -17
  202. package/src/metrics/metrics/beacon.ts +19 -0
  203. package/src/network/core/metrics.ts +94 -0
  204. package/src/network/discv5/utils.ts +11 -3
  205. package/src/network/gossip/encoding.ts +20 -7
  206. package/src/network/gossip/gossipsub.ts +9 -3
  207. package/src/network/gossip/topic.ts +59 -4
  208. package/src/network/peers/peerManager.ts +11 -0
  209. package/src/network/peers/score/score.ts +8 -1
  210. package/src/network/peers/score/store.ts +8 -1
  211. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  212. package/src/network/processor/gossipHandlers.ts +18 -8
  213. package/src/sync/unknownBlock.ts +8 -2
  214. package/src/util/graffiti.ts +78 -0
  215. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  216. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  217. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  218. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  219. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -1,6 +1,22 @@
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_DEPOSIT_REQUESTS_PER_PAYLOAD,
12
+ MAX_PAYLOAD_ATTESTATIONS,
13
+ MAX_PROPOSER_SLASHINGS,
14
+ MAX_VOLUNTARY_EXITS,
15
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
16
+ isForkPostBellatrix,
17
+ isForkPostDeneb,
18
+ isForkPostGloas,
19
+ } from "@lodestar/params";
4
20
  import {
5
21
  computeEpochAtSlot,
6
22
  computeStartSlotAtEpoch,
@@ -88,8 +104,8 @@ export async function validateGossipBlock(
88
104
  // 2. The parent is unknown to us, we probably want to download it since it might actually
89
105
  // descend from the finalized root.
90
106
  // (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});
107
+ // against known bad fork blocks, so we throw PARENT_BLOCK_UNKNOWN for cases (1) and (2)
108
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
93
109
  }
94
110
 
95
111
  // [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
@@ -164,6 +180,58 @@ export async function validateGossipBlock(
164
180
  });
165
181
  }
166
182
 
183
+ // [REJECT] The counts of `block.body.parent_execution_requests` are within
184
+ // their respective limits -- i.e. validate that
185
+ // `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`,
186
+ // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
187
+ // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
188
+ // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
189
+ // and
190
+ // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
191
+ // [REJECT] The counts of the block body operations are within their respective
192
+ // limits -- i.e. validate that
193
+ // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
194
+ // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
195
+ // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
196
+ // `len(block.body.deposits) == 0`,
197
+ // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
198
+ // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
199
+ // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
200
+ const body = (block as gloas.BeaconBlock).body;
201
+ const requests = body.parentExecutionRequests;
202
+ const countLimits: [string, number, number][] = [
203
+ ["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
204
+ ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
205
+ [
206
+ "parentExecutionRequests.consolidations",
207
+ requests.consolidations.length,
208
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
209
+ ],
210
+ [
211
+ "parentExecutionRequests.builderDeposits",
212
+ requests.builderDeposits.length,
213
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
214
+ ],
215
+ ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
216
+ ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
217
+ ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
218
+ ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
219
+ ["deposits", body.deposits.length, 0],
220
+ ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
221
+ ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
222
+ ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
223
+ ];
224
+ for (const [name, count, limit] of countLimits) {
225
+ if (count > limit) {
226
+ throw new BlockGossipError(GossipAction.REJECT, {
227
+ code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
228
+ name,
229
+ count,
230
+ limit,
231
+ });
232
+ }
233
+ }
234
+
167
235
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
168
236
  // This requires execution engine integration to verify the parent block hash
169
237
  }
@@ -176,7 +244,7 @@ export async function validateGossipBlock(
176
244
  const blockState = await chain.regen
177
245
  .getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
178
246
  .catch(() => {
179
- throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
247
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
180
248
  });
181
249
 
182
250
  // in forky condition, make sure to populate ShufflingCache with regened state
@@ -37,9 +37,11 @@ async function validateExecutionPayloadBid(
37
37
  const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
38
38
  const parentBlockHashHex = toRootHex(bid.parentBlockHash);
39
39
 
40
- // [IGNORE] `bid.slot` is the current slot or the next slot.
41
- const currentSlot = chain.clock.currentSlot;
42
- if (bid.slot !== currentSlot && bid.slot !== currentSlot + 1) {
40
+ // [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
41
+ if (
42
+ !chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
43
+ !chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
44
+ ) {
43
45
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
44
46
  code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
45
47
  builderIndex: bid.builderIndex,
@@ -122,17 +124,20 @@ async function validateExecutionPayloadBid(
122
124
  throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
123
125
  }
124
126
 
125
- // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
126
- // `is_active_builder(state, bid.builder_index)` returns `True`.
127
- let builder: gloas.Builder;
128
- try {
129
- builder = state.getBuilder(bid.builderIndex);
130
- } catch {
127
+ // [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
128
+ // `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
129
+ // out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
130
+ // `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
131
+ if (bid.builderIndex >= state.getBuildersLength()) {
131
132
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
132
133
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
133
134
  builderIndex: bid.builderIndex,
134
135
  });
135
136
  }
137
+
138
+ // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
139
+ // `is_active_builder(state, bid.builder_index)` returns `True`.
140
+ const builder = state.getBuilder(bid.builderIndex);
136
141
  if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
137
142
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
138
143
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
@@ -1,4 +1,12 @@
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_DEPOSIT_REQUESTS_PER_PAYLOAD,
7
+ MAX_WITHDRAWALS_PER_PAYLOAD,
8
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
9
+ } from "@lodestar/params";
2
10
  import {
3
11
  computeStartSlotAtEpoch,
4
12
  getExecutionPayloadEnvelopeSignatureSet,
@@ -115,6 +123,37 @@ async function validateExecutionPayloadEnvelope(
115
123
  });
116
124
  }
117
125
 
126
+ // [REJECT] The counts of `execution_requests` are within their respective limits.
127
+ // New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
128
+ // are enforced here in gossip validation.
129
+ const {executionRequests} = envelope;
130
+ const requestCountLimits: [string, number, number][] = [
131
+ ["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
132
+ ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
133
+ ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
134
+ ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
135
+ ["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
136
+ ];
137
+ for (const [name, count, limit] of requestCountLimits) {
138
+ if (count > limit) {
139
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
140
+ code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
141
+ name,
142
+ count,
143
+ limit,
144
+ });
145
+ }
146
+ }
147
+
148
+ // [REJECT] The number of withdrawals is within the limit.
149
+ if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
150
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
151
+ code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
152
+ count: payload.withdrawals.length,
153
+ limit: MAX_WITHDRAWALS_PER_PAYLOAD,
154
+ });
155
+ }
156
+
118
157
  // Get the block state to verify the builder's signature.
119
158
  const blockState = await chain.regen
120
159
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
@@ -32,8 +32,9 @@ export async function validateGossipProposerPreferences(
32
32
  });
33
33
  }
34
34
 
35
- // [IGNORE] `preferences.proposal_slot` has not already passed.
36
- const currentSlot = chain.clock.currentSlot;
35
+ // [IGNORE] `preferences.proposal_slot` has not already passed, i.e. `proposal_slot > current_slot`,
36
+ // allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
37
+ const currentSlot = chain.clock.currentSlotWithGossipDisparity;
37
38
  if (proposalSlot <= currentSlot) {
38
39
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
39
40
  code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED,
@@ -81,7 +82,7 @@ export async function validateGossipProposerPreferences(
81
82
  }
82
83
 
83
84
  // [IGNORE] First valid message for (dependent_root, proposal_slot, validator_index).
84
- if (chain.seenProposerPreferences.isKnown(dependentRootHex, proposalSlot, validatorIndex)) {
85
+ if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
85
86
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
86
87
  code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
87
88
  proposalSlot,
@@ -105,6 +106,15 @@ export async function validateGossipProposerPreferences(
105
106
  });
106
107
  }
107
108
 
108
- // Valid
109
- chain.seenProposerPreferences.add(dependentRootHex, proposalSlot, validatorIndex);
109
+ // Repeated check - deals with race-condition between preferences submissions
110
+ if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
111
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
112
+ code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
113
+ proposalSlot,
114
+ validatorIndex,
115
+ dependentRoot: dependentRootHex,
116
+ });
117
+ }
118
+
119
+ chain.proposerPreferencesPool.add(signedProposerPreferences);
110
120
  }
@@ -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
 
@@ -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
+ }
@@ -70,6 +70,33 @@ export class NoBidReceived extends Error {
70
70
  }
71
71
  }
72
72
 
73
+ /**
74
+ * Beacon clients select randomized values from the following ranges when initializing
75
+ * the circuit breaker (so at boot time and once for each unique boot).
76
+ *
77
+ * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
78
+ * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
79
+ *
80
+ * The values are randomized per node so builders cannot predict when a given proposer will
81
+ * fall back to local blocks. With fixed thresholds a builder could withhold payloads right up
82
+ * to the limit without ever tripping the breaker.
83
+ */
84
+ export function getFaultInspectionParams(opts: {faultInspectionWindow?: number; allowedFaults?: number}): {
85
+ faultInspectionWindow: number;
86
+ allowedFaults: number;
87
+ } {
88
+ const faultInspectionWindow = Math.max(
89
+ opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
90
+ SLOTS_PER_EPOCH
91
+ );
92
+ // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
93
+ const allowedFaults = Math.min(
94
+ opts.allowedFaults ?? Math.floor(faultInspectionWindow / 4),
95
+ Math.floor(faultInspectionWindow / 4)
96
+ );
97
+ return {faultInspectionWindow, allowedFaults};
98
+ }
99
+
73
100
  /**
74
101
  * Additional duration to account for potential event loop lag which causes
75
102
  * builder blocks to be rejected even though the response was sent in time.
@@ -122,23 +149,9 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
122
149
  this.registrations = new ValidatorRegistrationCache();
123
150
  this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient;
124
151
 
125
- /**
126
- * Beacon clients select randomized values from the following ranges when initializing
127
- * the circuit breaker (so at boot time and once for each unique boot).
128
- *
129
- * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
130
- * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
131
- *
132
- */
133
- this.faultInspectionWindow = Math.max(
134
- opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
135
- SLOTS_PER_EPOCH
136
- );
137
- // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
138
- this.allowedFaults = Math.min(
139
- opts.allowedFaults ?? Math.floor(this.faultInspectionWindow / 4),
140
- Math.floor(this.faultInspectionWindow / 4)
141
- );
152
+ const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
153
+ this.faultInspectionWindow = faultInspectionWindow;
154
+ this.allowedFaults = allowedFaults;
142
155
  }
143
156
 
144
157
  updateStatus(status: BuilderStatus): void {
@@ -138,6 +138,25 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
138
138
  help: "Count of cached produced results",
139
139
  }),
140
140
 
141
+ builderCircuitBreaker: {
142
+ active: register.gauge({
143
+ name: "beacon_builder_circuit_breaker_active",
144
+ help: "Whether the builder circuit breaker is active (1) causing builder bids to be ignored",
145
+ }),
146
+ faults: register.gauge({
147
+ name: "beacon_builder_circuit_breaker_faults",
148
+ help: "Count of blocks with unrevealed payloads in the fault inspection window",
149
+ }),
150
+ blocksPresent: register.gauge({
151
+ name: "beacon_builder_circuit_breaker_blocks_present",
152
+ help: "Count of blocks present in the fault inspection window",
153
+ }),
154
+ payloadsRevealed: register.gauge({
155
+ name: "beacon_builder_circuit_breaker_payloads_revealed",
156
+ help: "Count of blocks with revealed payloads in the fault inspection window",
157
+ }),
158
+ },
159
+
141
160
  blockPayload: {
142
161
  payloadAdvancePrepTime: register.histogram({
143
162
  name: "beacon_block_payload_prepare_time",
@@ -127,6 +127,100 @@ export function createNetworkCoreMetrics(register: RegistryMetricCreator) {
127
127
  name: "lodestar_peer_manager_starved_bool",
128
128
  help: "Whether lodestar is starved of data while syncing",
129
129
  }),
130
+ /**
131
+ * Core selection/pruning phase of the heartbeat, split out of heartbeatDuration so it
132
+ * can be attributed on its own. Divide by peersEvaluated for time-per-peer.
133
+ */
134
+ prioritizePeersDuration: register.histogram({
135
+ name: "lodestar_peer_manager_prioritize_peers_seconds",
136
+ help: "prioritizePeers function duration in seconds, the core peer selection/pruning algorithm",
137
+ buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
138
+ }),
139
+ /**
140
+ * Score decay + map prune over every tracked peer, split out of heartbeatDuration.
141
+ * This loop iterates the whole score store, so divide by scoreMapSize (not
142
+ * peersEvaluated) for time-per-peer.
143
+ */
144
+ scoreUpdateDuration: register.histogram({
145
+ name: "lodestar_peer_score_update_seconds",
146
+ help: "Peer score store update (decay + prune over all peers) duration in seconds",
147
+ buckets: [0.0005, 0.001, 0.005, 0.01, 0.05, 0.1],
148
+ }),
149
+ /**
150
+ * Denominator for prioritizePeersDuration: it scales with the number of connected
151
+ * healthy peers, so time-per-peer (prioritizePeersDuration / this) is the comparable
152
+ * figure. For scoreUpdateDuration use scoreMapSize instead.
153
+ */
154
+ peersEvaluated: register.histogram({
155
+ name: "lodestar_peer_manager_peers_evaluated_count",
156
+ help: "Number of connected healthy peers evaluated by prioritizePeers per heartbeat, denominator for prioritize_peers_seconds",
157
+ buckets: [0, 25, 50, 75, 100, 150, 200],
158
+ }),
159
+ /**
160
+ * Every peer the manager intends to disconnect. Covers both the top-of-heartbeat
161
+ * bad-score disconnects ("banned"/"score_too_low") and the prioritization reasons;
162
+ * the older peersRequestedToDisconnect gauge only recorded the latter. Counts intent,
163
+ * not completion (goodbye is fire-and-forget).
164
+ */
165
+ peersPruned: register.counter<{reason: string}>({
166
+ name: "lodestar_peer_manager_peers_pruned_total",
167
+ help: "Total peers the peer manager intends to disconnect, labeled by reason (incl. bad-score and prioritization reasons)",
168
+ labelNames: ["reason"],
169
+ }),
170
+ /**
171
+ * Actual peer count per active subnet, to check the min-peers-per-subnet invariant.
172
+ * Buckets straddle the target (TARGET_SUBNET_PEERS = 6).
173
+ */
174
+ peersPerActiveSubnet: register.histogram<{type: SubnetType}>({
175
+ name: "lodestar_peer_manager_peers_per_active_subnet",
176
+ help: "Histogram of connected peer count per active subnet, labeled by subnet type",
177
+ labelNames: ["type"],
178
+ buckets: [0, 2, 4, 6, 8, 12],
179
+ }),
180
+ /**
181
+ * Live outbound ratio, to check the OUTBOUND_PEERS_RATIO (10%) invariant (issue #2215).
182
+ * Denominator is connected healthy peers (banned/disconnected already removed).
183
+ */
184
+ outboundPeersRatio: register.gauge({
185
+ name: "lodestar_peer_manager_outbound_peers_ratio",
186
+ help: "Ratio of outbound peers to total connected healthy peers, verifies the outbound peers invariant",
187
+ }),
188
+ /**
189
+ * Score state crossings (Healthy/Disconnected/Banned). Sensitive to the decay formula,
190
+ * thresholds, and gossip-score weighting.
191
+ */
192
+ scoreStateTransitions: register.counter<{from: string; to: string}>({
193
+ name: "lodestar_peer_score_state_transitions_total",
194
+ help: "Total peer score state transitions, labeled by from and to state (Healthy/Disconnected/Banned)",
195
+ labelNames: ["from", "to"],
196
+ }),
197
+ /**
198
+ * Entry count of the score store (not bytes). Reflects the prune-to-MAX_ENTRIES /
199
+ * SCORE_THRESHOLD retention logic. Also the denominator for scoreUpdateDuration
200
+ * time-per-peer (that loop iterates the whole store, not just connected peers).
201
+ */
202
+ scoreMapSize: register.gauge({
203
+ name: "lodestar_peer_manager_score_map_size",
204
+ help: "Current number of entries in the peer score store",
205
+ }),
206
+ /**
207
+ * Entry count of the connectedPeers map (not bytes). Should track libp2p_peers; a
208
+ * persistent gap indicates the connection leak guarded by leakedConnectionsCount.
209
+ */
210
+ connectedPeersMapSize: register.gauge({
211
+ name: "lodestar_peer_manager_connected_peers_map_size",
212
+ help: "Current number of entries in the peer manager connectedPeers map",
213
+ }),
214
+ /**
215
+ * assertPeerRelevance outcome per Status exchange: "relevant", an irrelevant reason
216
+ * code, or "error". Fires on every status evaluation (inbound requests and our own
217
+ * requestStatus responses both route through onStatus).
218
+ */
219
+ relevanceCheck: register.counter<{result: string}>({
220
+ name: "lodestar_peer_relevance_check_total",
221
+ help: "Total peer relevance checks on Status, labeled by result (relevant or irrelevant reason code)",
222
+ labelNames: ["result"],
223
+ }),
130
224
  },
131
225
  leakedConnectionsCount: register.gauge({
132
226
  name: "lodestar_peer_manager_leaked_connections_count",
@@ -13,9 +13,17 @@ export enum ENRRelevance {
13
13
  }
14
14
 
15
15
  export function enrRelevance(enr: ENR, config: BeaconConfig, clock: IClock): ENRRelevance {
16
- // We are not interested in peers that don't advertise at least one transport (tcp or quic)
17
- const multiaddrTCP = enr.getLocationMultiaddr(ENRKey.tcp);
18
- const multiaddrQUIC = enr.getLocationMultiaddr(ENRKey.quic);
16
+ // We are not interested in peers that don't advertise at least one transport (tcp or quic).
17
+ let multiaddrTCP: ReturnType<ENR["getLocationMultiaddr"]>;
18
+ let multiaddrQUIC: ReturnType<ENR["getLocationMultiaddr"]>;
19
+ // Malformed ENR endpoint fields can make multiaddr conversion throw.
20
+ // Treat endpoints that cannot be converted as unusable.
21
+ try {
22
+ multiaddrTCP = enr.getLocationMultiaddr(ENRKey.tcp);
23
+ multiaddrQUIC = enr.getLocationMultiaddr(ENRKey.quic);
24
+ } catch {
25
+ return ENRRelevance.no_transport;
26
+ }
19
27
  if (!multiaddrTCP && !multiaddrQUIC) {
20
28
  return ENRRelevance.no_transport;
21
29
  }
@@ -9,7 +9,7 @@ import {ForkName} from "@lodestar/params";
9
9
  import {intToBytes} from "@lodestar/utils";
10
10
  import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js";
11
11
  import {Eth2GossipsubMetrics} from "./metrics.js";
12
- import {GossipTopicCache, getGossipSSZType} from "./topic.js";
12
+ import {GossipTopicCache, getGossipSSZMaxSize, getGossipSSZType} from "./topic.js";
13
13
 
14
14
  // Load WASM
15
15
  const xxhash = await xxhashFactory();
@@ -52,6 +52,13 @@ export function fastMsgIdFn(rpcMsg: RPC.Message): string {
52
52
  }
53
53
 
54
54
  export function msgIdToStrFn(msgId: Uint8Array): string {
55
+ // Spec mandates a 20-byte gossipsub message-id, but control-message IDs from peers
56
+ // can be any length. Reject non-20-byte IDs to avoid a Buffer.set() RangeError on
57
+ // longer IDs and stale shared-buffer bytes on shorter ones.
58
+ // Ref: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.10/specs/phase0/p2p-interface.md?plain=1#L486
59
+ if (msgId.length !== 20) {
60
+ throw Error(`Expect msgId to be 20 bytes, got ${msgId.length}`);
61
+ }
55
62
  // this is the same logic to `toHex(msgId)` with better performance
56
63
  sharedMsgIdBuf.set(msgId);
57
64
  return `0x${sharedMsgIdBuf.toString("hex")}`;
@@ -91,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
91
98
  export class DataTransformSnappy implements DataTransform {
92
99
  constructor(
93
100
  private readonly gossipTopicCache: GossipTopicCache,
94
- private readonly maxSizePerMessage: number,
101
+ private readonly maxPayloadSize: number,
95
102
  private readonly metrics: Eth2GossipsubMetrics | null
96
103
  ) {}
97
104
 
@@ -104,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
104
111
  inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
105
112
  // check uncompressed data length before we actually decompress
106
113
  const uncompressedDataLength = snappyWasm.decompress_len(data);
107
- if (uncompressedDataLength > this.maxSizePerMessage) {
108
- throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
109
- }
110
114
 
111
115
  const topic = this.gossipTopicCache.getTopic(topicStr);
112
116
  const sszType = getGossipSSZType(topic);
117
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
113
118
  this.metrics?.dataTransform.inbound.inc({type: topic.type});
114
119
 
115
120
  if (uncompressedDataLength < sszType.minSize) {
116
121
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
117
122
  }
123
+ if (uncompressedDataLength > maxSize) {
124
+ throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
125
+ }
118
126
  if (uncompressedDataLength > sszType.maxSize) {
119
127
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
120
128
  }
@@ -132,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
132
140
  */
133
141
  outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
134
142
  const topic = this.gossipTopicCache.getTopic(topicStr);
143
+ const sszType = getGossipSSZType(topic);
144
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
135
145
  this.metrics?.dataTransform.outbound.inc({type: topic.type});
136
- if (data.length > this.maxSizePerMessage) {
137
- throw Error(`ssz_snappy encoded data length ${data.length} > ${this.maxSizePerMessage}`);
146
+ if (data.length > maxSize) {
147
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${maxSize}`);
148
+ }
149
+ if (data.length > sszType.maxSize) {
150
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${sszType.maxSize}`);
138
151
  }
139
152
 
140
153
  // Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments