@lodestar/beacon-node 1.48.0-dev.7c6505e8e6 → 1.48.0-dev.82e7577c8b
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 +4 -4
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +39 -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 +1 -0
- 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.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +31 -34
- 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 +0 -13
- 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 +65 -12
- 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 +10 -1
- package/lib/chain/blocks/utils/chainSegment.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 +11 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +129 -75
- 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/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/interface.d.ts +4 -4
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.js +13 -13
- package/lib/chain/prepareNextSlot.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/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +35 -54
- 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/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/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/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 +18 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +55 -1
- 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 -7
- 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/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +6 -7
- package/lib/network/processor/gossipHandlers.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 +243 -98
- 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/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +24 -1
- 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 -19
- package/src/api/impl/beacon/blocks/index.ts +8 -4
- package/src/api/impl/validator/index.ts +44 -43
- package/src/chain/archiveStore/archiveStore.ts +6 -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 +34 -39
- package/src/chain/blocks/importBlock.ts +0 -13
- package/src/chain/blocks/index.ts +102 -18
- package/src/chain/blocks/types.ts +9 -0
- package/src/chain/blocks/utils/chainSegment.ts +10 -1
- package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
- package/src/chain/chain.ts +153 -84
- 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/executionPayloadEnvelope.ts +14 -11
- package/src/chain/errors/gossipValidation.ts +1 -0
- package/src/chain/interface.ts +4 -4
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/prepareNextSlot.ts +16 -16
- package/src/chain/produceBlock/produceBlockBody.ts +50 -71
- package/src/chain/validation/block.ts +35 -70
- package/src/chain/validation/dataColumnSidecar.ts +47 -14
- package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
- 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/engine/mock.ts +4 -1
- package/src/metrics/metrics/lodestar.ts +57 -1
- package/src/monitoring/clientStats.ts +1 -1
- package/src/network/gossip/encoding.ts +0 -7
- package/src/network/gossip/topic.ts +3 -25
- package/src/network/processor/gossipHandlers.ts +11 -11
- package/src/network/processor/index.ts +276 -148
- 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/range.ts +23 -1
- package/src/util/sszBytes.ts +1 -1
|
@@ -133,40 +133,37 @@ type BidCandidate = {
|
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* p2p bid, and the earlier received bid between builder api bids.
|
|
136
|
+
* Ranking value of a bid, the boosted total payment scaled by 100 as `boostFactor` is a percentage.
|
|
137
|
+
* Kept scaled so bids that differ by less than a percent are not truncated into a tie.
|
|
139
138
|
*/
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (candidateIsMaxBoost !== bestIsMaxBoost) {
|
|
152
|
-
if (candidateIsMaxBoost) {
|
|
153
|
-
best = candidate;
|
|
154
|
-
}
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
const candidateValue = boostedValue(candidate);
|
|
158
|
-
const bestValue = boostedValue(best);
|
|
159
|
-
if (candidateValue > bestValue) {
|
|
160
|
-
best = candidate;
|
|
161
|
-
} else if (
|
|
162
|
-
candidateValue === bestValue &&
|
|
163
|
-
// A tie prefers a builder api bid over the p2p bid, and the earlier received bid otherwise
|
|
164
|
-
(best.url === undefined || (candidate.url !== undefined && candidate.receivedMs < best.receivedMs))
|
|
165
|
-
) {
|
|
166
|
-
best = candidate;
|
|
167
|
-
}
|
|
139
|
+
function getBoostedTotalScaled({totalGwei, boostFactor}: BidCandidate): bigint {
|
|
140
|
+
return boostFactor * totalGwei;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Order bid candidates by preference, best first, used to select a bid and to log the ranking */
|
|
144
|
+
function compareBidCandidates(a: BidCandidate, b: BidCandidate): number {
|
|
145
|
+
// Preserve max boost preference before comparing bid values
|
|
146
|
+
const aIsMaxBoost = a.boostFactor === MAX_BUILDER_BOOST_FACTOR;
|
|
147
|
+
const bIsMaxBoost = b.boostFactor === MAX_BUILDER_BOOST_FACTOR;
|
|
148
|
+
if (aIsMaxBoost !== bIsMaxBoost) {
|
|
149
|
+
return aIsMaxBoost ? -1 : 1;
|
|
168
150
|
}
|
|
169
|
-
|
|
151
|
+
|
|
152
|
+
const aBoostedTotal = getBoostedTotalScaled(a);
|
|
153
|
+
const bBoostedTotal = getBoostedTotalScaled(b);
|
|
154
|
+
if (aBoostedTotal !== bBoostedTotal) {
|
|
155
|
+
return aBoostedTotal > bBoostedTotal ? -1 : 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// A tie prefers a builder api bid over the p2p bid
|
|
159
|
+
const aIsBuilderApi = a.url !== undefined;
|
|
160
|
+
const bIsBuilderApi = b.url !== undefined;
|
|
161
|
+
if (aIsBuilderApi !== bIsBuilderApi) {
|
|
162
|
+
return aIsBuilderApi ? -1 : 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// and the earlier received bid between builder api bids
|
|
166
|
+
return a.receivedMs - b.receivedMs;
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
type ProduceBlockContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & {
|
|
@@ -1049,20 +1046,24 @@ export function getValidatorApi(
|
|
|
1049
1046
|
});
|
|
1050
1047
|
}
|
|
1051
1048
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1049
|
+
if (candidates.length === 0) {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const rankedCandidates = candidates.toSorted(compareBidCandidates);
|
|
1054
|
+
for (const [index, candidate] of rankedCandidates.entries()) {
|
|
1055
|
+
logger.debug("Builder bid candidate", {
|
|
1055
1056
|
slot,
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1057
|
+
rank: index + 1,
|
|
1058
|
+
source: candidate.url !== undefined ? toPrintableUrl(candidate.url) : "p2p",
|
|
1059
|
+
builder: candidate.signedBid.message.builderIndex,
|
|
1060
|
+
total: prettyGweiToEth(candidate.totalGwei),
|
|
1061
|
+
boost: candidate.boostFactor,
|
|
1062
|
+
boosted: prettyGweiToEth(getBoostedTotalScaled(candidate) / 100n),
|
|
1063
|
+
receivedMs: candidate.receivedMs,
|
|
1063
1064
|
});
|
|
1064
1065
|
}
|
|
1065
|
-
return
|
|
1066
|
+
return rankedCandidates[0];
|
|
1066
1067
|
})();
|
|
1067
1068
|
|
|
1068
1069
|
const commonBlockBodyPromise = chain.produceCommonBlockBody({
|
|
@@ -25,7 +25,11 @@ type ArchiveStoreModules = {
|
|
|
25
25
|
metrics: Metrics | null;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
type ArchiveStoreInitOpts = ArchiveStoreOpts & {
|
|
28
|
+
type ArchiveStoreInitOpts = ArchiveStoreOpts & {
|
|
29
|
+
dbName: string;
|
|
30
|
+
dataColumnDir: string;
|
|
31
|
+
anchorState: {finalizedCheckpoint: Checkpoint};
|
|
32
|
+
};
|
|
29
33
|
|
|
30
34
|
export enum ArchiveStoreTask {
|
|
31
35
|
ArchiveBlocks = "archive_blocks",
|
|
@@ -121,6 +125,7 @@ export class ArchiveStore {
|
|
|
121
125
|
opts: {
|
|
122
126
|
genesisTime: this.chain.clock.genesisTime,
|
|
123
127
|
dbLocation: this.opts.dbName,
|
|
128
|
+
dataColumnDir: this.opts.dataColumnDir,
|
|
124
129
|
nativeStateView: this.opts.nativeStateView ?? false,
|
|
125
130
|
},
|
|
126
131
|
config: this.chain.config,
|
|
@@ -33,6 +33,7 @@ export class HistoricalStateRegen implements HistoricalStateWorkerApi {
|
|
|
33
33
|
maxConcurrency: 1,
|
|
34
34
|
maxLength: 50,
|
|
35
35
|
dbLocation: modules.opts.dbLocation,
|
|
36
|
+
dataColumnDir: modules.opts.dataColumnDir,
|
|
36
37
|
metricsEnabled: Boolean(modules.metrics),
|
|
37
38
|
loggerOpts: modules.logger.toOpts(),
|
|
38
39
|
nativeStateView: modules.opts.nativeStateView,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {ForkName} from "@lodestar/params";
|
|
2
2
|
import {
|
|
3
3
|
BeaconStateTransitionMetrics,
|
|
4
|
+
BlockProcessStep,
|
|
4
5
|
EpochTransitionStep,
|
|
6
|
+
ProcessOperationsStep,
|
|
5
7
|
ProposerRewardType,
|
|
6
8
|
StateCloneSource,
|
|
7
9
|
StateHashTreeRootSource,
|
|
@@ -70,10 +72,21 @@ export function createHistoricalStateTransitionMetrics(
|
|
|
70
72
|
processBlockTime: metricsRegister.histogram({
|
|
71
73
|
name: "lodestar_historical_state_stfn_process_block_seconds",
|
|
72
74
|
help: "Time to process a single block in seconds",
|
|
73
|
-
// TODO: Add metrics for each step
|
|
74
75
|
// Block processing can take 5-40ms, 100ms max
|
|
75
76
|
buckets: [0.005, 0.01, 0.02, 0.05, 0.1, 1],
|
|
76
77
|
}),
|
|
78
|
+
processBlockStepTime: metricsRegister.histogram<{step: BlockProcessStep}>({
|
|
79
|
+
name: "lodestar_historical_state_stfn_process_block_step_seconds",
|
|
80
|
+
help: "Time to call each step of process block in seconds",
|
|
81
|
+
labelNames: ["step"],
|
|
82
|
+
buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1],
|
|
83
|
+
}),
|
|
84
|
+
processOperationsStepTime: metricsRegister.histogram<{step: ProcessOperationsStep}>({
|
|
85
|
+
name: "lodestar_historical_state_stfn_process_operations_step_seconds",
|
|
86
|
+
help: "Time to call each step of process operations in seconds",
|
|
87
|
+
labelNames: ["step"],
|
|
88
|
+
buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1],
|
|
89
|
+
}),
|
|
77
90
|
processBlockCommitTime: metricsRegister.histogram({
|
|
78
91
|
name: "lodestar_historical_state_stfn_process_block_commit_seconds",
|
|
79
92
|
help: "Time to call commit after process a single block in seconds",
|
|
@@ -7,6 +7,7 @@ export type HistoricalStateRegenInitModules = {
|
|
|
7
7
|
opts: {
|
|
8
8
|
genesisTime: number;
|
|
9
9
|
dbLocation: string;
|
|
10
|
+
dataColumnDir: string;
|
|
10
11
|
nativeStateView: boolean;
|
|
11
12
|
};
|
|
12
13
|
config: BeaconConfig;
|
|
@@ -25,6 +26,7 @@ export type HistoricalStateWorkerData = {
|
|
|
25
26
|
maxConcurrency: number;
|
|
26
27
|
maxLength: number;
|
|
27
28
|
dbLocation: string;
|
|
29
|
+
dataColumnDir: string;
|
|
28
30
|
metricsEnabled: boolean;
|
|
29
31
|
loggerOpts: LoggerNodeOpts;
|
|
30
32
|
nativeStateView: boolean;
|
|
@@ -25,7 +25,10 @@ logger.info("Historical state worker started");
|
|
|
25
25
|
|
|
26
26
|
const config = createBeaconConfig(chainConfigFromJson(workerData.chainConfigJson), workerData.genesisValidatorsRoot);
|
|
27
27
|
|
|
28
|
-
const db = new BeaconDb(config, await LevelDbController.create({name: workerData.dbLocation}, {logger})
|
|
28
|
+
const db = new BeaconDb(config, await LevelDbController.create({name: workerData.dbLocation}, {logger}), {
|
|
29
|
+
dataColumnDir: workerData.dataColumnDir,
|
|
30
|
+
logger,
|
|
31
|
+
});
|
|
29
32
|
|
|
30
33
|
const abortController = new AbortController();
|
|
31
34
|
|
|
@@ -134,6 +134,8 @@ export async function archiveBlocks(
|
|
|
134
134
|
logger.verbose("Migrated blobSidecars from hot DB to cold DB", {...logCtx, migratedEntries});
|
|
135
135
|
}
|
|
136
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.
|
|
137
139
|
if (finalizedPostFulu) {
|
|
138
140
|
const migratedSlots = await migrateDataColumnSidecarsFromHotToColdDb(
|
|
139
141
|
config,
|
|
@@ -142,7 +144,7 @@ export async function archiveBlocks(
|
|
|
142
144
|
finalizedCanonicalBlocks,
|
|
143
145
|
currentEpoch
|
|
144
146
|
);
|
|
145
|
-
logger.verbose("Migrated dataColumnSidecars from hot DB to cold DB", {
|
|
147
|
+
logger.verbose("Migrated legacy dataColumnSidecars from hot DB to cold DB", {
|
|
146
148
|
...logCtx,
|
|
147
149
|
migratedEntries: migratedSlots.length,
|
|
148
150
|
slotRange: prettyPrintIndices(migratedSlots),
|
|
@@ -195,6 +197,23 @@ export async function archiveBlocks(
|
|
|
195
197
|
slotRange: prettyPrintIndices(nonCanonicalSlots),
|
|
196
198
|
};
|
|
197
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
|
+
|
|
198
217
|
await db.block.batchDelete(nonCanonicalBlockRoots);
|
|
199
218
|
logger.verbose("Deleted non canonical blocks from hot DB", nonCanonicalLogCtx);
|
|
200
219
|
|
|
@@ -203,11 +222,6 @@ export async function archiveBlocks(
|
|
|
203
222
|
logger.verbose("Deleted non canonical blobSidecars from hot DB", nonCanonicalLogCtx);
|
|
204
223
|
}
|
|
205
224
|
|
|
206
|
-
if (finalizedPostFulu) {
|
|
207
|
-
await db.dataColumnSidecar.deleteMany(nonCanonicalBlockRoots);
|
|
208
|
-
logger.verbose("Deleted non canonical dataColumnSidecars from hot DB", nonCanonicalLogCtx);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
225
|
if (finalizedPostGloas) {
|
|
212
226
|
await db.executionPayloadEnvelope.batchDelete(nonCanonicalBlockRoots);
|
|
213
227
|
logger.verbose("Deleted non canonical executionPayloadEnvelopes from hot DB", nonCanonicalLogCtx);
|
|
@@ -245,23 +259,16 @@ export async function archiveBlocks(
|
|
|
245
259
|
);
|
|
246
260
|
const dataColumnSidecarsMinEpoch = currentEpoch - dataColumnSidecarsArchiveWindow;
|
|
247
261
|
if (dataColumnSidecarsMinEpoch >= config.FULU_FORK_EPOCH) {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
});
|
|
252
|
-
// for each slot there could be multiple dataColumnSidecar, so we need to deduplicate it
|
|
253
|
-
const slotsToDelete = [...new Set(prefixedKeys.map(({prefix}) => prefix))].sort((a, b) => a - b);
|
|
254
|
-
|
|
255
|
-
if (slotsToDelete.length > 0) {
|
|
256
|
-
await db.dataColumnSidecarArchive.deleteMany(slotsToDelete);
|
|
262
|
+
const columnsPruneSlot = computeStartSlotAtEpoch(dataColumnSidecarsMinEpoch);
|
|
263
|
+
const prunedColumnSlots = await db.dataColumns.pruneBefore(columnsPruneSlot);
|
|
264
|
+
if (prunedColumnSlots.length > 0) {
|
|
257
265
|
logger.verbose("dataColumnSidecars prune", {
|
|
258
266
|
...logCtx,
|
|
259
|
-
slotRange: prettyPrintIndices(
|
|
260
|
-
numOfSlots:
|
|
261
|
-
totalNumOfSidecars: prefixedKeys.length,
|
|
267
|
+
slotRange: prettyPrintIndices(prunedColumnSlots),
|
|
268
|
+
numOfSlots: prunedColumnSlots.length,
|
|
262
269
|
});
|
|
263
270
|
} else {
|
|
264
|
-
logger.verbose(
|
|
271
|
+
logger.verbose("dataColumnSidecars prune: no entries before slot", {...logCtx, slot: columnsPruneSlot});
|
|
265
272
|
}
|
|
266
273
|
} else {
|
|
267
274
|
logger.verbose(
|
|
@@ -389,7 +396,6 @@ async function migrateBlobSidecarsFromHotToColdDb(
|
|
|
389
396
|
)
|
|
390
397
|
).filter((e): e is KeyValue<Slot, Uint8Array> => e !== null);
|
|
391
398
|
|
|
392
|
-
// put to blockArchive db and delete block db
|
|
393
399
|
await Promise.all([
|
|
394
400
|
db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries),
|
|
395
401
|
db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)),
|
|
@@ -402,11 +408,9 @@ async function migrateBlobSidecarsFromHotToColdDb(
|
|
|
402
408
|
|
|
403
409
|
// TODO: This function can be simplified further by reducing layers of promises in a loop
|
|
404
410
|
/**
|
|
405
|
-
* Post-gloas the data columns of a Gloas block are tied to its execution payload envelope
|
|
406
|
-
*
|
|
407
|
-
* blocks only have a FULL variant, so the
|
|
408
|
-
* Blocks whose canonical variant is PENDING/EMPTY are skipped here — their columns will be picked
|
|
409
|
-
* 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.
|
|
410
414
|
*/
|
|
411
415
|
async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
412
416
|
config: ChainForkConfig,
|
|
@@ -428,32 +432,24 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
428
432
|
if (batch.length === 0) break;
|
|
429
433
|
|
|
430
434
|
const promises: Promise<void>[] = [];
|
|
431
|
-
|
|
432
|
-
// load Buffer instead of ssz deserialized to improve performance
|
|
433
435
|
for (const block of batch) {
|
|
434
|
-
const
|
|
435
|
-
const blockEpoch = computeEpochAtSlot(blockSlot);
|
|
436
|
-
|
|
436
|
+
const blockEpoch = computeEpochAtSlot(block.slot);
|
|
437
437
|
if (
|
|
438
|
-
config.getForkSeq(
|
|
439
|
-
// if block is out of ${config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS}, skip this step
|
|
438
|
+
config.getForkSeq(block.slot) < ForkSeq.fulu ||
|
|
440
439
|
blockEpoch < currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
|
|
441
440
|
) {
|
|
442
441
|
continue;
|
|
443
442
|
}
|
|
444
443
|
|
|
445
|
-
// Here we assume the data column sidecars are already in the hot db
|
|
446
444
|
const dataColumnSidecarBytes = await fromAsync(db.dataColumnSidecar.valuesStreamBinary(block.root));
|
|
447
445
|
if (dataColumnSidecarBytes.length === 0) {
|
|
448
|
-
// Empty stream: either the block has no blobs, or columns were already archived on a
|
|
449
|
-
// previous run (boundary block). Nothing to migrate.
|
|
450
446
|
logger.debug("DataColumnSidecars in forkchoice but missing in hot db, could be already archived", {
|
|
451
447
|
slot: block.slot,
|
|
452
448
|
root: toRootHex(block.root),
|
|
453
449
|
});
|
|
454
450
|
continue;
|
|
455
451
|
}
|
|
456
|
-
logger.verbose("Migrated dataColumnSidecars for block", {
|
|
452
|
+
logger.verbose("Migrated legacy dataColumnSidecars for block", {
|
|
457
453
|
currentEpoch,
|
|
458
454
|
slot: block.slot,
|
|
459
455
|
root: toRootHex(block.root),
|
|
@@ -462,15 +458,14 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
462
458
|
promises.push(
|
|
463
459
|
db.dataColumnSidecarArchive.putManyBinary(
|
|
464
460
|
block.slot,
|
|
465
|
-
dataColumnSidecarBytes.map((
|
|
461
|
+
dataColumnSidecarBytes.map((entry) => ({key: entry.id, value: entry.value}))
|
|
466
462
|
)
|
|
467
463
|
);
|
|
468
464
|
migratedSlots.push(block.slot);
|
|
469
465
|
}
|
|
470
466
|
|
|
471
|
-
promises.push(db.dataColumnSidecar.deleteMany(batch.map((block) => block.root)));
|
|
472
|
-
|
|
473
467
|
await Promise.all(promises);
|
|
468
|
+
await db.dataColumnSidecar.deleteMany(batch.map((block) => block.root));
|
|
474
469
|
}
|
|
475
470
|
|
|
476
471
|
// Ancestor walk is newest to oldest; sort ascending so `prettyPrintIndices` renders cleanly.
|
|
@@ -524,19 +524,6 @@ export async function importBlock(
|
|
|
524
524
|
this.metrics?.previousJustifiedEpoch.set(checkpointState.previousJustifiedCheckpoint.epoch);
|
|
525
525
|
this.metrics?.currentJustifiedEpoch.set(justifiedCheckpoint.epoch);
|
|
526
526
|
}
|
|
527
|
-
const finalizedCheckpoint = checkpointState.finalizedCheckpoint;
|
|
528
|
-
const finalizedEpoch = finalizedCheckpoint.epoch;
|
|
529
|
-
const preFinalizedEpoch = parentBlockSummary.finalizedEpoch;
|
|
530
|
-
if (finalizedEpoch > preFinalizedEpoch) {
|
|
531
|
-
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
|
|
532
|
-
block: toRootHex(finalizedCheckpoint.root),
|
|
533
|
-
epoch: finalizedCheckpoint.epoch,
|
|
534
|
-
state: toRootHex(checkpointState.hashTreeRoot()),
|
|
535
|
-
executionOptimistic: false,
|
|
536
|
-
});
|
|
537
|
-
this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint));
|
|
538
|
-
this.metrics?.finalizedEpoch.set(finalizedCheckpoint.epoch);
|
|
539
|
-
}
|
|
540
527
|
}
|
|
541
528
|
}
|
|
542
529
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
1
2
|
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
2
3
|
import {SignedBeaconBlock, Slot} from "@lodestar/types";
|
|
3
4
|
import {isErrorAborted, toRootHex} from "@lodestar/utils";
|
|
@@ -6,15 +7,17 @@ import {nextEventLoop} from "../../util/eventLoop.js";
|
|
|
6
7
|
import {JobItemQueue, isQueueErrorAborted} from "../../util/queue/index.js";
|
|
7
8
|
import type {BeaconChain} from "../chain.js";
|
|
8
9
|
import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.js";
|
|
10
|
+
import {ForkchoiceCaller} from "../forkChoice/index.js";
|
|
9
11
|
import {BlockProcessOpts} from "../options.js";
|
|
10
12
|
import {IBlockInput} from "./blockInput/types.js";
|
|
11
13
|
import {importBlock, importsBlockAttestations} from "./importBlock.js";
|
|
12
14
|
import {PayloadError, importExecutionPayload} from "./importExecutionPayload.js";
|
|
13
15
|
import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
14
|
-
import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
|
|
15
|
-
import {assertLinearChainSegment} from "./utils/chainSegment.js";
|
|
16
|
+
import {FullyVerifiedBlock, ImportBlockOpts, ProcessBlocksResult} from "./types.js";
|
|
17
|
+
import {OrphanedPayloadEnvelope, assertLinearChainSegment} from "./utils/chainSegment.js";
|
|
16
18
|
import {verifyBlocksInEpoch} from "./verifyBlock.js";
|
|
17
19
|
import {verifyBlocksSanityChecks} from "./verifyBlocksSanityChecks.js";
|
|
20
|
+
import {verifyPayloadsDataAvailability} from "./verifyPayloadsDataAvailability.js";
|
|
18
21
|
|
|
19
22
|
export {AttestationImportOpt, type ImportBlockOpts} from "./types.js";
|
|
20
23
|
|
|
@@ -24,10 +27,16 @@ const QUEUE_MAX_LENGTH = 256;
|
|
|
24
27
|
* BlockProcessor processes block jobs in a queued fashion, one after the other.
|
|
25
28
|
*/
|
|
26
29
|
export class BlockProcessor {
|
|
27
|
-
readonly jobQueue: JobItemQueue<
|
|
30
|
+
readonly jobQueue: JobItemQueue<
|
|
31
|
+
[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts],
|
|
32
|
+
ProcessBlocksResult
|
|
33
|
+
>;
|
|
28
34
|
|
|
29
35
|
constructor(chain: BeaconChain, metrics: Metrics | null, opts: BlockProcessOpts, signal: AbortSignal) {
|
|
30
|
-
this.jobQueue = new JobItemQueue<
|
|
36
|
+
this.jobQueue = new JobItemQueue<
|
|
37
|
+
[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts],
|
|
38
|
+
ProcessBlocksResult
|
|
39
|
+
>(
|
|
31
40
|
(job, payloadEnvelopes, importOpts) => {
|
|
32
41
|
return processBlocks.call(chain, job, payloadEnvelopes, {...opts, ...importOpts});
|
|
33
42
|
},
|
|
@@ -40,8 +49,8 @@ export class BlockProcessor {
|
|
|
40
49
|
job: IBlockInput[],
|
|
41
50
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
42
51
|
opts: ImportBlockOpts = {}
|
|
43
|
-
): Promise<
|
|
44
|
-
|
|
52
|
+
): Promise<ProcessBlocksResult> {
|
|
53
|
+
return this.jobQueue.push(job, payloadEnvelopes, opts);
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
|
|
@@ -60,9 +69,10 @@ export async function processBlocks(
|
|
|
60
69
|
blocks: IBlockInput[],
|
|
61
70
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
62
71
|
opts: BlockProcessOpts & ImportBlockOpts
|
|
63
|
-
): Promise<
|
|
72
|
+
): Promise<ProcessBlocksResult> {
|
|
73
|
+
const emptyResult: ProcessBlocksResult = {orphaned: [], skipped: false};
|
|
64
74
|
if (blocks.length === 0) {
|
|
65
|
-
return; // TODO: or throw?
|
|
75
|
+
return emptyResult; // TODO: or throw?
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
try {
|
|
@@ -71,7 +81,7 @@ export async function processBlocks(
|
|
|
71
81
|
// No relevant blocks, skip verifyBlocksInEpoch()
|
|
72
82
|
if (relevantBlocks.length === 0 || parentBlock === null) {
|
|
73
83
|
// parentBlock can only be null if relevantBlocks are empty
|
|
74
|
-
return;
|
|
84
|
+
return await importPayloadEnvelopesOfKnownBlocks.call(this, payloadEnvelopes, opts);
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
const {warnings: orphanedPayloads} = assertLinearChainSegment(
|
|
@@ -86,18 +96,15 @@ export async function processBlocks(
|
|
|
86
96
|
// status tiebreaker then picks FULL and parks the head there.
|
|
87
97
|
const blockEpoch = computeEpochAtSlot(relevantBlocks[0].getBlock().message.slot);
|
|
88
98
|
const importsAttestations = importsBlockAttestations(opts, blockEpoch, this.clock.currentEpoch);
|
|
99
|
+
const skipped = orphanedPayloads != null && payloadEnvelopes !== null && !importsAttestations;
|
|
89
100
|
let payloadEnvelopesToImport = payloadEnvelopes;
|
|
90
|
-
if (
|
|
101
|
+
if (skipped) {
|
|
91
102
|
payloadEnvelopesToImport = new Map(payloadEnvelopes);
|
|
92
|
-
for (const orphaned of orphanedPayloads) {
|
|
103
|
+
for (const orphaned of orphanedPayloads ?? []) {
|
|
93
104
|
payloadEnvelopesToImport.delete(orphaned.slot);
|
|
94
105
|
// never validated, drop it from the shared cache so it is not served to peers, by-root sync reloads the
|
|
95
106
|
// entry from db if the payload is ever needed
|
|
96
107
|
this.seenPayloadEnvelopeInputCache.prune(orphaned.payloadEnvelopeInput.blockRootHex);
|
|
97
|
-
this.logger.debug("Skipping orphaned payload envelope in chain segment", {
|
|
98
|
-
slot: orphaned.slot,
|
|
99
|
-
blockRoot: orphaned.payloadEnvelopeInput.blockRootHex,
|
|
100
|
-
});
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
|
|
@@ -172,9 +179,11 @@ export async function processBlocks(
|
|
|
172
179
|
|
|
173
180
|
await nextEventLoop();
|
|
174
181
|
}
|
|
182
|
+
|
|
183
|
+
return {orphaned: orphanedPayloads ?? [], skipped};
|
|
175
184
|
} catch (e) {
|
|
176
185
|
if (isErrorAborted(e) || isQueueErrorAborted(e) || isBlockErrorAborted(e)) {
|
|
177
|
-
return; // Ignore
|
|
186
|
+
return emptyResult; // Ignore
|
|
178
187
|
}
|
|
179
188
|
|
|
180
189
|
// above functions should only throw BlockError, or PayloadError from the gloas payload import
|
|
@@ -200,8 +209,19 @@ export async function processBlocks(
|
|
|
200
209
|
const blockSlot = signedBlock.message.slot;
|
|
201
210
|
const {state} = err.type;
|
|
202
211
|
const forkTypes = this.config.getForkTypes(blockSlot);
|
|
203
|
-
|
|
204
|
-
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
|
+
);
|
|
205
225
|
} else if (err.type.code === BlockErrorCode.INVALID_STATE_ROOT) {
|
|
206
226
|
const {signedBlock} = err;
|
|
207
227
|
const blockSlot = signedBlock.message.slot;
|
|
@@ -222,6 +242,70 @@ export async function processBlocks(
|
|
|
222
242
|
}
|
|
223
243
|
}
|
|
224
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
|
+
|
|
225
309
|
function getBlockOrPayloadError(e: unknown, block: SignedBeaconBlock): BlockError | PayloadError {
|
|
226
310
|
if (e instanceof BlockError) {
|
|
227
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(
|
|
@@ -48,6 +48,15 @@ export function assertLinearChainSegment(
|
|
|
48
48
|
if (parentBlock !== null) {
|
|
49
49
|
const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot);
|
|
50
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
|
+
}
|
|
51
60
|
const parentPayloadBlockHash = parentPayloadInput.getBlockHashHex();
|
|
52
61
|
if (currentExecHash === parentPayloadBlockHash) {
|
|
53
62
|
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|