@lodestar/beacon-node 1.40.0-dev.cfd894719f → 1.40.0-dev.d608259b85

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 (102) 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 +10 -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/chain/ColumnReconstructionTracker.d.ts +2 -0
  10. package/lib/chain/ColumnReconstructionTracker.d.ts.map +1 -1
  11. package/lib/chain/ColumnReconstructionTracker.js +7 -3
  12. package/lib/chain/ColumnReconstructionTracker.js.map +1 -1
  13. package/lib/chain/blocks/blockInput/blockInput.d.ts +28 -0
  14. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.js +36 -1
  16. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  17. package/lib/chain/blocks/importBlock.js +1 -1
  18. package/lib/chain/blocks/importBlock.js.map +1 -1
  19. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  20. package/lib/chain/blocks/writeBlockInputToDb.js +8 -0
  21. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  22. package/lib/chain/chain.d.ts +1 -1
  23. package/lib/chain/chain.d.ts.map +1 -1
  24. package/lib/chain/chain.js +17 -29
  25. package/lib/chain/chain.js.map +1 -1
  26. package/lib/chain/options.d.ts +0 -1
  27. package/lib/chain/options.d.ts.map +1 -1
  28. package/lib/chain/options.js +0 -1
  29. package/lib/chain/options.js.map +1 -1
  30. package/lib/chain/regen/interface.d.ts +1 -1
  31. package/lib/chain/regen/queued.d.ts +1 -1
  32. package/lib/chain/regen/queued.d.ts.map +1 -1
  33. package/lib/chain/regen/queued.js.map +1 -1
  34. package/lib/chain/regen/regen.d.ts +2 -0
  35. package/lib/chain/regen/regen.d.ts.map +1 -1
  36. package/lib/chain/regen/regen.js +4 -1
  37. package/lib/chain/regen/regen.js.map +1 -1
  38. package/lib/chain/seenCache/seenGossipBlockInput.js +1 -1
  39. package/lib/chain/stateCache/index.d.ts +0 -2
  40. package/lib/chain/stateCache/index.d.ts.map +1 -1
  41. package/lib/chain/stateCache/index.js +0 -2
  42. package/lib/chain/stateCache/index.js.map +1 -1
  43. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +2 -1
  44. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  45. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -0
  46. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  47. package/lib/chain/validation/block.d.ts.map +1 -1
  48. package/lib/chain/validation/block.js +1 -2
  49. package/lib/chain/validation/block.js.map +1 -1
  50. package/lib/chain/validation/executionPayloadBid.js +4 -3
  51. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  52. package/lib/chain/validation/payloadAttestationMessage.js +1 -1
  53. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  54. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  55. package/lib/chain/validatorMonitor.js +7 -4
  56. package/lib/chain/validatorMonitor.js.map +1 -1
  57. package/lib/network/gossip/gossipsub.d.ts +19 -0
  58. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  59. package/lib/network/gossip/gossipsub.js +71 -0
  60. package/lib/network/gossip/gossipsub.js.map +1 -1
  61. package/lib/network/options.d.ts +6 -0
  62. package/lib/network/options.d.ts.map +1 -1
  63. package/lib/network/options.js.map +1 -1
  64. package/lib/network/peers/peerManager.d.ts.map +1 -1
  65. package/lib/network/peers/peerManager.js +9 -0
  66. package/lib/network/peers/peerManager.js.map +1 -1
  67. package/lib/network/processor/gossipHandlers.js +1 -1
  68. package/lib/network/processor/gossipHandlers.js.map +1 -1
  69. package/package.json +15 -15
  70. package/src/api/impl/beacon/blocks/index.ts +31 -19
  71. package/src/api/impl/lodestar/index.ts +12 -0
  72. package/src/api/impl/validator/index.ts +2 -1
  73. package/src/chain/ColumnReconstructionTracker.ts +8 -4
  74. package/src/chain/blocks/blockInput/blockInput.ts +45 -2
  75. package/src/chain/blocks/importBlock.ts +1 -1
  76. package/src/chain/blocks/writeBlockInputToDb.ts +9 -0
  77. package/src/chain/chain.ts +22 -33
  78. package/src/chain/options.ts +0 -2
  79. package/src/chain/regen/interface.ts +1 -1
  80. package/src/chain/regen/queued.ts +1 -2
  81. package/src/chain/regen/regen.ts +6 -1
  82. package/src/chain/seenCache/seenGossipBlockInput.ts +1 -1
  83. package/src/chain/stateCache/index.ts +0 -2
  84. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  85. package/src/chain/validation/block.ts +1 -2
  86. package/src/chain/validation/executionPayloadBid.ts +5 -4
  87. package/src/chain/validation/payloadAttestationMessage.ts +1 -1
  88. package/src/chain/validatorMonitor.ts +10 -5
  89. package/src/network/gossip/gossipsub.ts +86 -1
  90. package/src/network/options.ts +6 -0
  91. package/src/network/peers/peerManager.ts +11 -0
  92. package/src/network/processor/gossipHandlers.ts +1 -1
  93. package/lib/chain/stateCache/blockStateCacheImpl.d.ts +0 -54
  94. package/lib/chain/stateCache/blockStateCacheImpl.d.ts.map +0 -1
  95. package/lib/chain/stateCache/blockStateCacheImpl.js +0 -130
  96. package/lib/chain/stateCache/blockStateCacheImpl.js.map +0 -1
  97. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts +0 -60
  98. package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts.map +0 -1
  99. package/lib/chain/stateCache/inMemoryCheckpointsCache.js +0 -156
  100. package/lib/chain/stateCache/inMemoryCheckpointsCache.js.map +0 -1
  101. package/src/chain/stateCache/blockStateCacheImpl.ts +0 -149
  102. package/src/chain/stateCache/inMemoryCheckpointsCache.ts +0 -192
@@ -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) {
@@ -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(",")};
@@ -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--;
@@ -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}`;
@@ -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);
@@ -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
 
@@ -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",
@@ -1,7 +1,10 @@
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";
5
8
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
6
9
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
7
10
  import {SubnetID} from "@lodestar/types";
@@ -55,6 +58,12 @@ export type Eth2GossipsubOpts = {
55
58
  disableFloodPublish?: boolean;
56
59
  skipParamsLog?: boolean;
57
60
  disableLightClientServer?: boolean;
61
+ /**
62
+ * Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
63
+ * Supports multiaddr strings with peer ID (e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...")
64
+ * or ENR strings (e.g., "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...")
65
+ */
66
+ directPeers?: string[];
58
67
  };
59
68
 
60
69
  export type ForkBoundaryLabel = string;
@@ -97,6 +106,9 @@ export class Eth2Gossipsub extends GossipSub {
97
106
  );
98
107
  }
99
108
 
109
+ // Parse direct peers from multiaddr strings to AddrInfo objects
110
+ const directPeers = parseDirectPeers(opts.directPeers ?? [], logger);
111
+
100
112
  // Gossipsub parameters defined here:
101
113
  // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
102
114
  super(modules.libp2p.services.components, {
@@ -106,6 +118,7 @@ export class Eth2Gossipsub extends GossipSub {
106
118
  Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
107
119
  Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
108
120
  Dlazy: 6,
121
+ directPeers,
109
122
  heartbeatInterval: GOSSIPSUB_HEARTBEAT_INTERVAL,
110
123
  fanoutTTL: 60 * 1000,
111
124
  mcacheLength: 6,
@@ -381,3 +394,75 @@ function getForkBoundaryLabel(boundary: ForkBoundary): ForkBoundaryLabel {
381
394
 
382
395
  return label;
383
396
  }
397
+
398
+ /**
399
+ * Parse direct peer strings into AddrInfo objects for GossipSub.
400
+ * Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
401
+ *
402
+ * Supported formats:
403
+ * - Multiaddr with peer ID: `/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...`
404
+ * - ENR: `enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...`
405
+ *
406
+ * For multiaddrs, the string must contain a /p2p/ component with the peer ID.
407
+ * For ENRs, the TCP multiaddr and peer ID are extracted from the encoded record.
408
+ */
409
+ export function parseDirectPeers(directPeerStrs: string[], logger: Logger): AddrInfo[] {
410
+ const directPeers: AddrInfo[] = [];
411
+
412
+ for (const peerStr of directPeerStrs) {
413
+ // Check if this is an ENR (starts with "enr:")
414
+ if (peerStr.startsWith("enr:")) {
415
+ try {
416
+ const enr = ENR.decodeTxt(peerStr);
417
+ const peerId = enr.peerId;
418
+
419
+ // Get TCP multiaddr from ENR
420
+ const multiaddrTCP = enr.getLocationMultiaddr("tcp");
421
+ if (!multiaddrTCP) {
422
+ logger.warn("ENR does not contain TCP multiaddr", {enr: peerStr});
423
+ continue;
424
+ }
425
+
426
+ directPeers.push({
427
+ id: peerId,
428
+ addrs: [multiaddrTCP],
429
+ });
430
+
431
+ logger.info("Added direct peer from ENR", {peerId: peerId.toString(), addr: multiaddrTCP.toString()});
432
+ } catch (e) {
433
+ logger.warn("Failed to parse direct peer ENR", {enr: peerStr}, e as Error);
434
+ }
435
+ } else {
436
+ // Parse as multiaddr
437
+ try {
438
+ const ma = multiaddr(peerStr);
439
+
440
+ const peerIdStr = ma.getPeerId();
441
+ if (!peerIdStr) {
442
+ logger.warn("Direct peer multiaddr must contain /p2p/ component with peer ID", {multiaddr: peerStr});
443
+ continue;
444
+ }
445
+
446
+ try {
447
+ const peerId = peerIdFromString(peerIdStr);
448
+
449
+ // Get the address without the /p2p/ component
450
+ const addr = ma.decapsulate("/p2p/" + peerIdStr);
451
+
452
+ directPeers.push({
453
+ id: peerId,
454
+ addrs: [addr],
455
+ });
456
+
457
+ logger.info("Added direct peer", {peerId: peerIdStr, addr: addr.toString()});
458
+ } catch (e) {
459
+ logger.warn("Invalid peer ID in direct peer multiaddr", {multiaddr: peerStr, peerId: peerIdStr}, e as Error);
460
+ }
461
+ } catch (e) {
462
+ logger.warn("Failed to parse direct peer multiaddr", {multiaddr: peerStr}, e as Error);
463
+ }
464
+ }
465
+ }
466
+
467
+ return directPeers;
468
+ }
@@ -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;
@@ -721,6 +721,17 @@ export class PeerManager {
721
721
  // NOTE: libp2p may emit two "peer:connect" events: One for inbound, one for outbound
722
722
  // If that happens, it's okay. Only the "outbound" connection triggers immediate action
723
723
  const now = Date.now();
724
+
725
+ // Ethereum uses secp256k1 for node IDs, reject peers with other key types
726
+ if (remotePeer.type !== "secp256k1") {
727
+ this.logger.debug("Peer does not have secp256k1 key, disconnecting", {
728
+ peer: remotePeerPrettyStr,
729
+ type: remotePeer.type,
730
+ });
731
+ void this.goodbyeAndDisconnect(remotePeer, GoodByeReasonCode.IRRELEVANT_NETWORK);
732
+ return;
733
+ }
734
+
724
735
  const nodeId = computeNodeId(remotePeer);
725
736
  const peerData: PeerData = {
726
737
  lastReceivedMsgUnixTsMs: direction === "outbound" ? 0 : now,
@@ -579,7 +579,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
579
579
  break;
580
580
  }
581
581
 
582
- if (!blockInput.hasAllData()) {
582
+ if (!blockInput.hasComputedAllData()) {
583
583
  // immediately attempt fetch of data columns from execution engine
584
584
  chain.getBlobsTracker.triggerGetBlobs(blockInput);
585
585
  // if we've received at least half of the columns, trigger reconstruction of the rest
@@ -1,54 +0,0 @@
1
- import { routes } from "@lodestar/api";
2
- import { CachedBeaconStateAllForks } from "@lodestar/state-transition";
3
- import { Epoch, RootHex } from "@lodestar/types";
4
- import { Metrics } from "../../metrics/index.js";
5
- import { BlockStateCache } from "./types.js";
6
- /**
7
- * Old implementation of StateCache (used to call `StateContextCache`)
8
- * - Prune per checkpoint so number of states ranges from 96 to 128
9
- * - Keep a separate head state to make sure it is always available
10
- */
11
- export declare class BlockStateCacheImpl implements BlockStateCache {
12
- /**
13
- * Max number of states allowed in the cache
14
- */
15
- readonly maxStates: number;
16
- private readonly cache;
17
- /** Epoch -> Set<blockRoot> */
18
- private readonly epochIndex;
19
- private readonly metrics;
20
- /**
21
- * Strong reference to prevent head state from being pruned.
22
- * null if head state is being regen and not available at the moment.
23
- */
24
- private head;
25
- constructor({ maxStates, metrics }: {
26
- maxStates?: number;
27
- metrics?: Metrics | null;
28
- });
29
- get(rootHex: RootHex): CachedBeaconStateAllForks | null;
30
- add(item: CachedBeaconStateAllForks): void;
31
- setHeadState(item: CachedBeaconStateAllForks | null): void;
32
- /**
33
- * Get a seed state for state reload.
34
- * This is to conform to the api only as this cache is not used in n-historical state.
35
- * See ./fifoBlockStateCache.ts for implementation
36
- */
37
- getSeedState(): CachedBeaconStateAllForks;
38
- clear(): void;
39
- get size(): number;
40
- /**
41
- * TODO make this more robust.
42
- * Without more thought, this currently breaks our assumptions about recent state availablity
43
- */
44
- prune(headStateRootHex: RootHex): void;
45
- /**
46
- * Prune per finalized epoch.
47
- */
48
- deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
49
- /** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
50
- dumpSummary(): routes.lodestar.StateCacheItem[];
51
- getStates(): IterableIterator<CachedBeaconStateAllForks>;
52
- private deleteAllEpochItems;
53
- }
54
- //# sourceMappingURL=blockStateCacheImpl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blockStateCacheImpl.d.ts","sourceRoot":"","sources":["../../../src/chain/stateCache/blockStateCacheImpl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AACrC,OAAO,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAC,KAAK,EAAE,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAC,OAAO,EAAC,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAC,eAAe,EAAC,MAAM,YAAY,CAAC;AAI3C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,eAAe;IACzD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IACtE,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE;;;OAGG;IACH,OAAO,CAAC,IAAI,CAAuE;gBAEvE,EAAC,SAAsB,EAAE,OAAO,EAAC,EAAE;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAC;IAS7F,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,yBAAyB,GAAG,IAAI;IAavD,GAAG,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI;IAgB1C,YAAY,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,GAAG,IAAI;IAS1D;;;;OAIG;IACH,YAAY,IAAI,yBAAyB;IAIzC,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB,EAAE,OAAO,GAAG,IAAI;IAgBtC;;OAEG;IACH,oBAAoB,CAAC,cAAc,EAAE,KAAK,GAAG,IAAI;IAQjD,0DAA0D;IAC1D,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE;IAU/C,SAAS,IAAI,gBAAgB,CAAC,yBAAyB,CAAC;IAIxD,OAAO,CAAC,mBAAmB;CAM5B"}