@lodestar/beacon-node 1.50.0-dev.9c7f3c203f → 1.50.0-dev.9fd806c446

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 (56) hide show
  1. package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
  2. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  3. package/lib/chain/archiveStore/archiveStore.js +13 -23
  4. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  5. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
  6. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  7. package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
  8. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  9. package/lib/chain/chain.d.ts +2 -1
  10. package/lib/chain/chain.d.ts.map +1 -1
  11. package/lib/chain/chain.js +3 -3
  12. package/lib/chain/chain.js.map +1 -1
  13. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
  14. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  15. package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
  16. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  17. package/lib/chain/shufflingCache.d.ts +1 -0
  18. package/lib/chain/shufflingCache.d.ts.map +1 -1
  19. package/lib/chain/shufflingCache.js +4 -0
  20. package/lib/chain/shufflingCache.js.map +1 -1
  21. package/lib/db/beacon.d.ts +2 -0
  22. package/lib/db/beacon.d.ts.map +1 -1
  23. package/lib/db/beacon.js +3 -0
  24. package/lib/db/beacon.js.map +1 -1
  25. package/lib/db/buckets.d.ts +2 -1
  26. package/lib/db/buckets.d.ts.map +1 -1
  27. package/lib/db/buckets.js +1 -0
  28. package/lib/db/buckets.js.map +1 -1
  29. package/lib/db/interface.d.ts +2 -0
  30. package/lib/db/interface.d.ts.map +1 -1
  31. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
  32. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  33. package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
  34. package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
  35. package/lib/db/repositories/earliestAvailableSlot.js +19 -0
  36. package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
  37. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  38. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  39. package/lib/metrics/metrics/lodestar.js +4 -0
  40. package/lib/metrics/metrics/lodestar.js.map +1 -1
  41. package/lib/node/nodejs.d.ts +3 -1
  42. package/lib/node/nodejs.d.ts.map +1 -1
  43. package/lib/node/nodejs.js +2 -1
  44. package/lib/node/nodejs.js.map +1 -1
  45. package/package.json +15 -15
  46. package/src/chain/archiveStore/archiveStore.ts +18 -24
  47. package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
  48. package/src/chain/chain.ts +4 -2
  49. package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
  50. package/src/chain/shufflingCache.ts +5 -0
  51. package/src/db/beacon.ts +3 -0
  52. package/src/db/buckets.ts +1 -0
  53. package/src/db/interface.ts +2 -0
  54. package/src/db/repositories/earliestAvailableSlot.ts +19 -0
  55. package/src/metrics/metrics/lodestar.ts +4 -0
  56. package/src/node/nodejs.ts +4 -0
@@ -30,7 +30,7 @@ import {
30
30
  isStatePostGloas,
31
31
  } from "@lodestar/state-transition";
32
32
  import {Attestation, Epoch, RootHex, Slot, electra, isElectraAttestation, phase0, ssz} from "@lodestar/types";
33
- import {MapDef, assert, toRootHex} from "@lodestar/utils";
33
+ import {Logger, MapDef, assert, toRootHex} from "@lodestar/utils";
34
34
  import {Metrics} from "../../metrics/metrics.js";
35
35
  import {IntersectResult, intersectUint8Arrays} from "../../util/bitArray.js";
36
36
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
@@ -58,7 +58,7 @@ export type AttestationsConsolidation = {
58
58
 
59
59
  /**
60
60
  * This function returns not seen participation for a given epoch and slot and committee index.
61
- * Return null if all validators are seen or no info to check.
61
+ * Return an empty set if all validators are seen, or null if the committee cannot be computed (e.g. missing shuffling).
62
62
  */
63
63
  type GetNotSeenValidatorsFn = (epoch: Epoch, slot: Slot, committeeIndex: number) => Set<number> | null;
64
64
 
@@ -140,7 +140,8 @@ export class AggregatedAttestationPool {
140
140
 
141
141
  constructor(
142
142
  private readonly config: BeaconConfig,
143
- private readonly metrics: Metrics | null = null
143
+ private readonly metrics: Metrics | null = null,
144
+ private readonly logger: Logger | null = null
144
145
  ) {
145
146
  metrics?.opPool.aggregatedAttestationPool.attDataPerSlot.addCollect(() => this.onScrapeMetrics(metrics));
146
147
  }
@@ -244,6 +245,7 @@ export class AggregatedAttestationPool {
244
245
  const consolidations = new Map<AttestationsConsolidation, number>();
245
246
  let scannedSlots = 0;
246
247
  let stopReason: ScannedSlotsTerminationReason | null = null;
248
+ const missingShufflingCommitteesByEpoch = new Map<Epoch, number>();
247
249
  slot: for (const slot of slots) {
248
250
  const attestationGroupByIndexByDataHash = this.attestationGroupByIndexByDataHexBySlot.get(slot);
249
251
  // should not happen
@@ -295,7 +297,12 @@ export class AggregatedAttestationPool {
295
297
 
296
298
  for (const [committeeIndex, attestationGroup] of attestationGroupByIndex.entries()) {
297
299
  const notSeenCommitteeMembers = notSeenValidatorsFn(epoch, slot, committeeIndex);
298
- if (notSeenCommitteeMembers === null || notSeenCommitteeMembers.size === 0) {
300
+ if (notSeenCommitteeMembers === null) {
301
+ // skip this committee instead of failing the whole block production, other epochs may still have shuffling
302
+ missingShufflingCommitteesByEpoch.set(epoch, (missingShufflingCommitteesByEpoch.get(epoch) ?? 0) + 1);
303
+ continue;
304
+ }
305
+ if (notSeenCommitteeMembers.size === 0) {
299
306
  this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.seenCommittees.inc();
300
307
  continue;
301
308
  }
@@ -386,6 +393,18 @@ export class AggregatedAttestationPool {
386
393
  scannedSlots++;
387
394
  }
388
395
 
396
+ for (const [epoch, missingCommittees] of missingShufflingCommitteesByEpoch) {
397
+ this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.missingShufflingCommittees.inc(
398
+ missingCommittees
399
+ );
400
+ this.logger?.debug("Missing shuffling when packing attestations, skipped committees", {
401
+ slot: stateSlot,
402
+ epoch,
403
+ decisionRoot: state.getShufflingDecisionRoot(epoch),
404
+ missingCommittees,
405
+ });
406
+ }
407
+
389
408
  this.metrics?.opPool.aggregatedAttestationPool.packedAttestations.totalConsolidations.set(consolidations.size);
390
409
 
391
410
  const sortedConsolidationsByScore = Array.from(consolidations.entries())
@@ -770,13 +789,18 @@ export function getNotSeenValidatorsFn(
770
789
  }
771
790
  const cacheKey = slot + "_" + committeeIndex;
772
791
  let notSeenCommitteeMembers = cachedNotSeenValidators.get(cacheKey);
773
- if (notSeenCommitteeMembers != null) {
774
- // if all validators are seen then return null, we don't need to check for any attestations of same committee again
775
- return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
792
+ if (notSeenCommitteeMembers !== undefined) {
793
+ return notSeenCommitteeMembers;
776
794
  }
777
795
 
778
796
  const decisionRoot = state.getShufflingDecisionRoot(computeEpochAtSlot(slot));
779
- const committee = shufflingCache.getBeaconCommittee(epoch, decisionRoot, slot, committeeIndex);
797
+ const committee = shufflingCache.getBeaconCommitteeOrNull(epoch, decisionRoot, slot, committeeIndex);
798
+ if (committee === null) {
799
+ // attestations in the pool were validated against a cached shuffling of the previous or current epoch,
800
+ // so a miss is a ShufflingCache bug. Return null so the caller tracks it via metrics/logs without failing
801
+ // block production. Don't fall back to the state's shuffling, that would hide the bug
802
+ return null;
803
+ }
780
804
  notSeenCommitteeMembers = new Set<number>();
781
805
  for (const [i, validatorIndex] of committee.entries()) {
782
806
  // no need to check flagIsTimelySource as if validator is not seen, it's participation status is 0
@@ -786,8 +810,8 @@ export function getNotSeenValidatorsFn(
786
810
  }
787
811
  }
788
812
  cachedNotSeenValidators.set(cacheKey, notSeenCommitteeMembers);
789
- // if all validators are seen then return null, we don't need to check for any attestations of same committee again
790
- return notSeenCommitteeMembers.size === 0 ? null : notSeenCommitteeMembers;
813
+ // an empty set means all validators are seen, we don't need to check for any attestations of same committee again
814
+ return notSeenCommitteeMembers;
791
815
  };
792
816
  }
793
817
 
@@ -205,6 +205,11 @@ export class ShufflingCache {
205
205
  return this.getBeaconCommittees(epoch, decisionRoot, slot, [index])[0];
206
206
  }
207
207
 
208
+ getBeaconCommitteeOrNull(epoch: number, decisionRoot: string, slot: Slot, index: CommitteeIndex): Uint32Array | null {
209
+ const shuffling = this.getSync(epoch, decisionRoot);
210
+ return shuffling === null ? null : getBeaconCommittees(shuffling, slot, [index])[0];
211
+ }
212
+
208
213
  getBeaconCommittees(epoch: number, decisionRoot: string, slot: Slot, indices: CommitteeIndex[]): Uint32Array[] {
209
214
  const shuffling = this.getShufflingOrThrow(epoch, decisionRoot);
210
215
  return getBeaconCommittees(shuffling, slot, indices);
package/src/db/beacon.ts CHANGED
@@ -8,6 +8,7 @@ import {FlatFileStore} from "./flatFileStore/flatFileStore.js";
8
8
  import type {FlatFileStoreMetrics} from "./flatFileStore/metrics.js";
9
9
  import {IBeaconDb} from "./interface.js";
10
10
  import {CheckpointStateRepository} from "./repositories/checkpointState.js";
11
+ import {EarliestAvailableSlotRepository} from "./repositories/earliestAvailableSlot.js";
11
12
  import {
12
13
  AttesterSlashingRepository,
13
14
  BLSToExecutionChangeRepository,
@@ -48,6 +49,7 @@ export class BeaconDb implements IBeaconDb {
48
49
 
49
50
  stateArchive: StateArchiveRepository;
50
51
  checkpointState: CheckpointStateRepository;
52
+ earliestAvailableSlot: EarliestAvailableSlotRepository;
51
53
 
52
54
  voluntaryExit: VoluntaryExitRepository;
53
55
  proposerSlashing: ProposerSlashingRepository;
@@ -83,6 +85,7 @@ export class BeaconDb implements IBeaconDb {
83
85
 
84
86
  this.stateArchive = new StateArchiveRepository(config, db);
85
87
  this.checkpointState = new CheckpointStateRepository(config, db);
88
+ this.earliestAvailableSlot = new EarliestAvailableSlotRepository(db);
86
89
  this.voluntaryExit = new VoluntaryExitRepository(config, db);
87
90
  this.blsToExecutionChange = new BLSToExecutionChangeRepository(config, db);
88
91
  this.proposerSlashing = new ProposerSlashingRepository(config, db);
package/src/db/buckets.ts CHANGED
@@ -72,6 +72,7 @@ export enum Bucket {
72
72
 
73
73
  gloas_executionPayloadEnvelope = 59, // GLOAS BeaconBlockRoot -> SignedExecutionPayloadEnvelope
74
74
  gloas_executionPayloadEnvelopeArchive = 60, // GLOAS Slot -> SignedExecutionPayloadEnvelope, or 0x00 + SignedExecutionPayloadHeaderEnvelope
75
+ earliestAvailableSlot = 61, // singleton -> Slot
75
76
  }
76
77
 
77
78
  export function getBucketNameByValue<T extends Bucket>(enumValue: T): keyof typeof Bucket {
@@ -3,6 +3,7 @@ import {Slot} from "@lodestar/types";
3
3
  import type {IDataColumnStore} from "./dataColumnStore.js";
4
4
  import type {FlatFileStoreMetrics} from "./flatFileStore/metrics.js";
5
5
  import {CheckpointStateRepository} from "./repositories/checkpointState.js";
6
+ import {EarliestAvailableSlotRepository} from "./repositories/earliestAvailableSlot.js";
6
7
  import {
7
8
  AttesterSlashingRepository,
8
9
  BLSToExecutionChangeRepository,
@@ -46,6 +47,7 @@ export interface IBeaconDb {
46
47
  stateArchive: StateArchiveRepository;
47
48
  // checkpoint states
48
49
  checkpointState: CheckpointStateRepository;
50
+ earliestAvailableSlot: EarliestAvailableSlotRepository;
49
51
 
50
52
  // op pool
51
53
  voluntaryExit: VoluntaryExitRepository;
@@ -0,0 +1,19 @@
1
+ import {Db, encodeKey} from "@lodestar/db";
2
+ import {Slot, ssz} from "@lodestar/types";
3
+ import {Bucket, getBucketNameByValue} from "../buckets.js";
4
+
5
+ export class EarliestAvailableSlotRepository {
6
+ private readonly key = encodeKey(Bucket.earliestAvailableSlot, 0);
7
+ private readonly opts = {bucketId: getBucketNameByValue(Bucket.earliestAvailableSlot)};
8
+
9
+ constructor(private readonly db: Db) {}
10
+
11
+ async get(): Promise<Slot | null> {
12
+ const bytes = await this.db.get(this.key, this.opts);
13
+ return bytes === null ? null : ssz.Slot.deserialize(bytes);
14
+ }
15
+
16
+ async set(slot: Slot): Promise<void> {
17
+ await this.db.put(this.key, ssz.Slot.serialize(slot), this.opts);
18
+ }
19
+ }
@@ -1281,6 +1281,10 @@ export function createLodestarMetrics(
1281
1281
  name: "lodestar_oppool_aggregated_attestation_pool_packed_attestations_seen_committees_total",
1282
1282
  help: "Total number of committees for which all members are seen when producing packed attestations",
1283
1283
  }),
1284
+ missingShufflingCommittees: register.gauge({
1285
+ name: "lodestar_oppool_aggregated_attestation_pool_packed_attestations_missing_shuffling_committees_total",
1286
+ help: "Total number of committees skipped due to missing shuffling when producing packed attestations",
1287
+ }),
1284
1288
  },
1285
1289
  },
1286
1290
  attestationPool: {
@@ -14,6 +14,7 @@ import {BeaconConfig} from "@lodestar/config";
14
14
  import type {LoggerNode} from "@lodestar/logger/node";
15
15
  import {ZERO_HASH_HEX} from "@lodestar/params";
16
16
  import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
17
+ import {Slot} from "@lodestar/types";
17
18
  import {sleep, toRootHex} from "@lodestar/utils";
18
19
  import {ProcessShutdownCallback} from "@lodestar/validator";
19
20
  import {BeaconRestApiServer, getApi} from "../api/index.js";
@@ -61,6 +62,7 @@ export type BeaconNodeInitModules = {
61
62
  peerStoreDir?: string;
62
63
  anchorState: IBeaconStateView;
63
64
  isAnchorStateFinalized: boolean;
65
+ earliestAvailableSlot: Slot;
64
66
  metricsRegistries?: Registry[];
65
67
  };
66
68
 
@@ -159,6 +161,7 @@ export class BeaconNode {
159
161
  peerStoreDir,
160
162
  anchorState,
161
163
  isAnchorStateFinalized,
164
+ earliestAvailableSlot,
162
165
  metricsRegistries = [],
163
166
  }: BeaconNodeInitModules): Promise<T> {
164
167
  if (hasher.name !== "hashtree") {
@@ -266,6 +269,7 @@ export class BeaconNode {
266
269
  validatorMonitor,
267
270
  anchorState,
268
271
  isAnchorStateFinalized,
272
+ earliestAvailableSlot,
269
273
  executionEngine: initializeExecutionEngine(executionEngineOpts, {
270
274
  metrics,
271
275
  signal,