@lodestar/beacon-node 1.40.0-dev.9defa5c09b → 1.40.0-dev.b87b37f4d0

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 (263) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +15 -9
  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 +24 -0
  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.map +1 -1
  9. package/lib/api/rest/base.d.ts.map +1 -1
  10. package/lib/api/rest/base.js +2 -2
  11. package/lib/api/rest/base.js.map +1 -1
  12. package/lib/chain/ColumnReconstructionTracker.d.ts +2 -0
  13. package/lib/chain/ColumnReconstructionTracker.d.ts.map +1 -1
  14. package/lib/chain/ColumnReconstructionTracker.js +7 -3
  15. package/lib/chain/ColumnReconstructionTracker.js.map +1 -1
  16. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +2 -1
  18. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  19. package/lib/chain/blocks/blockInput/blockInput.d.ts +28 -0
  20. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  21. package/lib/chain/blocks/blockInput/blockInput.js +38 -3
  22. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  23. package/lib/chain/blocks/importBlock.js +1 -1
  24. package/lib/chain/blocks/importBlock.js.map +1 -1
  25. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  26. package/lib/chain/blocks/verifyBlock.js +1 -1
  27. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  28. package/lib/chain/blocks/verifyBlocksSignatures.d.ts +2 -2
  29. package/lib/chain/blocks/verifyBlocksSignatures.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlocksSignatures.js +2 -2
  31. package/lib/chain/blocks/verifyBlocksSignatures.js.map +1 -1
  32. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +1 -2
  34. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  35. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  36. package/lib/chain/blocks/writeBlockInputToDb.js +8 -0
  37. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  38. package/lib/chain/bls/multithread/index.d.ts +3 -1
  39. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  40. package/lib/chain/bls/multithread/index.js +5 -3
  41. package/lib/chain/bls/multithread/index.js.map +1 -1
  42. package/lib/chain/bls/multithread/jobItem.d.ts +2 -2
  43. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  44. package/lib/chain/bls/multithread/jobItem.js +2 -2
  45. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  46. package/lib/chain/bls/singleThread.d.ts +4 -2
  47. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  48. package/lib/chain/bls/singleThread.js +4 -2
  49. package/lib/chain/bls/singleThread.js.map +1 -1
  50. package/lib/chain/bls/utils.d.ts +2 -2
  51. package/lib/chain/bls/utils.d.ts.map +1 -1
  52. package/lib/chain/bls/utils.js +9 -6
  53. package/lib/chain/bls/utils.js.map +1 -1
  54. package/lib/chain/chain.d.ts +1 -1
  55. package/lib/chain/chain.d.ts.map +1 -1
  56. package/lib/chain/chain.js +19 -31
  57. package/lib/chain/chain.js.map +1 -1
  58. package/lib/chain/initState.d.ts.map +1 -1
  59. package/lib/chain/initState.js +2 -2
  60. package/lib/chain/initState.js.map +1 -1
  61. package/lib/chain/lightClient/index.d.ts.map +1 -1
  62. package/lib/chain/lightClient/index.js +1 -2
  63. package/lib/chain/lightClient/index.js.map +1 -1
  64. package/lib/chain/options.d.ts +0 -1
  65. package/lib/chain/options.d.ts.map +1 -1
  66. package/lib/chain/options.js +0 -1
  67. package/lib/chain/options.js.map +1 -1
  68. package/lib/chain/regen/interface.d.ts +1 -1
  69. package/lib/chain/regen/queued.d.ts +1 -1
  70. package/lib/chain/regen/queued.d.ts.map +1 -1
  71. package/lib/chain/regen/queued.js.map +1 -1
  72. package/lib/chain/regen/regen.d.ts +2 -0
  73. package/lib/chain/regen/regen.d.ts.map +1 -1
  74. package/lib/chain/regen/regen.js +4 -1
  75. package/lib/chain/regen/regen.js.map +1 -1
  76. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  77. package/lib/chain/seenCache/seenGossipBlockInput.js +3 -3
  78. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  79. package/lib/chain/serializeState.d.ts.map +1 -1
  80. package/lib/chain/serializeState.js +2 -1
  81. package/lib/chain/serializeState.js.map +1 -1
  82. package/lib/chain/stateCache/index.d.ts +0 -2
  83. package/lib/chain/stateCache/index.d.ts.map +1 -1
  84. package/lib/chain/stateCache/index.js +0 -2
  85. package/lib/chain/stateCache/index.js.map +1 -1
  86. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +2 -1
  87. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  88. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -0
  89. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  90. package/lib/chain/validation/aggregateAndProof.js +3 -4
  91. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  92. package/lib/chain/validation/attestation.d.ts +2 -2
  93. package/lib/chain/validation/attestation.d.ts.map +1 -1
  94. package/lib/chain/validation/attestation.js +4 -4
  95. package/lib/chain/validation/attestation.js.map +1 -1
  96. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  97. package/lib/chain/validation/attesterSlashing.js +1 -1
  98. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  99. package/lib/chain/validation/blobSidecar.d.ts.map +1 -1
  100. package/lib/chain/validation/blobSidecar.js +4 -4
  101. package/lib/chain/validation/blobSidecar.js.map +1 -1
  102. package/lib/chain/validation/block.d.ts.map +1 -1
  103. package/lib/chain/validation/block.js +2 -3
  104. package/lib/chain/validation/block.js.map +1 -1
  105. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  106. package/lib/chain/validation/dataColumnSidecar.js +4 -4
  107. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  108. package/lib/chain/validation/executionPayloadBid.js +4 -3
  109. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  110. package/lib/chain/validation/payloadAttestationMessage.js +1 -1
  111. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  112. package/lib/chain/validation/proposerSlashing.js +1 -1
  113. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  114. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts +2 -3
  115. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  116. package/lib/chain/validation/signatureSets/aggregateAndProof.js +8 -3
  117. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  118. package/lib/chain/validation/signatureSets/contributionAndProof.d.ts +2 -2
  119. package/lib/chain/validation/signatureSets/contributionAndProof.d.ts.map +1 -1
  120. package/lib/chain/validation/signatureSets/contributionAndProof.js +3 -3
  121. package/lib/chain/validation/signatureSets/contributionAndProof.js.map +1 -1
  122. package/lib/chain/validation/signatureSets/selectionProof.d.ts +2 -3
  123. package/lib/chain/validation/signatureSets/selectionProof.d.ts.map +1 -1
  124. package/lib/chain/validation/signatureSets/selectionProof.js +8 -3
  125. package/lib/chain/validation/signatureSets/selectionProof.js.map +1 -1
  126. package/lib/chain/validation/signatureSets/syncCommittee.d.ts +2 -2
  127. package/lib/chain/validation/signatureSets/syncCommittee.d.ts.map +1 -1
  128. package/lib/chain/validation/signatureSets/syncCommittee.js +3 -3
  129. package/lib/chain/validation/signatureSets/syncCommittee.js.map +1 -1
  130. package/lib/chain/validation/signatureSets/syncCommitteeContribution.d.ts +1 -2
  131. package/lib/chain/validation/signatureSets/syncCommitteeContribution.d.ts.map +1 -1
  132. package/lib/chain/validation/signatureSets/syncCommitteeContribution.js +2 -2
  133. package/lib/chain/validation/signatureSets/syncCommitteeContribution.js.map +1 -1
  134. package/lib/chain/validation/signatureSets/syncCommitteeSelectionProof.d.ts +2 -2
  135. package/lib/chain/validation/signatureSets/syncCommitteeSelectionProof.d.ts.map +1 -1
  136. package/lib/chain/validation/signatureSets/syncCommitteeSelectionProof.js +3 -3
  137. package/lib/chain/validation/signatureSets/syncCommitteeSelectionProof.js.map +1 -1
  138. package/lib/chain/validation/syncCommittee.js +1 -1
  139. package/lib/chain/validation/syncCommittee.js.map +1 -1
  140. package/lib/chain/validation/syncCommitteeContributionAndProof.d.ts.map +1 -1
  141. package/lib/chain/validation/syncCommitteeContributionAndProof.js +3 -5
  142. package/lib/chain/validation/syncCommitteeContributionAndProof.js.map +1 -1
  143. package/lib/chain/validation/voluntaryExit.js +1 -1
  144. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  145. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  146. package/lib/chain/validatorMonitor.js +7 -4
  147. package/lib/chain/validatorMonitor.js.map +1 -1
  148. package/lib/network/core/networkCore.d.ts +3 -0
  149. package/lib/network/core/networkCore.d.ts.map +1 -1
  150. package/lib/network/core/networkCore.js +9 -0
  151. package/lib/network/core/networkCore.js.map +1 -1
  152. package/lib/network/core/networkCoreWorker.js +3 -0
  153. package/lib/network/core/networkCoreWorker.js.map +1 -1
  154. package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -0
  155. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  156. package/lib/network/core/networkCoreWorkerHandler.js +9 -0
  157. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  158. package/lib/network/core/types.d.ts +3 -0
  159. package/lib/network/core/types.d.ts.map +1 -1
  160. package/lib/network/gossip/gossipsub.d.ts +34 -0
  161. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  162. package/lib/network/gossip/gossipsub.js +123 -0
  163. package/lib/network/gossip/gossipsub.js.map +1 -1
  164. package/lib/network/network.d.ts +3 -0
  165. package/lib/network/network.d.ts.map +1 -1
  166. package/lib/network/network.js +9 -0
  167. package/lib/network/network.js.map +1 -1
  168. package/lib/network/options.d.ts +6 -0
  169. package/lib/network/options.d.ts.map +1 -1
  170. package/lib/network/options.js.map +1 -1
  171. package/lib/network/peers/peerManager.d.ts.map +1 -1
  172. package/lib/network/peers/peerManager.js +9 -0
  173. package/lib/network/peers/peerManager.js.map +1 -1
  174. package/lib/network/processor/gossipHandlers.js +1 -1
  175. package/lib/network/processor/gossipHandlers.js.map +1 -1
  176. package/lib/sync/backfill/backfill.d.ts.map +1 -1
  177. package/lib/sync/backfill/backfill.js +3 -4
  178. package/lib/sync/backfill/backfill.js.map +1 -1
  179. package/lib/sync/backfill/verify.d.ts +1 -2
  180. package/lib/sync/backfill/verify.d.ts.map +1 -1
  181. package/lib/sync/backfill/verify.js +2 -2
  182. package/lib/sync/backfill/verify.js.map +1 -1
  183. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  184. package/lib/sync/utils/downloadByRange.js +2 -2
  185. package/lib/sync/utils/downloadByRange.js.map +1 -1
  186. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  187. package/lib/sync/utils/downloadByRoot.js +1 -2
  188. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  189. package/package.json +16 -16
  190. package/src/api/impl/beacon/blocks/index.ts +31 -19
  191. package/src/api/impl/lodestar/index.ts +29 -0
  192. package/src/api/impl/validator/index.ts +2 -1
  193. package/src/api/rest/base.ts +4 -4
  194. package/src/chain/ColumnReconstructionTracker.ts +8 -4
  195. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +2 -1
  196. package/src/chain/blocks/blockInput/blockInput.ts +47 -4
  197. package/src/chain/blocks/importBlock.ts +1 -1
  198. package/src/chain/blocks/verifyBlock.ts +0 -1
  199. package/src/chain/blocks/verifyBlocksSignatures.ts +4 -12
  200. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +1 -2
  201. package/src/chain/blocks/writeBlockInputToDb.ts +9 -0
  202. package/src/chain/bls/multithread/index.ts +7 -4
  203. package/src/chain/bls/multithread/jobItem.ts +7 -3
  204. package/src/chain/bls/singleThread.ts +5 -3
  205. package/src/chain/bls/utils.ts +15 -7
  206. package/src/chain/chain.ts +24 -35
  207. package/src/chain/initState.ts +2 -2
  208. package/src/chain/lightClient/index.ts +1 -2
  209. package/src/chain/options.ts +0 -2
  210. package/src/chain/regen/interface.ts +1 -1
  211. package/src/chain/regen/queued.ts +1 -2
  212. package/src/chain/regen/regen.ts +6 -1
  213. package/src/chain/seenCache/seenGossipBlockInput.ts +3 -3
  214. package/src/chain/serializeState.ts +2 -1
  215. package/src/chain/stateCache/index.ts +0 -2
  216. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  217. package/src/chain/validation/aggregateAndProof.ts +3 -4
  218. package/src/chain/validation/attestation.ts +9 -13
  219. package/src/chain/validation/attesterSlashing.ts +1 -6
  220. package/src/chain/validation/blobSidecar.ts +3 -8
  221. package/src/chain/validation/block.ts +2 -3
  222. package/src/chain/validation/dataColumnSidecar.ts +3 -8
  223. package/src/chain/validation/executionPayloadBid.ts +5 -4
  224. package/src/chain/validation/payloadAttestationMessage.ts +1 -1
  225. package/src/chain/validation/proposerSlashing.ts +1 -6
  226. package/src/chain/validation/signatureSets/aggregateAndProof.ts +9 -14
  227. package/src/chain/validation/signatureSets/contributionAndProof.ts +2 -4
  228. package/src/chain/validation/signatureSets/selectionProof.ts +9 -9
  229. package/src/chain/validation/signatureSets/syncCommittee.ts +2 -4
  230. package/src/chain/validation/signatureSets/syncCommitteeContribution.ts +2 -3
  231. package/src/chain/validation/signatureSets/syncCommitteeSelectionProof.ts +2 -4
  232. package/src/chain/validation/syncCommittee.ts +1 -1
  233. package/src/chain/validation/syncCommitteeContributionAndProof.ts +3 -5
  234. package/src/chain/validation/voluntaryExit.ts +1 -1
  235. package/src/chain/validatorMonitor.ts +10 -5
  236. package/src/network/core/networkCore.ts +12 -0
  237. package/src/network/core/networkCoreWorker.ts +3 -0
  238. package/src/network/core/networkCoreWorkerHandler.ts +9 -0
  239. package/src/network/core/types.ts +6 -0
  240. package/src/network/gossip/gossipsub.ts +147 -1
  241. package/src/network/network.ts +12 -0
  242. package/src/network/options.ts +6 -0
  243. package/src/network/peers/peerManager.ts +11 -0
  244. package/src/network/processor/gossipHandlers.ts +1 -1
  245. package/src/sync/backfill/backfill.ts +3 -4
  246. package/src/sync/backfill/verify.ts +2 -3
  247. package/src/sync/utils/downloadByRange.ts +2 -2
  248. package/src/sync/utils/downloadByRoot.ts +1 -2
  249. package/lib/chain/stateCache/blockStateCacheImpl.d.ts +0 -54
  250. package/lib/chain/stateCache/blockStateCacheImpl.d.ts.map +0 -1
  251. package/lib/chain/stateCache/blockStateCacheImpl.js +0 -130
  252. package/lib/chain/stateCache/blockStateCacheImpl.js.map +0 -1
  253. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts +0 -60
  254. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts.map +0 -1
  255. package/lib/chain/stateCache/inMemoryCheckpointsCache.js +0 -156
  256. package/lib/chain/stateCache/inMemoryCheckpointsCache.js.map +0 -1
  257. package/lib/util/bytes.d.ts +0 -3
  258. package/lib/util/bytes.d.ts.map +0 -1
  259. package/lib/util/bytes.js +0 -11
  260. package/lib/util/bytes.js.map +0 -1
  261. package/src/chain/stateCache/blockStateCacheImpl.ts +0 -149
  262. package/src/chain/stateCache/inMemoryCheckpointsCache.ts +0 -192
  263. package/src/util/bytes.ts +0 -11
@@ -138,11 +138,10 @@ export async function validateGossipBlock(
138
138
  // in forky condition, make sure to populate ShufflingCache with regened state
139
139
  chain.shufflingCache.processState(blockState);
140
140
 
141
- // Extra conditions for merge fork blocks
142
141
  // [REJECT] The block's execution payload timestamp is correct with respect to the slot
143
142
  // -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot).
144
143
  if (isForkPostBellatrix(fork) && !isForkPostGloas(fork)) {
145
- if (!isExecutionBlockBodyType(block.body)) throw Error("Not merge block type");
144
+ if (!isExecutionBlockBodyType(block.body)) throw Error("Not execution block body type");
146
145
  const executionPayload = block.body.executionPayload;
147
146
  if (isExecutionStateType(blockState) && isExecutionEnabled(blockState, block)) {
148
147
  const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime);
@@ -158,7 +157,7 @@ export async function validateGossipBlock(
158
157
 
159
158
  // [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
160
159
  if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockSlot, blockRoot, signedBlock.signature)) {
161
- const signatureSet = getBlockProposerSignatureSet(chain.config, chain.index2pubkey, signedBlock);
160
+ const signatureSet = getBlockProposerSignatureSet(chain.config, signedBlock);
162
161
  // Don't batch so verification is not delayed
163
162
  if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) {
164
163
  throw new BlockGossipError(GossipAction.REJECT, {
@@ -11,7 +11,7 @@ import {
11
11
  getBlockHeaderProposerSignatureSetByParentStateSlot,
12
12
  } from "@lodestar/state-transition";
13
13
  import {Root, Slot, SubnetID, fulu, ssz} from "@lodestar/types";
14
- import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
14
+ import {byteArrayEquals, toRootHex, verifyMerkleBranch} from "@lodestar/utils";
15
15
  import {Metrics} from "../../metrics/metrics.js";
16
16
  import {kzg} from "../../util/kzg.js";
17
17
  import {
@@ -136,7 +136,6 @@ export async function validateGossipDataColumnSidecar(
136
136
  if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockHeader.slot, blockRootHex, signature)) {
137
137
  const signatureSet = getBlockHeaderProposerSignatureSetByParentStateSlot(
138
138
  chain.config,
139
- chain.index2pubkey,
140
139
  blockState.slot,
141
140
  dataColumnSidecar.signedBlockHeader
142
141
  );
@@ -319,7 +318,7 @@ export async function validateBlockDataColumnSidecars(
319
318
  const firstSidecarSignedBlockHeader = dataColumnSidecars[0].signedBlockHeader;
320
319
  const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
321
320
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
322
- if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
321
+ if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
323
322
  throw new DataColumnSidecarValidationError(
324
323
  {
325
324
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
@@ -337,11 +336,7 @@ export async function validateBlockDataColumnSidecars(
337
336
  const slot = firstSidecarSignedBlockHeader.message.slot;
338
337
  const signature = firstSidecarSignedBlockHeader.signature;
339
338
  if (!chain.seenBlockInputCache.isVerifiedProposerSignature(slot, rootHex, signature)) {
340
- const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(
341
- chain.config,
342
- chain.index2pubkey,
343
- firstSidecarSignedBlockHeader
344
- );
339
+ const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(chain.config, firstSidecarSignedBlockHeader);
345
340
 
346
341
  if (
347
342
  !(await chain.bls.verifySignatureSets([signatureSet], {
@@ -53,7 +53,8 @@ async function validateExecutionPayloadBid(
53
53
 
54
54
  // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
55
55
  // `is_active_builder(state, bid.builder_index)` returns `True`.
56
- if (!isActiveBuilder(state, bid.builderIndex)) {
56
+ const builder = state.builders.getReadonly(bid.builderIndex);
57
+ if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
57
58
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
58
59
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
59
60
  builderIndex: bid.builderIndex,
@@ -102,7 +103,7 @@ async function validateExecutionPayloadBid(
102
103
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
103
104
  code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH,
104
105
  bidValue: bid.value,
105
- builderBalance: state.builders.getReadonly(bid.builderIndex).balance,
106
+ builderBalance: builder.balance,
106
107
  });
107
108
  }
108
109
 
@@ -122,8 +123,8 @@ async function validateExecutionPayloadBid(
122
123
 
123
124
  // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
124
125
  const signatureSet = createSingleSignatureSetFromComponents(
125
- PublicKey.fromBytes(state.builders.getReadonly(bid.builderIndex).pubkey),
126
- getExecutionPayloadBidSigningRoot(chain.config, state as CachedBeaconStateGloas, bid),
126
+ PublicKey.fromBytes(builder.pubkey),
127
+ getExecutionPayloadBidSigningRoot(chain.config, state.slot, bid),
127
128
  signedExecutionPayloadBid.signature
128
129
  );
129
130
 
@@ -89,7 +89,7 @@ async function validatePayloadAttestationMessage(
89
89
  // [REJECT] `payload_attestation_message.signature` is valid with respect to the validator's public key.
90
90
  const signatureSet = createSingleSignatureSetFromComponents(
91
91
  chain.index2pubkey[validatorIndex],
92
- getPayloadAttestationDataSigningRoot(state, data),
92
+ getPayloadAttestationDataSigningRoot(chain.config, state.slot, data),
93
93
  payloadAttestationMessage.signature
94
94
  );
95
95
 
@@ -45,12 +45,7 @@ async function validateProposerSlashing(
45
45
  });
46
46
  }
47
47
 
48
- const signatureSets = getProposerSlashingSignatureSets(
49
- chain.config,
50
- chain.index2pubkey,
51
- state.slot,
52
- proposerSlashing
53
- );
48
+ const signatureSets = getProposerSlashingSignatureSets(chain.config, state.slot, proposerSlashing);
54
49
  if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true, priority: prioritizeBls}))) {
55
50
  throw new ProposerSlashingError(GossipAction.REJECT, {
56
51
  code: ProposerSlashingErrorCode.INVALID,
@@ -1,13 +1,7 @@
1
- import {PublicKey} from "@chainsafe/blst";
2
1
  import {BeaconConfig} from "@lodestar/config";
3
2
  import {DOMAIN_AGGREGATE_AND_PROOF, ForkSeq} from "@lodestar/params";
4
- import {
5
- ISignatureSet,
6
- computeSigningRoot,
7
- computeStartSlotAtEpoch,
8
- createSingleSignatureSetFromComponents,
9
- } from "@lodestar/state-transition";
10
- import {Epoch, SignedAggregateAndProof, ssz} from "@lodestar/types";
3
+ import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
+ import {Epoch, SignedAggregateAndProof, ValidatorIndex, ssz} from "@lodestar/types";
11
5
 
12
6
  export function getAggregateAndProofSigningRoot(
13
7
  config: BeaconConfig,
@@ -27,12 +21,13 @@ export function getAggregateAndProofSigningRoot(
27
21
  export function getAggregateAndProofSignatureSet(
28
22
  config: BeaconConfig,
29
23
  epoch: Epoch,
30
- aggregator: PublicKey,
24
+ aggregatorIndex: ValidatorIndex,
31
25
  aggregateAndProof: SignedAggregateAndProof
32
26
  ): ISignatureSet {
33
- return createSingleSignatureSetFromComponents(
34
- aggregator,
35
- getAggregateAndProofSigningRoot(config, epoch, aggregateAndProof),
36
- aggregateAndProof.signature
37
- );
27
+ return {
28
+ type: SignatureSetType.indexed,
29
+ index: aggregatorIndex,
30
+ signingRoot: getAggregateAndProofSigningRoot(config, epoch, aggregateAndProof),
31
+ signature: aggregateAndProof.signature,
32
+ };
38
33
  }
@@ -3,7 +3,6 @@ import {DOMAIN_CONTRIBUTION_AND_PROOF} from "@lodestar/params";
3
3
  import {
4
4
  CachedBeaconStateAllForks,
5
5
  ISignatureSet,
6
- Index2PubkeyCache,
7
6
  SignatureSetType,
8
7
  computeSigningRoot,
9
8
  } from "@lodestar/state-transition";
@@ -11,7 +10,6 @@ import {altair, ssz} from "@lodestar/types";
11
10
 
12
11
  export function getContributionAndProofSignatureSet(
13
12
  config: BeaconConfig,
14
- index2pubkey: Index2PubkeyCache,
15
13
  state: CachedBeaconStateAllForks,
16
14
  signedContributionAndProof: altair.SignedContributionAndProof
17
15
  ): ISignatureSet {
@@ -22,8 +20,8 @@ export function getContributionAndProofSignatureSet(
22
20
  );
23
21
  const signingData = signedContributionAndProof.message;
24
22
  return {
25
- type: SignatureSetType.single,
26
- pubkey: index2pubkey[signedContributionAndProof.message.aggregatorIndex],
23
+ type: SignatureSetType.indexed,
24
+ index: signedContributionAndProof.message.aggregatorIndex,
27
25
  signingRoot: computeSigningRoot(ssz.altair.ContributionAndProof, signingData, domain),
28
26
  signature: signedContributionAndProof.signature,
29
27
  };
@@ -1,8 +1,7 @@
1
- import {PublicKey} from "@chainsafe/blst";
2
1
  import {BeaconConfig} from "@lodestar/config";
3
2
  import {DOMAIN_SELECTION_PROOF} from "@lodestar/params";
4
- import {ISignatureSet, computeSigningRoot, createSingleSignatureSetFromComponents} from "@lodestar/state-transition";
5
- import {Slot, phase0, ssz} from "@lodestar/types";
3
+ import {ISignatureSet, SignatureSetType, computeSigningRoot} from "@lodestar/state-transition";
4
+ import {Slot, ValidatorIndex, phase0, ssz} from "@lodestar/types";
6
5
 
7
6
  export function getSelectionProofSigningRoot(config: BeaconConfig, slot: Slot): Uint8Array {
8
7
  // previously, we call `const selectionProofDomain = config.getDomain(state.slot, DOMAIN_SELECTION_PROOF, slot)`
@@ -16,12 +15,13 @@ export function getSelectionProofSigningRoot(config: BeaconConfig, slot: Slot):
16
15
  export function getSelectionProofSignatureSet(
17
16
  config: BeaconConfig,
18
17
  slot: Slot,
19
- aggregator: PublicKey,
18
+ aggregatorIndex: ValidatorIndex,
20
19
  aggregateAndProof: phase0.SignedAggregateAndProof
21
20
  ): ISignatureSet {
22
- return createSingleSignatureSetFromComponents(
23
- aggregator,
24
- getSelectionProofSigningRoot(config, slot),
25
- aggregateAndProof.message.selectionProof
26
- );
21
+ return {
22
+ type: SignatureSetType.indexed,
23
+ index: aggregatorIndex,
24
+ signingRoot: getSelectionProofSigningRoot(config, slot),
25
+ signature: aggregateAndProof.message.selectionProof,
26
+ };
27
27
  }
@@ -3,7 +3,6 @@ import {DOMAIN_SYNC_COMMITTEE} from "@lodestar/params";
3
3
  import {
4
4
  CachedBeaconStateAllForks,
5
5
  ISignatureSet,
6
- Index2PubkeyCache,
7
6
  SignatureSetType,
8
7
  computeSigningRoot,
9
8
  } from "@lodestar/state-transition";
@@ -11,15 +10,14 @@ import {altair, ssz} from "@lodestar/types";
11
10
 
12
11
  export function getSyncCommitteeSignatureSet(
13
12
  config: BeaconConfig,
14
- index2pubkey: Index2PubkeyCache,
15
13
  state: CachedBeaconStateAllForks,
16
14
  syncCommittee: altair.SyncCommitteeMessage
17
15
  ): ISignatureSet {
18
16
  const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, syncCommittee.slot);
19
17
 
20
18
  return {
21
- type: SignatureSetType.single,
22
- pubkey: index2pubkey[syncCommittee.validatorIndex],
19
+ type: SignatureSetType.indexed,
20
+ index: syncCommittee.validatorIndex,
23
21
  signingRoot: computeSigningRoot(ssz.Root, syncCommittee.beaconBlockRoot, domain),
24
22
  signature: syncCommittee.signature,
25
23
  };
@@ -1,4 +1,3 @@
1
- import {PublicKey} from "@chainsafe/blst";
2
1
  import {BeaconConfig} from "@lodestar/config";
3
2
  import {DOMAIN_SYNC_COMMITTEE} from "@lodestar/params";
4
3
  import {CachedBeaconStateAltair, ISignatureSet, SignatureSetType, computeSigningRoot} from "@lodestar/state-transition";
@@ -8,12 +7,12 @@ export function getSyncCommitteeContributionSignatureSet(
8
7
  config: BeaconConfig,
9
8
  state: CachedBeaconStateAltair,
10
9
  contribution: altair.SyncCommitteeContribution,
11
- pubkeys: PublicKey[]
10
+ participantIndices: number[]
12
11
  ): ISignatureSet {
13
12
  const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, contribution.slot);
14
13
  return {
15
14
  type: SignatureSetType.aggregate,
16
- pubkeys,
15
+ indices: participantIndices,
17
16
  signingRoot: computeSigningRoot(ssz.Root, contribution.beaconBlockRoot, domain),
18
17
  signature: contribution.signature,
19
18
  };
@@ -3,7 +3,6 @@ import {DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF} from "@lodestar/params";
3
3
  import {
4
4
  CachedBeaconStateAllForks,
5
5
  ISignatureSet,
6
- Index2PubkeyCache,
7
6
  SignatureSetType,
8
7
  computeSigningRoot,
9
8
  } from "@lodestar/state-transition";
@@ -11,7 +10,6 @@ import {altair, ssz} from "@lodestar/types";
11
10
 
12
11
  export function getSyncCommitteeSelectionProofSignatureSet(
13
12
  config: BeaconConfig,
14
- index2pubkey: Index2PubkeyCache,
15
13
  state: CachedBeaconStateAllForks,
16
14
  contributionAndProof: altair.ContributionAndProof
17
15
  ): ISignatureSet {
@@ -22,8 +20,8 @@ export function getSyncCommitteeSelectionProofSignatureSet(
22
20
  subcommitteeIndex: contributionAndProof.contribution.subcommitteeIndex,
23
21
  };
24
22
  return {
25
- type: SignatureSetType.single,
26
- pubkey: index2pubkey[contributionAndProof.aggregatorIndex],
23
+ type: SignatureSetType.indexed,
24
+ index: contributionAndProof.aggregatorIndex,
27
25
  signingRoot: computeSigningRoot(ssz.altair.SyncAggregatorSelectionData, signingData, domain),
28
26
  signature: contributionAndProof.selectionProof,
29
27
  };
@@ -89,7 +89,7 @@ async function validateSyncCommitteeSigOnly(
89
89
  syncCommittee: altair.SyncCommitteeMessage,
90
90
  prioritizeBls = false
91
91
  ): Promise<void> {
92
- const signatureSet = getSyncCommitteeSignatureSet(chain.config, chain.index2pubkey, headState, syncCommittee);
92
+ const signatureSet = getSyncCommitteeSignatureSet(chain.config, headState, syncCommittee);
93
93
  if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
94
94
  throw new SyncCommitteeError(GossipAction.REJECT, {
95
95
  code: SyncCommitteeErrorCode.INVALID_SIGNATURE,
@@ -21,7 +21,6 @@ export async function validateSyncCommitteeGossipContributionAndProof(
21
21
  const contributionAndProof = signedContributionAndProof.message;
22
22
  const {contribution, aggregatorIndex} = contributionAndProof;
23
23
  const {subcommitteeIndex, slot} = contribution;
24
- const {index2pubkey} = chain;
25
24
 
26
25
  const headState = chain.getHeadState();
27
26
  validateGossipSyncCommitteeExceptSig(chain, headState, subcommitteeIndex, {
@@ -74,14 +73,13 @@ export async function validateSyncCommitteeGossipContributionAndProof(
74
73
  // i.e. state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index).
75
74
  // > Checked in validateGossipSyncCommitteeExceptSig()
76
75
 
77
- const participantPubkeys = syncCommitteeParticipantIndices.map((validatorIndex) => index2pubkey[validatorIndex]);
78
76
  const signatureSets = [
79
77
  // [REJECT] The contribution_and_proof.selection_proof is a valid signature of the SyncAggregatorSelectionData
80
78
  // derived from the contribution by the validator with index contribution_and_proof.aggregator_index.
81
- getSyncCommitteeSelectionProofSignatureSet(chain.config, index2pubkey, headState, contributionAndProof),
79
+ getSyncCommitteeSelectionProofSignatureSet(chain.config, headState, contributionAndProof),
82
80
 
83
81
  // [REJECT] The aggregator signature, signed_contribution_and_proof.signature, is valid.
84
- getContributionAndProofSignatureSet(chain.config, index2pubkey, headState, signedContributionAndProof),
82
+ getContributionAndProofSignatureSet(chain.config, headState, signedContributionAndProof),
85
83
 
86
84
  // [REJECT] The aggregate signature is valid for the message beacon_block_root and aggregate pubkey derived from
87
85
  // the participation info in aggregation_bits for the subcommittee specified by the contribution.subcommittee_index.
@@ -89,7 +87,7 @@ export async function validateSyncCommitteeGossipContributionAndProof(
89
87
  chain.config,
90
88
  headState as CachedBeaconStateAltair,
91
89
  contribution,
92
- participantPubkeys
90
+ syncCommitteeParticipantIndices
93
91
  ),
94
92
  ];
95
93
 
@@ -59,7 +59,7 @@ async function validateVoluntaryExit(
59
59
  });
60
60
  }
61
61
 
62
- const signatureSet = getVoluntaryExitSignatureSet(chain.config, chain.index2pubkey, state.slot, voluntaryExit);
62
+ const signatureSet = getVoluntaryExitSignatureSet(chain.config, state.slot, voluntaryExit);
63
63
  if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
64
64
  throw new VoluntaryExitError(GossipAction.REJECT, {
65
65
  code: VoluntaryExitErrorCode.INVALID_SIGNATURE,
@@ -347,6 +347,9 @@ export function createValidatorMonitor(
347
347
  return;
348
348
  }
349
349
 
350
+ // Track total balance instead of per-validator balance to reduce metric cardinality
351
+ let totalBalance = 0;
352
+
350
353
  for (const [index, monitoredValidator] of validators.entries()) {
351
354
  // We subtract two from the state of the epoch that generated these summaries.
352
355
  //
@@ -405,7 +408,7 @@ export function createValidatorMonitor(
405
408
 
406
409
  const balance = balances?.[index];
407
410
  if (balance !== undefined) {
408
- validatorMonitorMetrics?.prevEpochOnChainBalance.set({index}, balance);
411
+ totalBalance += balance;
409
412
  }
410
413
 
411
414
  if (!summary.isPrevSourceAttester || !summary.isPrevTargetAttester || !summary.isPrevHeadAttester) {
@@ -420,6 +423,10 @@ export function createValidatorMonitor(
420
423
  });
421
424
  }
422
425
  }
426
+
427
+ if (balances !== undefined) {
428
+ validatorMonitorMetrics?.prevEpochOnChainBalance.set(totalBalance);
429
+ }
423
430
  },
424
431
 
425
432
  registerBeaconBlock(src, delaySec, block) {
@@ -1153,11 +1160,9 @@ function createValidatorMonitorMetrics(register: RegistryMetricCreator) {
1153
1160
  }),
1154
1161
 
1155
1162
  // Validator Monitor Metrics (per-epoch summaries)
1156
- // Only track prevEpochOnChainBalance per index
1157
- prevEpochOnChainBalance: register.gauge<{index: number}>({
1163
+ prevEpochOnChainBalance: register.gauge({
1158
1164
  name: "validator_monitor_prev_epoch_on_chain_balance",
1159
- help: "Balance of validator after an epoch",
1160
- labelNames: ["index"],
1165
+ help: "Total balance of all monitored validators after an epoch",
1161
1166
  }),
1162
1167
  prevEpochOnChainAttesterHit: register.gauge({
1163
1168
  name: "validator_monitor_prev_epoch_on_chain_attester_hit_total",
@@ -454,6 +454,18 @@ export class NetworkCore implements INetworkCore {
454
454
  await this.libp2p.hangUp(peerIdFromString(peerIdStr));
455
455
  }
456
456
 
457
+ async addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
458
+ return this.gossip.addDirectPeer(peer);
459
+ }
460
+
461
+ async removeDirectPeer(peerIdStr: PeerIdStr): Promise<boolean> {
462
+ return this.gossip.removeDirectPeer(peerIdStr);
463
+ }
464
+
465
+ async getDirectPeers(): Promise<string[]> {
466
+ return this.gossip.getDirectPeers();
467
+ }
468
+
457
469
  private _dumpPeer(peerIdStr: string, connections: Connection[]): routes.lodestar.LodestarNodePeer {
458
470
  const peerData = this.peersData.connectedPeers.get(peerIdStr);
459
471
  const fork = this.config.getForkName(this.clock.currentSlot);
@@ -153,6 +153,9 @@ const libp2pWorkerApi: NetworkWorkerApi = {
153
153
  getConnectedPeerCount: () => core.getConnectedPeerCount(),
154
154
  connectToPeer: (peer, multiaddr) => core.connectToPeer(peer, multiaddr),
155
155
  disconnectPeer: (peer) => core.disconnectPeer(peer),
156
+ addDirectPeer: (peer) => core.addDirectPeer(peer),
157
+ removeDirectPeer: (peerId) => core.removeDirectPeer(peerId),
158
+ getDirectPeers: () => core.getDirectPeers(),
156
159
  dumpPeers: () => core.dumpPeers(),
157
160
  dumpPeer: (peerIdStr) => core.dumpPeer(peerIdStr),
158
161
  dumpPeerScoreStats: () => core.dumpPeerScoreStats(),
@@ -247,6 +247,15 @@ export class WorkerNetworkCore implements INetworkCore {
247
247
  disconnectPeer(peer: PeerIdStr): Promise<void> {
248
248
  return this.getApi().disconnectPeer(peer);
249
249
  }
250
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
251
+ return this.getApi().addDirectPeer(peer);
252
+ }
253
+ removeDirectPeer(peerId: PeerIdStr): Promise<boolean> {
254
+ return this.getApi().removeDirectPeer(peerId);
255
+ }
256
+ getDirectPeers(): Promise<string[]> {
257
+ return this.getApi().getDirectPeers();
258
+ }
250
259
  dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]> {
251
260
  return this.getApi().dumpPeers();
252
261
  }
@@ -30,6 +30,12 @@ export interface INetworkCorePublic {
30
30
  // Debug
31
31
  connectToPeer(peer: PeerIdStr, multiaddr: MultiaddrStr[]): Promise<void>;
32
32
  disconnectPeer(peer: PeerIdStr): Promise<void>;
33
+
34
+ // Direct peers management
35
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null>;
36
+ removeDirectPeer(peerId: PeerIdStr): Promise<boolean>;
37
+ getDirectPeers(): Promise<string[]>;
38
+
33
39
  dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]>;
34
40
  dumpPeer(peerIdStr: PeerIdStr): Promise<routes.lodestar.LodestarNodePeer | undefined>;
35
41
  dumpPeerScoreStats(): Promise<PeerScoreStats>;
@@ -1,7 +1,11 @@
1
+ import {peerIdFromString} from "@libp2p/peer-id";
2
+ import {multiaddr} from "@multiformats/multiaddr";
3
+ import {ENR} from "@chainsafe/enr";
1
4
  import {GossipSub, GossipsubEvents} from "@chainsafe/libp2p-gossipsub";
2
5
  import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-gossipsub/metrics";
3
6
  import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
4
- import {SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
7
+ import {AddrInfo, SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
8
+ import {routes} from "@lodestar/api";
5
9
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
6
10
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
7
11
  import {SubnetID} from "@lodestar/types";
@@ -55,6 +59,12 @@ export type Eth2GossipsubOpts = {
55
59
  disableFloodPublish?: boolean;
56
60
  skipParamsLog?: boolean;
57
61
  disableLightClientServer?: boolean;
62
+ /**
63
+ * Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
64
+ * Supports multiaddr strings with peer ID (e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...")
65
+ * or ENR strings (e.g., "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...")
66
+ */
67
+ directPeers?: string[];
58
68
  };
59
69
 
60
70
  export type ForkBoundaryLabel = string;
@@ -78,6 +88,7 @@ export class Eth2Gossipsub extends GossipSub {
78
88
  private readonly logger: Logger;
79
89
  private readonly peersData: PeersData;
80
90
  private readonly events: NetworkEventBus;
91
+ private readonly libp2p: Libp2p;
81
92
 
82
93
  // Internal caches
83
94
  private readonly gossipTopicCache: GossipTopicCache;
@@ -97,6 +108,9 @@ export class Eth2Gossipsub extends GossipSub {
97
108
  );
98
109
  }
99
110
 
111
+ // Parse direct peers from multiaddr strings to AddrInfo objects
112
+ const directPeers = parseDirectPeers(opts.directPeers ?? [], logger);
113
+
100
114
  // Gossipsub parameters defined here:
101
115
  // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
102
116
  super(modules.libp2p.services.components, {
@@ -106,6 +120,7 @@ export class Eth2Gossipsub extends GossipSub {
106
120
  Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
107
121
  Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
108
122
  Dlazy: 6,
123
+ directPeers,
109
124
  heartbeatInterval: GOSSIPSUB_HEARTBEAT_INTERVAL,
110
125
  fanoutTTL: 60 * 1000,
111
126
  mcacheLength: 6,
@@ -146,6 +161,7 @@ export class Eth2Gossipsub extends GossipSub {
146
161
  this.logger = logger;
147
162
  this.peersData = peersData;
148
163
  this.events = events;
164
+ this.libp2p = modules.libp2p;
149
165
  this.gossipTopicCache = gossipTopicCache;
150
166
 
151
167
  this.addEventListener("gossipsub:message", this.onGossipsubMessage.bind(this));
@@ -328,6 +344,64 @@ export class Eth2Gossipsub extends GossipSub {
328
344
  this.reportMessageValidationResult(data.msgId, data.propagationSource, data.acceptance);
329
345
  });
330
346
  }
347
+
348
+ /**
349
+ * Add a peer as a direct peer at runtime. Accepts multiaddr with peer ID or ENR string.
350
+ * Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
351
+ */
352
+ async addDirectPeer(peerStr: routes.lodestar.DirectPeer): Promise<string | null> {
353
+ const parsed = parseDirectPeers([peerStr], this.logger);
354
+ if (parsed.length === 0) {
355
+ return null;
356
+ }
357
+
358
+ const {id: peerId, addrs} = parsed[0];
359
+ const peerIdStr = peerId.toString();
360
+
361
+ // Prevent adding self as a direct peer
362
+ if (peerId.equals(this.libp2p.peerId)) {
363
+ this.logger.warn("Cannot add self as a direct peer", {peerId: peerIdStr});
364
+ return null;
365
+ }
366
+
367
+ // Direct peers need addresses to connect - reject if none provided
368
+ if (addrs.length === 0) {
369
+ this.logger.warn("Cannot add direct peer without addresses", {peerId: peerIdStr});
370
+ return null;
371
+ }
372
+
373
+ // Add addresses to peer store first so we can connect
374
+ try {
375
+ await this.libp2p.peerStore.merge(peerId, {multiaddrs: addrs});
376
+ } catch (e) {
377
+ this.logger.warn("Failed to add direct peer addresses to peer store", {peerId: peerIdStr}, e as Error);
378
+ return null;
379
+ }
380
+
381
+ // Add to direct peers set only after addresses are stored
382
+ this.direct.add(peerIdStr);
383
+
384
+ this.logger.info("Added direct peer via API", {peerId: peerIdStr});
385
+ return peerIdStr;
386
+ }
387
+
388
+ /**
389
+ * Remove a peer from direct peers.
390
+ */
391
+ removeDirectPeer(peerIdStr: string): boolean {
392
+ const removed = this.direct.delete(peerIdStr);
393
+ if (removed) {
394
+ this.logger.info("Removed direct peer via API", {peerId: peerIdStr});
395
+ }
396
+ return removed;
397
+ }
398
+
399
+ /**
400
+ * Get list of current direct peer IDs.
401
+ */
402
+ getDirectPeers(): string[] {
403
+ return Array.from(this.direct);
404
+ }
331
405
  }
332
406
 
333
407
  /**
@@ -381,3 +455,75 @@ function getForkBoundaryLabel(boundary: ForkBoundary): ForkBoundaryLabel {
381
455
 
382
456
  return label;
383
457
  }
458
+
459
+ /**
460
+ * Parse direct peer strings into AddrInfo objects for GossipSub.
461
+ * Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
462
+ *
463
+ * Supported formats:
464
+ * - Multiaddr with peer ID: `/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...`
465
+ * - ENR: `enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...`
466
+ *
467
+ * For multiaddrs, the string must contain a /p2p/ component with the peer ID.
468
+ * For ENRs, the TCP multiaddr and peer ID are extracted from the encoded record.
469
+ */
470
+ export function parseDirectPeers(directPeerStrs: routes.lodestar.DirectPeer[], logger: Logger): AddrInfo[] {
471
+ const directPeers: AddrInfo[] = [];
472
+
473
+ for (const peerStr of directPeerStrs) {
474
+ // Check if this is an ENR (starts with "enr:")
475
+ if (peerStr.startsWith("enr:")) {
476
+ try {
477
+ const enr = ENR.decodeTxt(peerStr);
478
+ const peerId = enr.peerId;
479
+
480
+ // Get TCP multiaddr from ENR
481
+ const multiaddrTCP = enr.getLocationMultiaddr("tcp");
482
+ if (!multiaddrTCP) {
483
+ logger.warn("ENR does not contain TCP multiaddr", {enr: peerStr});
484
+ continue;
485
+ }
486
+
487
+ directPeers.push({
488
+ id: peerId,
489
+ addrs: [multiaddrTCP],
490
+ });
491
+
492
+ logger.info("Added direct peer from ENR", {peerId: peerId.toString(), addr: multiaddrTCP.toString()});
493
+ } catch (e) {
494
+ logger.warn("Failed to parse direct peer ENR", {enr: peerStr}, e as Error);
495
+ }
496
+ } else {
497
+ // Parse as multiaddr
498
+ try {
499
+ const ma = multiaddr(peerStr);
500
+
501
+ const peerIdStr = ma.getPeerId();
502
+ if (!peerIdStr) {
503
+ logger.warn("Direct peer multiaddr must contain /p2p/ component with peer ID", {multiaddr: peerStr});
504
+ continue;
505
+ }
506
+
507
+ try {
508
+ const peerId = peerIdFromString(peerIdStr);
509
+
510
+ // Get the address without the /p2p/ component
511
+ const addr = ma.decapsulate("/p2p/" + peerIdStr);
512
+
513
+ directPeers.push({
514
+ id: peerId,
515
+ addrs: [addr],
516
+ });
517
+
518
+ logger.info("Added direct peer", {peerId: peerIdStr, addr: addr.toString()});
519
+ } catch (e) {
520
+ logger.warn("Invalid peer ID in direct peer multiaddr", {multiaddr: peerStr, peerId: peerIdStr}, e as Error);
521
+ }
522
+ } catch (e) {
523
+ logger.warn("Failed to parse direct peer multiaddr", {multiaddr: peerStr}, e as Error);
524
+ }
525
+ }
526
+ }
527
+
528
+ return directPeers;
529
+ }
@@ -641,6 +641,18 @@ export class Network implements INetwork {
641
641
  return this.core.disconnectPeer(peer);
642
642
  }
643
643
 
644
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
645
+ return this.core.addDirectPeer(peer);
646
+ }
647
+
648
+ removeDirectPeer(peerId: string): Promise<boolean> {
649
+ return this.core.removeDirectPeer(peerId);
650
+ }
651
+
652
+ getDirectPeers(): Promise<string[]> {
653
+ return this.core.getDirectPeers();
654
+ }
655
+
644
656
  dumpPeer(peerIdStr: string): Promise<routes.lodestar.LodestarNodePeer | undefined> {
645
657
  return this.core.dumpPeer(peerIdStr);
646
658
  }