@lodestar/beacon-node 1.43.0-dev.aef3645690 → 1.43.0-dev.e341cdc614
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/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +4 -3
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/GetBlobsTracker.d.ts +1 -1
- package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
- package/lib/chain/GetBlobsTracker.js +1 -2
- package/lib/chain/GetBlobsTracker.js.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
- package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +27 -35
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +10 -8
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.js +1 -1
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +1 -1
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +17 -37
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +13 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/attestationError.d.ts +8 -1
- package/lib/chain/errors/attestationError.d.ts.map +1 -1
- package/lib/chain/errors/attestationError.js +4 -0
- package/lib/chain/errors/attestationError.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +6 -2
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +2 -6
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +0 -6
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/errors.d.ts +1 -11
- package/lib/chain/regen/errors.d.ts.map +1 -1
- package/lib/chain/regen/errors.js +0 -2
- package/lib/chain/regen/errors.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +6 -12
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/queued.d.ts +6 -11
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +8 -40
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +0 -5
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +7 -34
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/stateCache/datastore/db.d.ts +5 -4
- package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/db.js +10 -32
- package/lib/chain/stateCache/datastore/db.js.map +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/file.js +5 -5
- package/lib/chain/stateCache/datastore/file.js.map +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +116 -215
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +8 -15
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/stateCache/types.js.map +1 -1
- package/lib/chain/validation/aggregateAndProof.js +12 -0
- package/lib/chain/validation/aggregateAndProof.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +12 -0
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +2 -2
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -729
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +19 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/validator/index.ts +6 -5
- package/src/chain/GetBlobsTracker.ts +1 -2
- package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +2 -4
- package/src/chain/blocks/importBlock.ts +26 -39
- package/src/chain/blocks/importExecutionPayload.ts +11 -7
- package/src/chain/blocks/index.ts +1 -1
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
- package/src/chain/blocks/types.ts +1 -1
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
- package/src/chain/chain.ts +16 -47
- package/src/chain/emitter.ts +12 -0
- package/src/chain/errors/attestationError.ts +6 -1
- package/src/chain/forkChoice/index.ts +6 -2
- package/src/chain/prepareNextSlot.ts +2 -6
- package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
- package/src/chain/produceBlock/produceBlockBody.ts +0 -6
- package/src/chain/regen/errors.ts +1 -6
- package/src/chain/regen/interface.ts +6 -12
- package/src/chain/regen/queued.ts +12 -48
- package/src/chain/regen/regen.ts +8 -36
- package/src/chain/stateCache/datastore/db.ts +10 -33
- package/src/chain/stateCache/datastore/file.ts +5 -6
- package/src/chain/stateCache/datastore/types.ts +2 -3
- package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
- package/src/chain/stateCache/persistentCheckpointsCache.ts +135 -246
- package/src/chain/stateCache/types.ts +8 -14
- package/src/chain/validation/aggregateAndProof.ts +13 -0
- package/src/chain/validation/attestation.ts +13 -0
- package/src/chain/validation/executionPayloadEnvelope.ts +2 -2
- package/src/network/processor/gossipHandlers.ts +23 -7
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
-
import {CheckpointWithPayloadStatus} from "@lodestar/fork-choice";
|
|
4
3
|
import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
5
4
|
import {Epoch, RootHex, phase0} from "@lodestar/types";
|
|
6
5
|
import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils";
|
|
@@ -10,7 +9,7 @@ import {IClock} from "../../util/clock.js";
|
|
|
10
9
|
import {serializeState} from "../serializeState.js";
|
|
11
10
|
import {CPStateDatastore, DatastoreKey} from "./datastore/index.js";
|
|
12
11
|
import {MapTracker} from "./mapMetrics.js";
|
|
13
|
-
import {BlockStateCache, CacheItemType,
|
|
12
|
+
import {BlockStateCache, CacheItemType, CheckpointHex, CheckpointStateCache} from "./types.js";
|
|
14
13
|
|
|
15
14
|
export type PersistentCheckpointStateCacheOpts = {
|
|
16
15
|
/** Keep max n state epochs in memory, persist the rest to disk */
|
|
@@ -50,22 +49,6 @@ type CacheItem = InMemoryCacheItem | PersistedCacheItem;
|
|
|
50
49
|
|
|
51
50
|
type LoadedStateBytesData = {persistedKey: DatastoreKey; stateBytes: Uint8Array};
|
|
52
51
|
|
|
53
|
-
/** Bitmask for tracking which payload variants exist per root in the epochIndex */
|
|
54
|
-
enum PayloadAvailability {
|
|
55
|
-
NOT_PRESENT = 1,
|
|
56
|
-
PRESENT = 2,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const PAYLOAD_AVAILABILITY_ALL = [PayloadAvailability.NOT_PRESENT, PayloadAvailability.PRESENT] as const;
|
|
60
|
-
|
|
61
|
-
function toPayloadAvailability(payloadPresent: boolean): PayloadAvailability {
|
|
62
|
-
return payloadPresent ? PayloadAvailability.PRESENT : PayloadAvailability.NOT_PRESENT;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function fromPayloadAvailability(flag: PayloadAvailability): boolean {
|
|
66
|
-
return flag === PayloadAvailability.PRESENT;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
52
|
/**
|
|
70
53
|
* Before n-historical states, lodestar keeps all checkpoint states since finalized
|
|
71
54
|
* Since Sep 2024, lodestar stores 3 most recent checkpoint states in memory and the rest on disk. The finalized state
|
|
@@ -118,8 +101,8 @@ const PROCESS_CHECKPOINT_STATES_BPS = 6667;
|
|
|
118
101
|
*/
|
|
119
102
|
export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
120
103
|
private readonly cache: MapTracker<CacheKey, CacheItem>;
|
|
121
|
-
/** Epoch ->
|
|
122
|
-
private readonly epochIndex = new MapDef<Epoch,
|
|
104
|
+
/** Epoch -> Set<blockRoot> */
|
|
105
|
+
private readonly epochIndex = new MapDef<Epoch, Set<RootHex>>(() => new Set<string>());
|
|
123
106
|
private readonly config: BeaconConfig;
|
|
124
107
|
private readonly metrics: Metrics | null | undefined;
|
|
125
108
|
private readonly logger: Logger;
|
|
@@ -215,18 +198,13 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
215
198
|
* - Get block for processing
|
|
216
199
|
* - Regen head state
|
|
217
200
|
*/
|
|
218
|
-
async getOrReload(cp:
|
|
201
|
+
async getOrReload(cp: CheckpointHex): Promise<IBeaconStateView | null> {
|
|
219
202
|
const stateOrStateBytesData = await this.getStateOrLoadDb(cp);
|
|
220
203
|
if (stateOrStateBytesData === null || isBeaconStateView(stateOrStateBytesData)) {
|
|
221
204
|
return stateOrStateBytesData ?? null;
|
|
222
205
|
}
|
|
223
206
|
const {persistedKey, stateBytes} = stateOrStateBytesData;
|
|
224
|
-
const logMeta = {
|
|
225
|
-
epoch: cp.epoch,
|
|
226
|
-
rootHex: cp.rootHex,
|
|
227
|
-
payloadPresent: cp.payloadPresent,
|
|
228
|
-
persistedKey: toHex(persistedKey),
|
|
229
|
-
};
|
|
207
|
+
const logMeta = {persistedKey: toHex(persistedKey)};
|
|
230
208
|
this.logger.debug("Reload: read state successful", logMeta);
|
|
231
209
|
this.metrics?.cpStateCache.stateReloadSecFromSlot.observe(
|
|
232
210
|
this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0
|
|
@@ -264,7 +242,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
264
242
|
// only remove persisted state once we reload successfully
|
|
265
243
|
const cpKey = toCacheKey(cp);
|
|
266
244
|
this.cache.set(cpKey, {type: CacheItemType.inMemory, state: newCachedState, persistedKey});
|
|
267
|
-
this.
|
|
245
|
+
this.epochIndex.getOrDefault(cp.epoch).add(cp.rootHex);
|
|
268
246
|
// don't prune from memory here, call it at the last 1/3 of slot 0 of an epoch
|
|
269
247
|
return newCachedState;
|
|
270
248
|
} catch (e) {
|
|
@@ -276,7 +254,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
276
254
|
/**
|
|
277
255
|
* Return either state or state bytes loaded from db.
|
|
278
256
|
*/
|
|
279
|
-
async getStateOrBytes(cp:
|
|
257
|
+
async getStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null> {
|
|
280
258
|
const stateOrLoadedState = await this.getStateOrLoadDb(cp);
|
|
281
259
|
if (stateOrLoadedState === null || isBeaconStateView(stateOrLoadedState)) {
|
|
282
260
|
return stateOrLoadedState;
|
|
@@ -287,7 +265,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
287
265
|
/**
|
|
288
266
|
* Return either state or state bytes with persisted key loaded from db.
|
|
289
267
|
*/
|
|
290
|
-
async getStateOrLoadDb(cp:
|
|
268
|
+
async getStateOrLoadDb(cp: CheckpointHex): Promise<IBeaconStateView | LoadedStateBytesData | null> {
|
|
291
269
|
const cpKey = toCacheKey(cp);
|
|
292
270
|
const inMemoryState = this.get(cpKey);
|
|
293
271
|
if (inMemoryState) {
|
|
@@ -318,7 +296,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
318
296
|
/**
|
|
319
297
|
* Similar to get() api without reloading from disk
|
|
320
298
|
*/
|
|
321
|
-
get(cpOrKey:
|
|
299
|
+
get(cpOrKey: CheckpointHex | CacheKey): IBeaconStateView | null {
|
|
322
300
|
this.metrics?.cpStateCache.lookups.inc();
|
|
323
301
|
const cpKey = typeof cpOrKey === "string" ? cpOrKey : toCacheKey(cpOrKey);
|
|
324
302
|
const cacheItem = this.cache.get(cpKey);
|
|
@@ -344,11 +322,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
344
322
|
|
|
345
323
|
/**
|
|
346
324
|
* Add a state of a checkpoint to this cache, prune from memory if necessary.
|
|
347
|
-
* @param payloadPresent - For Gloas: true if this is payload state, false if block state.
|
|
348
|
-
* Always true for pre-Gloas.
|
|
349
325
|
*/
|
|
350
|
-
add(cp: phase0.Checkpoint, state: IBeaconStateView
|
|
351
|
-
const cpHex =
|
|
326
|
+
add(cp: phase0.Checkpoint, state: IBeaconStateView): void {
|
|
327
|
+
const cpHex = toCheckpointHex(cp);
|
|
352
328
|
const key = toCacheKey(cpHex);
|
|
353
329
|
const cacheItem = this.cache.get(key);
|
|
354
330
|
this.metrics?.cpStateCache.adds.inc();
|
|
@@ -359,32 +335,27 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
359
335
|
this.logger.verbose("Added checkpoint state to memory but a persisted key existed", {
|
|
360
336
|
epoch: cp.epoch,
|
|
361
337
|
rootHex: cpHex.rootHex,
|
|
362
|
-
payloadPresent,
|
|
363
338
|
persistedKey: toHex(persistedKey),
|
|
364
339
|
});
|
|
365
340
|
} else {
|
|
366
341
|
this.cache.set(key, {type: CacheItemType.inMemory, state});
|
|
367
|
-
this.logger.verbose("Added checkpoint state to memory", {
|
|
368
|
-
epoch: cp.epoch,
|
|
369
|
-
rootHex: cpHex.rootHex,
|
|
370
|
-
payloadPresent,
|
|
371
|
-
});
|
|
342
|
+
this.logger.verbose("Added checkpoint state to memory", {epoch: cp.epoch, rootHex: cpHex.rootHex});
|
|
372
343
|
}
|
|
373
|
-
this.
|
|
344
|
+
this.epochIndex.getOrDefault(cp.epoch).add(cpHex.rootHex);
|
|
374
345
|
this.prunePersistedStates();
|
|
375
346
|
}
|
|
376
347
|
|
|
377
348
|
/**
|
|
378
349
|
* Searches in-memory state for the latest cached state with a `root` without reload, starting with `epoch` and descending
|
|
379
350
|
*/
|
|
380
|
-
getLatest(rootHex: RootHex, maxEpoch: Epoch
|
|
351
|
+
getLatest(rootHex: RootHex, maxEpoch: Epoch): IBeaconStateView | null {
|
|
381
352
|
// sort epochs in descending order, only consider epochs lte `epoch`
|
|
382
353
|
const epochs = Array.from(this.epochIndex.keys())
|
|
383
354
|
.sort((a, b) => b - a)
|
|
384
355
|
.filter((e) => e <= maxEpoch);
|
|
385
356
|
for (const epoch of epochs) {
|
|
386
|
-
if (this.
|
|
387
|
-
const inMemoryClonedState = this.get({rootHex, epoch
|
|
357
|
+
if (this.epochIndex.get(epoch)?.has(rootHex)) {
|
|
358
|
+
const inMemoryClonedState = this.get({rootHex, epoch});
|
|
388
359
|
if (inMemoryClonedState) {
|
|
389
360
|
return inMemoryClonedState;
|
|
390
361
|
}
|
|
@@ -400,24 +371,20 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
400
371
|
* - Get block for processing
|
|
401
372
|
* - Regen head state
|
|
402
373
|
*/
|
|
403
|
-
async getOrReloadLatest(
|
|
404
|
-
rootHex: RootHex,
|
|
405
|
-
maxEpoch: Epoch,
|
|
406
|
-
payloadPresent: boolean
|
|
407
|
-
): Promise<IBeaconStateView | null> {
|
|
374
|
+
async getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null> {
|
|
408
375
|
// sort epochs in descending order, only consider epochs lte `epoch`
|
|
409
376
|
const epochs = Array.from(this.epochIndex.keys())
|
|
410
377
|
.sort((a, b) => b - a)
|
|
411
378
|
.filter((e) => e <= maxEpoch);
|
|
412
379
|
for (const epoch of epochs) {
|
|
413
|
-
if (this.
|
|
380
|
+
if (this.epochIndex.get(epoch)?.has(rootHex)) {
|
|
414
381
|
try {
|
|
415
|
-
const state = await this.getOrReload({rootHex, epoch
|
|
382
|
+
const state = await this.getOrReload({rootHex, epoch});
|
|
416
383
|
if (state) {
|
|
417
384
|
return state;
|
|
418
385
|
}
|
|
419
386
|
} catch (e) {
|
|
420
|
-
this.logger.debug("Error get or reload state", {epoch, rootHex
|
|
387
|
+
this.logger.debug("Error get or reload state", {epoch, rootHex}, e as Error);
|
|
421
388
|
}
|
|
422
389
|
}
|
|
423
390
|
}
|
|
@@ -427,12 +394,10 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
427
394
|
/**
|
|
428
395
|
* Update the precomputed checkpoint and return the number of hits for the
|
|
429
396
|
* previous one (if any).
|
|
430
|
-
* @param payloadPresent - For Gloas: true if head block has FULL payload, false if EMPTY.
|
|
431
|
-
* Always true for pre-Gloas.
|
|
432
397
|
*/
|
|
433
|
-
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch
|
|
398
|
+
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null {
|
|
434
399
|
const previousHits = this.preComputedCheckpointHits;
|
|
435
|
-
this.preComputedCheckpoint = toCacheKey({rootHex, epoch
|
|
400
|
+
this.preComputedCheckpoint = toCacheKey({rootHex, epoch});
|
|
436
401
|
this.preComputedCheckpointHits = 0;
|
|
437
402
|
return previousHits;
|
|
438
403
|
}
|
|
@@ -506,9 +471,6 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
506
471
|
* - 2 then we'll persist {root: b2, epoch n-2} checkpoint state to disk, there are also 2 checkpoint states in memory at epoch n, same to the above (maxEpochsInMemory=1)
|
|
507
472
|
*
|
|
508
473
|
* As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
|
|
509
|
-
*
|
|
510
|
-
* For Gloas: Processes both block state and payload state variants together. The decision of which roots to persist/prune
|
|
511
|
-
* is based on root canonicality (from state's view), not payload presence. Both variants are managed as a unit.
|
|
512
474
|
*/
|
|
513
475
|
async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
|
|
514
476
|
let persistCount = 0;
|
|
@@ -579,7 +541,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
579
541
|
*
|
|
580
542
|
* Use seed state from the block cache if cannot find any seed states within this cache.
|
|
581
543
|
*/
|
|
582
|
-
findSeedStateToReload(reloadedCp:
|
|
544
|
+
findSeedStateToReload(reloadedCp: CheckpointHex): IBeaconStateView {
|
|
583
545
|
const maxEpoch = Math.max(...Array.from(this.epochIndex.keys()));
|
|
584
546
|
const reloadedCpSlot = computeStartSlotAtEpoch(reloadedCp.epoch);
|
|
585
547
|
let firstState: IBeaconStateView | null = null;
|
|
@@ -592,35 +554,31 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
592
554
|
return firstState;
|
|
593
555
|
}
|
|
594
556
|
|
|
595
|
-
for (const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
557
|
+
for (const rootHex of this.epochIndex.get(epoch) || []) {
|
|
558
|
+
const cpKey = toCacheKey({rootHex, epoch});
|
|
559
|
+
const cacheItem = this.cache.get(cpKey);
|
|
560
|
+
if (cacheItem === undefined) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (isInMemoryCacheItem(cacheItem)) {
|
|
564
|
+
const {state} = cacheItem;
|
|
565
|
+
if (firstState === null) {
|
|
566
|
+
firstState = state;
|
|
603
567
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
reloadedCpSlot < state.slot &&
|
|
615
|
-
toRootHex(state.getBlockRootAtSlot(reloadedCpSlot)) === reloadedCp.rootHex
|
|
616
|
-
) {
|
|
617
|
-
this.logger.verbose("Reload: use checkpoint state as seed state", {...cpLog, ...logCtx});
|
|
618
|
-
return state;
|
|
619
|
-
}
|
|
620
|
-
} catch (e) {
|
|
621
|
-
// getBlockRootAtSlot may throw error
|
|
622
|
-
this.logger.debug("Error finding checkpoint state to reload", {...cpLog, ...logCtx}, e as Error);
|
|
568
|
+
const cpLog = {cpEpoch: epoch, cpRoot: rootHex};
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
// amongst states of the same epoch, choose the one with the same view of reloadedCp
|
|
572
|
+
if (
|
|
573
|
+
reloadedCpSlot < state.slot &&
|
|
574
|
+
toRootHex(state.getBlockRootAtSlot(reloadedCpSlot)) === reloadedCp.rootHex
|
|
575
|
+
) {
|
|
576
|
+
this.logger.verbose("Reload: use checkpoint state as seed state", {...cpLog, ...logCtx});
|
|
577
|
+
return state;
|
|
623
578
|
}
|
|
579
|
+
} catch (e) {
|
|
580
|
+
// getBlockRootAtSlot may throw error
|
|
581
|
+
this.logger.debug("Error finding checkpoint state to reload", {...cpLog, ...logCtx}, e as Error);
|
|
624
582
|
}
|
|
625
583
|
}
|
|
626
584
|
}
|
|
@@ -637,31 +595,6 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
637
595
|
this.epochIndex.clear();
|
|
638
596
|
}
|
|
639
597
|
|
|
640
|
-
private addToEpochIndex(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): void {
|
|
641
|
-
const rootMap = this.epochIndex.getOrDefault(epoch);
|
|
642
|
-
rootMap.set(rootHex, (rootMap.get(rootHex) ?? 0) | toPayloadAvailability(payloadPresent));
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
private removeFromEpochIndex(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): void {
|
|
646
|
-
const rootMap = this.epochIndex.get(epoch);
|
|
647
|
-
if (rootMap === undefined) return;
|
|
648
|
-
const existing = rootMap.get(rootHex);
|
|
649
|
-
if (existing === undefined) return;
|
|
650
|
-
const updated = existing & ~toPayloadAvailability(payloadPresent);
|
|
651
|
-
if (updated === 0) {
|
|
652
|
-
rootMap.delete(rootHex);
|
|
653
|
-
if (rootMap.size === 0) {
|
|
654
|
-
this.epochIndex.delete(epoch);
|
|
655
|
-
}
|
|
656
|
-
} else {
|
|
657
|
-
rootMap.set(rootHex, updated);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
private hasPayloadVariant(epoch: Epoch, rootHex: RootHex, payloadPresent: boolean): boolean {
|
|
662
|
-
return Boolean((this.epochIndex.get(epoch)?.get(rootHex) ?? 0) & toPayloadAvailability(payloadPresent));
|
|
663
|
-
}
|
|
664
|
-
|
|
665
598
|
/** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
|
|
666
599
|
dumpSummary(): routes.lodestar.StateCacheItem[] {
|
|
667
600
|
return Array.from(this.cache.keys()).map((key) => {
|
|
@@ -736,7 +669,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
736
669
|
const prevEpochRoot = toRootHex(state.getBlockRootAtSlot(epochBoundarySlot - 1));
|
|
737
670
|
|
|
738
671
|
// for each epoch, usually there are 2 rootHexes respective to the 2 checkpoint states: Previous Root Checkpoint State and Current Root Checkpoint State
|
|
739
|
-
const
|
|
672
|
+
const cpRootHexes = this.epochIndex.get(epoch) ?? [];
|
|
740
673
|
const persistedRootHexes = new Set<RootHex>();
|
|
741
674
|
|
|
742
675
|
// 1) if there is no CRCS, persist PRCS (block 0 of epoch is skipped). In this case prevEpochRoot === epochBoundaryHex
|
|
@@ -745,81 +678,82 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
745
678
|
persistedRootHexes.add(epochBoundaryHex);
|
|
746
679
|
|
|
747
680
|
// 3) persist any states with unknown roots to this state
|
|
748
|
-
for (const rootHex of
|
|
681
|
+
for (const rootHex of cpRootHexes) {
|
|
749
682
|
if (rootHex !== epochBoundaryHex && rootHex !== prevEpochRoot) {
|
|
750
683
|
persistedRootHexes.add(rootHex);
|
|
751
684
|
}
|
|
752
685
|
}
|
|
753
686
|
|
|
754
|
-
for (const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const payloadPresent = fromPayloadAvailability(flag);
|
|
758
|
-
const cpKey = toCacheKey({epoch: epoch, rootHex, payloadPresent});
|
|
759
|
-
const cacheItem = this.cache.get(cpKey);
|
|
687
|
+
for (const rootHex of cpRootHexes) {
|
|
688
|
+
const cpKey = toCacheKey({epoch: epoch, rootHex});
|
|
689
|
+
const cacheItem = this.cache.get(cpKey);
|
|
760
690
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
if
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
this.
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
691
|
+
if (cacheItem !== undefined && isInMemoryCacheItem(cacheItem)) {
|
|
692
|
+
let {persistedKey} = cacheItem;
|
|
693
|
+
const {state} = cacheItem;
|
|
694
|
+
const logMeta = {
|
|
695
|
+
stateSlot: state.slot,
|
|
696
|
+
rootHex,
|
|
697
|
+
epochBoundaryHex,
|
|
698
|
+
persistedKey: persistedKey ? toHex(persistedKey) : "",
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
if (persistedRootHexes.has(rootHex)) {
|
|
702
|
+
if (persistedKey) {
|
|
703
|
+
// we don't care if the checkpoint state is already persisted
|
|
704
|
+
this.logger.verbose("Pruned checkpoint state from memory but no need to persist", logMeta);
|
|
705
|
+
} else {
|
|
706
|
+
// persist and do not update epochIndex
|
|
707
|
+
this.metrics?.cpStateCache.statePersistSecFromSlot.observe(
|
|
708
|
+
this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0
|
|
709
|
+
);
|
|
710
|
+
const cpPersist = {epoch: epoch, root: fromHex(rootHex)};
|
|
711
|
+
// It's not sustainable to allocate ~240MB for each state every epoch, so we use buffer pool to reuse the memory.
|
|
712
|
+
// As monitored on holesky as of Jan 2024:
|
|
713
|
+
// - This does not increase heap allocation while gc time is the same
|
|
714
|
+
// - It helps stabilize persist time and save ~300ms in average (1.5s vs 1.2s)
|
|
715
|
+
// - It also helps the state reload to save ~500ms in average (4.3s vs 3.8s)
|
|
716
|
+
// - Also `serializeState.test.ts` perf test shows a lot of differences allocating ~240MB once vs per state serialization
|
|
717
|
+
const timer = this.metrics?.stateSerializeDuration.startTimer({
|
|
718
|
+
source: AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
|
|
719
|
+
});
|
|
720
|
+
persistedKey = await serializeState(
|
|
721
|
+
state,
|
|
722
|
+
AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE,
|
|
723
|
+
(stateBytes) => {
|
|
724
|
+
timer?.();
|
|
725
|
+
return this.datastore.write(cpPersist, stateBytes);
|
|
726
|
+
},
|
|
727
|
+
this.bufferPool
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
persistCount++;
|
|
731
|
+
this.logger.verbose("Pruned checkpoint state from memory and persisted to disk", {
|
|
732
|
+
...logMeta,
|
|
733
|
+
persistedKey: toHex(persistedKey),
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
// overwrite cpKey, this means the state is deleted from memory
|
|
737
|
+
this.cache.set(cpKey, {type: CacheItemType.persisted, value: persistedKey});
|
|
738
|
+
} else {
|
|
739
|
+
if (persistedKey) {
|
|
740
|
+
// persisted file will be eventually deleted by the archive task
|
|
741
|
+
// this also means the state is deleted from memory
|
|
808
742
|
this.cache.set(cpKey, {type: CacheItemType.persisted, value: persistedKey});
|
|
743
|
+
// do not update epochIndex
|
|
809
744
|
} else {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
this.removeFromEpochIndex(epoch, rootHex, payloadPresent);
|
|
745
|
+
// delete the state from memory
|
|
746
|
+
this.cache.delete(cpKey);
|
|
747
|
+
const rootSet = this.epochIndex.get(epoch);
|
|
748
|
+
if (rootSet) {
|
|
749
|
+
rootSet.delete(rootHex);
|
|
750
|
+
if (rootSet.size === 0) {
|
|
751
|
+
this.epochIndex.delete(epoch);
|
|
752
|
+
}
|
|
819
753
|
}
|
|
820
|
-
this.metrics?.cpStateCache.statePruneFromMemoryCount.inc();
|
|
821
|
-
this.logger.verbose("Pruned checkpoint state from memory", logMeta);
|
|
822
754
|
}
|
|
755
|
+
this.metrics?.cpStateCache.statePruneFromMemoryCount.inc();
|
|
756
|
+
this.logger.verbose("Pruned checkpoint state from memory", logMeta);
|
|
823
757
|
}
|
|
824
758
|
}
|
|
825
759
|
}
|
|
@@ -832,40 +766,26 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
832
766
|
*/
|
|
833
767
|
private async deleteAllEpochItems(epoch: Epoch): Promise<void> {
|
|
834
768
|
let persistCount = 0;
|
|
835
|
-
const
|
|
836
|
-
for (const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
await this.datastore.remove(persistedKey);
|
|
847
|
-
persistCount++;
|
|
848
|
-
this.metrics?.cpStateCache.persistedStateRemoveCount.inc();
|
|
849
|
-
}
|
|
769
|
+
const rootHexes = this.epochIndex.get(epoch) || [];
|
|
770
|
+
for (const rootHex of rootHexes) {
|
|
771
|
+
const key = toCacheKey({rootHex, epoch});
|
|
772
|
+
const cacheItem = this.cache.get(key);
|
|
773
|
+
|
|
774
|
+
if (cacheItem) {
|
|
775
|
+
const persistedKey = isPersistedCacheItem(cacheItem) ? cacheItem.value : cacheItem.persistedKey;
|
|
776
|
+
if (persistedKey) {
|
|
777
|
+
await this.datastore.remove(persistedKey);
|
|
778
|
+
persistCount++;
|
|
779
|
+
this.metrics?.cpStateCache.persistedStateRemoveCount.inc();
|
|
850
780
|
}
|
|
851
|
-
this.cache.delete(key);
|
|
852
|
-
this.logger.verbose("Pruned checkpoint state", {
|
|
853
|
-
epoch,
|
|
854
|
-
rootHex,
|
|
855
|
-
payloadPresent,
|
|
856
|
-
type: cacheItem ? (isPersistedCacheItem(cacheItem) ? "persisted" : "in-memory") : "missing",
|
|
857
|
-
});
|
|
858
781
|
}
|
|
782
|
+
this.cache.delete(key);
|
|
859
783
|
}
|
|
860
784
|
this.epochIndex.delete(epoch);
|
|
861
|
-
this.logger.verbose("Pruned
|
|
785
|
+
this.logger.verbose("Pruned checkpoint states for epoch", {
|
|
862
786
|
epoch,
|
|
863
787
|
persistCount,
|
|
864
|
-
|
|
865
|
-
.flatMap(([rootHex, bitmask]) =>
|
|
866
|
-
PAYLOAD_AVAILABILITY_ALL.filter((f) => bitmask & f).map((f) => `${rootHex}:${fromPayloadAvailability(f)}`)
|
|
867
|
-
)
|
|
868
|
-
.join(","),
|
|
788
|
+
rootHexes: Array.from(rootHexes).join(","),
|
|
869
789
|
});
|
|
870
790
|
}
|
|
871
791
|
|
|
@@ -916,57 +836,26 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
916
836
|
}
|
|
917
837
|
}
|
|
918
838
|
|
|
919
|
-
export function
|
|
839
|
+
export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex {
|
|
920
840
|
return {
|
|
921
841
|
epoch: checkpoint.epoch,
|
|
922
842
|
rootHex: toRootHex(checkpoint.root),
|
|
923
|
-
payloadPresent,
|
|
924
843
|
};
|
|
925
844
|
}
|
|
926
845
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
* Maps PayloadStatus enum to boolean payloadPresent.
|
|
930
|
-
* @throws Error if checkpoint has PENDING payload status (ambiguous which variant to use)
|
|
931
|
-
*/
|
|
932
|
-
export function fcCheckpointToHexPayload(checkpoint: CheckpointWithPayloadStatus): CheckpointHexPayload {
|
|
933
|
-
const PayloadStatus = {PENDING: 0, EMPTY: 1, FULL: 2} as const;
|
|
934
|
-
|
|
935
|
-
if (checkpoint.payloadStatus === PayloadStatus.PENDING) {
|
|
936
|
-
throw Error(
|
|
937
|
-
`Cannot convert checkpoint with PENDING payload status at epoch ${checkpoint.epoch} root ${checkpoint.rootHex}`
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
return {
|
|
942
|
-
epoch: checkpoint.epoch,
|
|
943
|
-
rootHex: checkpoint.rootHex,
|
|
944
|
-
payloadPresent: checkpoint.payloadStatus === PayloadStatus.FULL,
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
export function toCheckpointKey(cp: CheckpointHexPayload): string {
|
|
949
|
-
return `${cp.rootHex}:${cp.epoch}:${cp.payloadPresent}`;
|
|
846
|
+
export function toCheckpointKey(cp: CheckpointHex): string {
|
|
847
|
+
return `${cp.rootHex}:${cp.epoch}`;
|
|
950
848
|
}
|
|
951
849
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
* Format: `{rootHex}_{epoch}_{payloadPresent}`
|
|
955
|
-
*/
|
|
956
|
-
function toCacheKey(cp: CheckpointHexPayload): CacheKey {
|
|
957
|
-
return `${cp.rootHex}_${cp.epoch}_${cp.payloadPresent}`;
|
|
850
|
+
function toCacheKey(cp: CheckpointHex): CacheKey {
|
|
851
|
+
return `${cp.rootHex}_${cp.epoch}`;
|
|
958
852
|
}
|
|
959
853
|
|
|
960
|
-
function fromCacheKey(key: CacheKey):
|
|
961
|
-
const
|
|
962
|
-
const rootHex = parts[0];
|
|
963
|
-
const epoch = Number(parts[1]);
|
|
964
|
-
// For backward compatibility with old format (rootHex_epoch), default to true
|
|
965
|
-
const payloadPresent = parts.length > 2 ? parts[2] === "true" : true;
|
|
854
|
+
function fromCacheKey(key: CacheKey): CheckpointHex {
|
|
855
|
+
const [rootHex, epoch] = key.split("_");
|
|
966
856
|
return {
|
|
967
857
|
rootHex,
|
|
968
|
-
epoch,
|
|
969
|
-
payloadPresent,
|
|
858
|
+
epoch: Number(epoch),
|
|
970
859
|
};
|
|
971
860
|
}
|
|
972
861
|
|
|
@@ -2,11 +2,7 @@ import {routes} from "@lodestar/api";
|
|
|
2
2
|
import {IBeaconStateView} from "@lodestar/state-transition";
|
|
3
3
|
import {Epoch, RootHex, phase0} from "@lodestar/types";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Checkpoint hex representation for state cache keys.
|
|
7
|
-
* Extends CheckpointWithHex (from fork-choice) with payloadPresent.
|
|
8
|
-
*/
|
|
9
|
-
export type CheckpointHexPayload = {epoch: Epoch; rootHex: RootHex; payloadPresent: boolean};
|
|
5
|
+
export type CheckpointHex = {epoch: Epoch; rootHex: RootHex};
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* Lodestar currently keeps two state caches around.
|
|
@@ -35,8 +31,6 @@ export interface BlockStateCache {
|
|
|
35
31
|
size: number;
|
|
36
32
|
prune(headStateRootHex: RootHex): void;
|
|
37
33
|
deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
|
|
38
|
-
/** Upgrade cache capacity for Gloas fork (2x states for block + payload states) */
|
|
39
|
-
upgradeToGloas(): void;
|
|
40
34
|
dumpSummary(): routes.lodestar.StateCacheItem[];
|
|
41
35
|
/** Expose beacon states stored in cache. Use with caution */
|
|
42
36
|
getStates(): IterableIterator<IBeaconStateView>;
|
|
@@ -65,13 +59,13 @@ export interface BlockStateCache {
|
|
|
65
59
|
*/
|
|
66
60
|
export interface CheckpointStateCache {
|
|
67
61
|
init?: () => Promise<void>;
|
|
68
|
-
getOrReload(cp:
|
|
69
|
-
getStateOrBytes(cp:
|
|
70
|
-
get(cpOrKey:
|
|
71
|
-
add(cp: phase0.Checkpoint, state: IBeaconStateView
|
|
72
|
-
getLatest(rootHex: RootHex, maxEpoch: Epoch
|
|
73
|
-
getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch
|
|
74
|
-
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch
|
|
62
|
+
getOrReload(cp: CheckpointHex): Promise<IBeaconStateView | null>;
|
|
63
|
+
getStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null>;
|
|
64
|
+
get(cpOrKey: CheckpointHex | string): IBeaconStateView | null;
|
|
65
|
+
add(cp: phase0.Checkpoint, state: IBeaconStateView): void;
|
|
66
|
+
getLatest(rootHex: RootHex, maxEpoch: Epoch): IBeaconStateView | null;
|
|
67
|
+
getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
|
|
68
|
+
updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
|
|
75
69
|
prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
|
|
76
70
|
pruneFinalized(finalizedEpoch: Epoch): void;
|
|
77
71
|
processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;
|
|
@@ -90,6 +90,19 @@ async function validateAggregateAndProof(
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// [REJECT] If `aggregate.data.index == 1` (payload present for a past
|
|
94
|
+
// block), the execution payload for `block` passes validation.
|
|
95
|
+
// [IGNORE] When `aggregate.data.index == 1` (payload present for a past block),
|
|
96
|
+
// the corresponding execution payload for `block` has been seen (a client MAY queue
|
|
97
|
+
// attestations for processing once the payload is retrieved and SHOULD request the
|
|
98
|
+
// payload envelope via `ExecutionPayloadEnvelopesByRoot`).
|
|
99
|
+
if (block !== null && attData.index === 1 && !chain.seenPayloadEnvelope(toRootHex(attData.beaconBlockRoot))) {
|
|
100
|
+
throw new AttestationError(GossipAction.IGNORE, {
|
|
101
|
+
code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN,
|
|
102
|
+
beaconBlockRoot: toRootHex(attData.beaconBlockRoot),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
// [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate)
|
|
94
107
|
committeeIndex = (aggregate as electra.Attestation).committeeBits.getSingleTrueBit();
|
|
95
108
|
if (committeeIndex === null) {
|