@lodestar/beacon-node 1.45.0-dev.f535421f29 → 1.45.0-rc.0

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/index.d.ts +1 -1
  2. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  3. package/lib/api/impl/beacon/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +6 -43
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/index.d.ts +1 -1
  8. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/index.js +48 -5
  10. package/lib/api/impl/beacon/state/index.js.map +1 -1
  11. package/lib/api/impl/beacon/state/utils.d.ts +13 -3
  12. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  13. package/lib/api/impl/beacon/state/utils.js +48 -4
  14. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  15. package/lib/api/impl/debug/index.d.ts +1 -1
  16. package/lib/api/impl/debug/index.d.ts.map +1 -1
  17. package/lib/api/impl/debug/index.js +2 -2
  18. package/lib/api/impl/debug/index.js.map +1 -1
  19. package/lib/api/impl/lodestar/index.js +2 -2
  20. package/lib/api/impl/lodestar/index.js.map +1 -1
  21. package/lib/api/impl/proof/index.d.ts +1 -1
  22. package/lib/api/impl/proof/index.d.ts.map +1 -1
  23. package/lib/api/impl/proof/index.js +2 -2
  24. package/lib/api/impl/proof/index.js.map +1 -1
  25. package/lib/api/impl/utils.d.ts +23 -0
  26. package/lib/api/impl/utils.d.ts.map +1 -1
  27. package/lib/api/impl/utils.js +44 -1
  28. package/lib/api/impl/utils.js.map +1 -1
  29. package/lib/api/impl/validator/index.d.ts +0 -14
  30. package/lib/api/impl/validator/index.d.ts.map +1 -1
  31. package/lib/api/impl/validator/index.js +67 -62
  32. package/lib/api/impl/validator/index.js.map +1 -1
  33. package/lib/api/rest/base.d.ts.map +1 -1
  34. package/lib/api/rest/base.js +8 -0
  35. package/lib/api/rest/base.js.map +1 -1
  36. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/importBlock.js +2 -5
  38. package/lib/chain/blocks/importBlock.js.map +1 -1
  39. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  40. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  41. package/lib/chain/builderCircuitBreaker.js +59 -0
  42. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  43. package/lib/chain/chain.d.ts +3 -2
  44. package/lib/chain/chain.d.ts.map +1 -1
  45. package/lib/chain/chain.js +4 -3
  46. package/lib/chain/chain.js.map +1 -1
  47. package/lib/chain/interface.d.ts +3 -2
  48. package/lib/chain/interface.d.ts.map +1 -1
  49. package/lib/chain/interface.js.map +1 -1
  50. package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
  51. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  52. package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
  53. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  54. package/lib/chain/options.d.ts +5 -0
  55. package/lib/chain/options.d.ts.map +1 -1
  56. package/lib/chain/options.js +1 -0
  57. package/lib/chain/options.js.map +1 -1
  58. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  59. package/lib/chain/prepareNextSlot.js +12 -7
  60. package/lib/chain/prepareNextSlot.js.map +1 -1
  61. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  62. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  63. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  64. package/lib/chain/seenCache/index.d.ts +0 -1
  65. package/lib/chain/seenCache/index.d.ts.map +1 -1
  66. package/lib/chain/seenCache/index.js +0 -1
  67. package/lib/chain/seenCache/index.js.map +1 -1
  68. package/lib/chain/validation/executionPayloadBid.js +11 -10
  69. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  70. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  71. package/lib/chain/validation/proposerPreferences.js +14 -5
  72. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  73. package/lib/execution/builder/http.d.ts +18 -0
  74. package/lib/execution/builder/http.d.ts.map +1 -1
  75. package/lib/execution/builder/http.js +20 -11
  76. package/lib/execution/builder/http.js.map +1 -1
  77. package/lib/metrics/metrics/beacon.d.ts +6 -0
  78. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  79. package/lib/metrics/metrics/beacon.js +18 -0
  80. package/lib/metrics/metrics/beacon.js.map +1 -1
  81. package/lib/network/core/metrics.d.ts +65 -0
  82. package/lib/network/core/metrics.d.ts.map +1 -1
  83. package/lib/network/core/metrics.js +94 -0
  84. package/lib/network/core/metrics.js.map +1 -1
  85. package/lib/network/discv5/utils.d.ts.map +1 -1
  86. package/lib/network/discv5/utils.js +12 -3
  87. package/lib/network/discv5/utils.js.map +1 -1
  88. package/lib/network/gossip/encoding.d.ts.map +1 -1
  89. package/lib/network/gossip/encoding.js +7 -0
  90. package/lib/network/gossip/encoding.js.map +1 -1
  91. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  92. package/lib/network/gossip/gossipsub.js +2 -1
  93. package/lib/network/gossip/gossipsub.js.map +1 -1
  94. package/lib/network/gossip/topic.d.ts +765 -3
  95. package/lib/network/gossip/topic.d.ts.map +1 -1
  96. package/lib/network/gossip/topic.js +23 -3
  97. package/lib/network/gossip/topic.js.map +1 -1
  98. package/lib/network/peers/peerManager.d.ts.map +1 -1
  99. package/lib/network/peers/peerManager.js +10 -0
  100. package/lib/network/peers/peerManager.js.map +1 -1
  101. package/lib/network/peers/score/score.d.ts +3 -1
  102. package/lib/network/peers/score/score.d.ts.map +1 -1
  103. package/lib/network/peers/score/score.js +6 -1
  104. package/lib/network/peers/score/score.js.map +1 -1
  105. package/lib/network/peers/score/store.d.ts.map +1 -1
  106. package/lib/network/peers/score/store.js +6 -1
  107. package/lib/network/peers/score/store.js.map +1 -1
  108. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  109. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  110. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  111. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  112. package/lib/network/processor/gossipHandlers.js +6 -3
  113. package/lib/network/processor/gossipHandlers.js.map +1 -1
  114. package/lib/util/graffiti.d.ts +15 -0
  115. package/lib/util/graffiti.d.ts.map +1 -1
  116. package/lib/util/graffiti.js +55 -0
  117. package/lib/util/graffiti.js.map +1 -1
  118. package/package.json +19 -19
  119. package/src/api/impl/beacon/index.ts +1 -1
  120. package/src/api/impl/beacon/pool/index.ts +5 -53
  121. package/src/api/impl/beacon/state/index.ts +55 -3
  122. package/src/api/impl/beacon/state/utils.ts +68 -4
  123. package/src/api/impl/debug/index.ts +3 -2
  124. package/src/api/impl/lodestar/index.ts +2 -2
  125. package/src/api/impl/proof/index.ts +2 -2
  126. package/src/api/impl/utils.ts +51 -1
  127. package/src/api/impl/validator/index.ts +75 -67
  128. package/src/api/rest/base.ts +9 -0
  129. package/src/chain/blocks/importBlock.ts +2 -7
  130. package/src/chain/builderCircuitBreaker.ts +84 -0
  131. package/src/chain/chain.ts +7 -3
  132. package/src/chain/interface.ts +2 -2
  133. package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
  134. package/src/chain/options.ts +6 -0
  135. package/src/chain/prepareNextSlot.ts +11 -7
  136. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  137. package/src/chain/seenCache/index.ts +0 -1
  138. package/src/chain/validation/executionPayloadBid.ts +14 -9
  139. package/src/chain/validation/proposerPreferences.ts +15 -5
  140. package/src/execution/builder/http.ts +30 -17
  141. package/src/metrics/metrics/beacon.ts +19 -0
  142. package/src/network/core/metrics.ts +94 -0
  143. package/src/network/discv5/utils.ts +11 -3
  144. package/src/network/gossip/encoding.ts +7 -0
  145. package/src/network/gossip/gossipsub.ts +2 -1
  146. package/src/network/gossip/topic.ts +28 -4
  147. package/src/network/peers/peerManager.ts +11 -0
  148. package/src/network/peers/score/score.ts +8 -1
  149. package/src/network/peers/score/store.ts +8 -1
  150. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  151. package/src/network/processor/gossipHandlers.ts +6 -2
  152. package/src/util/graffiti.ts +78 -0
  153. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  154. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  155. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  156. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  157. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -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
  }
@@ -415,8 +415,10 @@ export class PeerManager {
415
415
  );
416
416
  if (irrelevantReasonType === null) {
417
417
  isIrrelevant = false;
418
+ this.metrics?.peerManager.relevanceCheck.inc({result: "relevant"});
418
419
  } else {
419
420
  isIrrelevant = true;
421
+ this.metrics?.peerManager.relevanceCheck.inc({result: irrelevantReasonType.code});
420
422
  this.logger.debug("Irrelevant peer", {
421
423
  peer: prettyPrintPeerId(peer),
422
424
  reason: renderIrrelevantPeerType(irrelevantReasonType),
@@ -424,6 +426,7 @@ export class PeerManager {
424
426
  }
425
427
  } catch (e) {
426
428
  this.logger.error("Irrelevant peer - unexpected error", {peer: prettyPrintPeerId(peer)}, e as Error);
429
+ this.metrics?.peerManager.relevanceCheck.inc({result: "error"});
427
430
  isIrrelevant = true;
428
431
  }
429
432
 
@@ -552,15 +555,18 @@ export class PeerManager {
552
555
  for (const peer of connectedPeers) {
553
556
  switch (this.peerRpcScores.getScoreState(peer)) {
554
557
  case ScoreState.Banned:
558
+ this.metrics?.peerManager.peersPruned.inc({reason: "banned"});
555
559
  void this.goodbyeAndDisconnect(peer, GoodByeReasonCode.BANNED);
556
560
  break;
557
561
  case ScoreState.Disconnected:
562
+ this.metrics?.peerManager.peersPruned.inc({reason: "score_too_low"});
558
563
  void this.goodbyeAndDisconnect(peer, GoodByeReasonCode.SCORE_TOO_LOW);
559
564
  break;
560
565
  case ScoreState.Healthy:
561
566
  connectedHealthyPeers.push(peer);
562
567
  }
563
568
  }
569
+ this.metrics?.peerManager.peersEvaluated.observe(connectedHealthyPeers.length);
564
570
 
565
571
  const status = this.statusCache.get();
566
572
  const starved =
@@ -572,6 +578,7 @@ export class PeerManager {
572
578
  this.metrics?.peerManager.starved.set(starved ? 1 : 0);
573
579
  const forkSeq = this.config.getForkSeq(this.clock.currentSlot);
574
580
 
581
+ const prioritizeTimer = this.metrics?.peerManager.prioritizePeersDuration.startTimer();
575
582
  const {peersToDisconnect, peersToConnect, attnetQueries, syncnetQueries, custodyGroupQueries} = prioritizePeers(
576
583
  connectedHealthyPeers.map((peer) => {
577
584
  const peerData = this.connectedPeers.get(peer.toString());
@@ -601,6 +608,7 @@ export class PeerManager {
601
608
  this.config,
602
609
  this.metrics
603
610
  );
611
+ prioritizeTimer?.();
604
612
 
605
613
  const queriesMerged: SubnetDiscvQueryMs[] = [];
606
614
  for (const {type, queries} of [
@@ -632,6 +640,7 @@ export class PeerManager {
632
640
  // disconnect first to have more slots before we dial new peers
633
641
  for (const [reason, peers] of peersToDisconnect) {
634
642
  this.metrics?.peersRequestedToDisconnect.inc({reason}, peers.length);
643
+ this.metrics?.peerManager.peersPruned.inc({reason}, peers.length);
635
644
  for (const peer of peers) {
636
645
  void this.goodbyeAndDisconnect(peer, GoodByeReasonCode.TOO_MANY_PEERS);
637
646
  }
@@ -659,6 +668,8 @@ export class PeerManager {
659
668
  }
660
669
  }
661
670
 
671
+ this.metrics?.peerManager.connectedPeersMapSize.set(this.connectedPeers.size);
672
+
662
673
  timer?.();
663
674
 
664
675
  this.logger.debug("peerManager heartbeat result", {
@@ -1,4 +1,5 @@
1
1
  import {GoodByeReasonCode} from "../../../constants/network.js";
2
+ import {NetworkCoreMetrics} from "../../core/metrics.js";
2
3
  import {
3
4
  COOL_DOWN_BEFORE_DECAY_MS,
4
5
  DEFAULT_SCORE,
@@ -23,13 +24,15 @@ export class RealScore implements IPeerScore {
23
24
  /** The final score, computed from the above */
24
25
  private score: number;
25
26
  private lastUpdate: number;
27
+ private readonly metrics: NetworkCoreMetrics | null;
26
28
 
27
- constructor() {
29
+ constructor(metrics: NetworkCoreMetrics | null = null) {
28
30
  this.lodestarScore = DEFAULT_SCORE;
29
31
  this.gossipScore = DEFAULT_SCORE;
30
32
  this.score = DEFAULT_SCORE;
31
33
  this.ignoreNegativeGossipScore = false;
32
34
  this.lastUpdate = Date.now();
35
+ this.metrics = metrics;
33
36
  }
34
37
 
35
38
  isCoolingDown(): boolean {
@@ -136,6 +139,10 @@ export class RealScore implements IPeerScore {
136
139
  this.recomputeScore();
137
140
  const newState = scoreToState(this.score);
138
141
 
142
+ if (prevState !== newState) {
143
+ this.metrics?.peerManager.scoreStateTransitions.inc({from: prevState, to: newState});
144
+ }
145
+
139
146
  if (prevState !== ScoreState.Banned && newState === ScoreState.Banned) {
140
147
  // ban this peer for at least BANNED_BEFORE_DECAY_MS seconds
141
148
  this.lastUpdate = Date.now() + COOL_DOWN_BEFORE_DECAY_MS;
@@ -32,7 +32,9 @@ export class PeerRpcScoreStore implements IPeerRpcScoreStore {
32
32
  constructor(opts: PeerRpcScoreOpts = {}, metrics: NetworkCoreMetrics | null = null, logger: Logger | null = null) {
33
33
  this.metrics = metrics;
34
34
  this.logger = logger;
35
- this.scores = opts.disablePeerScoring ? new MapDef(() => new MaxScore()) : new MapDef(() => new RealScore());
35
+ this.scores = opts.disablePeerScoring
36
+ ? new MapDef(() => new MaxScore())
37
+ : new MapDef(() => new RealScore(this.metrics));
36
38
  }
37
39
 
38
40
  getScore(peer: PeerId): number {
@@ -75,6 +77,8 @@ export class PeerRpcScoreStore implements IPeerRpcScoreStore {
75
77
  }
76
78
 
77
79
  update(): void {
80
+ const timer = this.metrics?.peerManager.scoreUpdateDuration.startTimer();
81
+
78
82
  // Bound size of data structures
79
83
  pruneSetToMax(this.scores, MAX_ENTRIES);
80
84
 
@@ -86,6 +90,9 @@ export class PeerRpcScoreStore implements IPeerRpcScoreStore {
86
90
  this.scores.delete(peerIdStr);
87
91
  }
88
92
  }
93
+
94
+ this.metrics?.peerManager.scoreMapSize.set(this.scores.size);
95
+ timer?.();
89
96
  }
90
97
 
91
98
  updateGossipsubScore(peerId: PeerIdStr, newScore: number, ignore: boolean): void {
@@ -7,6 +7,7 @@ import {MapDef} from "@lodestar/utils";
7
7
  import {shuffle} from "../../../util/shuffle.js";
8
8
  import {sortBy} from "../../../util/sortBy.js";
9
9
  import {NetworkCoreMetrics} from "../../core/metrics.js";
10
+ import {SubnetType} from "../../metadata.js";
10
11
  import {RequestedSubnet} from "./subnetMap.js";
11
12
 
12
13
  /** Target number of peers we'd like to have connected to a given long-lived subnet */
@@ -189,6 +190,18 @@ export function prioritizePeers(
189
190
 
190
191
  const connectedPeerCount = connectedPeers.length;
191
192
 
193
+ // Track the outbound peers invariant (we keep at least OUTBOUND_PEERS_RATIO outbound peers).
194
+ // Report 0 when there are no peers so the gauge doesn't go stale at its last value.
195
+ if (metrics) {
196
+ let outboundPeerCount = 0;
197
+ for (const peer of connectedPeers) {
198
+ if (peer.direction === "outbound") {
199
+ outboundPeerCount++;
200
+ }
201
+ }
202
+ metrics.peerManager.outboundPeersRatio.set(connectedPeerCount > 0 ? outboundPeerCount / connectedPeerCount : 0);
203
+ }
204
+
192
205
  if (connectedPeerCount < targetPeers) {
193
206
  // Need more peers.
194
207
  // Instead of attempting to connect the exact amount necessary this will overshoot a little since the success
@@ -256,6 +269,7 @@ function requestSubnetPeers(
256
269
 
257
270
  for (const {subnet, toSlot} of activeAttnets) {
258
271
  const peersInSubnet = peersPerSubnet.get(subnet) ?? 0;
272
+ metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.attnets}, peersInSubnet);
259
273
  if (peersInSubnet < targetSubnetPeers) {
260
274
  // We need more peers
261
275
  attnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peersInSubnet});
@@ -282,6 +296,7 @@ function requestSubnetPeers(
282
296
 
283
297
  for (const {subnet, toSlot} of activeSyncnets) {
284
298
  const peersInSubnet = peersPerSubnet.get(subnet) ?? 0;
299
+ metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.syncnets}, peersInSubnet);
285
300
  if (peersInSubnet < targetSubnetPeers) {
286
301
  // We need more peers
287
302
  syncnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peersInSubnet});
@@ -1241,6 +1241,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1241
1241
  payloadAttestationMessage.data.payloadPresent,
1242
1242
  payloadAttestationMessage.data.blobDataAvailable
1243
1243
  );
1244
+
1245
+ chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
1246
+ version: config.getForkName(payloadAttestationMessage.data.slot),
1247
+ data: payloadAttestationMessage,
1248
+ });
1244
1249
  },
1245
1250
  [GossipType.execution_payload_bid]: async ({
1246
1251
  gossipData,
@@ -1273,9 +1278,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1273
1278
  const signedProposerPreferences = sszDeserialize(topic, serializedData);
1274
1279
  await validateGossipProposerPreferences(chain, signedProposerPreferences);
1275
1280
 
1276
- chain.proposerPreferencesPool.add(signedProposerPreferences);
1277
1281
  chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1278
- version: ForkName.gloas,
1282
+ version: config.getForkName(signedProposerPreferences.message.proposalSlot),
1279
1283
  data: signedProposerPreferences,
1280
1284
  });
1281
1285
  },
@@ -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
- }