@lodestar/beacon-node 1.45.0-dev.9d930bdc5b → 1.45.0-dev.b620872107

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 (89) hide show
  1. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/pool/index.js +2 -43
  3. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/index.js +46 -3
  6. package/lib/api/impl/beacon/state/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +11 -2
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js +39 -3
  10. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +46 -6
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.js +1 -1
  15. package/lib/chain/blocks/importBlock.js.map +1 -1
  16. package/lib/chain/chain.d.ts +1 -2
  17. package/lib/chain/chain.d.ts.map +1 -1
  18. package/lib/chain/chain.js +1 -3
  19. package/lib/chain/chain.js.map +1 -1
  20. package/lib/chain/interface.d.ts +1 -2
  21. package/lib/chain/interface.d.ts.map +1 -1
  22. package/lib/chain/interface.js.map +1 -1
  23. package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
  24. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  25. package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
  26. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  27. package/lib/chain/options.d.ts +1 -0
  28. package/lib/chain/options.d.ts.map +1 -1
  29. package/lib/chain/options.js +1 -0
  30. package/lib/chain/options.js.map +1 -1
  31. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  32. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  33. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  34. package/lib/chain/seenCache/index.d.ts +0 -1
  35. package/lib/chain/seenCache/index.d.ts.map +1 -1
  36. package/lib/chain/seenCache/index.js +0 -1
  37. package/lib/chain/seenCache/index.js.map +1 -1
  38. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  39. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
  40. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  41. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  42. package/lib/chain/validation/proposerPreferences.js +11 -3
  43. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  44. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  45. package/lib/network/gossip/gossipsub.js +2 -1
  46. package/lib/network/gossip/gossipsub.js.map +1 -1
  47. package/lib/network/gossip/topic.d.ts +765 -3
  48. package/lib/network/gossip/topic.d.ts.map +1 -1
  49. package/lib/network/gossip/topic.js +23 -3
  50. package/lib/network/gossip/topic.js.map +1 -1
  51. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  52. package/lib/network/processor/gossipHandlers.js +2 -3
  53. package/lib/network/processor/gossipHandlers.js.map +1 -1
  54. package/lib/sync/range/chain.d.ts +4 -1
  55. package/lib/sync/range/chain.d.ts.map +1 -1
  56. package/lib/sync/range/chain.js +3 -3
  57. package/lib/sync/range/chain.js.map +1 -1
  58. package/lib/sync/range/range.d.ts.map +1 -1
  59. package/lib/sync/range/range.js +10 -0
  60. package/lib/sync/range/range.js.map +1 -1
  61. package/lib/util/graffiti.d.ts +15 -0
  62. package/lib/util/graffiti.d.ts.map +1 -1
  63. package/lib/util/graffiti.js +55 -0
  64. package/lib/util/graffiti.js.map +1 -1
  65. package/package.json +14 -14
  66. package/src/api/impl/beacon/pool/index.ts +0 -53
  67. package/src/api/impl/beacon/state/index.ts +52 -1
  68. package/src/api/impl/beacon/state/utils.ts +57 -4
  69. package/src/api/impl/validator/index.ts +54 -5
  70. package/src/chain/blocks/importBlock.ts +1 -1
  71. package/src/chain/chain.ts +0 -3
  72. package/src/chain/interface.ts +0 -2
  73. package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
  74. package/src/chain/options.ts +2 -0
  75. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  76. package/src/chain/seenCache/index.ts +0 -1
  77. package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
  78. package/src/chain/validation/proposerPreferences.ts +12 -3
  79. package/src/network/gossip/gossipsub.ts +2 -1
  80. package/src/network/gossip/topic.ts +28 -4
  81. package/src/network/processor/gossipHandlers.ts +1 -2
  82. package/src/sync/range/chain.ts +4 -5
  83. package/src/sync/range/range.ts +10 -0
  84. package/src/util/graffiti.ts +78 -0
  85. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  86. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  87. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  88. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  89. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -81,7 +81,7 @@ export async function validateGossipProposerPreferences(
81
81
  }
82
82
 
83
83
  // [IGNORE] First valid message for (dependent_root, proposal_slot, validator_index).
84
- if (chain.seenProposerPreferences.isKnown(dependentRootHex, proposalSlot, validatorIndex)) {
84
+ if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
85
85
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
86
86
  code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
87
87
  proposalSlot,
@@ -105,6 +105,15 @@ export async function validateGossipProposerPreferences(
105
105
  });
106
106
  }
107
107
 
108
- // Valid
109
- chain.seenProposerPreferences.add(dependentRootHex, proposalSlot, validatorIndex);
108
+ // Repeated check - deals with race-condition between preferences submissions
109
+ if (chain.proposerPreferencesPool.isKnown(proposalSlot, dependentRootHex, validatorIndex)) {
110
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
111
+ code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
112
+ proposalSlot,
113
+ validatorIndex,
114
+ dependentRoot: dependentRootHex,
115
+ });
116
+ }
117
+
118
+ chain.proposerPreferencesPool.add(signedProposerPreferences);
110
119
  }
@@ -35,7 +35,7 @@ import {
35
35
  computeGossipPeerScoreParams,
36
36
  gossipScoreThresholds,
37
37
  } from "./scoringParameters.js";
38
- import {GossipTopicCache, getCoreTopicsAtFork, stringifyGossipTopic} from "./topic.js";
38
+ import {GossipTopicCache, getAllowedTopics, getCoreTopicsAtFork, stringifyGossipTopic} from "./topic.js";
39
39
 
40
40
  /** As specified in https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md */
41
41
  const GOSSIPSUB_HEARTBEAT_INTERVAL = 0.7 * 1000;
@@ -140,6 +140,7 @@ export class Eth2Gossipsub {
140
140
  const gossipsubInstance = gossipsub({
141
141
  globalSignaturePolicy: StrictNoSign,
142
142
  allowPublishToZeroTopicPeers: allowPublishToZeroPeers,
143
+ allowedTopics: getAllowedTopics(networkConfig),
143
144
  D: gossipsubD ?? GOSSIP_D,
144
145
  Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
145
146
  Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
@@ -246,7 +246,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr:
246
246
  export function getCoreTopicsAtFork(
247
247
  networkConfig: NetworkConfig,
248
248
  fork: ForkName,
249
- opts: {subscribeAllSubnets?: boolean; disableLightClientServer?: boolean}
249
+ opts: {subscribeAllSubnets?: boolean; subscribeAllColumnSubnets?: boolean; disableLightClientServer?: boolean}
250
250
  ): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
251
251
  // Common topics for all forks
252
252
  const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [
@@ -266,7 +266,7 @@ export function getCoreTopicsAtFork(
266
266
 
267
267
  // After fulu also track data_column_sidecar_{index}
268
268
  if (ForkSeq[fork] >= ForkSeq.fulu) {
269
- topics.push(...getDataColumnSidecarTopics(networkConfig));
269
+ topics.push(...getDataColumnSidecarTopics(networkConfig, opts.subscribeAllColumnSubnets));
270
270
  }
271
271
 
272
272
  // After Deneb and before Fulu also track blob_sidecar_{subnet_id}
@@ -309,15 +309,39 @@ export function getCoreTopicsAtFork(
309
309
  return topics;
310
310
  }
311
311
 
312
+ /**
313
+ * Build the complete set of valid gossip topic strings across every fork boundary.
314
+ */
315
+ export function getAllowedTopics(networkConfig: NetworkConfig): Set<string> {
316
+ const {config} = networkConfig;
317
+ const allowedTopics = new Set<string>();
318
+
319
+ for (const boundary of config.forkBoundariesAscendingEpochOrder) {
320
+ const topics = getCoreTopicsAtFork(networkConfig, boundary.fork, {
321
+ subscribeAllSubnets: true,
322
+ subscribeAllColumnSubnets: true,
323
+ disableLightClientServer: false,
324
+ });
325
+ for (const topic of topics) {
326
+ allowedTopics.add(stringifyGossipTopic(config, {...topic, boundary}));
327
+ }
328
+ }
329
+
330
+ return allowedTopics;
331
+ }
332
+
312
333
  /**
313
334
  * Pick data column subnets to subscribe to post-fulu.
314
335
  */
315
336
  export function getDataColumnSidecarTopics(
316
- networkConfig: NetworkConfig
337
+ networkConfig: NetworkConfig,
338
+ subscribeAllColumnSubnets = false
317
339
  ): GossipTopicTypeMap[keyof GossipTopicTypeMap][] {
318
340
  const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [];
319
341
 
320
- const subnets = networkConfig.custodyConfig.sampledSubnets;
342
+ const subnets = subscribeAllColumnSubnets
343
+ ? Array.from({length: networkConfig.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT}, (_, i) => i)
344
+ : networkConfig.custodyConfig.sampledSubnets;
321
345
  for (const subnet of subnets) {
322
346
  topics.push({type: GossipType.data_column_sidecar, subnet});
323
347
  }
@@ -1273,9 +1273,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1273
1273
  const signedProposerPreferences = sszDeserialize(topic, serializedData);
1274
1274
  await validateGossipProposerPreferences(chain, signedProposerPreferences);
1275
1275
 
1276
- chain.proposerPreferencesPool.add(signedProposerPreferences);
1277
1276
  chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1278
- version: ForkName.gloas,
1277
+ version: config.getForkName(signedProposerPreferences.message.proposalSlot),
1279
1278
  data: signedProposerPreferences,
1280
1279
  });
1281
1280
  },
@@ -186,10 +186,6 @@ export class SyncChain {
186
186
  this.logger = logger;
187
187
  this.logId = `${syncType}-${nextChainId++}`;
188
188
 
189
- if (metrics) {
190
- metrics.syncRange.headSyncPeers.addCollect(() => this.scrapeMetrics(metrics));
191
- }
192
-
193
189
  // Trigger event on parent class
194
190
  this.sync().then(
195
191
  () => fns.onEnd(null, this.target),
@@ -775,7 +771,10 @@ export class SyncChain {
775
771
  });
776
772
  }
777
773
 
778
- private scrapeMetrics(metrics: Metrics): void {
774
+ /**
775
+ * Called by `RangeSync`'s to avoid collecting metrics of removed chains.
776
+ */
777
+ scrapeMetrics(metrics: Metrics): void {
779
778
  const syncPeersMetric =
780
779
  this.syncType === RangeSyncType.Finalized
781
780
  ? metrics.syncRange.finalizedSyncPeers
@@ -105,6 +105,16 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
105
105
 
106
106
  if (metrics) {
107
107
  metrics.syncStatus.addCollect(() => this.scrapeMetrics(metrics));
108
+ metrics.syncRange.headSyncPeers.addCollect(() => {
109
+ // Gauges retain their last set value, so a removed chain would keep reporting stale
110
+ // per-column peer counts (finalizedSyncPeers especially, as it is rarely recreated once
111
+ // synced)
112
+ metrics.syncRange.headSyncPeers.reset();
113
+ metrics.syncRange.finalizedSyncPeers.reset();
114
+ for (const syncChain of this.chains.values()) {
115
+ syncChain.scrapeMetrics(metrics);
116
+ }
117
+ });
108
118
  }
109
119
  }
110
120
 
@@ -37,3 +37,81 @@ export function getDefaultGraffiti(
37
37
  // No EL client info available. We still want to include CL info albeit not spec compliant
38
38
  return `${consensusClientVersion.code}${consensusClientVersion.commit.slice(0, 4)}`;
39
39
  }
40
+
41
+ function appendLongestFittingSuffix(userGraffiti: string, suffixes: string[]): string {
42
+ const userGraffitiBytes = Buffer.byteLength(userGraffiti, "utf8");
43
+ if (userGraffitiBytes >= GRAFFITI_SIZE) {
44
+ return userGraffiti;
45
+ }
46
+
47
+ const availableBytes = GRAFFITI_SIZE - userGraffitiBytes;
48
+
49
+ for (const suffix of suffixes) {
50
+ if (Buffer.byteLength(suffix, "utf8") <= availableBytes) {
51
+ return `${userGraffiti}${suffix}`;
52
+ }
53
+ }
54
+
55
+ return userGraffiti;
56
+ }
57
+
58
+ /**
59
+ * Appends the richest available client watermark that fits after user graffiti.
60
+ *
61
+ * Tiers are:
62
+ * - full EL/CL watermark, e.g. " BU9b0eLS80c2"
63
+ * - EL/CL client codes, e.g. " BULS"
64
+ * - CL client code, e.g. " LS"
65
+ */
66
+ export function appendClientInfoToGraffiti(
67
+ userGraffiti: string,
68
+ consensusClientVersion: ClientVersion,
69
+ executionClientVersion: ClientVersion | null | undefined,
70
+ opts: {private?: boolean} = {}
71
+ ): string {
72
+ if (opts.private) {
73
+ return userGraffiti;
74
+ }
75
+
76
+ // Graffiti supplied via the beacon API is decoded from a fixed 32-byte field (see
77
+ // fromGraffitiHex) and arrives right-padded with NUL bytes. Trim only trailing padding
78
+ // NULs; a NUL that appears in the middle of the string is data, not padding.
79
+ let end = userGraffiti.length;
80
+ while (end > 0 && userGraffiti.charCodeAt(end - 1) === 0) {
81
+ end--;
82
+ }
83
+ const graffiti = userGraffiti.slice(0, end);
84
+
85
+ const fullClientInfo = getDefaultGraffiti(consensusClientVersion, executionClientVersion, {private: false});
86
+ if (graffiti.length === 0) {
87
+ return fullClientInfo;
88
+ }
89
+
90
+ const suffixes =
91
+ executionClientVersion != null
92
+ ? [
93
+ ` ${fullClientInfo}`,
94
+ ` ${executionClientVersion.code}${consensusClientVersion.code}`,
95
+ ` ${consensusClientVersion.code}`,
96
+ ]
97
+ : [` ${fullClientInfo}`, ` ${consensusClientVersion.code}`];
98
+
99
+ return appendLongestFittingSuffix(graffiti, suffixes);
100
+ }
101
+
102
+ export function getBlockGraffiti(
103
+ userGraffiti: string | undefined,
104
+ consensusClientVersion: ClientVersion,
105
+ executionClientVersion: ClientVersion | null | undefined,
106
+ opts: {private?: boolean; graffitiAppend?: boolean}
107
+ ): string {
108
+ if (userGraffiti === undefined) {
109
+ return getDefaultGraffiti(consensusClientVersion, executionClientVersion, opts);
110
+ }
111
+
112
+ if (opts.graffitiAppend === false) {
113
+ return userGraffiti;
114
+ }
115
+
116
+ return appendClientInfoToGraffiti(userGraffiti, consensusClientVersion, executionClientVersion, opts);
117
+ }
@@ -1,16 +0,0 @@
1
- import { RootHex, Slot, ValidatorIndex } from "@lodestar/types";
2
- /**
3
- * Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
4
- */
5
- export declare class SeenProposerPreferences {
6
- private readonly validatorByDependentRootBySlot;
7
- isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean;
8
- add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void;
9
- /**
10
- * Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
11
- * passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
12
- * on each slot tick.
13
- */
14
- prune(currentSlot: Slot): void;
15
- }
16
- //# sourceMappingURL=seenProposerPreferences.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"seenProposerPreferences.d.ts","sourceRoot":"","sources":["../../../src/chain/seenCache/seenProposerPreferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAG9D;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,8BAA8B,CAE7C;IAEF,OAAO,CAAC,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAE3F;IAED,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI,CAEpF;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAM7B;CACF"}
@@ -1,26 +0,0 @@
1
- import { MapDef } from "@lodestar/utils";
2
- /**
3
- * Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
4
- */
5
- export class SeenProposerPreferences {
6
- validatorByDependentRootBySlot = new MapDef(() => new Map());
7
- isKnown(dependentRoot, proposalSlot, validatorIndex) {
8
- return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
9
- }
10
- add(dependentRoot, proposalSlot, validatorIndex) {
11
- this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
12
- }
13
- /**
14
- * Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
15
- * passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
16
- * on each slot tick.
17
- */
18
- prune(currentSlot) {
19
- for (const slot of this.validatorByDependentRootBySlot.keys()) {
20
- if (slot < currentSlot) {
21
- this.validatorByDependentRootBySlot.delete(slot);
22
- }
23
- }
24
- }
25
- }
26
- //# sourceMappingURL=seenProposerPreferences.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"seenProposerPreferences.js","sourceRoot":"","sources":["../../../src/chain/seenCache/seenProposerPreferences.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAEvC;;GAEG;AACH,MAAM,OAAO,uBAAuB;IACjB,8BAA8B,GAAG,IAAI,MAAM,CAC1D,GAAG,EAAE,CAAC,IAAI,GAAG,EAA2B,CACzC,CAAC;IAEF,OAAO,CAAC,aAAsB,EAAE,YAAkB,EAAE,cAA8B,EAAW;QAC3F,OAAO,IAAI,CAAC,8BAA8B,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,cAAc,CAAC;IAAA,CACrG;IAED,GAAG,CAAC,aAAsB,EAAE,YAAkB,EAAE,cAA8B,EAAQ;QACpF,IAAI,CAAC,8BAA8B,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;IAAA,CACnG;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAiB,EAAQ;QAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9D,IAAI,IAAI,GAAG,WAAW,EAAE,CAAC;gBACvB,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IAAA,CACF;CACF"}
@@ -1,32 +0,0 @@
1
- import {RootHex, Slot, ValidatorIndex} from "@lodestar/types";
2
- import {MapDef} from "@lodestar/utils";
3
-
4
- /**
5
- * Tracks signed proposer preferences we've already seen per (dependent_root, proposal_slot, validator_index).
6
- */
7
- export class SeenProposerPreferences {
8
- private readonly validatorByDependentRootBySlot = new MapDef<Slot, Map<RootHex, ValidatorIndex>>(
9
- () => new Map<RootHex, ValidatorIndex>()
10
- );
11
-
12
- isKnown(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
13
- return this.validatorByDependentRootBySlot.get(proposalSlot)?.get(dependentRoot) === validatorIndex;
14
- }
15
-
16
- add(dependentRoot: RootHex, proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
17
- this.validatorByDependentRootBySlot.getOrDefault(proposalSlot).set(dependentRoot, validatorIndex);
18
- }
19
-
20
- /**
21
- * Entries are only load-bearing while `proposal_slot > current_slot`. Once the slot has
22
- * passed the `[IGNORE] proposal_slot > current_slot` gossip rule takes over, so drop them
23
- * on each slot tick.
24
- */
25
- prune(currentSlot: Slot): void {
26
- for (const slot of this.validatorByDependentRootBySlot.keys()) {
27
- if (slot < currentSlot) {
28
- this.validatorByDependentRootBySlot.delete(slot);
29
- }
30
- }
31
- }
32
- }