@lodestar/beacon-node 1.41.0-dev.f36ec31497 → 1.41.0-dev.f9a3a81538

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 (43) hide show
  1. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  2. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  3. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +3 -8
  4. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  5. package/lib/chain/archiveStore/utils/archiveBlocks.js +1 -1
  6. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  7. package/lib/chain/blocks/blockInput/types.d.ts +11 -0
  8. package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
  9. package/lib/chain/blocks/importBlock.js +1 -1
  10. package/lib/chain/blocks/importBlock.js.map +1 -1
  11. package/lib/chain/blocks/writeBlockInputToDb.d.ts +12 -3
  12. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  13. package/lib/chain/blocks/writeBlockInputToDb.js +101 -96
  14. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  15. package/lib/chain/chain.d.ts +1 -1
  16. package/lib/chain/chain.d.ts.map +1 -1
  17. package/lib/chain/chain.js +5 -5
  18. package/lib/chain/chain.js.map +1 -1
  19. package/lib/chain/emitter.d.ts +3 -3
  20. package/lib/chain/emitter.d.ts.map +1 -1
  21. package/lib/chain/regen/regen.js +1 -1
  22. package/lib/chain/regen/regen.js.map +1 -1
  23. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  24. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +3 -2
  25. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  26. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  27. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +3 -2
  28. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  29. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  30. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +3 -2
  31. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  32. package/package.json +15 -15
  33. package/src/chain/archiveStore/archiveStore.ts +5 -5
  34. package/src/chain/archiveStore/utils/archiveBlocks.ts +4 -5
  35. package/src/chain/blocks/blockInput/types.ts +12 -0
  36. package/src/chain/blocks/importBlock.ts +1 -1
  37. package/src/chain/blocks/writeBlockInputToDb.ts +119 -101
  38. package/src/chain/chain.ts +20 -8
  39. package/src/chain/emitter.ts +3 -3
  40. package/src/chain/regen/regen.ts +1 -1
  41. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +3 -2
  42. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +3 -2
  43. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +3 -2
@@ -1,4 +1,4 @@
1
- import {CheckpointWithHex} from "@lodestar/fork-choice";
1
+ import {CheckpointWithPayloadStatus} from "@lodestar/fork-choice";
2
2
  import {LoggerNode} from "@lodestar/logger/node";
3
3
  import {ForkSeq} from "@lodestar/params";
4
4
  import {Checkpoint} from "@lodestar/types/phase0";
@@ -44,7 +44,7 @@ export enum ArchiveStoreTask {
44
44
  */
45
45
  export class ArchiveStore {
46
46
  private archiveMode: ArchiveMode;
47
- private jobQueue: JobItemQueue<[CheckpointWithHex], void>;
47
+ private jobQueue: JobItemQueue<[CheckpointWithPayloadStatus], void>;
48
48
 
49
49
  private archiveDataEpochs?: number;
50
50
  private readonly statesArchiverStrategy: StateArchiveStrategy;
@@ -67,7 +67,7 @@ export class ArchiveStore {
67
67
  this.archiveMode = opts.archiveMode;
68
68
  this.archiveDataEpochs = opts.archiveDataEpochs;
69
69
 
70
- this.jobQueue = new JobItemQueue<[CheckpointWithHex], void>(this.processFinalizedCheckpoint, {
70
+ this.jobQueue = new JobItemQueue<[CheckpointWithPayloadStatus], void>(this.processFinalizedCheckpoint, {
71
71
  maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LENGTH,
72
72
  signal,
73
73
  });
@@ -168,7 +168,7 @@ export class ArchiveStore {
168
168
  //-------------------------------------------------------------------------
169
169
  // Event handlers
170
170
  //-------------------------------------------------------------------------
171
- private onFinalizedCheckpoint = (finalized: CheckpointWithHex): void => {
171
+ private onFinalizedCheckpoint = (finalized: CheckpointWithPayloadStatus): void => {
172
172
  this.jobQueue.push(finalized).catch((e) => {
173
173
  if (!isQueueErrorAborted(e)) {
174
174
  this.logger.error("Error queuing finalized checkpoint", {epoch: finalized.epoch}, e as Error);
@@ -189,7 +189,7 @@ export class ArchiveStore {
189
189
  });
190
190
  };
191
191
 
192
- private processFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise<void> => {
192
+ private processFinalizedCheckpoint = async (finalized: CheckpointWithPayloadStatus): Promise<void> => {
193
193
  try {
194
194
  const finalizedEpoch = finalized.epoch;
195
195
  const finalizedFork = this.chain.config.getForkSeqAtEpoch(finalizedEpoch);
@@ -1,10 +1,10 @@
1
1
  import path from "node:path";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {KeyValue} from "@lodestar/db";
4
- import {IForkChoice} from "@lodestar/fork-choice";
4
+ import {CheckpointWithPayloadStatus, IForkChoice} from "@lodestar/fork-choice";
5
5
  import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
6
6
  import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
7
- import {Epoch, RootHex, Slot} from "@lodestar/types";
7
+ import {Epoch, Slot} from "@lodestar/types";
8
8
  import {Logger, fromAsync, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
9
9
  import {IBeaconDb} from "../../../db/index.js";
10
10
  import {BlockArchiveBatchPutBinaryItem} from "../../../db/repositories/index.js";
@@ -19,7 +19,6 @@ const BLOCK_BATCH_SIZE = 256;
19
19
  const BLOB_SIDECAR_BATCH_SIZE = 32;
20
20
 
21
21
  type BlockRootSlot = {slot: Slot; root: Uint8Array};
22
- type CheckpointHex = {epoch: Epoch; rootHex: RootHex};
23
22
 
24
23
  /**
25
24
  * Persist orphaned block to disk
@@ -53,7 +52,7 @@ export async function archiveBlocks(
53
52
  forkChoice: IForkChoice,
54
53
  lightclientServer: LightClientServer | undefined,
55
54
  logger: Logger,
56
- finalizedCheckpoint: CheckpointHex,
55
+ finalizedCheckpoint: CheckpointWithPayloadStatus,
57
56
  currentEpoch: Epoch,
58
57
  archiveDataEpochs?: number,
59
58
  persistOrphanedBlocks?: boolean,
@@ -62,7 +61,7 @@ export async function archiveBlocks(
62
61
  // Use fork choice to determine the blocks to archive and delete
63
62
  // getAllAncestorBlocks response includes the finalized block, so it's also moved to the cold db
64
63
  const {ancestors: finalizedCanonicalBlocks, nonAncestors: finalizedNonCanonicalBlocks} =
65
- forkChoice.getAllAncestorAndNonAncestorBlocks(finalizedCheckpoint.rootHex);
64
+ forkChoice.getAllAncestorAndNonAncestorBlocks(finalizedCheckpoint.rootHex, finalizedCheckpoint.payloadStatus);
66
65
 
67
66
  // NOTE: The finalized block will be exactly the first block of `epoch` or previous
68
67
  const finalizedPostDeneb = finalizedCheckpoint.epoch >= config.DENEB_FORK_EPOCH;
@@ -100,6 +100,18 @@ export type MissingColumnMeta = {
100
100
  versionedHashes: VersionedHashes;
101
101
  };
102
102
 
103
+ /**
104
+ * Minimal interface required to write data columns to the DB.
105
+ * Used by `writeDataColumnsToDb` and designed to be reusable across forks (e.g. Fulu, Gloas).
106
+ */
107
+ export interface IDataColumnsInput {
108
+ readonly slot: Slot;
109
+ readonly blockRootHex: string;
110
+ getCustodyColumns(): fulu.DataColumnSidecars;
111
+ hasComputedAllData(): boolean;
112
+ waitForComputedAllData(timeout: number, signal?: AbortSignal): Promise<fulu.DataColumnSidecars>;
113
+ }
114
+
103
115
  /**
104
116
  * This is used to validate that BlockInput implementations follow some minimal subset of operations
105
117
  * and that adding a new implementation won't break consumers that rely on this subset.
@@ -95,7 +95,7 @@ export async function importBlock(
95
95
  // Without this, a supernode syncing from behind can accumulate many blocks worth of column
96
96
  // data in memory (up to 128 columns per block) causing OOM before persistence catches up.
97
97
  await this.unfinalizedBlockWrites.waitForSpace();
98
- this.unfinalizedBlockWrites.push([blockInput]).catch((e) => {
98
+ this.unfinalizedBlockWrites.push(blockInput).catch((e) => {
99
99
  if (!isQueueErrorAborted(e)) {
100
100
  this.logger.error("Error pushing block to unfinalized write queue", {slot: blockSlot}, e as Error);
101
101
  }
@@ -1,7 +1,15 @@
1
- import {fulu} from "@lodestar/types";
2
- import {prettyPrintIndices, toRootHex} from "@lodestar/utils";
1
+ import {ForkPostDeneb, isForkPostDeneb} from "@lodestar/params";
2
+ import {SignedBeaconBlock} from "@lodestar/types";
3
+ import {fromHex, toRootHex} from "@lodestar/utils";
4
+ import {getBlobKzgCommitments} from "../../util/dataColumns.js";
3
5
  import {BeaconChain} from "../chain.js";
4
- import {IBlockInput, isBlockInputBlobs, isBlockInputColumns} from "./blockInput/index.js";
6
+ import {
7
+ IBlockInput,
8
+ IDataColumnsInput,
9
+ isBlockInputBlobs,
10
+ isBlockInputColumns,
11
+ isBlockInputNoData,
12
+ } from "./blockInput/index.js";
5
13
  import {BLOB_AVAILABILITY_TIMEOUT} from "./verifyBlocksDataAvailability.js";
6
14
 
7
15
  /**
@@ -10,129 +18,139 @@ import {BLOB_AVAILABILITY_TIMEOUT} from "./verifyBlocksDataAvailability.js";
10
18
  *
11
19
  * This operation may be performed before, during or after importing to the fork-choice. As long as errors
12
20
  * are handled properly for eventual consistency.
21
+ *
22
+ * Block+blobs (pre-fulu) and data columns (fulu+) are written in parallel.
13
23
  */
14
- export async function writeBlockInputToDb(this: BeaconChain, blocksInputs: IBlockInput[]): Promise<void> {
15
- const fnPromises: Promise<void>[] = [];
16
- // track slots for logging
17
- const slots: number[] = [];
18
-
19
- for (const blockInput of blocksInputs) {
20
- const block = blockInput.getBlock();
21
- const slot = block.message.slot;
22
- slots.push(slot);
23
- const blockRoot = this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message);
24
- const blockRootHex = toRootHex(blockRoot);
25
- const blockBytes = this.serializedCache.get(block);
26
- if (blockBytes) {
27
- // skip serializing data if we already have it
28
- this.metrics?.importBlock.persistBlockWithSerializedDataCount.inc();
29
- fnPromises.push(this.db.block.putBinary(this.db.block.getId(block), blockBytes));
30
- } else {
31
- this.metrics?.importBlock.persistBlockNoSerializedDataCount.inc();
32
- fnPromises.push(this.db.block.add(block));
33
- }
24
+ export async function writeBlockInputToDb(this: BeaconChain, blockInput: IBlockInput): Promise<void> {
25
+ const promises: Promise<void>[] = [writeBlockAndBlobsToDb.call(this, blockInput)];
34
26
 
35
- this.logger.debug("Persist block to hot DB", {
36
- slot: block.message.slot,
37
- root: blockRootHex,
38
- inputType: blockInput.type,
39
- });
27
+ if (isBlockInputColumns(blockInput)) {
28
+ promises.push(writeDataColumnsToDb.call(this, blockInput));
29
+ }
40
30
 
41
- if (!blockInput.hasAllData()) {
42
- await blockInput.waitForAllData(BLOB_AVAILABILITY_TIMEOUT);
43
- }
31
+ await Promise.all(promises);
32
+ this.logger.debug("Persisted blockInput to db", {slot: blockInput.slot, root: blockInput.blockRootHex});
33
+ }
44
34
 
45
- // NOTE: Old data is pruned on archive
46
- if (isBlockInputColumns(blockInput)) {
47
- if (!blockInput.hasComputedAllData()) {
48
- // Supernodes may only have a subset of the data columns by the time the block begins to be imported
49
- // because full data availability can be assumed after NUMBER_OF_COLUMNS / 2 columns are available.
50
- // Here, however, all data columns must be fully available/reconstructed before persisting to the DB.
51
- await blockInput.waitForComputedAllData(BLOB_AVAILABILITY_TIMEOUT).catch(() => {
52
- this.logger.debug("Failed to wait for computed all data", {slot, blockRoot: blockRootHex});
53
- });
54
- }
35
+ async function writeBlockAndBlobsToDb(this: BeaconChain, blockInput: IBlockInput): Promise<void> {
36
+ const block = blockInput.getBlock();
37
+ const slot = block.message.slot;
38
+ const blockRoot = this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block.message);
39
+ const blockRootHex = toRootHex(blockRoot);
40
+ const numBlobs = isForkPostDeneb(blockInput.forkName)
41
+ ? getBlobKzgCommitments(blockInput.forkName, block as SignedBeaconBlock<ForkPostDeneb>).length
42
+ : undefined;
43
+ const fnPromises: Promise<void>[] = [];
55
44
 
56
- const {custodyColumns} = this.custodyConfig;
57
- const blobsLen = (block.message as fulu.BeaconBlock).body.blobKzgCommitments.length;
58
- let dataColumnsLen: number;
59
- if (blobsLen === 0) {
60
- dataColumnsLen = 0;
61
- } else {
62
- dataColumnsLen = custodyColumns.length;
63
- }
45
+ const blockBytes = this.serializedCache.get(block);
46
+ if (blockBytes) {
47
+ // skip serializing data if we already have it
48
+ this.metrics?.importBlock.persistBlockWithSerializedDataCount.inc();
49
+ fnPromises.push(this.db.block.putBinary(this.db.block.getId(block), blockBytes));
50
+ } else {
51
+ this.metrics?.importBlock.persistBlockNoSerializedDataCount.inc();
52
+ fnPromises.push(this.db.block.add(block));
53
+ }
64
54
 
65
- const dataColumnSidecars = blockInput.getCustodyColumns();
66
- if (dataColumnSidecars.length !== dataColumnsLen) {
67
- this.logger.debug(
68
- `Invalid dataColumnSidecars=${dataColumnSidecars.length} for custody expected custodyColumnsLen=${dataColumnsLen}`
69
- );
70
- }
55
+ this.logger.debug("Persist block to hot DB", {slot, root: blockRootHex, inputType: blockInput.type, numBlobs});
71
56
 
72
- const binaryPuts = [];
73
- const nonbinaryPuts = [];
74
- for (const dataColumnSidecar of dataColumnSidecars) {
75
- // skip reserializing column if we already have it
76
- const serialized = this.serializedCache.get(dataColumnSidecar);
77
- if (serialized) {
78
- binaryPuts.push({key: dataColumnSidecar.index, value: serialized});
79
- } else {
80
- nonbinaryPuts.push(dataColumnSidecar);
57
+ if (isBlockInputBlobs(blockInput)) {
58
+ fnPromises.push(
59
+ (async () => {
60
+ if (!blockInput.hasAllData()) {
61
+ await blockInput.waitForAllData(BLOB_AVAILABILITY_TIMEOUT);
81
62
  }
82
- }
83
- fnPromises.push(this.db.dataColumnSidecar.putManyBinary(blockRoot, binaryPuts));
84
- fnPromises.push(this.db.dataColumnSidecar.putMany(blockRoot, nonbinaryPuts));
85
- this.logger.debug("Persisted dataColumnSidecars to hot DB", {
86
- slot: block.message.slot,
87
- root: blockRootHex,
88
- dataColumnSidecars: dataColumnSidecars.length,
89
- numBlobs: blobsLen,
90
- custodyColumns: custodyColumns.length,
91
- });
92
- } else if (isBlockInputBlobs(blockInput)) {
93
- const blobSidecars = blockInput.getBlobs();
94
- fnPromises.push(this.db.blobSidecars.add({blockRoot, slot: block.message.slot, blobSidecars}));
95
- this.logger.debug("Persisted blobSidecars to hot DB", {
96
- blobsLen: blobSidecars.length,
97
- slot: block.message.slot,
98
- root: blockRootHex,
99
- });
100
- }
63
+ const blobSidecars = blockInput.getBlobs();
64
+ await this.db.blobSidecars.add({blockRoot, slot, blobSidecars});
65
+ this.logger.debug("Persisted blobSidecars to hot DB", {
66
+ slot,
67
+ root: blockRootHex,
68
+ numBlobs: blobSidecars.length,
69
+ });
70
+ })()
71
+ );
72
+ }
101
73
 
102
- await Promise.all(fnPromises);
103
- this.logger.debug("Persisted blocksInput to db", {
104
- blocksInput: blocksInputs.length,
105
- slots: prettyPrintIndices(slots),
74
+ await Promise.all(fnPromises);
75
+ }
76
+
77
+ /**
78
+ * Persists data columns to DB for a given block. Accepts a narrow sub-interface of IBlockInput
79
+ * so it can be reused across forks (e.g. Fulu, Gloas).
80
+ *
81
+ * NOTE: Old data is pruned on archive.
82
+ */
83
+ export async function writeDataColumnsToDb(this: BeaconChain, blockInput: IDataColumnsInput): Promise<void> {
84
+ const {slot, blockRootHex} = blockInput;
85
+ const blockRoot = fromHex(blockRootHex);
86
+
87
+ if (!blockInput.hasComputedAllData()) {
88
+ // Supernodes may only have a subset of the data columns by the time the block begins to be imported
89
+ // because full data availability can be assumed after NUMBER_OF_COLUMNS / 2 columns are available.
90
+ // Here, however, all data columns must be fully available/reconstructed before persisting to the DB.
91
+ await blockInput.waitForComputedAllData(BLOB_AVAILABILITY_TIMEOUT).catch(() => {
92
+ this.logger.debug("Failed to wait for computed all data", {slot, blockRoot: blockRootHex});
106
93
  });
107
94
  }
95
+
96
+ const {custodyColumns} = this.custodyConfig;
97
+ const dataColumnSidecars = blockInput.getCustodyColumns();
98
+
99
+ const binaryPuts: {key: number; value: Uint8Array}[] = [];
100
+ const nonbinaryPuts = [];
101
+ for (const dataColumnSidecar of dataColumnSidecars) {
102
+ // skip reserializing column if we already have it
103
+ const serialized = this.serializedCache.get(dataColumnSidecar);
104
+ if (serialized) {
105
+ binaryPuts.push({key: dataColumnSidecar.index, value: serialized});
106
+ } else {
107
+ nonbinaryPuts.push(dataColumnSidecar);
108
+ }
109
+ }
110
+
111
+ await Promise.all([
112
+ this.db.dataColumnSidecar.putManyBinary(blockRoot, binaryPuts),
113
+ this.db.dataColumnSidecar.putMany(blockRoot, nonbinaryPuts),
114
+ ]);
115
+
116
+ this.logger.debug("Persisted dataColumnSidecars to hot DB", {
117
+ slot,
118
+ root: blockRootHex,
119
+ dataColumnSidecars: dataColumnSidecars.length,
120
+ custodyColumns: custodyColumns.length,
121
+ numBlobs: dataColumnSidecars[0]?.column.length,
122
+ });
108
123
  }
109
124
 
110
- export async function persistBlockInputs(this: BeaconChain, blockInputs: IBlockInput[]): Promise<void> {
125
+ export async function persistBlockInput(this: BeaconChain, blockInput: IBlockInput): Promise<void> {
111
126
  await writeBlockInputToDb
112
- .call(this, blockInputs)
127
+ .call(this, blockInput)
113
128
  .catch((e) => {
114
129
  this.logger.debug(
115
130
  "Error persisting block input in hot db",
116
131
  {
117
- count: blockInputs.length,
118
- slot: blockInputs[0].slot,
119
- root: blockInputs[0].blockRootHex,
132
+ slot: blockInput.slot,
133
+ root: blockInput.blockRootHex,
120
134
  },
121
135
  e
122
136
  );
123
137
  })
124
138
  .finally(() => {
125
- for (const blockInput of blockInputs) {
126
- this.seenBlockInputCache.prune(blockInput.blockRootHex);
127
- }
139
+ this.seenBlockInputCache.prune(blockInput.blockRootHex);
128
140
  // Without forcefully clearing this cache, we would rely on WeakMap to evict memory which is not reliable.
129
141
  // Clear here (after the DB write) so that writeBlockInputToDb can still use the cached serialized bytes.
130
- this.serializedCache.clear();
131
- if (blockInputs.length === 1) {
132
- this.logger.debug("Pruned block input", {
133
- slot: blockInputs[0].slot,
134
- root: blockInputs[0].blockRootHex,
135
- });
142
+ //
143
+ // For Gloas (BlockInputNoData), the execution payload and columns arrive separately after the beacon block.
144
+ // Do NOT clear the cache here — it must remain available for writeDataColumnsToDb when the payload arrives.
145
+ // The cache is cleared in the Gloas payload persistence path instead.
146
+ if (!isBlockInputNoData(blockInput)) {
147
+ // TODO: enhance this SerializedCache for Gloas because payload may not come
148
+ // see https://github.com/ChainSafe/lodestar/pull/8974#discussion_r2885598229
149
+ this.serializedCache.clear();
136
150
  }
151
+ this.logger.debug("Pruned block input", {
152
+ slot: blockInput.slot,
153
+ root: blockInput.blockRootHex,
154
+ });
137
155
  });
138
156
  }
@@ -2,7 +2,13 @@ import path from "node:path";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz";
4
4
  import {BeaconConfig} from "@lodestar/config";
5
- import {CheckpointWithHex, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
5
+ import {
6
+ CheckpointWithHex,
7
+ CheckpointWithPayloadStatus,
8
+ IForkChoice,
9
+ ProtoBlock,
10
+ UpdateHeadOpt,
11
+ } from "@lodestar/fork-choice";
6
12
  import {LoggerNode} from "@lodestar/logger/node";
7
13
  import {
8
14
  BUILDER_INDEX_SELF_BUILD,
@@ -77,7 +83,7 @@ import {CheckpointBalancesCache} from "./balancesCache.js";
77
83
  import {BeaconProposerCache} from "./beaconProposerCache.js";
78
84
  import {IBlockInput, isBlockInputBlobs, isBlockInputColumns} from "./blocks/blockInput/index.js";
79
85
  import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
80
- import {persistBlockInputs} from "./blocks/writeBlockInputToDb.ts";
86
+ import {persistBlockInput} from "./blocks/writeBlockInputToDb.ts";
81
87
  import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
82
88
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
83
89
  import {ChainEvent, ChainEventEmitter} from "./emitter.js";
@@ -164,7 +170,7 @@ export class BeaconChain implements IBeaconChain {
164
170
  readonly lightClientServer?: LightClientServer;
165
171
  readonly reprocessController: ReprocessController;
166
172
  readonly archiveStore: ArchiveStore;
167
- readonly unfinalizedBlockWrites: JobItemQueue<[IBlockInput[]], void>;
173
+ readonly unfinalizedBlockWrites: JobItemQueue<[IBlockInput], void>;
168
174
 
169
175
  // Ops pool
170
176
  readonly attestationPool: AttestationPool;
@@ -429,7 +435,7 @@ export class BeaconChain implements IBeaconChain {
429
435
  );
430
436
 
431
437
  this.unfinalizedBlockWrites = new JobItemQueue(
432
- persistBlockInputs.bind(this),
438
+ persistBlockInput.bind(this),
433
439
  {
434
440
  maxLength: DEFAULT_MAX_PENDING_UNFINALIZED_BLOCK_WRITES,
435
441
  signal,
@@ -1186,7 +1192,7 @@ export class BeaconChain implements IBeaconChain {
1186
1192
  * @param blockState state that declares justified checkpoint `checkpoint`
1187
1193
  */
1188
1194
  private justifiedBalancesGetter(
1189
- checkpoint: CheckpointWithHex,
1195
+ checkpoint: CheckpointWithPayloadStatus,
1190
1196
  blockState: CachedBeaconStateAllForks
1191
1197
  ): EffectiveBalanceIncrements {
1192
1198
  this.metrics?.balancesCache.requests.inc();
@@ -1225,7 +1231,7 @@ export class BeaconChain implements IBeaconChain {
1225
1231
  * @param blockState state that declares justified checkpoint `checkpoint`
1226
1232
  */
1227
1233
  private closestJustifiedBalancesStateToCheckpoint(
1228
- checkpoint: CheckpointWithHex,
1234
+ checkpoint: CheckpointWithPayloadStatus,
1229
1235
  blockState: CachedBeaconStateAllForks
1230
1236
  ): {state: CachedBeaconStateAllForks; stateId: string; shouldWarn: boolean} {
1231
1237
  const state = this.regen.getCheckpointStateSync(checkpoint);
@@ -1239,7 +1245,10 @@ export class BeaconChain implements IBeaconChain {
1239
1245
  }
1240
1246
 
1241
1247
  // Find a state in the same branch of checkpoint at same epoch. Balances should exactly the same
1242
- for (const descendantBlock of this.forkChoice.forwardIterateDescendants(checkpoint.rootHex)) {
1248
+ for (const descendantBlock of this.forkChoice.forwardIterateDescendants(
1249
+ checkpoint.rootHex,
1250
+ checkpoint.payloadStatus
1251
+ )) {
1243
1252
  if (computeEpochAtSlot(descendantBlock.slot) === checkpoint.epoch) {
1244
1253
  const descendantBlockState = this.regen.getStateSync(descendantBlock.stateRoot);
1245
1254
  if (descendantBlockState) {
@@ -1255,7 +1264,10 @@ export class BeaconChain implements IBeaconChain {
1255
1264
 
1256
1265
  // Find a state in the same branch of checkpoint at a latter epoch. Balances are not the same, but should be close
1257
1266
  // Note: must call .forwardIterateDescendants() again since nodes are not sorted
1258
- for (const descendantBlock of this.forkChoice.forwardIterateDescendants(checkpoint.rootHex)) {
1267
+ for (const descendantBlock of this.forkChoice.forwardIterateDescendants(
1268
+ checkpoint.rootHex,
1269
+ checkpoint.payloadStatus
1270
+ )) {
1259
1271
  if (computeEpochAtSlot(descendantBlock.slot) > checkpoint.epoch) {
1260
1272
  const descendantBlockState = this.regen.getStateSync(descendantBlock.stateRoot);
1261
1273
  if (descendantBlockState) {
@@ -1,7 +1,7 @@
1
1
  import {EventEmitter} from "node:events";
2
2
  import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {routes} from "@lodestar/api";
4
- import {CheckpointWithPayload} from "@lodestar/fork-choice";
4
+ import {CheckpointWithPayloadStatus} from "@lodestar/fork-choice";
5
5
  import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
6
6
  import {DataColumnSidecars, RootHex, deneb, phase0} from "@lodestar/types";
7
7
  import {PeerIdStr} from "../util/peerId.js";
@@ -83,8 +83,8 @@ export type ChainEventData = {
83
83
  export type IChainEvents = ApiEvents & {
84
84
  [ChainEvent.checkpoint]: (checkpoint: phase0.Checkpoint, state: CachedBeaconStateAllForks) => void;
85
85
 
86
- [ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithPayload) => void;
87
- [ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithPayload) => void;
86
+ [ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithPayloadStatus) => void;
87
+ [ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithPayloadStatus) => void;
88
88
 
89
89
  [ChainEvent.updateTargetCustodyGroupCount]: (targetGroupCount: number) => void;
90
90
 
@@ -158,7 +158,7 @@ export class StateRegenerator implements IStateRegeneratorInternal {
158
158
 
159
159
  const getSeedStateTimer = this.modules.metrics?.regenGetState.getSeedState.startTimer({caller});
160
160
  // iterateAncestorBlocks only returns ancestor blocks, not the block itself
161
- for (const b of this.modules.forkChoice.iterateAncestorBlocks(block.blockRoot)) {
161
+ for (const b of this.modules.forkChoice.iterateAncestorBlocks(block.blockRoot, block.payloadStatus)) {
162
162
  state = this.modules.blockStateCache.get(b.stateRoot);
163
163
  if (state) {
164
164
  break;
@@ -47,9 +47,10 @@ export async function* onBeaconBlocksByRange(
47
47
 
48
48
  // Non-finalized range of blocks
49
49
  if (endSlot > finalizedSlot) {
50
- const headRoot = chain.forkChoice.getHeadRoot();
50
+ const headBlock = chain.forkChoice.getHead();
51
+ const headRoot = headBlock.blockRoot;
51
52
  // TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
52
- const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot);
53
+ const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
53
54
  // getAllAncestorBlocks response includes the head node, so it's the full chain.
54
55
 
55
56
  // Iterate head chain with ascending block numbers
@@ -34,9 +34,10 @@ export async function* onBlobSidecarsByRange(
34
34
 
35
35
  // Non-finalized range of blobs
36
36
  if (endSlot > finalizedSlot) {
37
- const headRoot = chain.forkChoice.getHeadRoot();
37
+ const headBlock = chain.forkChoice.getHead();
38
+ const headRoot = headBlock.blockRoot;
38
39
  // TODO DENEB: forkChoice should mantain an array of canonical blocks, and change only on reorg
39
- const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot);
40
+ const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
40
41
 
41
42
  // Iterate head chain with ascending block numbers
42
43
  for (let i = headChain.length - 1; i >= 0; i--) {
@@ -78,8 +78,9 @@ export async function* onDataColumnSidecarsByRange(
78
78
 
79
79
  // Non-finalized range of columns
80
80
  if (endSlot > finalizedSlot) {
81
- const headRoot = chain.forkChoice.getHeadRoot();
82
- const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot);
81
+ const headBlock = chain.forkChoice.getHead();
82
+ const headRoot = headBlock.blockRoot;
83
+ const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
83
84
 
84
85
  // Iterate head chain with ascending block numbers
85
86
  for (let i = headChain.length - 1; i >= 0; i--) {