@lodestar/beacon-node 1.48.0-dev.0dbea0de1b → 1.48.0-dev.3211c1bdd4
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 +0 -7
- 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/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +4 -1
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts +5 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +12 -5
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +74 -7
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +16 -10
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +16 -0
- package/lib/chain/validation/executionPayloadBid.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/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +3 -0
- package/lib/execution/builder/validateBid.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -3
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -5
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +382 -278
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +11 -16
- package/lib/network/processor/index.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +0 -10
- package/src/api/impl/beacon/state/utils.ts +15 -0
- package/src/api/impl/config/index.ts +4 -1
- package/src/chain/archiveStore/archiveStore.ts +6 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
- package/src/chain/blocks/importBlock.ts +25 -7
- package/src/chain/blocks/index.ts +87 -7
- package/src/chain/blocks/utils/chainSegment.ts +16 -11
- package/src/chain/errors/executionPayloadBid.ts +2 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/validation/executionPayloadBid.ts +18 -0
- package/src/chain/validation/payloadAttestationMessage.ts +8 -9
- package/src/execution/builder/validateBid.ts +4 -0
- package/src/metrics/metrics/lodestar.ts +6 -5
- package/src/network/processor/gossipHandlers.ts +489 -344
- package/src/network/processor/index.ts +11 -17
|
@@ -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}>();
|
|
@@ -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
16
|
import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
|
|
14
17
|
import {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
|
|
|
@@ -70,6 +74,7 @@ export async function processBlocks(
|
|
|
70
74
|
// No relevant blocks, skip verifyBlocksInEpoch()
|
|
71
75
|
if (relevantBlocks.length === 0 || parentBlock === null) {
|
|
72
76
|
// parentBlock can only be null if relevantBlocks are empty
|
|
77
|
+
await importPayloadEnvelopesOfKnownBlocks.call(this, payloadEnvelopes, opts);
|
|
73
78
|
return;
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -79,9 +84,21 @@ export async function processBlocks(
|
|
|
79
84
|
payloadEnvelopes,
|
|
80
85
|
parentBlock
|
|
81
86
|
);
|
|
82
|
-
|
|
87
|
+
// Orphaned payloads of recent blocks are still imported, a reorg of the child could make the payload canonical
|
|
88
|
+
// again and later blocks build on it. Older ones are skipped: without the block attestations the orphaned FULL
|
|
89
|
+
// variant has no descendants and ties at zero weight with the EMPTY variant carrying the chain, the payload
|
|
90
|
+
// status tiebreaker then picks FULL and parks the head there.
|
|
91
|
+
const blockEpoch = computeEpochAtSlot(relevantBlocks[0].getBlock().message.slot);
|
|
92
|
+
const importsAttestations = importsBlockAttestations(opts, blockEpoch, this.clock.currentEpoch);
|
|
93
|
+
let payloadEnvelopesToImport = payloadEnvelopes;
|
|
94
|
+
if (orphanedPayloads != null && payloadEnvelopes !== null && !importsAttestations) {
|
|
95
|
+
payloadEnvelopesToImport = new Map(payloadEnvelopes);
|
|
83
96
|
for (const orphaned of orphanedPayloads) {
|
|
84
|
-
|
|
97
|
+
payloadEnvelopesToImport.delete(orphaned.slot);
|
|
98
|
+
// never validated, drop it from the shared cache so it is not served to peers, by-root sync reloads the
|
|
99
|
+
// entry from db if the payload is ever needed
|
|
100
|
+
this.seenPayloadEnvelopeInputCache.prune(orphaned.payloadEnvelopeInput.blockRootHex);
|
|
101
|
+
this.logger.debug("Skipping orphaned payload envelope in chain segment", {
|
|
85
102
|
slot: orphaned.slot,
|
|
86
103
|
blockRoot: orphaned.payloadEnvelopeInput.blockRootHex,
|
|
87
104
|
});
|
|
@@ -97,7 +114,7 @@ export async function processBlocks(
|
|
|
97
114
|
proposerBalanceDeltas,
|
|
98
115
|
segmentExecStatus,
|
|
99
116
|
indexedAttestationsByBlock,
|
|
100
|
-
} = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks,
|
|
117
|
+
} = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopesToImport, opts);
|
|
101
118
|
|
|
102
119
|
// If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
|
|
103
120
|
// and we need to further propagate
|
|
@@ -132,8 +149,8 @@ export async function processBlocks(
|
|
|
132
149
|
}
|
|
133
150
|
|
|
134
151
|
const slotSet = new Set<Slot>(blocks.map((b) => b.getBlock().message.slot));
|
|
135
|
-
if (
|
|
136
|
-
for (const slot of
|
|
152
|
+
if (payloadEnvelopesToImport) {
|
|
153
|
+
for (const slot of payloadEnvelopesToImport.keys()) slotSet.add(slot);
|
|
137
154
|
}
|
|
138
155
|
const slots = Array.from(slotSet).sort((a, b) => a - b);
|
|
139
156
|
for (const slot of slots) {
|
|
@@ -146,7 +163,7 @@ export async function processBlocks(
|
|
|
146
163
|
// PayloadEnvelopeInput is shared and may receive an envelope after the DA snapshot was taken.
|
|
147
164
|
const payloadDA = payloadDAStatuses.get(slot);
|
|
148
165
|
if (payloadDA !== undefined) {
|
|
149
|
-
const payloadInput =
|
|
166
|
+
const payloadInput = payloadEnvelopesToImport?.get(slot);
|
|
150
167
|
if (payloadInput === undefined) {
|
|
151
168
|
throw new Error(`Missing payload input for slot ${slot} after DA verification`);
|
|
152
169
|
}
|
|
@@ -209,6 +226,69 @@ export async function processBlocks(
|
|
|
209
226
|
}
|
|
210
227
|
}
|
|
211
228
|
|
|
229
|
+
/**
|
|
230
|
+
* A peer can serve a block without its envelope, the block is then imported with a PENDING payload. Later batches
|
|
231
|
+
* carry the envelope but all their blocks are known, without importing it here every child building on the FULL
|
|
232
|
+
* variant fails with PARENT_PAYLOAD_UNKNOWN and range sync never progresses. If our head already descends from the
|
|
233
|
+
* block's EMPTY variant the chain did not build on the payload, importing it would only add a dead FULL leaf. As for
|
|
234
|
+
* orphaned payloads in a segment this only applies while the block attestations are not imported, a recent payload
|
|
235
|
+
* is imported anyway so a reorg building on it does not have to fetch it through unknown block sync.
|
|
236
|
+
*/
|
|
237
|
+
async function importPayloadEnvelopesOfKnownBlocks(
|
|
238
|
+
this: BeaconChain,
|
|
239
|
+
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
240
|
+
opts: ImportBlockOpts
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
if (payloadEnvelopes === null) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const head = this.forkChoice.getHead();
|
|
247
|
+
const currentEpoch = this.clock.currentEpoch;
|
|
248
|
+
const payloadInputs: PayloadEnvelopeInput[] = [];
|
|
249
|
+
const orphanedSlots: Slot[] = [];
|
|
250
|
+
for (const payloadInput of payloadEnvelopes.values()) {
|
|
251
|
+
const {blockRootHex, slot} = payloadInput;
|
|
252
|
+
if (
|
|
253
|
+
!payloadInput.hasPayloadEnvelope() ||
|
|
254
|
+
!this.forkChoice.hasBlockHex(blockRootHex) ||
|
|
255
|
+
this.forkChoice.getBlockHexAndBlockHash(blockRootHex, payloadInput.getBlockHashHex()) !== null
|
|
256
|
+
) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (
|
|
260
|
+
head.blockRoot !== blockRootHex &&
|
|
261
|
+
!importsBlockAttestations(opts, computeEpochAtSlot(slot), currentEpoch) &&
|
|
262
|
+
this.forkChoice.isDescendant(blockRootHex, PayloadStatus.EMPTY, head.blockRoot, head.payloadStatus)
|
|
263
|
+
) {
|
|
264
|
+
// never validated, see processBlocks()
|
|
265
|
+
this.seenPayloadEnvelopeInputCache.prune(blockRootHex);
|
|
266
|
+
orphanedSlots.push(slot);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
payloadInputs.push(payloadInput);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (orphanedSlots.length > 0) {
|
|
273
|
+
this.logger.debug("Skipping orphaned payload envelopes of known blocks", {slots: orphanedSlots.join(",")});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (payloadInputs.length === 0) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability(payloadInputs, new AbortController().signal);
|
|
281
|
+
for (let i = 0; i < payloadInputs.length; i++) {
|
|
282
|
+
this.logger.debug("Importing payload envelope of known block", {
|
|
283
|
+
slot: payloadInputs[i].slot,
|
|
284
|
+
blockRoot: payloadInputs[i].blockRootHex,
|
|
285
|
+
});
|
|
286
|
+
await importExecutionPayload.call(this, payloadInputs[i], dataAvailabilityStatuses[i], {validSignature: false});
|
|
287
|
+
}
|
|
288
|
+
// the new FULL variants are not seen by the head cached from the last block import
|
|
289
|
+
this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock);
|
|
290
|
+
}
|
|
291
|
+
|
|
212
292
|
function getBlockOrPayloadError(e: unknown, block: SignedBeaconBlock): BlockError | PayloadError {
|
|
213
293
|
if (e instanceof BlockError) {
|
|
214
294
|
return e;
|
|
@@ -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,22 @@ 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
|
+
const parentPayloadBlockHash = parentPayloadInput.getBlockHashHex();
|
|
52
|
+
if (currentExecHash === parentPayloadBlockHash) {
|
|
53
|
+
// Checkpoint-sync first batch: the anchor is stored PENDING with the inherited (grandparent)
|
|
54
|
+
// hash, its own payload is in this batch and is the real parent EL head
|
|
55
|
+
parentExecHash = parentPayloadBlockHash;
|
|
56
|
+
} else {
|
|
57
|
+
// First block builds on the parent's EMPTY variant, the parent's payload in this batch is orphaned
|
|
58
|
+
warnings.push({slot: parentBlock.slot, payloadEnvelopeInput: parentPayloadInput});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
// First block must build on the parent's EL head. PARENT_PAYLOAD_UNKNOWN (vs the in-segment
|
|
59
64
|
// NON_LINEAR_PAYLOAD_ROOTS below) tells range sync this is a parent-link problem, not an in-batch one.
|
|
60
65
|
if (parentExecHash !== null && currentExecHash !== null && currentExecHash !== parentExecHash) {
|
|
@@ -5,6 +5,7 @@ export enum ExecutionPayloadBidErrorCode {
|
|
|
5
5
|
BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
|
|
6
6
|
INVALID_BUILDER_VERSION = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_BUILDER_VERSION",
|
|
7
7
|
NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
|
|
8
|
+
BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH",
|
|
8
9
|
INCOMPATIBLE_WITH_HEAD = "EXECUTION_PAYLOAD_BID_ERROR_INCOMPATIBLE_WITH_HEAD",
|
|
9
10
|
BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
|
|
10
11
|
BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
|
|
@@ -63,6 +64,7 @@ export type ExecutionPayloadBidErrorType =
|
|
|
63
64
|
}
|
|
64
65
|
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT; parentBlockRoot: RootHex}
|
|
65
66
|
| {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
|
|
67
|
+
| {code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH; blockHash: RootHex}
|
|
66
68
|
| {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
|
|
67
69
|
| {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
|
|
68
70
|
| {
|
|
@@ -135,6 +135,7 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
135
135
|
blockRoot: toRootHex(checkpoint.root),
|
|
136
136
|
timeliness: true, // Optimistically assume is timely
|
|
137
137
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
138
|
+
importedTimely: true, // Optimistically assume is timely
|
|
138
139
|
proposerIndex: blockHeader.proposerIndex,
|
|
139
140
|
|
|
140
141
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -233,6 +234,7 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
233
234
|
targetRoot: headRoot,
|
|
234
235
|
timeliness: true, // Optimistically assume is timely
|
|
235
236
|
ptcTimeliness: true, // Spec: block_timeliness for anchor = [True, True]
|
|
237
|
+
importedTimely: true, // Optimistically assume is timely
|
|
236
238
|
proposerIndex: blockHeader.proposerIndex,
|
|
237
239
|
|
|
238
240
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
@@ -113,6 +113,15 @@ export async function validateApiExecutionPayloadBid(
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
// [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
|
|
117
|
+
// `bid.block_hash != bid.parent_block_hash`.
|
|
118
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
119
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
120
|
+
code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
|
|
121
|
+
blockHash: toRootHex(bid.blockHash),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
116
125
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
117
126
|
// consensus layer -- i.e. validate that
|
|
118
127
|
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
@@ -266,6 +275,15 @@ async function validateExecutionPayloadBid(
|
|
|
266
275
|
});
|
|
267
276
|
}
|
|
268
277
|
|
|
278
|
+
// [REJECT] The bid's block hash is not equal to its parent block hash -- i.e.
|
|
279
|
+
// `bid.block_hash != bid.parent_block_hash`.
|
|
280
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
281
|
+
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
|
|
282
|
+
code: ExecutionPayloadBidErrorCode.BLOCK_HASH_EQUALS_PARENT_BLOCK_HASH,
|
|
283
|
+
blockHash: toRootHex(bid.blockHash),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
269
287
|
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
|
|
270
288
|
// consensus layer -- i.e. validate that
|
|
271
289
|
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
|
|
@@ -7,7 +7,6 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
|
|
|
7
7
|
import {toRootHex} from "@lodestar/utils";
|
|
8
8
|
import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
|
|
9
9
|
import {IBeaconChain} from "../index.js";
|
|
10
|
-
import {RegenCaller} from "../regen/index.js";
|
|
11
10
|
|
|
12
11
|
export type PayloadAttestationValidationResult = {
|
|
13
12
|
attDataRootHex: RootHex;
|
|
@@ -82,15 +81,15 @@ async function validatePayloadAttestationMessage(
|
|
|
82
81
|
// TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
|
|
83
82
|
// it is possible that the block didn't pass the validation
|
|
84
83
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
84
|
+
// block.slot === data.slot is enforced above, so use the block's post-state directly to avoid
|
|
85
|
+
// getting through regen queue
|
|
86
|
+
const state = chain.regen.getStateSync(block.stateRoot);
|
|
87
|
+
if (state == null) {
|
|
88
|
+
throw new PayloadAttestationError(GossipAction.IGNORE, {
|
|
89
|
+
code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
|
|
90
|
+
blockRoot: toRootHex(data.beaconBlockRoot),
|
|
93
91
|
});
|
|
92
|
+
}
|
|
94
93
|
|
|
95
94
|
if (!isStatePostGloas(state)) {
|
|
96
95
|
throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
|
|
@@ -56,6 +56,10 @@ export async function validateBuilderApiExecutionPayloadBid(
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
if (byteArrayEquals(bid.blockHash, bid.parentBlockHash)) {
|
|
60
|
+
throw Error(`Bid block hash ${toRootHex(bid.blockHash)} must not equal its parent block hash`);
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
|
|
60
64
|
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
|
|
61
65
|
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {NotReorgedReason} from "@lodestar/fork-choice";
|
|
3
3
|
import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
|
+
import type {LateCanonicalBlockReason} from "../../chain/archiveStore/utils/archiveBlocks.js";
|
|
5
6
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
7
|
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
7
8
|
import {
|
|
@@ -1047,6 +1048,11 @@ export function createLodestarMetrics(
|
|
|
1047
1048
|
name: "lodestar_import_block_set_head_after_cutoff_total",
|
|
1048
1049
|
help: "Total times an imported block is set as head after ATTESTATION_DUE_BPS of the slot",
|
|
1049
1050
|
}),
|
|
1051
|
+
lateCanonicalBlock: register.counter<{reason: LateCanonicalBlockReason}>({
|
|
1052
|
+
name: "lodestar_import_block_late_canonical_total",
|
|
1053
|
+
help: "Total finalized-canonical blocks this node imported after the attestation cutoff; reason distinguishes this node's processing lag (slow_import) from late reception (late_receive)",
|
|
1054
|
+
labelNames: ["reason"],
|
|
1055
|
+
}),
|
|
1050
1056
|
bySource: register.gauge<{source: BlockInputSource}>({
|
|
1051
1057
|
name: "lodestar_import_block_by_source_total",
|
|
1052
1058
|
help: "Total number of imported blocks by source",
|
|
@@ -1352,11 +1358,6 @@ export function createLodestarMetrics(
|
|
|
1352
1358
|
help: "Total number of InsertOutcome as a result of adding an execution payload bid from gossip to the pool",
|
|
1353
1359
|
labelNames: ["insertOutcome"],
|
|
1354
1360
|
}),
|
|
1355
|
-
apiInsertOutcome: register.counter<{insertOutcome: InsertOutcome}>({
|
|
1356
|
-
name: "lodestar_oppool_execution_payload_bid_pool_api_insert_outcome_total",
|
|
1357
|
-
help: "Total number of InsertOutcome as a result of adding an execution payload bid from api to the pool",
|
|
1358
|
-
labelNames: ["insertOutcome"],
|
|
1359
|
-
}),
|
|
1360
1361
|
apiValidationTime: register.histogram({
|
|
1361
1362
|
name: "lodestar_api_execution_payload_bid_validation_time_seconds",
|
|
1362
1363
|
help: "Time elapsed for signed execution payload bid validation - api path",
|