@lodestar/beacon-node 1.47.0-rc.0 → 1.48.0-dev.02b355c4db
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/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +8 -11
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +14 -0
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/config/index.d.ts.map +1 -1
- package/lib/api/impl/config/index.js +4 -1
- package/lib/api/impl/config/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +44 -39
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +5 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +4 -1
- package/lib/chain/archiveStore/historicalState/worker.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 +68 -38
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts +5 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +12 -18
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +8 -4
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +89 -23
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +8 -0
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/types.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +26 -11
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +5 -2
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +12 -6
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +131 -76
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +1 -1
- package/lib/chain/emitter.js +1 -1
- package/lib/chain/errors/blockError.d.ts +19 -7
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -2
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
- package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
- package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +9 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +2 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -11
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +2 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +1 -0
- package/lib/chain/errors/gossipValidation.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/interface.d.ts +6 -6
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/lightClient/index.d.ts +3 -3
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +21 -23
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts +2 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +5 -1
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts +5 -0
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +37 -14
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +3 -25
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +46 -64
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +1 -0
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +1 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +74 -63
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
- package/lib/chain/validation/dataColumnSidecar.js +38 -13
- package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts +15 -3
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +199 -28
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
- package/lib/chain/validation/executionPayloadEnvelope.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/chain/validatorMonitor.d.ts +7 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +22 -5
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +14 -5
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +17 -3
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/dataColumnStore.d.ts +39 -0
- package/lib/db/dataColumnStore.d.ts.map +1 -0
- package/lib/db/dataColumnStore.js +81 -0
- package/lib/db/dataColumnStore.js.map +1 -0
- package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
- package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
- package/lib/db/flatFileStore/atomicWrite.js +61 -0
- package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
- package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
- package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
- package/lib/db/flatFileStore/dcolFormat.js +286 -0
- package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
- package/lib/db/flatFileStore/errors.d.ts +82 -0
- package/lib/db/flatFileStore/errors.d.ts.map +1 -0
- package/lib/db/flatFileStore/errors.js +32 -0
- package/lib/db/flatFileStore/errors.js.map +1 -0
- package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
- package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
- package/lib/db/flatFileStore/flatFileStore.js +251 -0
- package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
- package/lib/db/flatFileStore/index.d.ts +4 -0
- package/lib/db/flatFileStore/index.d.ts.map +1 -0
- package/lib/db/flatFileStore/index.js +2 -0
- package/lib/db/flatFileStore/index.js.map +1 -0
- package/lib/db/flatFileStore/interface.d.ts +25 -0
- package/lib/db/flatFileStore/interface.d.ts.map +1 -0
- package/lib/db/flatFileStore/interface.js +2 -0
- package/lib/db/flatFileStore/interface.js.map +1 -0
- package/lib/db/flatFileStore/metrics.d.ts +24 -0
- package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
- package/lib/db/flatFileStore/metrics.js +25 -0
- package/lib/db/flatFileStore/metrics.js.map +1 -0
- package/lib/db/flatFileStore/path.d.ts +9 -0
- package/lib/db/flatFileStore/path.d.ts.map +1 -0
- package/lib/db/flatFileStore/path.js +21 -0
- package/lib/db/flatFileStore/path.js.map +1 -0
- package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
- package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
- package/lib/db/flatFileStore/slotIndex.js +46 -0
- package/lib/db/flatFileStore/slotIndex.js.map +1 -0
- package/lib/db/flatFileStore/snappy.d.ts +3 -0
- package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
- package/lib/db/flatFileStore/snappy.js +12 -0
- package/lib/db/flatFileStore/snappy.js.map +1 -0
- package/lib/db/index.d.ts +1 -0
- package/lib/db/index.d.ts.map +1 -1
- package/lib/db/interface.d.ts +8 -1
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts +8 -1
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +65 -28
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.js +3 -7
- package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
- package/lib/execution/builder/apiClient.d.ts +6 -0
- package/lib/execution/builder/apiClient.d.ts.map +1 -1
- package/lib/execution/builder/apiClient.js +19 -1
- package/lib/execution/builder/apiClient.js.map +1 -1
- package/lib/execution/builder/validateBid.d.ts +1 -0
- package/lib/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +17 -6
- package/lib/execution/builder/validateBid.js.map +1 -1
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +4 -1
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +32 -4
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +78 -8
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/monitoring/clientStats.js +1 -1
- package/lib/monitoring/clientStats.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +0 -6
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +4 -20
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +7 -6
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +388 -285
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipQueues/indexed.d.ts.map +1 -1
- package/lib/network/processor/gossipQueues/indexed.js +0 -1
- package/lib/network/processor/gossipQueues/indexed.js.map +1 -1
- package/lib/network/processor/index.d.ts +49 -3
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +270 -122
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +6 -22
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +2 -1
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
- package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +4 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +9 -2
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +25 -2
- package/lib/sync/range/range.js.map +1 -1
- package/lib/util/sszBytes.d.ts +6 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +1 -1
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +18 -18
- package/src/api/impl/beacon/blocks/index.ts +12 -14
- package/src/api/impl/beacon/state/utils.ts +15 -0
- package/src/api/impl/config/index.ts +4 -1
- package/src/api/impl/validator/index.ts +49 -43
- package/src/chain/archiveStore/archiveStore.ts +12 -1
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
- package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -0
- package/src/chain/archiveStore/historicalState/worker.ts +4 -1
- package/src/chain/archiveStore/utils/archiveBlocks.ts +73 -42
- package/src/chain/blocks/importBlock.ts +25 -20
- package/src/chain/blocks/index.ts +126 -29
- package/src/chain/blocks/types.ts +9 -0
- package/src/chain/blocks/utils/chainSegment.ts +26 -12
- package/src/chain/blocks/verifyBlock.ts +5 -2
- package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
- package/src/chain/chain.ts +155 -85
- package/src/chain/emitter.ts +1 -1
- package/src/chain/errors/blockError.ts +22 -10
- package/src/chain/errors/dataColumnSidecarError.ts +24 -8
- package/src/chain/errors/executionPayloadBid.ts +8 -0
- package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
- package/src/chain/errors/gossipValidation.ts +1 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/interface.ts +6 -6
- package/src/chain/lightClient/index.ts +32 -36
- package/src/chain/lightClient/proofs.ts +10 -0
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/prepareNextSlot.ts +42 -17
- package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
- package/src/chain/produceBlock/produceBlockBody.ts +50 -71
- package/src/chain/regen/interface.ts +1 -0
- package/src/chain/validation/block.ts +77 -79
- package/src/chain/validation/dataColumnSidecar.ts +47 -14
- package/src/chain/validation/executionPayloadBid.ts +230 -32
- package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
- package/src/chain/validation/payloadAttestationMessage.ts +8 -9
- package/src/chain/validatorMonitor.ts +24 -7
- package/src/db/beacon.ts +33 -6
- package/src/db/dataColumnStore.ts +113 -0
- package/src/db/flatFileStore/atomicWrite.ts +65 -0
- package/src/db/flatFileStore/dcolFormat.ts +389 -0
- package/src/db/flatFileStore/errors.ts +53 -0
- package/src/db/flatFileStore/flatFileStore.ts +310 -0
- package/src/db/flatFileStore/index.ts +3 -0
- package/src/db/flatFileStore/interface.ts +21 -0
- package/src/db/flatFileStore/metrics.ts +48 -0
- package/src/db/flatFileStore/path.ts +32 -0
- package/src/db/flatFileStore/slotIndex.ts +56 -0
- package/src/db/flatFileStore/snappy.ts +14 -0
- package/src/db/index.ts +1 -0
- package/src/db/interface.ts +9 -1
- package/src/db/repositories/blockArchive.ts +89 -30
- package/src/db/repositories/blockArchiveIndex.ts +4 -9
- package/src/execution/builder/apiClient.ts +21 -1
- package/src/execution/builder/validateBid.ts +26 -7
- package/src/execution/engine/mock.ts +4 -1
- package/src/metrics/metrics/lodestar.ts +83 -9
- package/src/monitoring/clientStats.ts +1 -1
- package/src/network/gossip/encoding.ts +0 -6
- package/src/network/gossip/topic.ts +3 -25
- package/src/network/network.ts +9 -6
- package/src/network/processor/gossipHandlers.ts +500 -355
- package/src/network/processor/gossipQueues/indexed.ts +0 -1
- package/src/network/processor/index.ts +312 -176
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
- package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
- package/src/network/reqresp/protocols.ts +7 -24
- package/src/network/reqresp/rateLimit.ts +2 -1
- package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
- package/src/node/nodejs.ts +6 -1
- package/src/sync/range/chain.ts +9 -2
- package/src/sync/range/range.ts +24 -2
- package/src/util/sszBytes.ts +1 -1
|
@@ -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", {
|
|
@@ -98,6 +134,8 @@ export async function archiveBlocks(
|
|
|
98
134
|
logger.verbose("Migrated blobSidecars from hot DB to cold DB", {...logCtx, migratedEntries});
|
|
99
135
|
}
|
|
100
136
|
|
|
137
|
+
// Keep the normal LevelDB hot-to-cold flow for legacy columns. New columns are written
|
|
138
|
+
// directly to flat files, so this only moves fallback data left by older versions.
|
|
101
139
|
if (finalizedPostFulu) {
|
|
102
140
|
const migratedSlots = await migrateDataColumnSidecarsFromHotToColdDb(
|
|
103
141
|
config,
|
|
@@ -106,7 +144,7 @@ export async function archiveBlocks(
|
|
|
106
144
|
finalizedCanonicalBlocks,
|
|
107
145
|
currentEpoch
|
|
108
146
|
);
|
|
109
|
-
logger.verbose("Migrated dataColumnSidecars from hot DB to cold DB", {
|
|
147
|
+
logger.verbose("Migrated legacy dataColumnSidecars from hot DB to cold DB", {
|
|
110
148
|
...logCtx,
|
|
111
149
|
migratedEntries: migratedSlots.length,
|
|
112
150
|
slotRange: prettyPrintIndices(migratedSlots),
|
|
@@ -159,6 +197,23 @@ export async function archiveBlocks(
|
|
|
159
197
|
slotRange: prettyPrintIndices(nonCanonicalSlots),
|
|
160
198
|
};
|
|
161
199
|
|
|
200
|
+
const columnItems = finalizedNonCanonicalBlocks
|
|
201
|
+
// Gloas EMPTY and FULL variants share a block root. EMPTY has no sidecars, so deleting by its root could
|
|
202
|
+
// remove the canonical FULL variant's columns. Pre-Gloas blocks are always FULL.
|
|
203
|
+
.filter(
|
|
204
|
+
(summary) => config.getForkSeq(summary.slot) >= ForkSeq.fulu && summary.payloadStatus === PayloadStatus.FULL
|
|
205
|
+
)
|
|
206
|
+
.map((summary) => ({slot: summary.slot, blockRoot: summary.blockRoot}));
|
|
207
|
+
if (columnItems.length > 0) {
|
|
208
|
+
// Delete sidecars first so their block roots remain available to retry cleanup after a failure or crash.
|
|
209
|
+
await db.dataColumns.deleteMany(columnItems);
|
|
210
|
+
logger.verbose("Deleted non canonical data columns of blocks", {
|
|
211
|
+
...logCtx,
|
|
212
|
+
blocks: columnItems.length,
|
|
213
|
+
slotRange: prettyPrintIndices(columnItems.map(({slot}) => slot).sort((a, b) => a - b)),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
162
217
|
await db.block.batchDelete(nonCanonicalBlockRoots);
|
|
163
218
|
logger.verbose("Deleted non canonical blocks from hot DB", nonCanonicalLogCtx);
|
|
164
219
|
|
|
@@ -167,11 +222,6 @@ export async function archiveBlocks(
|
|
|
167
222
|
logger.verbose("Deleted non canonical blobSidecars from hot DB", nonCanonicalLogCtx);
|
|
168
223
|
}
|
|
169
224
|
|
|
170
|
-
if (finalizedPostFulu) {
|
|
171
|
-
await db.dataColumnSidecar.deleteMany(nonCanonicalBlockRoots);
|
|
172
|
-
logger.verbose("Deleted non canonical dataColumnSidecars from hot DB", nonCanonicalLogCtx);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
225
|
if (finalizedPostGloas) {
|
|
176
226
|
await db.executionPayloadEnvelope.batchDelete(nonCanonicalBlockRoots);
|
|
177
227
|
logger.verbose("Deleted non canonical executionPayloadEnvelopes from hot DB", nonCanonicalLogCtx);
|
|
@@ -209,23 +259,16 @@ export async function archiveBlocks(
|
|
|
209
259
|
);
|
|
210
260
|
const dataColumnSidecarsMinEpoch = currentEpoch - dataColumnSidecarsArchiveWindow;
|
|
211
261
|
if (dataColumnSidecarsMinEpoch >= config.FULU_FORK_EPOCH) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
// for each slot there could be multiple dataColumnSidecar, so we need to deduplicate it
|
|
217
|
-
const slotsToDelete = [...new Set(prefixedKeys.map(({prefix}) => prefix))].sort((a, b) => a - b);
|
|
218
|
-
|
|
219
|
-
if (slotsToDelete.length > 0) {
|
|
220
|
-
await db.dataColumnSidecarArchive.deleteMany(slotsToDelete);
|
|
262
|
+
const columnsPruneSlot = computeStartSlotAtEpoch(dataColumnSidecarsMinEpoch);
|
|
263
|
+
const prunedColumnSlots = await db.dataColumns.pruneBefore(columnsPruneSlot);
|
|
264
|
+
if (prunedColumnSlots.length > 0) {
|
|
221
265
|
logger.verbose("dataColumnSidecars prune", {
|
|
222
266
|
...logCtx,
|
|
223
|
-
slotRange: prettyPrintIndices(
|
|
224
|
-
numOfSlots:
|
|
225
|
-
totalNumOfSidecars: prefixedKeys.length,
|
|
267
|
+
slotRange: prettyPrintIndices(prunedColumnSlots),
|
|
268
|
+
numOfSlots: prunedColumnSlots.length,
|
|
226
269
|
});
|
|
227
270
|
} else {
|
|
228
|
-
logger.verbose(
|
|
271
|
+
logger.verbose("dataColumnSidecars prune: no entries before slot", {...logCtx, slot: columnsPruneSlot});
|
|
229
272
|
}
|
|
230
273
|
} else {
|
|
231
274
|
logger.verbose(
|
|
@@ -298,7 +341,7 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, logger: Logger, block
|
|
|
298
341
|
]);
|
|
299
342
|
for (const entry of canonicalBlockEntries) migratedSlots.push(entry.slot);
|
|
300
343
|
}
|
|
301
|
-
// Ancestor walk is newest
|
|
344
|
+
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
302
345
|
return migratedSlots.sort((a, b) => a - b);
|
|
303
346
|
}
|
|
304
347
|
|
|
@@ -353,7 +396,6 @@ async function migrateBlobSidecarsFromHotToColdDb(
|
|
|
353
396
|
)
|
|
354
397
|
).filter((e): e is KeyValue<Slot, Uint8Array> => e !== null);
|
|
355
398
|
|
|
356
|
-
// put to blockArchive db and delete block db
|
|
357
399
|
await Promise.all([
|
|
358
400
|
db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries),
|
|
359
401
|
db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)),
|
|
@@ -366,11 +408,9 @@ async function migrateBlobSidecarsFromHotToColdDb(
|
|
|
366
408
|
|
|
367
409
|
// TODO: This function can be simplified further by reducing layers of promises in a loop
|
|
368
410
|
/**
|
|
369
|
-
* Post-gloas the data columns of a Gloas block are tied to its execution payload envelope
|
|
370
|
-
*
|
|
371
|
-
* blocks only have a FULL variant, so the
|
|
372
|
-
* Blocks whose canonical variant is PENDING/EMPTY are skipped here — their columns will be picked
|
|
373
|
-
* up on a later run once the FULL variant appears in the ancestor walk.
|
|
411
|
+
* Post-gloas the data columns of a Gloas block are tied to its execution payload envelope.
|
|
412
|
+
* Columns only exist once the FULL variant of the block is in the proto-array. Pre-Gloas
|
|
413
|
+
* blocks only have a FULL variant, so the payload-status filter passes them all.
|
|
374
414
|
*/
|
|
375
415
|
async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
376
416
|
config: ChainForkConfig,
|
|
@@ -392,32 +432,24 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
392
432
|
if (batch.length === 0) break;
|
|
393
433
|
|
|
394
434
|
const promises: Promise<void>[] = [];
|
|
395
|
-
|
|
396
|
-
// load Buffer instead of ssz deserialized to improve performance
|
|
397
435
|
for (const block of batch) {
|
|
398
|
-
const
|
|
399
|
-
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
400
|
-
|
|
436
|
+
const blockEpoch = computeEpochAtSlot(block.slot);
|
|
401
437
|
if (
|
|
402
|
-
config.getForkSeq(
|
|
403
|
-
// if block is out of ${config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS}, skip this step
|
|
438
|
+
config.getForkSeq(block.slot) < ForkSeq.fulu ||
|
|
404
439
|
blockEpoch < currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
|
|
405
440
|
) {
|
|
406
441
|
continue;
|
|
407
442
|
}
|
|
408
443
|
|
|
409
|
-
// Here we assume the data column sidecars are already in the hot db
|
|
410
444
|
const dataColumnSidecarBytes = await fromAsync(db.dataColumnSidecar.valuesStreamBinary(block.root));
|
|
411
445
|
if (dataColumnSidecarBytes.length === 0) {
|
|
412
|
-
// Empty stream: either the block has no blobs, or columns were already archived on a
|
|
413
|
-
// previous run (boundary block). Nothing to migrate.
|
|
414
446
|
logger.debug("DataColumnSidecars in forkchoice but missing in hot db, could be already archived", {
|
|
415
447
|
slot: block.slot,
|
|
416
448
|
root: toRootHex(block.root),
|
|
417
449
|
});
|
|
418
450
|
continue;
|
|
419
451
|
}
|
|
420
|
-
logger.verbose("Migrated dataColumnSidecars for block", {
|
|
452
|
+
logger.verbose("Migrated legacy dataColumnSidecars for block", {
|
|
421
453
|
currentEpoch,
|
|
422
454
|
slot: block.slot,
|
|
423
455
|
root: toRootHex(block.root),
|
|
@@ -426,18 +458,17 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
426
458
|
promises.push(
|
|
427
459
|
db.dataColumnSidecarArchive.putManyBinary(
|
|
428
460
|
block.slot,
|
|
429
|
-
dataColumnSidecarBytes.map((
|
|
461
|
+
dataColumnSidecarBytes.map((entry) => ({key: entry.id, value: entry.value}))
|
|
430
462
|
)
|
|
431
463
|
);
|
|
432
464
|
migratedSlots.push(block.slot);
|
|
433
465
|
}
|
|
434
466
|
|
|
435
|
-
promises.push(db.dataColumnSidecar.deleteMany(batch.map((block) => block.root)));
|
|
436
|
-
|
|
437
467
|
await Promise.all(promises);
|
|
468
|
+
await db.dataColumnSidecar.deleteMany(batch.map((block) => block.root));
|
|
438
469
|
}
|
|
439
470
|
|
|
440
|
-
// Ancestor walk is newest
|
|
471
|
+
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
441
472
|
return migratedSlots.sort((a, b) => a - b);
|
|
442
473
|
}
|
|
443
474
|
|
|
@@ -485,7 +516,7 @@ async function migrateExecutionPayloadEnvelopesFromHotToColdDb(
|
|
|
485
516
|
]);
|
|
486
517
|
|
|
487
518
|
// Slots are ascending in hot-db key order — sort to guarantee `prettyPrintIndices` output is clean
|
|
488
|
-
// regardless of ancestor-walk order (newest
|
|
519
|
+
// regardless of ancestor-walk order (newest to oldest).
|
|
489
520
|
return envelopeEntries.map((entry) => entry.key).sort((a, b) => a - b);
|
|
490
521
|
}
|
|
491
522
|
|
|
@@ -25,7 +25,17 @@ import {
|
|
|
25
25
|
isStatePostAltair,
|
|
26
26
|
isStatePostBellatrix,
|
|
27
27
|
} from "@lodestar/state-transition";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
Attestation,
|
|
30
|
+
BeaconBlock,
|
|
31
|
+
Epoch,
|
|
32
|
+
altair,
|
|
33
|
+
capella,
|
|
34
|
+
electra,
|
|
35
|
+
isGloasBeaconBlock,
|
|
36
|
+
phase0,
|
|
37
|
+
ssz,
|
|
38
|
+
} from "@lodestar/types";
|
|
29
39
|
import {isErrorAborted, toRootHex} from "@lodestar/utils";
|
|
30
40
|
import {GENESIS_SLOT, ZERO_HASH_HEX} from "../../constants/index.js";
|
|
31
41
|
import {callInNextEventLoop} from "../../util/eventLoop.js";
|
|
@@ -44,6 +54,15 @@ import {getCheckpointFromState} from "./utils/checkpoint.js";
|
|
|
44
54
|
* Fork-choice allows to import attestations from current (0) or past (1) epoch.
|
|
45
55
|
*/
|
|
46
56
|
const FORK_CHOICE_ATT_EPOCH_LIMIT = 1;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the attestations of a block at `blockEpoch` are imported into fork choice
|
|
59
|
+
*/
|
|
60
|
+
export function importsBlockAttestations(opts: ImportBlockOpts, blockEpoch: Epoch, currentEpoch: Epoch): boolean {
|
|
61
|
+
return (
|
|
62
|
+
opts.importAttestations === AttestationImportOpt.Force ||
|
|
63
|
+
(opts.importAttestations !== AttestationImportOpt.Skip && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
47
66
|
/**
|
|
48
67
|
* Emit eventstream events for block contents events only for blocks that are recent enough to clock
|
|
49
68
|
*/
|
|
@@ -85,7 +104,7 @@ export async function importBlock(
|
|
|
85
104
|
const currentEpoch = computeEpochAtSlot(currentSlot);
|
|
86
105
|
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
87
106
|
const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
|
|
88
|
-
const
|
|
107
|
+
const receiveDelaySec =
|
|
89
108
|
fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
|
|
90
109
|
const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
|
|
91
110
|
const fork = this.config.getForkSeq(blockSlot);
|
|
@@ -122,10 +141,12 @@ export async function importBlock(
|
|
|
122
141
|
executionStatus = parentBlock.executionStatus;
|
|
123
142
|
}
|
|
124
143
|
|
|
144
|
+
const importDelaySec = this.clock.secFromSlot(blockSlot);
|
|
125
145
|
const blockSummary = this.forkChoice.onBlock(
|
|
126
146
|
block.message,
|
|
127
147
|
postState,
|
|
128
|
-
|
|
148
|
+
receiveDelaySec,
|
|
149
|
+
importDelaySec,
|
|
129
150
|
currentSlot,
|
|
130
151
|
executionStatus,
|
|
131
152
|
dataAvailabilityStatus
|
|
@@ -147,10 +168,7 @@ export async function importBlock(
|
|
|
147
168
|
// Only process attestations of blocks with relevant attestations for the fork-choice:
|
|
148
169
|
// If current epoch is N, and block is epoch X, block may include attestations for epoch X or X - 1.
|
|
149
170
|
// The latest block that is useful is at epoch N - 1 which may include attestations for epoch N - 1 or N - 2.
|
|
150
|
-
if (
|
|
151
|
-
opts.importAttestations === AttestationImportOpt.Force ||
|
|
152
|
-
(opts.importAttestations !== AttestationImportOpt.Skip && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)
|
|
153
|
-
) {
|
|
171
|
+
if (importsBlockAttestations(opts, blockEpoch, currentEpoch)) {
|
|
154
172
|
const attestations = block.message.body.attestations;
|
|
155
173
|
const rootCache = new RootCache(postState);
|
|
156
174
|
const invalidAttestationErrorsByCode = new Map<string, {error: Error; count: number}>();
|
|
@@ -506,19 +524,6 @@ export async function importBlock(
|
|
|
506
524
|
this.metrics?.previousJustifiedEpoch.set(checkpointState.previousJustifiedCheckpoint.epoch);
|
|
507
525
|
this.metrics?.currentJustifiedEpoch.set(justifiedCheckpoint.epoch);
|
|
508
526
|
}
|
|
509
|
-
const finalizedCheckpoint = checkpointState.finalizedCheckpoint;
|
|
510
|
-
const finalizedEpoch = finalizedCheckpoint.epoch;
|
|
511
|
-
const preFinalizedEpoch = parentBlockSummary.finalizedEpoch;
|
|
512
|
-
if (finalizedEpoch > preFinalizedEpoch) {
|
|
513
|
-
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
|
|
514
|
-
block: toRootHex(finalizedCheckpoint.root),
|
|
515
|
-
epoch: finalizedCheckpoint.epoch,
|
|
516
|
-
state: toRootHex(checkpointState.hashTreeRoot()),
|
|
517
|
-
executionOptimistic: false,
|
|
518
|
-
});
|
|
519
|
-
this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint));
|
|
520
|
-
this.metrics?.finalizedEpoch.set(finalizedCheckpoint.epoch);
|
|
521
|
-
}
|
|
522
527
|
}
|
|
523
528
|
}
|
|
524
529
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
2
|
+
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
1
3
|
import {SignedBeaconBlock, Slot} from "@lodestar/types";
|
|
2
4
|
import {isErrorAborted, toRootHex} from "@lodestar/utils";
|
|
3
5
|
import {Metrics} from "../../metrics/metrics.js";
|
|
@@ -5,15 +7,17 @@ import {nextEventLoop} from "../../util/eventLoop.js";
|
|
|
5
7
|
import {JobItemQueue, isQueueErrorAborted} from "../../util/queue/index.js";
|
|
6
8
|
import type {BeaconChain} from "../chain.js";
|
|
7
9
|
import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.js";
|
|
10
|
+
import {ForkchoiceCaller} from "../forkChoice/index.js";
|
|
8
11
|
import {BlockProcessOpts} from "../options.js";
|
|
9
12
|
import {IBlockInput} from "./blockInput/types.js";
|
|
10
|
-
import {importBlock} from "./importBlock.js";
|
|
13
|
+
import {importBlock, importsBlockAttestations} from "./importBlock.js";
|
|
11
14
|
import {PayloadError, importExecutionPayload} from "./importExecutionPayload.js";
|
|
12
15
|
import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
13
|
-
import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
|
|
14
|
-
import {assertLinearChainSegment} from "./utils/chainSegment.js";
|
|
16
|
+
import {FullyVerifiedBlock, ImportBlockOpts, ProcessBlocksResult} from "./types.js";
|
|
17
|
+
import {OrphanedPayloadEnvelope, assertLinearChainSegment} from "./utils/chainSegment.js";
|
|
15
18
|
import {verifyBlocksInEpoch} from "./verifyBlock.js";
|
|
16
19
|
import {verifyBlocksSanityChecks} from "./verifyBlocksSanityChecks.js";
|
|
20
|
+
import {verifyPayloadsDataAvailability} from "./verifyPayloadsDataAvailability.js";
|
|
17
21
|
|
|
18
22
|
export {AttestationImportOpt, type ImportBlockOpts} from "./types.js";
|
|
19
23
|
|
|
@@ -23,10 +27,16 @@ const QUEUE_MAX_LENGTH = 256;
|
|
|
23
27
|
* BlockProcessor processes block jobs in a queued fashion, one after the other.
|
|
24
28
|
*/
|
|
25
29
|
export class BlockProcessor {
|
|
26
|
-
readonly jobQueue: JobItemQueue<
|
|
30
|
+
readonly jobQueue: JobItemQueue<
|
|
31
|
+
[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts],
|
|
32
|
+
ProcessBlocksResult
|
|
33
|
+
>;
|
|
27
34
|
|
|
28
35
|
constructor(chain: BeaconChain, metrics: Metrics | null, opts: BlockProcessOpts, signal: AbortSignal) {
|
|
29
|
-
this.jobQueue = new JobItemQueue<
|
|
36
|
+
this.jobQueue = new JobItemQueue<
|
|
37
|
+
[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts],
|
|
38
|
+
ProcessBlocksResult
|
|
39
|
+
>(
|
|
30
40
|
(job, payloadEnvelopes, importOpts) => {
|
|
31
41
|
return processBlocks.call(chain, job, payloadEnvelopes, {...opts, ...importOpts});
|
|
32
42
|
},
|
|
@@ -39,8 +49,8 @@ export class BlockProcessor {
|
|
|
39
49
|
job: IBlockInput[],
|
|
40
50
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
41
51
|
opts: ImportBlockOpts = {}
|
|
42
|
-
): Promise<
|
|
43
|
-
|
|
52
|
+
): Promise<ProcessBlocksResult> {
|
|
53
|
+
return this.jobQueue.push(job, payloadEnvelopes, opts);
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -59,9 +69,10 @@ export async function processBlocks(
|
|
|
59
69
|
blocks: IBlockInput[],
|
|
60
70
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
61
71
|
opts: BlockProcessOpts & ImportBlockOpts
|
|
62
|
-
): Promise<
|
|
72
|
+
): Promise<ProcessBlocksResult> {
|
|
73
|
+
const emptyResult: ProcessBlocksResult = {orphaned: [], skipped: false};
|
|
63
74
|
if (blocks.length === 0) {
|
|
64
|
-
return; // TODO: or throw?
|
|
75
|
+
return emptyResult; // TODO: or throw?
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
try {
|
|
@@ -70,7 +81,7 @@ export async function processBlocks(
|
|
|
70
81
|
// No relevant blocks, skip verifyBlocksInEpoch()
|
|
71
82
|
if (relevantBlocks.length === 0 || parentBlock === null) {
|
|
72
83
|
// parentBlock can only be null if relevantBlocks are empty
|
|
73
|
-
return;
|
|
84
|
+
return await importPayloadEnvelopesOfKnownBlocks.call(this, payloadEnvelopes, opts);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
const {warnings: orphanedPayloads} = assertLinearChainSegment(
|
|
@@ -79,12 +90,21 @@ export async function processBlocks(
|
|
|
79
90
|
payloadEnvelopes,
|
|
80
91
|
parentBlock
|
|
81
92
|
);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
// Orphaned payloads of recent blocks are still imported, a reorg of the child could make the payload canonical
|
|
94
|
+
// again and later blocks build on it. Older ones are skipped: without the block attestations the orphaned FULL
|
|
95
|
+
// variant has no descendants and ties at zero weight with the EMPTY variant carrying the chain, the payload
|
|
96
|
+
// status tiebreaker then picks FULL and parks the head there.
|
|
97
|
+
const blockEpoch = computeEpochAtSlot(relevantBlocks[0].getBlock().message.slot);
|
|
98
|
+
const importsAttestations = importsBlockAttestations(opts, blockEpoch, this.clock.currentEpoch);
|
|
99
|
+
const skipped = orphanedPayloads != null && payloadEnvelopes !== null && !importsAttestations;
|
|
100
|
+
let payloadEnvelopesToImport = payloadEnvelopes;
|
|
101
|
+
if (skipped) {
|
|
102
|
+
payloadEnvelopesToImport = new Map(payloadEnvelopes);
|
|
103
|
+
for (const orphaned of orphanedPayloads ?? []) {
|
|
104
|
+
payloadEnvelopesToImport.delete(orphaned.slot);
|
|
105
|
+
// never validated, drop it from the shared cache so it is not served to peers, by-root sync reloads the
|
|
106
|
+
// entry from db if the payload is ever needed
|
|
107
|
+
this.seenPayloadEnvelopeInputCache.prune(orphaned.payloadEnvelopeInput.blockRootHex);
|
|
88
108
|
}
|
|
89
109
|
}
|
|
90
110
|
|
|
@@ -97,7 +117,7 @@ export async function processBlocks(
|
|
|
97
117
|
proposerBalanceDeltas,
|
|
98
118
|
segmentExecStatus,
|
|
99
119
|
indexedAttestationsByBlock,
|
|
100
|
-
} = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks,
|
|
120
|
+
} = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopesToImport, opts);
|
|
101
121
|
|
|
102
122
|
// If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
|
|
103
123
|
// and we need to further propagate
|
|
@@ -132,8 +152,8 @@ export async function processBlocks(
|
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
const slotSet = new Set<Slot>(blocks.map((b) => b.getBlock().message.slot));
|
|
135
|
-
if (
|
|
136
|
-
for (const slot of
|
|
155
|
+
if (payloadEnvelopesToImport) {
|
|
156
|
+
for (const slot of payloadEnvelopesToImport.keys()) slotSet.add(slot);
|
|
137
157
|
}
|
|
138
158
|
const slots = Array.from(slotSet).sort((a, b) => a - b);
|
|
139
159
|
for (const slot of slots) {
|
|
@@ -143,25 +163,27 @@ export async function processBlocks(
|
|
|
143
163
|
await importBlock.call(this, fullyVerifiedBlock, opts);
|
|
144
164
|
}
|
|
145
165
|
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
// PayloadEnvelopeInput is shared and may receive an envelope after the DA snapshot was taken.
|
|
167
|
+
const payloadDA = payloadDAStatuses.get(slot);
|
|
168
|
+
if (payloadDA !== undefined) {
|
|
169
|
+
const payloadInput = payloadEnvelopesToImport?.get(slot);
|
|
170
|
+
if (payloadInput === undefined) {
|
|
171
|
+
throw new Error(`Missing payload input for slot ${slot} after DA verification`);
|
|
172
|
+
}
|
|
148
173
|
if (!payloadInput.isComplete()) {
|
|
149
174
|
// we validated DA before reaching this
|
|
150
175
|
throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
|
|
151
176
|
}
|
|
152
|
-
// we already awaited DA in verifyBlocksInEpoch for this segment
|
|
153
|
-
const payloadDA = payloadDAStatuses.get(slot);
|
|
154
|
-
if (payloadDA === undefined) {
|
|
155
|
-
throw new Error(`Missing payload DA status for slot ${slot}`);
|
|
156
|
-
}
|
|
157
177
|
await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
|
|
158
178
|
}
|
|
159
179
|
|
|
160
180
|
await nextEventLoop();
|
|
161
181
|
}
|
|
182
|
+
|
|
183
|
+
return {orphaned: orphanedPayloads ?? [], skipped};
|
|
162
184
|
} catch (e) {
|
|
163
185
|
if (isErrorAborted(e) || isQueueErrorAborted(e) || isBlockErrorAborted(e)) {
|
|
164
|
-
return; // Ignore
|
|
186
|
+
return emptyResult; // Ignore
|
|
165
187
|
}
|
|
166
188
|
|
|
167
189
|
// above functions should only throw BlockError, or PayloadError from the gloas payload import
|
|
@@ -187,8 +209,19 @@ export async function processBlocks(
|
|
|
187
209
|
const blockSlot = signedBlock.message.slot;
|
|
188
210
|
const {state} = err.type;
|
|
189
211
|
const forkTypes = this.config.getForkTypes(blockSlot);
|
|
190
|
-
|
|
191
|
-
this.
|
|
212
|
+
const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
|
|
213
|
+
this.persistInvalidSszValue(
|
|
214
|
+
forkTypes.SignedBeaconBlock,
|
|
215
|
+
signedBlock,
|
|
216
|
+
`${blockSlot}_invalid_signature`,
|
|
217
|
+
blockRootHex
|
|
218
|
+
);
|
|
219
|
+
this.persistInvalidSszBytes(
|
|
220
|
+
"BeaconState",
|
|
221
|
+
state.serialize(),
|
|
222
|
+
toRootHex(state.hashTreeRoot()),
|
|
223
|
+
`${state.slot}_invalid_signature`
|
|
224
|
+
);
|
|
192
225
|
} else if (err.type.code === BlockErrorCode.INVALID_STATE_ROOT) {
|
|
193
226
|
const {signedBlock} = err;
|
|
194
227
|
const blockSlot = signedBlock.message.slot;
|
|
@@ -209,6 +242,70 @@ export async function processBlocks(
|
|
|
209
242
|
}
|
|
210
243
|
}
|
|
211
244
|
|
|
245
|
+
/**
|
|
246
|
+
* A peer can serve a block without its envelope, the block is then imported with a PENDING payload. Later batches
|
|
247
|
+
* carry the envelope but all their blocks are known, without importing it here every child building on the FULL
|
|
248
|
+
* variant fails with PARENT_PAYLOAD_UNKNOWN and range sync never progresses. If our head already descends from the
|
|
249
|
+
* block's EMPTY variant the chain did not build on the payload, importing it would only add a dead FULL leaf. As for
|
|
250
|
+
* orphaned payloads in a segment this only applies while the block attestations are not imported, a recent payload
|
|
251
|
+
* is imported anyway so a reorg building on it does not have to fetch it through unknown block sync.
|
|
252
|
+
*/
|
|
253
|
+
async function importPayloadEnvelopesOfKnownBlocks(
|
|
254
|
+
this: BeaconChain,
|
|
255
|
+
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
256
|
+
opts: ImportBlockOpts
|
|
257
|
+
): Promise<ProcessBlocksResult> {
|
|
258
|
+
if (payloadEnvelopes === null) {
|
|
259
|
+
return {orphaned: [], skipped: false};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const head = this.forkChoice.getHead();
|
|
263
|
+
const currentEpoch = this.clock.currentEpoch;
|
|
264
|
+
const payloadInputs: PayloadEnvelopeInput[] = [];
|
|
265
|
+
const orphaned: OrphanedPayloadEnvelope[] = [];
|
|
266
|
+
for (const payloadInput of payloadEnvelopes.values()) {
|
|
267
|
+
const {blockRootHex, slot} = payloadInput;
|
|
268
|
+
if (
|
|
269
|
+
!payloadInput.hasPayloadEnvelope() ||
|
|
270
|
+
!this.forkChoice.hasBlockHex(blockRootHex) ||
|
|
271
|
+
this.forkChoice.getBlockHexAndBlockHash(blockRootHex, payloadInput.getBlockHashHex()) !== null
|
|
272
|
+
) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (
|
|
276
|
+
head.blockRoot !== blockRootHex &&
|
|
277
|
+
!importsBlockAttestations(opts, computeEpochAtSlot(slot), currentEpoch) &&
|
|
278
|
+
this.forkChoice.isDescendant(blockRootHex, PayloadStatus.EMPTY, head.blockRoot, head.payloadStatus)
|
|
279
|
+
) {
|
|
280
|
+
// never validated, see processBlocks()
|
|
281
|
+
this.seenPayloadEnvelopeInputCache.prune(blockRootHex);
|
|
282
|
+
orphaned.push({slot, payloadEnvelopeInput: payloadInput});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
payloadInputs.push(payloadInput);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// orphaned envelopes on this path are always skipped, range sync logs them with peer/client context
|
|
289
|
+
const result: ProcessBlocksResult = {orphaned, skipped: orphaned.length > 0};
|
|
290
|
+
|
|
291
|
+
if (payloadInputs.length === 0) {
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability(payloadInputs, new AbortController().signal);
|
|
296
|
+
for (let i = 0; i < payloadInputs.length; i++) {
|
|
297
|
+
this.logger.debug("Importing payload envelope of known block", {
|
|
298
|
+
slot: payloadInputs[i].slot,
|
|
299
|
+
blockRoot: payloadInputs[i].blockRootHex,
|
|
300
|
+
});
|
|
301
|
+
await importExecutionPayload.call(this, payloadInputs[i], dataAvailabilityStatuses[i], {validSignature: false});
|
|
302
|
+
}
|
|
303
|
+
// the new FULL variants are not seen by the head cached from the last block import
|
|
304
|
+
this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock);
|
|
305
|
+
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
|
|
212
309
|
function getBlockOrPayloadError(e: unknown, block: SignedBeaconBlock): BlockError | PayloadError {
|
|
213
310
|
if (e instanceof BlockError) {
|
|
214
311
|
return e;
|
|
@@ -4,6 +4,7 @@ import {ForkSeq} from "@lodestar/params";
|
|
|
4
4
|
import {DataAvailabilityStatus, IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
|
|
5
5
|
import type {IndexedAttestation, Slot, fulu} from "@lodestar/types";
|
|
6
6
|
import {IBlockInput} from "./blockInput/types.js";
|
|
7
|
+
import {OrphanedPayloadEnvelope} from "./utils/chainSegment.js";
|
|
7
8
|
|
|
8
9
|
export enum GossipedInputType {
|
|
9
10
|
block = "block",
|
|
@@ -110,3 +111,11 @@ export type FullyVerifiedBlock = {
|
|
|
110
111
|
/** If the execution payload couldn't be verified because of EL syncing status, used in optimistic sync */
|
|
111
112
|
executionStatus: BlockExecutionStatus | PayloadExecutionStatus;
|
|
112
113
|
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Result of processing a chain segment.
|
|
117
|
+
*/
|
|
118
|
+
export type ProcessBlocksResult = {
|
|
119
|
+
orphaned: OrphanedPayloadEnvelope[];
|
|
120
|
+
skipped: boolean;
|
|
121
|
+
};
|
|
@@ -22,7 +22,7 @@ export type ChainSegmentResult = {warnings: OrphanedPayloadEnvelope[] | null};
|
|
|
22
22
|
* - Verifies parent root + slot linearity
|
|
23
23
|
* - For gloas: verifies bid.parentBlockHash matches the tracked execution hash; if not, the
|
|
24
24
|
* previous FULL envelope is treated as orphaned (segment continues as if previous slot was EMPTY)
|
|
25
|
-
* - If an envelope exists for this slot: verifies it references this block's root
|
|
25
|
+
* - If an envelope exists for this slot (or the parent's slot): verifies it references this block's root
|
|
26
26
|
* - Advances the tracked execution hash (FULL if envelope present, EMPTY if not)
|
|
27
27
|
*/
|
|
28
28
|
export function assertLinearChainSegment(
|
|
@@ -37,17 +37,6 @@ export function assertLinearChainSegment(
|
|
|
37
37
|
|
|
38
38
|
const warnings: OrphanedPayloadEnvelope[] = [];
|
|
39
39
|
|
|
40
|
-
let parentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
|
|
41
|
-
if (parentBlock !== null) {
|
|
42
|
-
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
43
|
-
if (parentPayloadInput?.hasPayloadEnvelope()) {
|
|
44
|
-
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|
|
45
|
-
// hash, not its own payload. Its payload envelope is in this batch, so use that blockHash as
|
|
46
|
-
// the real parent EL head.
|
|
47
|
-
parentExecHash = parentPayloadInput.getBlockHashHex();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
40
|
// Track the expected execution payload block hash through the segment, seeded from the FIRST
|
|
52
41
|
// block's own bid
|
|
53
42
|
const firstBlockMessage = blocks[0].getBlock().message;
|
|
@@ -55,6 +44,31 @@ export function assertLinearChainSegment(
|
|
|
55
44
|
? toRootHex(firstBlockMessage.body.signedExecutionPayloadBid.message.parentBlockHash)
|
|
56
45
|
: null;
|
|
57
46
|
|
|
47
|
+
let parentExecHash: string | null = parentBlock?.executionPayloadBlockHash ?? null;
|
|
48
|
+
if (parentBlock !== null) {
|
|
49
|
+
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
50
|
+
if (parentPayloadInput?.hasPayloadEnvelope()) {
|
|
51
|
+
// Envelopes are served by slot, the one at the parent's slot must reference the parent block. Without this
|
|
52
|
+
// check an envelope of a sibling block is taken as orphaned and only fails downstream in the payload import
|
|
53
|
+
if (parentPayloadInput.blockRootHex !== parentBlock.blockRoot) {
|
|
54
|
+
throw new BlockError(blocks[0].getBlock(), {
|
|
55
|
+
code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH,
|
|
56
|
+
envelopeBlockRoot: parentPayloadInput.blockRootHex,
|
|
57
|
+
blockRoot: parentBlock.blockRoot,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const parentPayloadBlockHash = parentPayloadInput.getBlockHashHex();
|
|
61
|
+
if (currentExecHash === parentPayloadBlockHash) {
|
|
62
|
+
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|
|
63
|
+
// hash, its own payload is in this batch and is the real parent EL head
|
|
64
|
+
parentExecHash = parentPayloadBlockHash;
|
|
65
|
+
} else {
|
|
66
|
+
// First block builds on the parent's EMPTY variant, the parent's payload in this batch is orphaned
|
|
67
|
+
warnings.push({slot: parentBlock.slot, payloadEnvelopeInput: parentPayloadInput});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
// First block must build on the parent's EL head. PARENT_PAYLOAD_UNKNOWN (vs the in-segment
|
|
59
73
|
// NON_LINEAR_PAYLOAD_ROOTS below) tells range sync this is a parent-link problem, not an in-batch one.
|
|
60
74
|
if (parentExecHash !== null && currentExecHash !== null && currentExecHash !== parentExecHash) {
|