@lodestar/beacon-node 1.45.0-dev.3100972235 → 1.45.0-dev.33dbc1859a

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 (106) 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/blocks/payloadEnvelopeProcessor.d.ts +5 -2
  17. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  18. package/lib/chain/blocks/payloadEnvelopeProcessor.js +17 -17
  19. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  20. package/lib/chain/chain.d.ts +1 -2
  21. package/lib/chain/chain.d.ts.map +1 -1
  22. package/lib/chain/chain.js +1 -3
  23. package/lib/chain/chain.js.map +1 -1
  24. package/lib/chain/interface.d.ts +1 -2
  25. package/lib/chain/interface.d.ts.map +1 -1
  26. package/lib/chain/interface.js.map +1 -1
  27. package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
  28. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  29. package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
  30. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  31. package/lib/chain/options.d.ts +1 -0
  32. package/lib/chain/options.d.ts.map +1 -1
  33. package/lib/chain/options.js +1 -0
  34. package/lib/chain/options.js.map +1 -1
  35. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  36. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  37. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  38. package/lib/chain/seenCache/index.d.ts +0 -1
  39. package/lib/chain/seenCache/index.d.ts.map +1 -1
  40. package/lib/chain/seenCache/index.js +0 -1
  41. package/lib/chain/seenCache/index.js.map +1 -1
  42. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +1 -0
  43. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  44. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +7 -0
  45. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  46. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  47. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
  48. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  49. package/lib/chain/validation/executionPayloadBid.js +8 -7
  50. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  51. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  52. package/lib/chain/validation/proposerPreferences.js +11 -3
  53. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  54. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  55. package/lib/network/gossip/gossipsub.js +2 -1
  56. package/lib/network/gossip/gossipsub.js.map +1 -1
  57. package/lib/network/gossip/topic.d.ts +25 -777
  58. package/lib/network/gossip/topic.d.ts.map +1 -1
  59. package/lib/network/gossip/topic.js +23 -3
  60. package/lib/network/gossip/topic.js.map +1 -1
  61. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  62. package/lib/network/processor/gossipHandlers.js +28 -16
  63. package/lib/network/processor/gossipHandlers.js.map +1 -1
  64. package/lib/sync/range/chain.d.ts +4 -1
  65. package/lib/sync/range/chain.d.ts.map +1 -1
  66. package/lib/sync/range/chain.js +3 -3
  67. package/lib/sync/range/chain.js.map +1 -1
  68. package/lib/sync/range/range.d.ts.map +1 -1
  69. package/lib/sync/range/range.js +10 -0
  70. package/lib/sync/range/range.js.map +1 -1
  71. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  72. package/lib/sync/utils/downloadByRange.js +25 -19
  73. package/lib/sync/utils/downloadByRange.js.map +1 -1
  74. package/lib/util/graffiti.d.ts +15 -0
  75. package/lib/util/graffiti.d.ts.map +1 -1
  76. package/lib/util/graffiti.js +55 -0
  77. package/lib/util/graffiti.js.map +1 -1
  78. package/package.json +14 -14
  79. package/src/api/impl/beacon/pool/index.ts +0 -53
  80. package/src/api/impl/beacon/state/index.ts +52 -1
  81. package/src/api/impl/beacon/state/utils.ts +57 -4
  82. package/src/api/impl/validator/index.ts +54 -5
  83. package/src/chain/blocks/importBlock.ts +1 -1
  84. package/src/chain/blocks/payloadEnvelopeProcessor.ts +17 -17
  85. package/src/chain/chain.ts +0 -3
  86. package/src/chain/interface.ts +0 -2
  87. package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
  88. package/src/chain/options.ts +2 -0
  89. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  90. package/src/chain/seenCache/index.ts +0 -1
  91. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -0
  92. package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
  93. package/src/chain/validation/executionPayloadBid.ts +9 -6
  94. package/src/chain/validation/proposerPreferences.ts +12 -3
  95. package/src/network/gossip/gossipsub.ts +2 -1
  96. package/src/network/gossip/topic.ts +28 -4
  97. package/src/network/processor/gossipHandlers.ts +30 -16
  98. package/src/sync/range/chain.ts +4 -5
  99. package/src/sync/range/range.ts +10 -0
  100. package/src/sync/utils/downloadByRange.ts +26 -19
  101. package/src/util/graffiti.ts +78 -0
  102. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  103. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  104. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  105. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  106. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -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
 
@@ -142,6 +142,7 @@ export function cacheByRangeResponses({
142
142
  const blockRootHex = toRootHex(blockRoot);
143
143
 
144
144
  const existing = updatedBatchBlocks.get(block.message.slot);
145
+ let blockInput: IBlockInput;
145
146
  if (existing) {
146
147
  // In practice this code block shouldn't be reached because we shouldn't be refetching a block we already have, see Batch#getRequests.
147
148
  // Will throw if root hex does not match (meaning we are following the wrong chain)
@@ -155,8 +156,9 @@ export function cacheByRangeResponses({
155
156
  },
156
157
  {throwOnDuplicateAdd: false}
157
158
  );
159
+ blockInput = existing;
158
160
  } else {
159
- const blockInput = cache.getByBlock({
161
+ blockInput = cache.getByBlock({
160
162
  block,
161
163
  blockRootHex,
162
164
  source,
@@ -165,6 +167,21 @@ export function cacheByRangeResponses({
165
167
  });
166
168
  updatedBatchBlocks.set(blockInput.slot, blockInput);
167
169
  }
170
+
171
+ // Seed seenPayloadEnvelopeInputCache the instant we have the block, before the blob loop (or any
172
+ // later step) can throw and abort the batch — otherwise a gloas block would sit in
173
+ // seenBlockInputCache but unseeded here, and payload-by-root sync would later throw "Missing
174
+ // PayloadEnvelopeInput for known block" (see issue #9306). add() is idempotent.
175
+ if (isForkPostGloas(blockInput.forkName)) {
176
+ seenPayloadEnvelopeInputCache.add({
177
+ blockRootHex: blockInput.blockRootHex,
178
+ block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
179
+ forkName: blockInput.forkName,
180
+ sampledColumns: custodyConfig.sampledColumns,
181
+ custodyColumns: custodyConfig.custodyColumns,
182
+ timeCreatedSec: seenTimestampSec,
183
+ });
184
+ }
168
185
  }
169
186
 
170
187
  for (const {blockRoot, blobSidecars} of responses.validatedBlobSidecars ?? []) {
@@ -205,22 +222,6 @@ export function cacheByRangeResponses({
205
222
  }
206
223
  }
207
224
 
208
- // Seed seenPayloadEnvelopeInputCache for every gloas block in the batch, regardless of whether
209
- // the peer returned its envelope. Without this, a block returned without its envelope would be
210
- // imported with no cache entry, and later payload-by-root sync would throw
211
- // "Missing PayloadEnvelopeInput for known block" (see issue #9306).
212
- for (const blockInput of updatedBatchBlocks.values()) {
213
- if (!blockInput.hasBlock() || !isForkPostGloas(blockInput.forkName)) continue;
214
- seenPayloadEnvelopeInputCache.add({
215
- blockRootHex: blockInput.blockRootHex,
216
- block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
217
- forkName: blockInput.forkName,
218
- sampledColumns: custodyConfig.sampledColumns,
219
- custodyColumns: custodyConfig.custodyColumns,
220
- timeCreatedSec: seenTimestampSec,
221
- });
222
- }
223
-
224
225
  let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
225
226
  existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
226
227
  if (downloadedPayloadEnvelopes !== null) {
@@ -229,8 +230,8 @@ export function cacheByRangeResponses({
229
230
  const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
230
231
  const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
231
232
  if (payloadInput === undefined) {
232
- // Unreachable given the loop above seeded an entry for every gloas block in the batch.
233
- // for the parent block, it's populated at BeaconChain init
233
+ // Unreachable given the validatedBlocks loop above seeded an entry for every gloas block in
234
+ // the batch. for the parent block, it's populated at BeaconChain init
234
235
  throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
235
236
  }
236
237
 
@@ -980,6 +981,12 @@ export async function validateColumnsByRangeResponse(
980
981
  missingIndices: prettyPrintIndices(request.columns),
981
982
  })
982
983
  );
984
+ // Gloas: the bid commits to blobs even for payloads that get orphaned, whose columns are
985
+ // pruned and unservable. Skip instead of truncating + re-requesting forever; DA is enforced
986
+ // on envelope processing. Pre-Gloas commitments are canonical, so keep strict.
987
+ if (isForkPostGloas(forkName)) {
988
+ continue;
989
+ }
983
990
  break;
984
991
  }
985
992
 
@@ -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
- }