@lodestar/beacon-node 1.48.0-dev.0dbea0de1b → 1.48.0-dev.27e5bfcdb6
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 +4 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +3 -2
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +5 -6
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +10 -6
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +14 -14
- package/src/chain/archiveStore/archiveStore.ts +6 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
- package/src/chain/blocks/importBlock.ts +4 -2
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/validation/payloadAttestationMessage.ts +8 -9
- package/src/metrics/metrics/lodestar.ts +6 -0
- package/src/network/processor/gossipHandlers.ts +15 -9
|
@@ -8,6 +8,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";
|
|
11
|
+
import {Metrics} from "../../../metrics/metrics.js";
|
|
11
12
|
import {ensureDir, writeIfNotExist} from "../../../util/file.js";
|
|
12
13
|
import {BlockRootHex} from "../../../util/sszBytes.js";
|
|
13
14
|
import {LightClientServer} from "../../lightClient/index.js";
|
|
@@ -20,6 +21,16 @@ const BLOB_SIDECAR_BATCH_SIZE = 32;
|
|
|
20
21
|
|
|
21
22
|
type BlockRootSlot = {slot: Slot; root: Uint8Array};
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Why a finalized-canonical block was imported after the attestation cutoff.
|
|
26
|
+
*/
|
|
27
|
+
export enum LateCanonicalBlockReason {
|
|
28
|
+
// received on time, but finished importing after the attestation cutoff
|
|
29
|
+
SlowImport = "slow_import",
|
|
30
|
+
// block reached this node late on the network thread
|
|
31
|
+
LateReceive = "late_receive",
|
|
32
|
+
}
|
|
33
|
+
|
|
23
34
|
/**
|
|
24
35
|
* Persist orphaned block to disk
|
|
25
36
|
*/
|
|
@@ -54,6 +65,8 @@ export async function archiveBlocks(
|
|
|
54
65
|
logger: Logger,
|
|
55
66
|
finalizedCheckpoint: CheckpointWithHex,
|
|
56
67
|
currentEpoch: Epoch,
|
|
68
|
+
metrics: Metrics | null,
|
|
69
|
+
isNodeSynced: boolean,
|
|
57
70
|
archiveDataEpochs?: number,
|
|
58
71
|
persistOrphanedBlocks?: boolean,
|
|
59
72
|
persistOrphanedBlocksDir?: string
|
|
@@ -76,6 +89,29 @@ export async function archiveBlocks(
|
|
|
76
89
|
|
|
77
90
|
const logCtx = {currentEpoch, finalizedEpoch: finalizedCheckpoint.epoch, finalizedRoot: finalizedCheckpoint.rootHex};
|
|
78
91
|
|
|
92
|
+
// `finalizedCanonicalBlocks` is newest -> oldest; its LAST element is the previous-finalized
|
|
93
|
+
// boundary block, already audited on the prior run, so exclude it to avoid double counting.
|
|
94
|
+
if (isNodeSynced && finalizedCanonicalBlocks.length > 1) {
|
|
95
|
+
const lateBlocks = finalizedCanonicalBlocks.slice(0, -1).filter((block) => !block.importedTimely);
|
|
96
|
+
if (lateBlocks.length > 0) {
|
|
97
|
+
let slowImport = 0;
|
|
98
|
+
for (const block of lateBlocks) {
|
|
99
|
+
// received late => late_receive; otherwise we had it on time but were slow to import => slow_import
|
|
100
|
+
const reason = block.timeliness ? LateCanonicalBlockReason.SlowImport : LateCanonicalBlockReason.LateReceive;
|
|
101
|
+
if (block.timeliness) slowImport++;
|
|
102
|
+
metrics?.importBlock.lateCanonicalBlock.inc({reason});
|
|
103
|
+
}
|
|
104
|
+
const lateSlots = lateBlocks.map((block) => block.slot).sort((a, b) => a - b);
|
|
105
|
+
logger.verbose("Late imported canonical blocks", {
|
|
106
|
+
...logCtx,
|
|
107
|
+
count: lateBlocks.length,
|
|
108
|
+
slowImport,
|
|
109
|
+
lateReceive: lateBlocks.length - slowImport,
|
|
110
|
+
slotRange: prettyPrintIndices(lateSlots),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
79
115
|
if (finalizedCanonicalBlockRoots.length > 0) {
|
|
80
116
|
const migratedSlots = await migrateBlocksFromHotToColdDb(db, logger, finalizedCanonicalBlockRoots);
|
|
81
117
|
logger.verbose("Migrated blocks from hot DB to cold DB", {
|
|
@@ -298,7 +334,7 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, logger: Logger, block
|
|
|
298
334
|
]);
|
|
299
335
|
for (const entry of canonicalBlockEntries) migratedSlots.push(entry.slot);
|
|
300
336
|
}
|
|
301
|
-
// Ancestor walk is newest
|
|
337
|
+
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
302
338
|
return migratedSlots.sort((a, b) => a - b);
|
|
303
339
|
}
|
|
304
340
|
|
|
@@ -437,7 +473,7 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
437
473
|
await Promise.all(promises);
|
|
438
474
|
}
|
|
439
475
|
|
|
440
|
-
// Ancestor walk is newest
|
|
476
|
+
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
441
477
|
return migratedSlots.sort((a, b) => a - b);
|
|
442
478
|
}
|
|
443
479
|
|
|
@@ -485,7 +521,7 @@ async function migrateExecutionPayloadEnvelopesFromHotToColdDb(
|
|
|
485
521
|
]);
|
|
486
522
|
|
|
487
523
|
// Slots are ascending in hot-db key order — sort to guarantee `prettyPrintIndices` output is clean
|
|
488
|
-
// regardless of ancestor-walk order (newest
|
|
524
|
+
// regardless of ancestor-walk order (newest to oldest).
|
|
489
525
|
return envelopeEntries.map((entry) => entry.key).sort((a, b) => a - b);
|
|
490
526
|
}
|
|
491
527
|
|
|
@@ -85,7 +85,7 @@ export async function importBlock(
|
|
|
85
85
|
const currentEpoch = computeEpochAtSlot(currentSlot);
|
|
86
86
|
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
87
87
|
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
|
|
88
|
-
const
|
|
88
|
+
const receiveDelaySec =
|
|
89
89
|
fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
|
|
90
90
|
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
|
|
91
91
|
const fork = this.config.getForkSeq(blockSlot);
|
|
@@ -122,10 +122,12 @@ export async function importBlock(
|
|
|
122
122
|
executionStatus = parentBlock.executionStatus;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const importDelaySec = this.clock.secFromSlot(blockSlot);
|
|
125
126
|
const blockSummary = this.forkChoice.onBlock(
|
|
126
127
|
block.message,
|
|
127
128
|
postState,
|
|
128
|
-
|
|
129
|
+
receiveDelaySec,
|
|
130
|
+
importDelaySec,
|
|
129
131
|
currentSlot,
|
|
130
132
|
executionStatus,
|
|
131
133
|
dataAvailabilityStatus
|
|
@@ -135,6 +135,7 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
135
135
|
blockRoot: toRootHex(checkpoint.root),
|
|
136
136
|
timeliness: true, // Optimistically assume is timely
|
|
137
137
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
138
|
+
importedTimely: true, // Optimistically assume is timely
|
|
138
139
|
proposerIndex: blockHeader.proposerIndex,
|
|
139
140
|
|
|
140
141
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -233,6 +234,7 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
233
234
|
targetRoot: headRoot,
|
|
234
235
|
timeliness: true, // Optimistically assume is timely
|
|
235
236
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
237
|
+
importedTimely: true, // Optimistically assume is timely
|
|
236
238
|
proposerIndex: blockHeader.proposerIndex,
|
|
237
239
|
|
|
238
240
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -7,7 +7,6 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
|
|
|
7
7
|
import {toRootHex} from "@lodestar/utils";
|
|
8
8
|
import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
|
|
9
9
|
import {IBeaconChain} from "../index.js";
|
|
10
|
-
import {RegenCaller} from "../regen/index.js";
|
|
11
10
|
|
|
12
11
|
export type PayloadAttestationValidationResult = {
|
|
13
12
|
attDataRootHex: RootHex;
|
|
@@ -82,15 +81,15 @@ async function validatePayloadAttestationMessage(
|
|
|
82
81
|
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
83
82
|
// it is possible that the block didn't pass the validation
|
|
84
83
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
84
|
+
// block.slot === data.slot is enforced above, so use the block's post-state directly to avoid
|
|
85
|
+
// getting through regen queue
|
|
86
|
+
const state = chain.regen.getStateSync(block.stateRoot);
|
|
87
|
+
if (state == null) {
|
|
88
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
89
|
+
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
90
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
93
91
|
});
|
|
92
|
+
}
|
|
94
93
|
|
|
95
94
|
if (!isStatePostGloas(state)) {
|
|
96
95
|
throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {NotReorgedReason} from "@lodestar/fork-choice";
|
|
3
3
|
import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
|
+
import type {LateCanonicalBlockReason} from "../../chain/archiveStore/utils/archiveBlocks.js";
|
|
5
6
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
7
|
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
7
8
|
import {
|
|
@@ -1047,6 +1048,11 @@ export function createLodestarMetrics(
|
|
|
1047
1048
|
name: "lodestar_import_block_set_head_after_cutoff_total",
|
|
1048
1049
|
help: "Total times an imported block is set as head after ATTESTATION_DUE_BPS of the slot",
|
|
1049
1050
|
}),
|
|
1051
|
+
lateCanonicalBlock: register.counter<{reason: LateCanonicalBlockReason}>({
|
|
1052
|
+
name: "lodestar_import_block_late_canonical_total",
|
|
1053
|
+
help: "Total finalized-canonical blocks this node imported after the attestation cutoff; reason distinguishes this node's processing lag (slow_import) from late reception (late_receive)",
|
|
1054
|
+
labelNames: ["reason"],
|
|
1055
|
+
}),
|
|
1050
1056
|
bySource: register.gauge<{source: BlockInputSource}>({
|
|
1051
1057
|
name: "lodestar_import_block_by_source_total",
|
|
1052
1058
|
help: "Total number of imported blocks by source",
|
|
@@ -1268,16 +1268,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1268
1268
|
const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
|
|
1269
1269
|
metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
|
|
1270
1270
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1271
|
+
const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
|
|
1272
|
+
const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
|
|
1273
|
+
|
|
1274
|
+
if (isNextSlotProposer) {
|
|
1275
|
+
try {
|
|
1276
|
+
const insertOutcome = chain.payloadAttestationPool.add(
|
|
1277
|
+
payloadAttestationMessage,
|
|
1278
|
+
validationResult.attDataRootHex,
|
|
1279
|
+
validationResult.validatorCommitteeIndices
|
|
1280
|
+
);
|
|
1281
|
+
metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({insertOutcome});
|
|
1282
|
+
} catch (e) {
|
|
1283
|
+
logger.debug("Error adding to payloadAttestation pool", {}, e as Error);
|
|
1284
|
+
}
|
|
1280
1285
|
}
|
|
1286
|
+
|
|
1281
1287
|
chain.forkChoice.notifyPtcMessages(
|
|
1282
1288
|
toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
|
|
1283
1289
|
payloadAttestationMessage.data.slot,
|