@lodestar/beacon-node 1.48.0-dev.f22c5ce63e → 1.48.0-dev.fd70e5fc15
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/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.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 -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 +80 -15
- 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/chain.d.ts +7 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +69 -40
- 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 +3 -5
- 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/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/errors/executionPayloadEnvelope.d.ts +0 -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 +2 -2
- 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/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 +9 -50
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +56 -40
- 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 +0 -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/execution/builder/validateBid.d.ts.map +1 -1
- package/lib/execution/builder/validateBid.js +8 -5
- 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 +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/network.js +2 -2
- package/lib/network/network.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 +22 -16
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/rateLimit.js +2 -1
- package/lib/network/reqresp/rateLimit.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/package.json +15 -15
- 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/api/impl/validator/index.ts +44 -43
- package/src/chain/archiveStore/archiveStore.ts +6 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
- package/src/chain/blocks/importBlock.ts +25 -20
- package/src/chain/blocks/index.ts +107 -21
- package/src/chain/blocks/types.ts +9 -0
- package/src/chain/blocks/utils/chainSegment.ts +26 -12
- package/src/chain/chain.ts +78 -50
- package/src/chain/emitter.ts +1 -1
- package/src/chain/errors/blockError.ts +3 -3
- package/src/chain/errors/executionPayloadBid.ts +2 -0
- package/src/chain/errors/executionPayloadEnvelope.ts +0 -9
- package/src/chain/errors/gossipValidation.ts +1 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/interface.ts +2 -2
- 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/produceBlock/produceBlockBody.ts +50 -71
- package/src/chain/validation/block.ts +9 -66
- package/src/chain/validation/executionPayloadBid.ts +62 -44
- package/src/chain/validation/executionPayloadEnvelope.ts +0 -37
- package/src/chain/validation/payloadAttestationMessage.ts +8 -9
- package/src/chain/validatorMonitor.ts +24 -7
- package/src/execution/builder/validateBid.ts +10 -6
- package/src/execution/engine/mock.ts +4 -1
- package/src/metrics/metrics/lodestar.ts +6 -5
- package/src/network/network.ts +2 -2
- package/src/network/processor/gossipHandlers.ts +489 -344
- package/src/network/processor/index.ts +24 -17
- package/src/network/reqresp/rateLimit.ts +2 -1
- package/src/sync/range/range.ts +23 -1
|
@@ -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) {
|
package/src/chain/chain.ts
CHANGED
|
@@ -82,7 +82,7 @@ import {IBlockInput, isBlockInputBlobs, isBlockInputColumns} from "./blocks/bloc
|
|
|
82
82
|
import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
|
|
83
83
|
import {PayloadEnvelopeInputSource} from "./blocks/payloadEnvelopeInput/index.js";
|
|
84
84
|
import {PayloadEnvelopeProcessor} from "./blocks/payloadEnvelopeProcessor.js";
|
|
85
|
-
import {ImportPayloadOpts} from "./blocks/types.js";
|
|
85
|
+
import {ImportPayloadOpts, ProcessBlocksResult} from "./blocks/types.js";
|
|
86
86
|
import {persistBlockInput} from "./blocks/writeBlockInputToDb.js";
|
|
87
87
|
import {persistPayloadEnvelopeInput} from "./blocks/writePayloadEnvelopeInputToDb.js";
|
|
88
88
|
import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
|
|
@@ -1167,15 +1167,15 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1167
1167
|
}
|
|
1168
1168
|
|
|
1169
1169
|
async processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void> {
|
|
1170
|
-
|
|
1170
|
+
await this.blockProcessor.processBlocksJob([block], null, opts);
|
|
1171
1171
|
}
|
|
1172
1172
|
|
|
1173
1173
|
async processChainSegment(
|
|
1174
1174
|
blocks: IBlockInput[],
|
|
1175
1175
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
1176
1176
|
opts?: ImportBlockOpts
|
|
1177
|
-
): Promise<
|
|
1178
|
-
|
|
1177
|
+
): Promise<ProcessBlocksResult> {
|
|
1178
|
+
return this.blockProcessor.processBlocksJob(blocks, payloadEnvelopes, opts);
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
1181
|
async processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void> {
|
|
@@ -1271,59 +1271,65 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1271
1271
|
try {
|
|
1272
1272
|
const prevHead = this.forkChoice.getHead();
|
|
1273
1273
|
const head = this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicalHead}).head;
|
|
1274
|
+
this.emitHeadEvents(prevHead, head);
|
|
1275
|
+
return head;
|
|
1276
|
+
} catch (e) {
|
|
1277
|
+
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
|
|
1278
|
+
throw e;
|
|
1279
|
+
} finally {
|
|
1280
|
+
timer?.();
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1274
1283
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1284
|
+
/**
|
|
1285
|
+
* Emit `head` and `head_v2` when the fork choice head differs between two observations. Every caller that
|
|
1286
|
+
* can move the head, `updateAndGetHead()` and `updateTime()`, must capture the head before and pass both.
|
|
1287
|
+
*/
|
|
1288
|
+
private emitHeadEvents(prevHead: ProtoBlock, head: ProtoBlock): void {
|
|
1289
|
+
const headRootChanged = head.blockRoot !== prevHead.blockRoot;
|
|
1280
1290
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const epochTransition = computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot;
|
|
1285
|
-
const executionOptimistic = isOptimisticBlock(head);
|
|
1286
|
-
|
|
1287
|
-
if (headRootChanged) {
|
|
1288
|
-
this.emitter.emit(routes.events.EventType.head, {
|
|
1289
|
-
block: head.blockRoot,
|
|
1290
|
-
epochTransition,
|
|
1291
|
-
slot: head.slot,
|
|
1292
|
-
state: head.stateRoot,
|
|
1293
|
-
previousDutyDependentRoot,
|
|
1294
|
-
currentDutyDependentRoot,
|
|
1295
|
-
executionOptimistic,
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1291
|
+
if (!headRootChanged && prevHead.payloadStatus === head.payloadStatus) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1298
1294
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1295
|
+
try {
|
|
1296
|
+
const previousDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.previous);
|
|
1297
|
+
const currentDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.current);
|
|
1298
|
+
const epochTransition = computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot;
|
|
1299
|
+
const executionOptimistic = isOptimisticBlock(head);
|
|
1300
|
+
|
|
1301
|
+
if (headRootChanged) {
|
|
1302
|
+
this.emitter.emit(routes.events.EventType.head, {
|
|
1303
|
+
block: head.blockRoot,
|
|
1304
|
+
epochTransition,
|
|
1305
|
+
slot: head.slot,
|
|
1306
|
+
state: head.stateRoot,
|
|
1307
|
+
previousDutyDependentRoot,
|
|
1308
|
+
currentDutyDependentRoot,
|
|
1309
|
+
executionOptimistic,
|
|
1311
1310
|
});
|
|
1312
|
-
} catch (e) {
|
|
1313
|
-
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
|
|
1314
|
-
this.logger.debug(
|
|
1315
|
-
"Error emitting head/head_v2 event",
|
|
1316
|
-
{slot: head.slot, root: head.blockRoot, headRootChanged},
|
|
1317
|
-
e as Error
|
|
1318
|
-
);
|
|
1319
1311
|
}
|
|
1320
1312
|
|
|
1321
|
-
|
|
1313
|
+
this.emitter.emit(routes.events.EventType.headV2, {
|
|
1314
|
+
version: this.config.getForkName(head.slot),
|
|
1315
|
+
data: {
|
|
1316
|
+
slot: head.slot,
|
|
1317
|
+
block: head.blockRoot,
|
|
1318
|
+
state: head.stateRoot,
|
|
1319
|
+
payloadStatus: head.payloadStatus === PayloadStatus.FULL ? "full" : "empty",
|
|
1320
|
+
epochTransition,
|
|
1321
|
+
currentEpochDependentRoot: previousDutyDependentRoot,
|
|
1322
|
+
nextEpochDependentRoot: currentDutyDependentRoot,
|
|
1323
|
+
executionOptimistic,
|
|
1324
|
+
},
|
|
1325
|
+
});
|
|
1322
1326
|
} catch (e) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
+
// getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
|
|
1328
|
+
this.logger.debug(
|
|
1329
|
+
"Error emitting head/head_v2 event",
|
|
1330
|
+
{slot: head.slot, root: head.blockRoot, headRootChanged},
|
|
1331
|
+
e as Error
|
|
1332
|
+
);
|
|
1327
1333
|
}
|
|
1328
1334
|
}
|
|
1329
1335
|
|
|
@@ -1612,7 +1618,10 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1612
1618
|
this.processShutdownCallback(this.forkChoice.irrecoverableError);
|
|
1613
1619
|
}
|
|
1614
1620
|
|
|
1621
|
+
// updateTime() recomputes the head on epoch-boundary checkpoint pull-up and under fast confirmation
|
|
1622
|
+
const prevHead = this.forkChoice.getHead();
|
|
1615
1623
|
this.forkChoice.updateTime(slot);
|
|
1624
|
+
this.emitHeadEvents(prevHead, this.forkChoice.getHead());
|
|
1616
1625
|
this.metrics?.clockSlot.set(slot);
|
|
1617
1626
|
|
|
1618
1627
|
this.attestationPool.prune(slot);
|
|
@@ -1670,6 +1679,25 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1670
1679
|
|
|
1671
1680
|
private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
|
|
1672
1681
|
this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
|
|
1682
|
+
this.metrics?.finalizedEpoch.set(cp.epoch);
|
|
1683
|
+
const finalizedBlock = this.forkChoice.getBlockHexDefaultStatus(cp.rootHex);
|
|
1684
|
+
if (finalizedBlock) {
|
|
1685
|
+
// The callback runs synchronously inside fork choice checkpoint updates, defer writing to subscribers
|
|
1686
|
+
callInNextEventLoop(() => {
|
|
1687
|
+
this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
|
|
1688
|
+
block: cp.rootHex,
|
|
1689
|
+
epoch: cp.epoch,
|
|
1690
|
+
state: finalizedBlock.stateRoot,
|
|
1691
|
+
executionOptimistic: isOptimisticBlock(finalizedBlock),
|
|
1692
|
+
});
|
|
1693
|
+
});
|
|
1694
|
+
} else {
|
|
1695
|
+
this.logger.debug("Finalized block not found in fork choice, skip finalized_checkpoint event", {
|
|
1696
|
+
epoch: cp.epoch,
|
|
1697
|
+
root: cp.rootHex,
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1673
1701
|
const finalizedSlot = computeStartSlotAtEpoch(cp.epoch);
|
|
1674
1702
|
this.seenBlockProposers.prune(finalizedSlot);
|
|
1675
1703
|
|
package/src/chain/emitter.ts
CHANGED
|
@@ -33,7 +33,7 @@ export enum ChainEvent {
|
|
|
33
33
|
/**
|
|
34
34
|
* This event signals that the fork choice store has been updated.
|
|
35
35
|
*
|
|
36
|
-
* This event is guaranteed to be triggered whenever the fork choice
|
|
36
|
+
* This event is guaranteed to be triggered whenever the fork choice finalized checkpoint is updated. This is either in response to a newly processed block or a new clock tick.
|
|
37
37
|
*/
|
|
38
38
|
forkChoiceFinalized = "forkChoice:finalized",
|
|
39
39
|
/**
|
|
@@ -78,8 +78,8 @@ export enum BlockErrorCode {
|
|
|
78
78
|
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
|
|
79
79
|
/** Bid parent block root does not match block parent root */
|
|
80
80
|
BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
|
|
81
|
-
/** A block
|
|
82
|
-
|
|
81
|
+
/** A block contains deposits where none are allowed */
|
|
82
|
+
NON_ZERO_DEPOSITS = "BLOCK_ERROR_NON_ZERO_DEPOSITS",
|
|
83
83
|
/** The parent block's execution payload has been verified as invalid */
|
|
84
84
|
PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
|
|
85
85
|
/**
|
|
@@ -153,7 +153,7 @@ export type BlockErrorType =
|
|
|
153
153
|
| {code: BlockErrorCode.DATA_UNAVAILABLE}
|
|
154
154
|
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
|
|
155
155
|
| {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
|
|
156
|
-
| {code: BlockErrorCode.
|
|
156
|
+
| {code: BlockErrorCode.NON_ZERO_DEPOSITS; count: number}
|
|
157
157
|
| {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
|
|
158
158
|
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
|
|
159
159
|
| {code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS; parentBlockHash: RootHex; expectedBlockHash: RootHex};
|
|
@@ -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
|
| {
|
|
@@ -12,8 +12,6 @@ export enum ExecutionPayloadEnvelopeErrorCode {
|
|
|
12
12
|
BUILDER_INDEX_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BUILDER_INDEX_MISMATCH",
|
|
13
13
|
BLOCK_HASH_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_HASH_MISMATCH",
|
|
14
14
|
EXECUTION_REQUESTS_ROOT_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_ROOT_MISMATCH",
|
|
15
|
-
EXECUTION_REQUESTS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_COUNT_EXCEEDED",
|
|
16
|
-
WITHDRAWALS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_WITHDRAWALS_COUNT_EXCEEDED",
|
|
17
15
|
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_SIGNATURE",
|
|
18
16
|
PAYLOAD_ENVELOPE_INPUT_MISSING = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_PAYLOAD_ENVELOPE_INPUT_MISSING",
|
|
19
17
|
}
|
|
@@ -44,13 +42,6 @@ export type ExecutionPayloadEnvelopeErrorType =
|
|
|
44
42
|
envelopeRequestsRoot: RootHex;
|
|
45
43
|
bidRequestsRoot: RootHex;
|
|
46
44
|
}
|
|
47
|
-
| {
|
|
48
|
-
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED;
|
|
49
|
-
name: string;
|
|
50
|
-
count: number;
|
|
51
|
-
limit: number;
|
|
52
|
-
}
|
|
53
|
-
| {code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED; count: number; limit: number}
|
|
54
45
|
| {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
|
|
55
46
|
| {code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; blockRoot: RootHex};
|
|
56
47
|
|
|
@@ -8,6 +8,7 @@ export enum GossipAction {
|
|
|
8
8
|
export enum GossipErrorCode {
|
|
9
9
|
INVALID_SERIALIZED_BYTES_ERROR_CODE = "GOSSIP_ERROR_INVALID_SERIALIZED_BYTES",
|
|
10
10
|
PAST_SLOT = "GOSSIP_ERROR_PAST_SLOT",
|
|
11
|
+
FUTURE_SLOT = "GOSSIP_ERROR_FUTURE_SLOT",
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export class GossipActionError<T extends {code: string}> extends LodestarError<T> {
|
|
@@ -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,
|
package/src/chain/interface.ts
CHANGED
|
@@ -35,7 +35,7 @@ import {IArchiveStore} from "./archiveStore/interface.js";
|
|
|
35
35
|
import {CheckpointBalancesCache} from "./balancesCache.js";
|
|
36
36
|
import {BeaconProposerCache, ProposerPreparationData} from "./beaconProposerCache.js";
|
|
37
37
|
import {IBlockInput} from "./blocks/blockInput/index.js";
|
|
38
|
-
import {ImportBlockOpts, ImportPayloadOpts} from "./blocks/types.js";
|
|
38
|
+
import {ImportBlockOpts, ImportPayloadOpts, ProcessBlocksResult} from "./blocks/types.js";
|
|
39
39
|
import {IBlsVerifier} from "./bls/index.js";
|
|
40
40
|
import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
|
|
41
41
|
import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
|
|
@@ -262,7 +262,7 @@ export interface IBeaconChain {
|
|
|
262
262
|
blocks: IBlockInput[],
|
|
263
263
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
|
|
264
264
|
opts?: ImportBlockOpts
|
|
265
|
-
): Promise<
|
|
265
|
+
): Promise<ProcessBlocksResult>;
|
|
266
266
|
|
|
267
267
|
/** Process execution payload envelope: verify, import to fork choice, and persist to DB */
|
|
268
268
|
processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ForkName,
|
|
6
6
|
ForkPostAltair,
|
|
7
7
|
ForkPostBellatrix,
|
|
8
|
+
ForkPostGloas,
|
|
8
9
|
ForkPreGloas,
|
|
9
10
|
ForkSeq,
|
|
10
11
|
MIN_SYNC_COMMITTEE_PARTICIPANTS,
|
|
@@ -40,7 +41,6 @@ import {
|
|
|
40
41
|
Slot,
|
|
41
42
|
SyncPeriod,
|
|
42
43
|
altair,
|
|
43
|
-
electra,
|
|
44
44
|
phase0,
|
|
45
45
|
ssz,
|
|
46
46
|
} from "@lodestar/types";
|
|
@@ -52,7 +52,12 @@ import {Metrics} from "../../metrics/index.js";
|
|
|
52
52
|
import {IClock} from "../../util/clock.js";
|
|
53
53
|
import {ChainEventEmitter} from "../emitter.js";
|
|
54
54
|
import {LightClientServerError, LightClientServerErrorCode} from "../errors/lightClientError.js";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
getBlockBodyExecutionBlockHashProof,
|
|
57
|
+
getBlockBodyExecutionHeaderProof,
|
|
58
|
+
getCurrentSyncCommitteeBranch,
|
|
59
|
+
getNextSyncCommitteeBranch,
|
|
60
|
+
} from "./proofs.js";
|
|
56
61
|
|
|
57
62
|
export type LightClientServerOpts = {
|
|
58
63
|
disableLightClientServerOnImportBlockHead?: boolean;
|
|
@@ -61,6 +66,7 @@ export type LightClientServerOpts = {
|
|
|
61
66
|
|
|
62
67
|
type DependentRootHex = RootHex;
|
|
63
68
|
type BlockRooHex = RootHex;
|
|
69
|
+
type LightClientZeroFinality = Pick<LightClientUpdate, "finalityBranch" | "finalizedHeader">;
|
|
64
70
|
|
|
65
71
|
export type SyncAttestedData = {
|
|
66
72
|
attestedHeader: LightClientHeader;
|
|
@@ -199,6 +205,7 @@ export class LightClientServer {
|
|
|
199
205
|
private readonly clock: IClock;
|
|
200
206
|
private readonly signal: AbortSignal;
|
|
201
207
|
private readonly knownSyncCommittee = new MapDef<SyncPeriod, Set<DependentRootHex>>(() => new Set());
|
|
208
|
+
private readonly zeroFinalityByFork = new Map<ForkName, LightClientZeroFinality>();
|
|
202
209
|
private storedCurrentSyncCommittee = false;
|
|
203
210
|
|
|
204
211
|
/**
|
|
@@ -208,10 +215,6 @@ export class LightClientServer {
|
|
|
208
215
|
private checkpointHeaders = new Map<BlockRooHex, LightClientHeader>();
|
|
209
216
|
private latestHeadUpdate: LightClientOptimisticUpdate | null = null;
|
|
210
217
|
|
|
211
|
-
private readonly zero: Pick<
|
|
212
|
-
altair.LightClientUpdate | electra.LightClientUpdate,
|
|
213
|
-
"finalityBranch" | "finalizedHeader"
|
|
214
|
-
>;
|
|
215
218
|
private finalized: LightClientFinalityUpdate | null = null;
|
|
216
219
|
|
|
217
220
|
constructor(
|
|
@@ -227,14 +230,6 @@ export class LightClientServer {
|
|
|
227
230
|
this.logger = logger;
|
|
228
231
|
this.signal = signal;
|
|
229
232
|
|
|
230
|
-
this.zero = {
|
|
231
|
-
// Assign the highest pre-Gloas light-client header because post-Gloas light-client updates are skipped for now.
|
|
232
|
-
finalizedHeader: ssz.electra.LightClientHeader.defaultValue(),
|
|
233
|
-
// Electra finalityBranch has fixed length of 5 whereas altair has 4. The fifth element will be ignored
|
|
234
|
-
// when serializing as altair LightClientUpdate
|
|
235
|
-
finalityBranch: ssz.electra.LightClientUpdate.fields.finalityBranch.defaultValue(),
|
|
236
|
-
};
|
|
237
|
-
|
|
238
233
|
if (metrics) {
|
|
239
234
|
metrics.lightclientServer.highestSlot.addCollect(() => {
|
|
240
235
|
if (this.latestHeadUpdate) {
|
|
@@ -270,14 +265,6 @@ export class LightClientServer {
|
|
|
270
265
|
return;
|
|
271
266
|
}
|
|
272
267
|
|
|
273
|
-
// TODO GLOAS: Light client updates for gloas are not yet updated in the spec.
|
|
274
|
-
// The block body no longer contains execution payload, so `blockToLightClientHeader`
|
|
275
|
-
// cannot construct a header from a gloas block. Skip all light client processing
|
|
276
|
-
// for post-gloas blocks, revisit once there is a spec for it.
|
|
277
|
-
if (this.config.getForkSeq(block.slot) >= ForkSeq.gloas) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
268
|
// What is the syncAggregate signing?
|
|
282
269
|
// From the state-transition
|
|
283
270
|
// ```
|
|
@@ -695,9 +682,16 @@ export class LightClientServer {
|
|
|
695
682
|
}
|
|
696
683
|
} else {
|
|
697
684
|
isFinalized = false;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
685
|
+
let zeroFinality = this.zeroFinalityByFork.get(attestedFork);
|
|
686
|
+
if (!zeroFinality) {
|
|
687
|
+
const {finalityBranch, finalizedHeader} = this.config
|
|
688
|
+
.getPostAltairForkTypes(attestedHeader.beacon.slot)
|
|
689
|
+
.LightClientUpdate.defaultValue();
|
|
690
|
+
zeroFinality = {finalityBranch, finalizedHeader};
|
|
691
|
+
this.zeroFinalityByFork.set(attestedFork, zeroFinality);
|
|
692
|
+
}
|
|
693
|
+
finalityBranch = zeroFinality.finalityBranch;
|
|
694
|
+
finalizedHeader = zeroFinality.finalizedHeader;
|
|
701
695
|
}
|
|
702
696
|
|
|
703
697
|
const newUpdate = {
|
|
@@ -769,22 +763,24 @@ export function sumBits(bits: BitArray): number {
|
|
|
769
763
|
return bits.getTrueBitIndexes().length;
|
|
770
764
|
}
|
|
771
765
|
|
|
772
|
-
|
|
773
|
-
// in Gloas. So we can assume any types here are pre-gloas
|
|
774
|
-
export function blockToLightClientHeader(
|
|
775
|
-
fork: ForkName,
|
|
776
|
-
block: BeaconBlock<ForkPostAltair & ForkPreGloas>
|
|
777
|
-
): LightClientHeader {
|
|
778
|
-
const blockSlot = block.slot;
|
|
766
|
+
export function blockToLightClientHeader(fork: ForkName, block: BeaconBlock<ForkPostAltair>): LightClientHeader {
|
|
779
767
|
const beacon: phase0.BeaconBlockHeader = {
|
|
780
|
-
slot:
|
|
768
|
+
slot: block.slot,
|
|
781
769
|
proposerIndex: block.proposerIndex,
|
|
782
770
|
parentRoot: block.parentRoot,
|
|
783
771
|
stateRoot: block.stateRoot,
|
|
784
|
-
bodyRoot: (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostAltair
|
|
785
|
-
block.body
|
|
786
|
-
),
|
|
772
|
+
bodyRoot: (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostAltair, "BeaconBlockBody">).hashTreeRoot(block.body),
|
|
787
773
|
};
|
|
774
|
+
|
|
775
|
+
if (isForkPostGloas(fork)) {
|
|
776
|
+
const blockBody = block.body as BeaconBlockBody<ForkPostGloas>;
|
|
777
|
+
return {
|
|
778
|
+
beacon,
|
|
779
|
+
executionBlockHash: blockBody.signedExecutionPayloadBid.message.parentBlockHash,
|
|
780
|
+
executionBranch: getBlockBodyExecutionBlockHashProof(fork, blockBody),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
788
784
|
if (ForkSeq[fork] >= ForkSeq.capella) {
|
|
789
785
|
const blockBody = block.body as BeaconBlockBody<ForkPostBellatrix & ForkPreGloas>;
|
|
790
786
|
const execution = executionPayloadToPayloadHeader(ForkSeq[fork], blockBody.executionPayload);
|
|
@@ -2,11 +2,13 @@ import {Tree} from "@chainsafe/persistent-merkle-tree";
|
|
|
2
2
|
import {
|
|
3
3
|
BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
|
|
4
4
|
CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
5
|
+
EXECUTION_BLOCK_HASH_GINDEX_GLOAS,
|
|
5
6
|
FINALIZED_ROOT_GINDEX,
|
|
6
7
|
FINALIZED_ROOT_GINDEX_ELECTRA,
|
|
7
8
|
FINALIZED_ROOT_GINDEX_GLOAS,
|
|
8
9
|
ForkName,
|
|
9
10
|
ForkPostBellatrix,
|
|
11
|
+
ForkPostGloas,
|
|
10
12
|
NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
|
|
11
13
|
isForkPostElectra,
|
|
12
14
|
isForkPostGloas,
|
|
@@ -116,3 +118,11 @@ export function getBlockBodyExecutionHeaderProof(
|
|
|
116
118
|
const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostBellatrix, "BeaconBlockBody">).toView(body);
|
|
117
119
|
return new Tree(bodyView.node).getSingleProof(BigInt(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX));
|
|
118
120
|
}
|
|
121
|
+
|
|
122
|
+
export function getBlockBodyExecutionBlockHashProof(
|
|
123
|
+
fork: ForkPostGloas,
|
|
124
|
+
body: BeaconBlockBody<ForkPostGloas>
|
|
125
|
+
): Uint8Array[] {
|
|
126
|
+
const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostGloas, "BeaconBlockBody">).toView(body);
|
|
127
|
+
return new Tree(bodyView.node).getSingleProof(BigInt(EXECUTION_BLOCK_HASH_GINDEX_GLOAS));
|
|
128
|
+
}
|
|
@@ -363,7 +363,7 @@ export class AggregatedAttestationPool {
|
|
|
363
363
|
stateEpoch,
|
|
364
364
|
rootCache,
|
|
365
365
|
gloasState?.executionPayloadAvailability ?? null,
|
|
366
|
-
gloasState?.
|
|
366
|
+
gloasState?.latestBlockHeader.slot ?? null
|
|
367
367
|
);
|
|
368
368
|
|
|
369
369
|
const weight =
|