@lodestar/beacon-node 1.40.0-dev.f016f9d123 → 1.40.0-dev.f58ab9b042

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 (157) 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 +14 -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 +12 -10
  11. package/lib/api/rest/base.js.map +1 -1
  12. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  13. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +2 -1
  14. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.d.ts +28 -0
  16. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  17. package/lib/chain/blocks/blockInput/blockInput.js +38 -3
  18. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  19. package/lib/chain/blocks/importBlock.js +1 -1
  20. package/lib/chain/blocks/importBlock.js.map +1 -1
  21. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  22. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +1 -2
  23. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  24. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  25. package/lib/chain/blocks/writeBlockInputToDb.js +8 -0
  26. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  27. package/lib/chain/chain.d.ts +1 -1
  28. package/lib/chain/chain.d.ts.map +1 -1
  29. package/lib/chain/chain.js +17 -29
  30. package/lib/chain/chain.js.map +1 -1
  31. package/lib/chain/initState.d.ts.map +1 -1
  32. package/lib/chain/initState.js +2 -2
  33. package/lib/chain/initState.js.map +1 -1
  34. package/lib/chain/lightClient/index.d.ts.map +1 -1
  35. package/lib/chain/lightClient/index.js +1 -2
  36. package/lib/chain/lightClient/index.js.map +1 -1
  37. package/lib/chain/options.d.ts +0 -1
  38. package/lib/chain/options.d.ts.map +1 -1
  39. package/lib/chain/options.js +0 -1
  40. package/lib/chain/options.js.map +1 -1
  41. package/lib/chain/regen/interface.d.ts +1 -1
  42. package/lib/chain/regen/queued.d.ts +1 -1
  43. package/lib/chain/regen/queued.d.ts.map +1 -1
  44. package/lib/chain/regen/queued.js.map +1 -1
  45. package/lib/chain/regen/regen.d.ts +2 -0
  46. package/lib/chain/regen/regen.d.ts.map +1 -1
  47. package/lib/chain/regen/regen.js +4 -1
  48. package/lib/chain/regen/regen.js.map +1 -1
  49. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  50. package/lib/chain/seenCache/seenGossipBlockInput.js +3 -3
  51. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  52. package/lib/chain/serializeState.d.ts.map +1 -1
  53. package/lib/chain/serializeState.js +2 -1
  54. package/lib/chain/serializeState.js.map +1 -1
  55. package/lib/chain/stateCache/index.d.ts +0 -2
  56. package/lib/chain/stateCache/index.d.ts.map +1 -1
  57. package/lib/chain/stateCache/index.js +0 -2
  58. package/lib/chain/stateCache/index.js.map +1 -1
  59. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +2 -1
  60. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  61. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -0
  62. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  63. package/lib/chain/validation/blobSidecar.js +2 -2
  64. package/lib/chain/validation/blobSidecar.js.map +1 -1
  65. package/lib/chain/validation/block.d.ts.map +1 -1
  66. package/lib/chain/validation/block.js +1 -2
  67. package/lib/chain/validation/block.js.map +1 -1
  68. package/lib/chain/validation/dataColumnSidecar.js +2 -2
  69. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  70. package/lib/network/core/networkCore.d.ts +3 -0
  71. package/lib/network/core/networkCore.d.ts.map +1 -1
  72. package/lib/network/core/networkCore.js +9 -0
  73. package/lib/network/core/networkCore.js.map +1 -1
  74. package/lib/network/core/networkCoreWorker.js +3 -0
  75. package/lib/network/core/networkCoreWorker.js.map +1 -1
  76. package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -0
  77. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  78. package/lib/network/core/networkCoreWorkerHandler.js +9 -0
  79. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  80. package/lib/network/core/types.d.ts +3 -0
  81. package/lib/network/core/types.d.ts.map +1 -1
  82. package/lib/network/gossip/gossipsub.d.ts +34 -0
  83. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  84. package/lib/network/gossip/gossipsub.js +123 -0
  85. package/lib/network/gossip/gossipsub.js.map +1 -1
  86. package/lib/network/network.d.ts +3 -0
  87. package/lib/network/network.d.ts.map +1 -1
  88. package/lib/network/network.js +9 -0
  89. package/lib/network/network.js.map +1 -1
  90. package/lib/network/options.d.ts +6 -0
  91. package/lib/network/options.d.ts.map +1 -1
  92. package/lib/network/options.js.map +1 -1
  93. package/lib/network/peers/peerManager.d.ts.map +1 -1
  94. package/lib/network/peers/peerManager.js +9 -0
  95. package/lib/network/peers/peerManager.js.map +1 -1
  96. package/lib/network/processor/gossipHandlers.js +1 -1
  97. package/lib/network/processor/gossipHandlers.js.map +1 -1
  98. package/lib/sync/backfill/backfill.d.ts.map +1 -1
  99. package/lib/sync/backfill/backfill.js +1 -2
  100. package/lib/sync/backfill/backfill.js.map +1 -1
  101. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  102. package/lib/sync/utils/downloadByRange.js +2 -2
  103. package/lib/sync/utils/downloadByRange.js.map +1 -1
  104. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  105. package/lib/sync/utils/downloadByRoot.js +1 -2
  106. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  107. package/package.json +16 -16
  108. package/src/api/impl/beacon/blocks/index.ts +31 -19
  109. package/src/api/impl/lodestar/index.ts +17 -0
  110. package/src/api/impl/validator/index.ts +2 -1
  111. package/src/api/rest/base.ts +15 -13
  112. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +2 -1
  113. package/src/chain/blocks/blockInput/blockInput.ts +47 -4
  114. package/src/chain/blocks/importBlock.ts +1 -1
  115. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +1 -2
  116. package/src/chain/blocks/writeBlockInputToDb.ts +9 -0
  117. package/src/chain/chain.ts +22 -33
  118. package/src/chain/initState.ts +2 -2
  119. package/src/chain/lightClient/index.ts +1 -2
  120. package/src/chain/options.ts +0 -2
  121. package/src/chain/regen/interface.ts +1 -1
  122. package/src/chain/regen/queued.ts +1 -2
  123. package/src/chain/regen/regen.ts +6 -1
  124. package/src/chain/seenCache/seenGossipBlockInput.ts +3 -3
  125. package/src/chain/serializeState.ts +2 -1
  126. package/src/chain/stateCache/index.ts +0 -2
  127. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  128. package/src/chain/validation/blobSidecar.ts +2 -2
  129. package/src/chain/validation/block.ts +1 -2
  130. package/src/chain/validation/dataColumnSidecar.ts +2 -2
  131. package/src/network/core/networkCore.ts +12 -0
  132. package/src/network/core/networkCoreWorker.ts +3 -0
  133. package/src/network/core/networkCoreWorkerHandler.ts +9 -0
  134. package/src/network/core/types.ts +6 -0
  135. package/src/network/gossip/gossipsub.ts +147 -1
  136. package/src/network/network.ts +12 -0
  137. package/src/network/options.ts +6 -0
  138. package/src/network/peers/peerManager.ts +11 -0
  139. package/src/network/processor/gossipHandlers.ts +1 -1
  140. package/src/sync/backfill/backfill.ts +1 -2
  141. package/src/sync/utils/downloadByRange.ts +2 -2
  142. package/src/sync/utils/downloadByRoot.ts +1 -2
  143. package/lib/chain/stateCache/blockStateCacheImpl.d.ts +0 -54
  144. package/lib/chain/stateCache/blockStateCacheImpl.d.ts.map +0 -1
  145. package/lib/chain/stateCache/blockStateCacheImpl.js +0 -130
  146. package/lib/chain/stateCache/blockStateCacheImpl.js.map +0 -1
  147. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts +0 -60
  148. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts.map +0 -1
  149. package/lib/chain/stateCache/inMemoryCheckpointsCache.js +0 -156
  150. package/lib/chain/stateCache/inMemoryCheckpointsCache.js.map +0 -1
  151. package/lib/util/bytes.d.ts +0 -3
  152. package/lib/util/bytes.d.ts.map +0 -1
  153. package/lib/util/bytes.js +0 -11
  154. package/lib/util/bytes.js.map +0 -1
  155. package/src/chain/stateCache/blockStateCacheImpl.ts +0 -149
  156. package/src/chain/stateCache/inMemoryCheckpointsCache.ts +0 -192
  157. package/src/util/bytes.ts +0 -11
@@ -5,9 +5,8 @@ import {
5
5
  StateHashTreeRootSource,
6
6
  stateTransition,
7
7
  } from "@lodestar/state-transition";
8
- import {ErrorAborted, Logger} from "@lodestar/utils";
8
+ import {ErrorAborted, Logger, byteArrayEquals} from "@lodestar/utils";
9
9
  import {Metrics} from "../../metrics/index.js";
10
- import {byteArrayEquals} from "../../util/bytes.js";
11
10
  import {nextEventLoop} from "../../util/eventLoop.js";
12
11
  import {BlockError, BlockErrorCode} from "../errors/index.js";
13
12
  import {BlockProcessOpts} from "../options.js";
@@ -44,6 +44,15 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInputs: IBloc
44
44
 
45
45
  // NOTE: Old data is pruned on archive
46
46
  if (isBlockInputColumns(blockInput)) {
47
+ if (!blockInput.hasComputedAllData()) {
48
+ // Supernodes may only have a subset of the data columns by the time the block begins to be imported
49
+ // because full data availability can be assumed after NUMBER_OF_COLUMNS / 2 columns are available.
50
+ // Here, however, all data columns must be fully available/reconstructed before persisting to the DB.
51
+ await blockInput.waitForComputedAllData(BLOB_AVAILABILITY_TIMEOUT).catch(() => {
52
+ this.logger.debug("Failed to wait for computed all data", {slot, blockRoot: blockRootHex});
53
+ });
54
+ }
55
+
47
56
  const {custodyColumns} = this.custodyConfig;
48
57
  const blobsLen = (block.message as fulu.BeaconBlock).body.blobKzgCommitments.length;
49
58
  let dataColumnsLen: number;
@@ -107,12 +107,10 @@ import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
107
107
  import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
108
108
  import {SeenBlockInput} from "./seenCache/seenGossipBlockInput.js";
109
109
  import {ShufflingCache} from "./shufflingCache.js";
110
- import {BlockStateCacheImpl} from "./stateCache/blockStateCacheImpl.js";
111
110
  import {DbCPStateDatastore, checkpointToDatastoreKey} from "./stateCache/datastore/db.js";
112
111
  import {FileCPStateDatastore} from "./stateCache/datastore/file.js";
113
112
  import {CPStateDatastore} from "./stateCache/datastore/types.js";
114
113
  import {FIFOBlockStateCache} from "./stateCache/fifoBlockStateCache.js";
115
- import {InMemoryCheckpointStateCache} from "./stateCache/inMemoryCheckpointsCache.js";
116
114
  import {PersistentCheckpointStateCache} from "./stateCache/persistentCheckpointsCache.js";
117
115
  import {CheckpointStateCache} from "./stateCache/types.js";
118
116
  import {ValidatorMonitor} from "./validatorMonitor.js";
@@ -142,7 +140,7 @@ export class BeaconChain implements IBeaconChain {
142
140
  readonly logger: Logger;
143
141
  readonly metrics: Metrics | null;
144
142
  readonly validatorMonitor: ValidatorMonitor | null;
145
- readonly bufferPool: BufferPool | null;
143
+ readonly bufferPool: BufferPool;
146
144
 
147
145
  readonly anchorStateLatestBlockSlot: Slot;
148
146
 
@@ -339,32 +337,22 @@ export class BeaconChain implements IBeaconChain {
339
337
  this.index2pubkey = index2pubkey;
340
338
 
341
339
  const fileDataStore = opts.nHistoricalStatesFileDataStore ?? true;
342
- const blockStateCache = this.opts.nHistoricalStates
343
- ? new FIFOBlockStateCache(this.opts, {metrics})
344
- : new BlockStateCacheImpl({metrics});
345
- this.bufferPool = this.opts.nHistoricalStates
346
- ? new BufferPool(anchorState.type.tree_serializedSize(anchorState.node), metrics)
347
- : null;
348
-
349
- let checkpointStateCache: CheckpointStateCache;
350
- this.cpStateDatastore = undefined;
351
- if (this.opts.nHistoricalStates) {
352
- this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
353
- checkpointStateCache = new PersistentCheckpointStateCache(
354
- {
355
- config,
356
- metrics,
357
- logger,
358
- clock,
359
- blockStateCache,
360
- bufferPool: this.bufferPool,
361
- datastore: this.cpStateDatastore,
362
- },
363
- this.opts
364
- );
365
- } else {
366
- checkpointStateCache = new InMemoryCheckpointStateCache({metrics});
367
- }
340
+ const blockStateCache = new FIFOBlockStateCache(this.opts, {metrics});
341
+ this.bufferPool = new BufferPool(anchorState.type.tree_serializedSize(anchorState.node), metrics);
342
+
343
+ this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
344
+ const checkpointStateCache: CheckpointStateCache = new PersistentCheckpointStateCache(
345
+ {
346
+ config,
347
+ metrics,
348
+ logger,
349
+ clock,
350
+ blockStateCache,
351
+ bufferPool: this.bufferPool,
352
+ datastore: this.cpStateDatastore,
353
+ },
354
+ this.opts
355
+ );
368
356
 
369
357
  const {checkpoint} = computeAnchorCheckpoint(config, anchorState);
370
358
  blockStateCache.add(anchorState);
@@ -387,6 +375,7 @@ export class BeaconChain implements IBeaconChain {
387
375
  forkChoice,
388
376
  blockStateCache,
389
377
  checkpointStateCache,
378
+ seenBlockInputCache: this.seenBlockInputCache,
390
379
  db,
391
380
  metrics,
392
381
  validatorMonitor,
@@ -1368,13 +1357,13 @@ export class BeaconChain implements IBeaconChain {
1368
1357
  // TODO: Improve using regen here
1369
1358
  const {blockRoot, stateRoot, slot} = this.forkChoice.getHead();
1370
1359
  const headState = this.regen.getStateSync(stateRoot);
1371
- const headBlock = await this.db.block.get(fromHex(blockRoot));
1372
- if (headBlock == null) {
1373
- throw Error(`Head block ${slot} ${headBlock} is not available in database`);
1360
+ const blockResult = await this.getBlockByRoot(blockRoot);
1361
+ if (blockResult == null) {
1362
+ throw Error(`Head block for ${slot} is not available in cache or database`);
1374
1363
  }
1375
1364
 
1376
1365
  if (headState) {
1377
- this.opPool.pruneAll(headBlock, headState);
1366
+ this.opPool.pruneAll(blockResult.block, headState);
1378
1367
  }
1379
1368
 
1380
1369
  if (headState === null) {
@@ -2,7 +2,7 @@ import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ZERO_HASH} from "@lodestar/params";
3
3
  import {BeaconStateAllForks, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
4
  import {SignedBeaconBlock, ssz} from "@lodestar/types";
5
- import {Logger, toHex, toRootHex} from "@lodestar/utils";
5
+ import {Logger, byteArrayEquals, toHex, toRootHex} from "@lodestar/utils";
6
6
  import {GENESIS_SLOT} from "../constants/index.js";
7
7
  import {IBeaconDb} from "../db/index.js";
8
8
  import {Metrics} from "../metrics/index.js";
@@ -26,7 +26,7 @@ export async function persistAnchorState(
26
26
 
27
27
  const latestBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(latestBlockHeader);
28
28
 
29
- if (Buffer.compare(blockRoot, latestBlockRoot) !== 0) {
29
+ if (!byteArrayEquals(blockRoot, latestBlockRoot)) {
30
30
  throw Error(
31
31
  `Genesis block root ${toRootHex(blockRoot)} does not match genesis state latest block root ${toRootHex(latestBlockRoot)}`
32
32
  );
@@ -46,12 +46,11 @@ import {
46
46
  ssz,
47
47
  sszTypesFor,
48
48
  } from "@lodestar/types";
49
- import {Logger, MapDef, pruneSetToMax, toRootHex} from "@lodestar/utils";
49
+ import {Logger, MapDef, byteArrayEquals, pruneSetToMax, toRootHex} from "@lodestar/utils";
50
50
  import {ZERO_HASH} from "../../constants/index.js";
51
51
  import {IBeaconDb} from "../../db/index.js";
52
52
  import {NUM_WITNESS, NUM_WITNESS_ELECTRA} from "../../db/repositories/lightclientSyncCommitteeWitness.js";
53
53
  import {Metrics} from "../../metrics/index.js";
54
- import {byteArrayEquals} from "../../util/bytes.js";
55
54
  import {IClock} from "../../util/clock.js";
56
55
  import {ChainEventEmitter} from "../emitter.js";
57
56
  import {LightClientServerError, LightClientServerErrorCode} from "../errors/lightClientError.js";
@@ -45,7 +45,6 @@ export type IChainOptions = BlockProcessOpts &
45
45
  broadcastValidationStrictness?: string;
46
46
  minSameMessageSignatureSetsToBatch: number;
47
47
  archiveDateEpochs?: number;
48
- nHistoricalStates?: boolean;
49
48
  nHistoricalStatesFileDataStore?: boolean;
50
49
  };
51
50
 
@@ -119,7 +118,6 @@ export const defaultChainOptions: IChainOptions = {
119
118
  // batching too much may block the I/O thread so if useWorker=false, suggest this value to be 32
120
119
  // since this batch attestation work is designed to work with useWorker=true, make this the lowest value
121
120
  minSameMessageSignatureSetsToBatch: 2,
122
- nHistoricalStates: true,
123
121
  // as of Feb 2025, this option turned out to be very useful:
124
122
  // - it allows to share a persisted checkpoint state to other nodes
125
123
  // - users can prune the persisted checkpoint state files manually to save disc space
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
- import {CheckpointHex} from "../stateCache/index.js";
5
+ import {CheckpointHex} from "../stateCache/types.js";
6
6
 
7
7
  export enum RegenCaller {
8
8
  getDuties = "getDuties",
@@ -5,8 +5,7 @@ import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
5
  import {Logger, toRootHex} from "@lodestar/utils";
6
6
  import {Metrics} from "../../metrics/index.js";
7
7
  import {JobItemQueue} from "../../util/queue/index.js";
8
- import {CheckpointHex} from "../stateCache/index.js";
9
- import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js";
8
+ import {BlockStateCache, CheckpointHex, CheckpointStateCache} from "../stateCache/types.js";
10
9
  import {RegenError, RegenErrorCode} from "./errors.js";
11
10
  import {
12
11
  IStateRegenerator,
@@ -18,6 +18,7 @@ import {Metrics} from "../../metrics/index.js";
18
18
  import {nextEventLoop} from "../../util/eventLoop.js";
19
19
  import {getCheckpointFromState} from "../blocks/utils/checkpoint.js";
20
20
  import {ChainEvent, ChainEventEmitter} from "../emitter.js";
21
+ import {SeenBlockInput} from "../seenCache/seenGossipBlockInput.js";
21
22
  import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js";
22
23
  import {ValidatorMonitor} from "../validatorMonitor.js";
23
24
  import {RegenError, RegenErrorCode} from "./errors.js";
@@ -28,6 +29,7 @@ export type RegenModules = {
28
29
  forkChoice: IForkChoice;
29
30
  blockStateCache: BlockStateCache;
30
31
  checkpointStateCache: CheckpointStateCache;
32
+ seenBlockInputCache: SeenBlockInput;
31
33
  config: ChainForkConfig;
32
34
  emitter: ChainEventEmitter;
33
35
  logger: Logger;
@@ -191,7 +193,10 @@ export class StateRegenerator implements IStateRegeneratorInternal {
191
193
  const protoBlocksAsc = blocksToReplay.reverse();
192
194
  for (const [i, protoBlock] of protoBlocksAsc.entries()) {
193
195
  replaySlots[i] = protoBlock.slot;
194
- blockPromises[i] = this.modules.db.block.get(fromHex(protoBlock.blockRoot));
196
+ const blockInput = this.modules.seenBlockInputCache.get(protoBlock.blockRoot);
197
+ blockPromises[i] = blockInput?.hasBlock()
198
+ ? Promise.resolve(blockInput.getBlock())
199
+ : this.modules.db.block.get(fromHex(protoBlock.blockRoot));
195
200
  }
196
201
 
197
202
  const logCtx = {stateRoot, caller, replaySlots: replaySlots.join(",")};
@@ -3,7 +3,7 @@ import {CheckpointWithHex} from "@lodestar/fork-choice";
3
3
  import {ForkName, ForkPostFulu, ForkPreGloas, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
4
4
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
5
5
  import {BLSSignature, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
6
- import {LodestarError, Logger, pruneSetToMax} from "@lodestar/utils";
6
+ import {LodestarError, Logger, byteArrayEquals, pruneSetToMax} from "@lodestar/utils";
7
7
  import {Metrics} from "../../metrics/metrics.js";
8
8
  import {IClock} from "../../util/clock.js";
9
9
  import {CustodyConfig} from "../../util/dataColumns.js";
@@ -344,7 +344,7 @@ export class SeenBlockInput {
344
344
  return false;
345
345
  }
346
346
  // Only consider verified if the signature matches
347
- return Buffer.compare(cachedSignature, signature) === 0;
347
+ return byteArrayEquals(cachedSignature, signature);
348
348
  }
349
349
 
350
350
  /**
@@ -379,7 +379,7 @@ export class SeenBlockInput {
379
379
  let itemsToDelete = this.blockInputs.size - MAX_BLOCK_INPUT_CACHE_SIZE;
380
380
 
381
381
  if (itemsToDelete > 0) {
382
- const sorted = [...this.blockInputs.entries()].sort((a, b) => b[1].slot - a[1].slot);
382
+ const sorted = [...this.blockInputs.entries()].sort((a, b) => a[1].slot - b[1].slot);
383
383
  for (const [rootHex] of sorted) {
384
384
  this.blockInputs.delete(rootHex);
385
385
  itemsToDelete--;
@@ -20,7 +20,8 @@ export async function serializeState<T>(
20
20
  stateBytes = bufferWithKey.buffer;
21
21
  const dataView = new DataView(stateBytes.buffer, stateBytes.byteOffset, stateBytes.byteLength);
22
22
  state.serializeToBytes({uint8Array: stateBytes, dataView}, 0);
23
- return processFn(stateBytes);
23
+ // Await to ensure buffer is not released back to pool until processFn completes
24
+ return await processFn(stateBytes);
24
25
  }
25
26
  // release the buffer back to the pool automatically
26
27
  }
@@ -1,3 +1 @@
1
- export * from "./blockStateCacheImpl.js";
2
1
  export * from "./fifoBlockStateCache.js";
3
- export * from "./inMemoryCheckpointsCache.js";
@@ -31,7 +31,7 @@ type PersistentCheckpointStateCacheModules = {
31
31
  signal?: AbortSignal;
32
32
  datastore: CPStateDatastore;
33
33
  blockStateCache: BlockStateCache;
34
- bufferPool?: BufferPool | null;
34
+ bufferPool?: BufferPool;
35
35
  };
36
36
 
37
37
  /** checkpoint serialized as a string */
@@ -119,7 +119,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
119
119
  private readonly maxEpochsOnDisk: number;
120
120
  private readonly datastore: CPStateDatastore;
121
121
  private readonly blockStateCache: BlockStateCache;
122
- private readonly bufferPool?: BufferPool | null;
122
+ private readonly bufferPool?: BufferPool;
123
123
 
124
124
  constructor(
125
125
  {
@@ -851,6 +851,10 @@ export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex {
851
851
  };
852
852
  }
853
853
 
854
+ export function toCheckpointKey(cp: CheckpointHex): string {
855
+ return `${cp.rootHex}:${cp.epoch}`;
856
+ }
857
+
854
858
  function toCacheKey(cp: CheckpointHex | phase0.Checkpoint): CacheKey {
855
859
  if (isCheckpointHex(cp)) {
856
860
  return `${cp.rootHex}_${cp.epoch}`;
@@ -12,7 +12,7 @@ import {
12
12
  getBlockHeaderProposerSignatureSetByParentStateSlot,
13
13
  } from "@lodestar/state-transition";
14
14
  import {BlobIndex, Root, Slot, SubnetID, deneb, ssz} from "@lodestar/types";
15
- import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
15
+ import {byteArrayEquals, toRootHex, verifyMerkleBranch} from "@lodestar/utils";
16
16
  import {kzg} from "../../util/kzg.js";
17
17
  import {BlobSidecarErrorCode, BlobSidecarGossipError, BlobSidecarValidationError} from "../errors/blobSidecarError.js";
18
18
  import {GossipAction} from "../errors/gossipValidation.js";
@@ -226,7 +226,7 @@ export async function validateBlockBlobSidecars(
226
226
  const firstSidecarSignedBlockHeader = blobSidecars[0].signedBlockHeader;
227
227
  const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
228
228
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
229
- if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
229
+ if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
230
230
  throw new BlobSidecarValidationError(
231
231
  {
232
232
  code: BlobSidecarErrorCode.INCORRECT_BLOCK,
@@ -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);
@@ -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 {
@@ -318,7 +318,7 @@ export async function validateBlockDataColumnSidecars(
318
318
  const firstSidecarSignedBlockHeader = dataColumnSidecars[0].signedBlockHeader;
319
319
  const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
320
320
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
321
- if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
321
+ if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
322
322
  throw new DataColumnSidecarValidationError(
323
323
  {
324
324
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
@@ -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
  }
@@ -15,6 +15,12 @@ export interface NetworkOptions
15
15
  Omit<Eth2GossipsubOpts, "disableLightClientServer"> {
16
16
  localMultiaddrs: string[];
17
17
  bootMultiaddrs?: string[];
18
+ /**
19
+ * Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
20
+ * Format: multiaddr strings with peer ID, e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7..."
21
+ * Both peers must configure each other as direct peers for the feature to work properly.
22
+ */
23
+ directPeers?: string[];
18
24
  subscribeAllSubnets?: boolean;
19
25
  mdns?: boolean;
20
26
  connectToDiscv5Bootnodes?: boolean;