@lodestar/beacon-node 1.45.0 → 1.46.0-dev.20101a8cc1

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 (212) 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/lodestar/index.d.ts.map +1 -1
  5. package/lib/api/impl/lodestar/index.js +2 -3
  6. package/lib/api/impl/lodestar/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +172 -88
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/api/rest/base.d.ts.map +1 -1
  11. package/lib/api/rest/base.js +6 -3
  12. package/lib/api/rest/base.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  14. package/lib/chain/archiveStore/archiveStore.js +17 -3
  15. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  16. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +0 -1
  18. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  19. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  20. package/lib/chain/blocks/importBlock.js +2 -17
  21. package/lib/chain/blocks/importBlock.js.map +1 -1
  22. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  23. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  24. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  25. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  26. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  27. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  28. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  29. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  30. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  31. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  32. package/lib/chain/bls/multithread/index.js +0 -1
  33. package/lib/chain/bls/multithread/index.js.map +1 -1
  34. package/lib/chain/chain.d.ts.map +1 -1
  35. package/lib/chain/chain.js +44 -2
  36. package/lib/chain/chain.js.map +1 -1
  37. package/lib/chain/errors/blockError.d.ts +48 -6
  38. package/lib/chain/errors/blockError.d.ts.map +1 -1
  39. package/lib/chain/errors/blockError.js +25 -4
  40. package/lib/chain/errors/blockError.js.map +1 -1
  41. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  42. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  43. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  44. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  45. package/lib/chain/errors/proposerPreferences.d.ts +14 -1
  46. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  47. package/lib/chain/errors/proposerPreferences.js +2 -0
  48. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  49. package/lib/chain/lightClient/index.d.ts.map +1 -1
  50. package/lib/chain/lightClient/index.js +11 -6
  51. package/lib/chain/lightClient/index.js.map +1 -1
  52. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  53. package/lib/chain/lightClient/proofs.js +28 -2
  54. package/lib/chain/lightClient/proofs.js.map +1 -1
  55. package/lib/chain/lightClient/types.d.ts +3 -1
  56. package/lib/chain/lightClient/types.d.ts.map +1 -1
  57. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  58. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  59. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
  60. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  61. package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
  62. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  63. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  64. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  65. package/lib/chain/validation/aggregateAndProof.js +10 -6
  66. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  67. package/lib/chain/validation/attestation.d.ts.map +1 -1
  68. package/lib/chain/validation/attestation.js +10 -6
  69. package/lib/chain/validation/attestation.js.map +1 -1
  70. package/lib/chain/validation/blobSidecar.js +1 -1
  71. package/lib/chain/validation/block.d.ts.map +1 -1
  72. package/lib/chain/validation/block.js +53 -4
  73. package/lib/chain/validation/block.js.map +1 -1
  74. package/lib/chain/validation/executionPayloadBid.d.ts +5 -0
  75. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  76. package/lib/chain/validation/executionPayloadBid.js +32 -4
  77. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  78. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  79. package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
  80. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  81. package/lib/chain/validation/proposerPreferences.d.ts +9 -2
  82. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  83. package/lib/chain/validation/proposerPreferences.js +65 -13
  84. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  85. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  86. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  87. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  88. package/lib/chain/validatorMonitor.js +1 -1
  89. package/lib/chain/validatorMonitor.js.map +1 -1
  90. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  91. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  92. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  93. package/lib/metrics/nodeJsMetrics.js +1 -1
  94. package/lib/metrics/nodeJsMetrics.js.map +1 -1
  95. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  96. package/lib/network/core/networkCoreWorkerHandler.js +2 -7
  97. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  98. package/lib/network/discv5/index.d.ts.map +1 -1
  99. package/lib/network/discv5/index.js +1 -4
  100. package/lib/network/discv5/index.js.map +1 -1
  101. package/lib/network/gossip/encoding.d.ts +2 -2
  102. package/lib/network/gossip/encoding.d.ts.map +1 -1
  103. package/lib/network/gossip/encoding.js +15 -9
  104. package/lib/network/gossip/encoding.js.map +1 -1
  105. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  106. package/lib/network/gossip/gossipsub.js +2 -2
  107. package/lib/network/gossip/gossipsub.js.map +1 -1
  108. package/lib/network/gossip/topic.d.ts +76 -233
  109. package/lib/network/gossip/topic.d.ts.map +1 -1
  110. package/lib/network/gossip/topic.js +24 -1
  111. package/lib/network/gossip/topic.js.map +1 -1
  112. package/lib/network/interface.d.ts +4 -1
  113. package/lib/network/interface.d.ts.map +1 -1
  114. package/lib/network/libp2p/index.d.ts.map +1 -1
  115. package/lib/network/libp2p/index.js +2 -9
  116. package/lib/network/libp2p/index.js.map +1 -1
  117. package/lib/network/network.d.ts +4 -1
  118. package/lib/network/network.d.ts.map +1 -1
  119. package/lib/network/network.js +19 -11
  120. package/lib/network/network.js.map +1 -1
  121. package/lib/network/peers/datastore.d.ts +1 -1
  122. package/lib/network/peers/datastore.d.ts.map +1 -1
  123. package/lib/network/peers/datastore.js +1 -1
  124. package/lib/network/peers/datastore.js.map +1 -1
  125. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  126. package/lib/network/processor/gossipHandlers.js +12 -6
  127. package/lib/network/processor/gossipHandlers.js.map +1 -1
  128. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  129. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  130. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  131. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  132. package/lib/network/reqresp/handlers/index.js +20 -11
  133. package/lib/network/reqresp/handlers/index.js.map +1 -1
  134. package/lib/network/util.d.ts +1 -0
  135. package/lib/network/util.d.ts.map +1 -1
  136. package/lib/network/util.js +4 -0
  137. package/lib/network/util.js.map +1 -1
  138. package/lib/sync/sync.d.ts.map +1 -1
  139. package/lib/sync/sync.js +7 -0
  140. package/lib/sync/sync.js.map +1 -1
  141. package/lib/sync/unknownBlock.d.ts.map +1 -1
  142. package/lib/sync/unknownBlock.js +7 -2
  143. package/lib/sync/unknownBlock.js.map +1 -1
  144. package/lib/util/execution.js +1 -1
  145. package/lib/util/execution.js.map +1 -1
  146. package/lib/util/profile.d.ts +3 -0
  147. package/lib/util/profile.d.ts.map +1 -1
  148. package/lib/util/profile.js +1 -27
  149. package/lib/util/profile.js.map +1 -1
  150. package/lib/util/sszBytes.js +1 -1
  151. package/package.json +29 -38
  152. package/src/api/impl/beacon/blocks/index.ts +221 -63
  153. package/src/api/impl/lodestar/index.ts +2 -3
  154. package/src/api/impl/validator/index.ts +206 -98
  155. package/src/api/rest/base.ts +8 -3
  156. package/src/chain/archiveStore/archiveStore.ts +20 -3
  157. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +0 -1
  158. package/src/chain/blocks/importBlock.ts +0 -17
  159. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  160. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  161. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  162. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -3
  163. package/src/chain/bls/multithread/index.ts +0 -1
  164. package/src/chain/chain.ts +61 -2
  165. package/src/chain/errors/blockError.ts +44 -7
  166. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  167. package/src/chain/errors/proposerPreferences.ts +16 -1
  168. package/src/chain/lightClient/index.ts +12 -7
  169. package/src/chain/lightClient/proofs.ts +36 -1
  170. package/src/chain/lightClient/types.ts +3 -1
  171. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  172. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  173. package/src/chain/produceBlock/produceBlockBody.ts +1 -1
  174. package/src/chain/validation/aggregateAndProof.ts +12 -6
  175. package/src/chain/validation/attestation.ts +12 -6
  176. package/src/chain/validation/blobSidecar.ts +1 -1
  177. package/src/chain/validation/block.ts +69 -4
  178. package/src/chain/validation/executionPayloadBid.ts +34 -4
  179. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  180. package/src/chain/validation/proposerPreferences.ts +75 -13
  181. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  182. package/src/chain/validatorMonitor.ts +1 -1
  183. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  184. package/src/metrics/nodeJsMetrics.ts +1 -1
  185. package/src/network/core/networkCoreWorkerHandler.ts +2 -6
  186. package/src/network/discv5/index.ts +1 -4
  187. package/src/network/gossip/encoding.ts +13 -7
  188. package/src/network/gossip/gossipsub.ts +7 -2
  189. package/src/network/gossip/topic.ts +30 -0
  190. package/src/network/interface.ts +3 -1
  191. package/src/network/libp2p/index.ts +2 -10
  192. package/src/network/network.ts +24 -15
  193. package/src/network/peers/datastore.ts +1 -1
  194. package/src/network/processor/gossipHandlers.ts +12 -6
  195. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  196. package/src/network/reqresp/handlers/index.ts +21 -12
  197. package/src/network/util.ts +5 -0
  198. package/src/sync/sync.ts +10 -0
  199. package/src/sync/unknownBlock.ts +8 -2
  200. package/src/util/execution.ts +1 -1
  201. package/src/util/profile.ts +1 -31
  202. package/src/util/sszBytes.ts +1 -1
  203. package/lib/bun-wrappers/prometheus-gc-stats.d.ts +0 -2
  204. package/lib/bun-wrappers/prometheus-gc-stats.d.ts.map +0 -1
  205. package/lib/bun-wrappers/prometheus-gc-stats.js +0 -8
  206. package/lib/bun-wrappers/prometheus-gc-stats.js.map +0 -1
  207. package/lib/network/peers/datastore_bun.d.ts +0 -3
  208. package/lib/network/peers/datastore_bun.d.ts.map +0 -1
  209. package/lib/network/peers/datastore_bun.js +0 -3
  210. package/lib/network/peers/datastore_bun.js.map +0 -1
  211. package/src/bun-wrappers/prometheus-gc-stats.ts +0 -7
  212. package/src/network/peers/datastore_bun.ts +0 -3
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import {Registry, collectDefaultMetrics} from "prom-client";
2
- import {gcStats} from "#prometheus-gc-stats-wrapper";
2
+ import {gcStats} from "@chainsafe/prometheus-gc-stats";
3
3
 
4
4
  export function collectNodeJSMetrics(register: Registry, prefix?: string): () => void {
5
5
  collectDefaultMetrics({
@@ -132,10 +132,6 @@ export class WorkerNetworkCore implements INetworkCore {
132
132
 
133
133
  const workerOpts: ConstructorParameters<typeof Worker>[1] = {
134
134
  workerData,
135
- };
136
- if (globalThis.Bun) {
137
- workerOpts.suppressTranspileTS = true;
138
- } else {
139
135
  /**
140
136
  * maxYoungGenerationSizeMb defaults to 152mb through the cli option defaults.
141
137
  * That default value was determined via https://github.com/ChainSafe/lodestar/issues/2115 and
@@ -147,8 +143,8 @@ export class WorkerNetworkCore implements INetworkCore {
147
143
  * showed that there is a pretty big window of "correct" values but we can always tune as
148
144
  * necessary
149
145
  */
150
- workerOpts.resourceLimits = {maxYoungGenerationSizeMb: opts.maxYoungGenerationSizeMb};
151
- }
146
+ resourceLimits: {maxYoungGenerationSizeMb: opts.maxYoungGenerationSizeMb},
147
+ };
152
148
 
153
149
  const worker = new Worker(path.join(workerDir, "networkCoreWorker.js"), workerOpts);
154
150
 
@@ -51,10 +51,7 @@ export class Discv5Worker extends (EventEmitter as {new (): StrictEventEmitter<E
51
51
  loggerOpts: opts.logger.toOpts(),
52
52
  genesisTime: opts.genesisTime,
53
53
  };
54
- const worker = new Worker("./worker.js", {
55
- suppressTranspileTS: Boolean(globalThis.Bun),
56
- workerData,
57
- } as ConstructorParameters<typeof Worker>[1]);
54
+ const worker = new Worker("./worker.js", {workerData} as ConstructorParameters<typeof Worker>[1]);
58
55
 
59
56
  const workerApi = await spawn<Discv5WorkerApi>(worker, {
60
57
  // A Lodestar Node may do very expensive task at start blocking the event loop and causing
@@ -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();
@@ -98,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
98
98
  export class DataTransformSnappy implements DataTransform {
99
99
  constructor(
100
100
  private readonly gossipTopicCache: GossipTopicCache,
101
- private readonly maxSizePerMessage: number,
101
+ private readonly maxPayloadSize: number,
102
102
  private readonly metrics: Eth2GossipsubMetrics | null
103
103
  ) {}
104
104
 
@@ -111,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
111
111
  inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
112
112
  // check uncompressed data length before we actually decompress
113
113
  const uncompressedDataLength = snappyWasm.decompress_len(data);
114
- if (uncompressedDataLength > this.maxSizePerMessage) {
115
- throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
116
- }
117
114
 
118
115
  const topic = this.gossipTopicCache.getTopic(topicStr);
119
116
  const sszType = getGossipSSZType(topic);
117
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
120
118
  this.metrics?.dataTransform.inbound.inc({type: topic.type});
121
119
 
122
120
  if (uncompressedDataLength < sszType.minSize) {
123
121
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
124
122
  }
123
+ if (uncompressedDataLength > maxSize) {
124
+ throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
125
+ }
125
126
  if (uncompressedDataLength > sszType.maxSize) {
126
127
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
127
128
  }
@@ -139,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
139
140
  */
140
141
  outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
141
142
  const topic = this.gossipTopicCache.getTopic(topicStr);
143
+ const sszType = getGossipSSZType(topic);
144
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
142
145
  this.metrics?.dataTransform.outbound.inc({type: topic.type});
143
- if (data.length > this.maxSizePerMessage) {
144
- 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}`);
145
151
  }
146
152
 
147
153
  // Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments
@@ -15,7 +15,12 @@ import {type Multiaddr, multiaddr} from "@multiformats/multiaddr";
15
15
  import {ENR} from "@chainsafe/enr";
16
16
  import {routes} from "@lodestar/api";
17
17
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
18
- import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
18
+ import {
19
+ ATTESTATION_SUBNET_COUNT,
20
+ MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
21
+ SLOTS_PER_EPOCH,
22
+ SYNC_COMMITTEE_SUBNET_COUNT,
23
+ } from "@lodestar/params";
19
24
  import {SubnetID} from "@lodestar/types";
20
25
  import {Logger, Map2d, Map2dArr} from "@lodestar/utils";
21
26
  import {RegistryMetricCreator} from "../../metrics/index.js";
@@ -179,7 +184,7 @@ export class Eth2Gossipsub {
179
184
  // Only send IDONTWANT messages if the message size is larger than this
180
185
  // This should be large enough to not send IDONTWANT for "small" messages
181
186
  // See https://github.com/ChainSafe/lodestar/pull/7077#issuecomment-2383679472
182
- idontwantMinDataSize: 16829,
187
+ idontwantMinDataSize: MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
183
188
  })(modules.libp2p.services.components) as GossipSubInternal;
184
189
 
185
190
  if (metrics) {
@@ -1,12 +1,18 @@
1
+ import {type CompositeTypeAny} from "@chainsafe/ssz";
1
2
  import {ForkDigestContext} from "@lodestar/config";
2
3
  import {
3
4
  ATTESTATION_SUBNET_COUNT,
4
5
  ForkName,
5
6
  ForkSeq,
7
+ MAX_ATTESTER_SLASHING_SIZE,
8
+ MAX_DATA_COLUMN_SIDECAR_SIZE,
9
+ MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE,
10
+ MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE,
6
11
  SYNC_COMMITTEE_SUBNET_COUNT,
7
12
  isForkPostAltair,
8
13
  isForkPostElectra,
9
14
  isForkPostFulu,
15
+ isForkPostGloas,
10
16
  } from "@lodestar/params";
11
17
  import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types";
12
18
  import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js";
@@ -130,6 +136,30 @@ export function getGossipSSZType(topic: GossipTopic) {
130
136
  }
131
137
  }
132
138
 
139
+ /**
140
+ * Return the maximum uncompressed SSZ byte length accepted for a gossip object.
141
+ */
142
+ export function getGossipSSZMaxSize(topic: GossipTopic, maxPayloadSize: number, sszType?: CompositeTypeAny): number {
143
+ const {fork} = topic.boundary;
144
+ // Gloas progressive containers have broad theoretical SSZ max sizes; use the preset p2p bounds instead.
145
+ switch (topic.type) {
146
+ case GossipType.beacon_block:
147
+ return maxPayloadSize;
148
+ case GossipType.beacon_aggregate_and_proof:
149
+ return isForkPostGloas(fork) ? MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
150
+ case GossipType.attester_slashing:
151
+ return isForkPostGloas(fork) ? MAX_ATTESTER_SLASHING_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
152
+ case GossipType.data_column_sidecar:
153
+ return isForkPostGloas(fork) ? MAX_DATA_COLUMN_SIDECAR_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize;
154
+ case GossipType.execution_payload:
155
+ return maxPayloadSize;
156
+ case GossipType.execution_payload_bid:
157
+ return MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE;
158
+ default:
159
+ return (sszType ?? getGossipSSZType(topic)).maxSize;
160
+ }
161
+ }
162
+
133
163
  /**
134
164
  * Deserialize a gossip serialized data into an ssz object.
135
165
  */
@@ -103,7 +103,9 @@ export interface INetwork extends INetworkCorePublic {
103
103
  publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number>;
104
104
  publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise<number>;
105
105
  publishBeaconAttestation(attestation: SingleAttestation, subnet: SubnetID): Promise<number>;
106
- publishDataColumnSidecar(dataColumnSideCar: DataColumnSidecar): Promise<number>;
106
+ publishDataColumnSidecar(
107
+ dataColumnSideCar: DataColumnSidecar
108
+ ): Promise<{sentPeers: number; alreadyPublished: boolean}>;
107
109
  publishVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): Promise<number>;
108
110
  publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number>;
109
111
  publishProposerSlashing(proposerSlashing: phase0.ProposerSlashing): Promise<number>;
@@ -9,7 +9,7 @@ import {Libp2pInit, createLibp2p} from "libp2p";
9
9
  import {Registry} from "prom-client";
10
10
  import {ENR} from "@chainsafe/enr";
11
11
  import {noise} from "@chainsafe/libp2p-noise";
12
- import {asCrypto, defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
12
+ import {defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
13
13
  import {quic} from "@chainsafe/libp2p-quic";
14
14
  import {Libp2p, LodestarComponents} from "../interface.js";
15
15
  import {NetworkOptions, defaultNetworkOptions} from "../options.js";
@@ -111,14 +111,6 @@ export async function createNodeJsLibp2p(
111
111
  }
112
112
  }
113
113
 
114
- const noiseCrypto = {
115
- ...defaultCrypto,
116
- };
117
- if (globalThis.Bun) {
118
- noiseCrypto.chaCha20Poly1305Decrypt = asCrypto.chaCha20Poly1305Decrypt;
119
- noiseCrypto.chaCha20Poly1305Encrypt = asCrypto.chaCha20Poly1305Encrypt;
120
- }
121
-
122
114
  return createLibp2p({
123
115
  privateKey,
124
116
  nodeInfo: {
@@ -130,7 +122,7 @@ export async function createNodeJsLibp2p(
130
122
  listen: localMultiaddrs,
131
123
  announce: [],
132
124
  },
133
- connectionEncrypters: [noise({crypto: noiseCrypto})],
125
+ connectionEncrypters: [noise({crypto: defaultCrypto})],
134
126
  transports,
135
127
  streamMuxers: [mplex({disconnectThreshold})],
136
128
  peerDiscovery,
@@ -65,7 +65,7 @@ import {
65
65
  } from "./reqresp/utils/collect.js";
66
66
  import {collectSequentialBlocksInRange} from "./reqresp/utils/collectSequentialBlocksInRange.js";
67
67
  import {CommitteeSubscription} from "./subnets/index.js";
68
- import {isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
68
+ import {isPublishDuplicateError, isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
69
69
 
70
70
  type NetworkModules = {
71
71
  opts: NetworkOptions;
@@ -366,7 +366,9 @@ export class Network implements INetwork {
366
366
  });
367
367
  }
368
368
 
369
- async publishDataColumnSidecar(dataColumnSidecar: DataColumnSidecar): Promise<number> {
369
+ async publishDataColumnSidecar(
370
+ dataColumnSidecar: DataColumnSidecar
371
+ ): Promise<{sentPeers: number; alreadyPublished: boolean}> {
370
372
  const slot = isGloasDataColumnSidecar(dataColumnSidecar)
371
373
  ? dataColumnSidecar.slot
372
374
  : dataColumnSidecar.signedBlockHeader.message.slot;
@@ -374,18 +376,25 @@ export class Network implements INetwork {
374
376
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
375
377
 
376
378
  const subnet = computeSubnetForDataColumnSidecar(this.config, dataColumnSidecar);
377
- return this.publishGossip<GossipType.data_column_sidecar>(
378
- {type: GossipType.data_column_sidecar, boundary, subnet},
379
- dataColumnSidecar,
380
- {
381
- ignoreDuplicatePublishError: true,
382
- // we ensure having all topic peers via prioritizePeers() function
383
- // in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
384
- // because supernode will rebuild and publish missing data column sidecars for us
385
- // hence we want to track sent peers as 0 instead of an error
386
- allowPublishToZeroTopicPeers: true,
379
+ try {
380
+ const sentPeers = await this.publishGossip<GossipType.data_column_sidecar>(
381
+ {type: GossipType.data_column_sidecar, boundary, subnet},
382
+ dataColumnSidecar,
383
+ {
384
+ // we ensure having all topic peers via prioritizePeers() function
385
+ // in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
386
+ // because supernode will rebuild and publish missing data column sidecars for us
387
+ // hence we want to track sent peers as 0 instead of an error
388
+ allowPublishToZeroTopicPeers: true,
389
+ }
390
+ );
391
+ return {sentPeers, alreadyPublished: false};
392
+ } catch (e) {
393
+ if (isPublishDuplicateError(e as Error)) {
394
+ return {sentPeers: 0, alreadyPublished: true};
387
395
  }
388
- );
396
+ throw e;
397
+ }
389
398
  }
390
399
 
391
400
  async publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise<number> {
@@ -860,8 +869,8 @@ export class Network implements INetwork {
860
869
  this.core.setTargetGroupCount(count);
861
870
  };
862
871
 
863
- private onPublishDataColumns = (sidecars: DataColumnSidecar[]): Promise<number[]> => {
864
- return promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishDataColumnSidecar(sidecar)));
872
+ private onPublishDataColumns = async (sidecars: DataColumnSidecar[]): Promise<void> => {
873
+ await promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishDataColumnSidecar(sidecar)));
865
874
  };
866
875
 
867
876
  private onPublishBlobSidecars = (sidecars: deneb.BlobSidecar[]): Promise<number[]> => {
@@ -1,7 +1,7 @@
1
1
  import {AbortOptions} from "@libp2p/interface";
2
2
  import {BaseDatastore} from "datastore-core";
3
+ import {LevelDatastore} from "datastore-level";
3
4
  import {Key, KeyQuery, Pair, Query} from "interface-datastore";
4
- import {LevelDatastore} from "#datastore-wrapper";
5
5
 
6
6
  type MemoryItem = {
7
7
  lastAccessedMs: number;