@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.
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +3 -8
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/blockInput/types.d.ts +11 -0
- package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +1 -1
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.d.ts +12 -3
- package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +101 -96
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +5 -5
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +3 -3
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +1 -1
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +3 -2
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +3 -2
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +3 -2
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/package.json +15 -15
- package/src/chain/archiveStore/archiveStore.ts +5 -5
- package/src/chain/archiveStore/utils/archiveBlocks.ts +4 -5
- package/src/chain/blocks/blockInput/types.ts +12 -0
- package/src/chain/blocks/importBlock.ts +1 -1
- package/src/chain/blocks/writeBlockInputToDb.ts +119 -101
- package/src/chain/chain.ts +20 -8
- package/src/chain/emitter.ts +3 -3
- package/src/chain/regen/regen.ts +1 -1
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +3 -2
- package/src/network/reqresp/handlers/blobSidecarsByRange.ts +3 -2
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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<[
|
|
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<[
|
|
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:
|
|
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:
|
|
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,
|
|
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:
|
|
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(
|
|
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 {
|
|
2
|
-
import {
|
|
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 {
|
|
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,
|
|
15
|
-
const
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
inputType: blockInput.type,
|
|
39
|
-
});
|
|
27
|
+
if (isBlockInputColumns(blockInput)) {
|
|
28
|
+
promises.push(writeDataColumnsToDb.call(this, blockInput));
|
|
29
|
+
}
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
await Promise.all(promises);
|
|
32
|
+
this.logger.debug("Persisted blockInput to db", {slot: blockInput.slot, root: blockInput.blockRootHex});
|
|
33
|
+
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
125
|
+
export async function persistBlockInput(this: BeaconChain, blockInput: IBlockInput): Promise<void> {
|
|
111
126
|
await writeBlockInputToDb
|
|
112
|
-
.call(this,
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
}
|
package/src/chain/chain.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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) {
|
package/src/chain/emitter.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
87
|
-
[ChainEvent.forkChoiceFinalized]: (checkpoint:
|
|
86
|
+
[ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithPayloadStatus) => void;
|
|
87
|
+
[ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithPayloadStatus) => void;
|
|
88
88
|
|
|
89
89
|
[ChainEvent.updateTargetCustodyGroupCount]: (targetGroupCount: number) => void;
|
|
90
90
|
|
package/src/chain/regen/regen.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
82
|
-
const
|
|
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--) {
|