@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.
- package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +13 -23
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/chain.d.ts +2 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +3 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +1 -0
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +4 -0
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/db/beacon.d.ts +2 -0
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +3 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/buckets.d.ts +2 -1
- package/lib/db/buckets.d.ts.map +1 -1
- package/lib/db/buckets.js +1 -0
- package/lib/db/buckets.js.map +1 -1
- package/lib/db/interface.d.ts +2 -0
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
- package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
- package/lib/db/repositories/earliestAvailableSlot.js +19 -0
- package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
- package/lib/metrics/metrics/lodestar.d.ts +1 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +4 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/node/nodejs.d.ts +3 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -1
- package/lib/node/nodejs.js.map +1 -1
- package/package.json +15 -15
- package/src/chain/archiveStore/archiveStore.ts +18 -24
- package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
- package/src/chain/chain.ts +4 -2
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/shufflingCache.ts +5 -0
- package/src/db/beacon.ts +3 -0
- package/src/db/buckets.ts +1 -0
- package/src/db/interface.ts +2 -0
- package/src/db/repositories/earliestAvailableSlot.ts +19 -0
- package/src/metrics/metrics/lodestar.ts +4 -0
- 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
|
|
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
|
|
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
|
|
774
|
-
|
|
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.
|
|
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
|
-
//
|
|
790
|
-
return 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 {
|
package/src/db/interface.ts
CHANGED
|
@@ -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: {
|
package/src/node/nodejs.ts
CHANGED
|
@@ -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,
|