@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.
Files changed (149) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +0 -7
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/utils.js +14 -0
  6. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  7. package/lib/api/impl/config/index.d.ts.map +1 -1
  8. package/lib/api/impl/config/index.js +4 -1
  9. package/lib/api/impl/config/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +39 -39
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  14. package/lib/chain/archiveStore/archiveStore.js +4 -1
  15. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  16. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
  17. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
  19. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  20. package/lib/chain/blocks/importBlock.d.ts +5 -1
  21. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  22. package/lib/chain/blocks/importBlock.js +12 -18
  23. package/lib/chain/blocks/importBlock.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts +8 -4
  25. package/lib/chain/blocks/index.d.ts.map +1 -1
  26. package/lib/chain/blocks/index.js +80 -15
  27. package/lib/chain/blocks/index.js.map +1 -1
  28. package/lib/chain/blocks/types.d.ts +8 -0
  29. package/lib/chain/blocks/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/types.js.map +1 -1
  31. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
  32. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  33. package/lib/chain/blocks/utils/chainSegment.js +26 -11
  34. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  35. package/lib/chain/chain.d.ts +7 -2
  36. package/lib/chain/chain.d.ts.map +1 -1
  37. package/lib/chain/chain.js +69 -40
  38. package/lib/chain/chain.js.map +1 -1
  39. package/lib/chain/emitter.d.ts +1 -1
  40. package/lib/chain/emitter.js +1 -1
  41. package/lib/chain/errors/blockError.d.ts +3 -5
  42. package/lib/chain/errors/blockError.d.ts.map +1 -1
  43. package/lib/chain/errors/blockError.js +2 -2
  44. package/lib/chain/errors/blockError.js.map +1 -1
  45. package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
  46. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  47. package/lib/chain/errors/executionPayloadBid.js +1 -0
  48. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  49. package/lib/chain/errors/executionPayloadEnvelope.d.ts +0 -11
  50. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  51. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  52. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  53. package/lib/chain/errors/gossipValidation.d.ts +2 -1
  54. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  55. package/lib/chain/errors/gossipValidation.js +1 -0
  56. package/lib/chain/errors/gossipValidation.js.map +1 -1
  57. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  58. package/lib/chain/forkChoice/index.js +2 -0
  59. package/lib/chain/forkChoice/index.js.map +1 -1
  60. package/lib/chain/interface.d.ts +2 -2
  61. package/lib/chain/interface.d.ts.map +1 -1
  62. package/lib/chain/lightClient/index.d.ts +3 -3
  63. package/lib/chain/lightClient/index.d.ts.map +1 -1
  64. package/lib/chain/lightClient/index.js +21 -23
  65. package/lib/chain/lightClient/index.js.map +1 -1
  66. package/lib/chain/lightClient/proofs.d.ts +2 -1
  67. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  68. package/lib/chain/lightClient/proofs.js +5 -1
  69. package/lib/chain/lightClient/proofs.js.map +1 -1
  70. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  71. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  72. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  73. package/lib/chain/produceBlock/produceBlockBody.js +46 -64
  74. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  75. package/lib/chain/validation/block.d.ts.map +1 -1
  76. package/lib/chain/validation/block.js +9 -50
  77. package/lib/chain/validation/block.js.map +1 -1
  78. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  79. package/lib/chain/validation/executionPayloadBid.js +56 -40
  80. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  81. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  82. package/lib/chain/validation/executionPayloadEnvelope.js +0 -29
  83. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  84. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  85. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  86. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  87. package/lib/chain/validatorMonitor.d.ts +7 -1
  88. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  89. package/lib/chain/validatorMonitor.js +22 -5
  90. package/lib/chain/validatorMonitor.js.map +1 -1
  91. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  92. package/lib/execution/builder/validateBid.js +8 -5
  93. package/lib/execution/builder/validateBid.js.map +1 -1
  94. package/lib/execution/engine/mock.d.ts.map +1 -1
  95. package/lib/execution/engine/mock.js +4 -1
  96. package/lib/execution/engine/mock.js.map +1 -1
  97. package/lib/metrics/metrics/lodestar.d.ts +4 -3
  98. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  99. package/lib/metrics/metrics/lodestar.js +5 -5
  100. package/lib/metrics/metrics/lodestar.js.map +1 -1
  101. package/lib/network/network.js +2 -2
  102. package/lib/network/network.js.map +1 -1
  103. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  104. package/lib/network/processor/gossipHandlers.js +382 -278
  105. package/lib/network/processor/gossipHandlers.js.map +1 -1
  106. package/lib/network/processor/index.d.ts.map +1 -1
  107. package/lib/network/processor/index.js +22 -16
  108. package/lib/network/processor/index.js.map +1 -1
  109. package/lib/network/reqresp/rateLimit.js +2 -1
  110. package/lib/network/reqresp/rateLimit.js.map +1 -1
  111. package/lib/sync/range/range.d.ts.map +1 -1
  112. package/lib/sync/range/range.js +24 -1
  113. package/lib/sync/range/range.js.map +1 -1
  114. package/package.json +15 -15
  115. package/src/api/impl/beacon/blocks/index.ts +0 -10
  116. package/src/api/impl/beacon/state/utils.ts +15 -0
  117. package/src/api/impl/config/index.ts +4 -1
  118. package/src/api/impl/validator/index.ts +44 -43
  119. package/src/chain/archiveStore/archiveStore.ts +6 -0
  120. package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
  121. package/src/chain/blocks/importBlock.ts +25 -20
  122. package/src/chain/blocks/index.ts +107 -21
  123. package/src/chain/blocks/types.ts +9 -0
  124. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  125. package/src/chain/chain.ts +78 -50
  126. package/src/chain/emitter.ts +1 -1
  127. package/src/chain/errors/blockError.ts +3 -3
  128. package/src/chain/errors/executionPayloadBid.ts +2 -0
  129. package/src/chain/errors/executionPayloadEnvelope.ts +0 -9
  130. package/src/chain/errors/gossipValidation.ts +1 -0
  131. package/src/chain/forkChoice/index.ts +2 -0
  132. package/src/chain/interface.ts +2 -2
  133. package/src/chain/lightClient/index.ts +32 -36
  134. package/src/chain/lightClient/proofs.ts +10 -0
  135. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  136. package/src/chain/produceBlock/produceBlockBody.ts +50 -71
  137. package/src/chain/validation/block.ts +9 -66
  138. package/src/chain/validation/executionPayloadBid.ts +62 -44
  139. package/src/chain/validation/executionPayloadEnvelope.ts +0 -37
  140. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  141. package/src/chain/validatorMonitor.ts +24 -7
  142. package/src/execution/builder/validateBid.ts +10 -6
  143. package/src/execution/engine/mock.ts +4 -1
  144. package/src/metrics/metrics/lodestar.ts +6 -5
  145. package/src/network/network.ts +2 -2
  146. package/src/network/processor/gossipHandlers.ts +489 -344
  147. package/src/network/processor/index.ts +24 -17
  148. package/src/network/reqresp/rateLimit.ts +2 -1
  149. 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) {
@@ -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
- return this.blockProcessor.processBlocksJob([block], null, opts);
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<void> {
1178
- await this.blockProcessor.processBlocksJob(blocks, payloadEnvelopes, opts);
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
- const headRootChanged = head.blockRoot !== prevHead.blockRoot;
1276
-
1277
- if (!headRootChanged && prevHead.payloadStatus === head.payloadStatus) {
1278
- return head;
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
- try {
1282
- const previousDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.previous);
1283
- const currentDutyDependentRoot = this.forkChoice.getDependentRoot(head, EpochDifference.current);
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
- this.emitter.emit(routes.events.EventType.headV2, {
1300
- version: this.config.getForkName(head.slot),
1301
- data: {
1302
- slot: head.slot,
1303
- block: head.blockRoot,
1304
- state: head.stateRoot,
1305
- payloadStatus: head.payloadStatus === PayloadStatus.FULL ? "full" : "empty",
1306
- epochTransition,
1307
- currentEpochDependentRoot: previousDutyDependentRoot,
1308
- nextEpochDependentRoot: currentDutyDependentRoot,
1309
- executionOptimistic,
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
- return head;
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
- this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
1324
- throw e;
1325
- } finally {
1326
- timer?.();
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
 
@@ -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 justified checkpoint is updated. This is in response to a newly processed block.
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 body operation or parent execution request list exceeds its per-block limit */
82
- TOO_MANY_BLOCK_OPERATIONS = "BLOCK_ERROR_TOO_MANY_BLOCK_OPERATIONS",
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.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
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,
@@ -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<void>;
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 {getBlockBodyExecutionHeaderProof, getCurrentSyncCommitteeBranch, getNextSyncCommitteeBranch} from "./proofs.js";
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
- finalityBranch = this.zero.finalityBranch;
699
- // No need to upgrade finalizedHeader because its anyway set to zero of highest fork
700
- finalizedHeader = this.zero.finalizedHeader;
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
- // TODO GLOAS: Pending light-client spec but this function probably won't be used
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: blockSlot,
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 & ForkPreGloas, "BeaconBlockBody">).hashTreeRoot(
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?.latestExecutionPayloadBid.slot ?? null
366
+ gloasState?.latestBlockHeader.slot ?? null
367
367
  );
368
368
 
369
369
  const weight =