@lodestar/beacon-node 1.48.0-dev.67344272c0 → 1.48.0-dev.68ce7b06a0

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 (170) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +4 -11
  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/historicalState/metrics.d.ts.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
  18. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  19. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
  20. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/utils/archiveBlocks.js +37 -4
  22. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  23. package/lib/chain/blocks/importBlock.d.ts +5 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +12 -18
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/blocks/index.d.ts +8 -4
  28. package/lib/chain/blocks/index.d.ts.map +1 -1
  29. package/lib/chain/blocks/index.js +83 -17
  30. package/lib/chain/blocks/index.js.map +1 -1
  31. package/lib/chain/blocks/types.d.ts +8 -0
  32. package/lib/chain/blocks/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/types.js.map +1 -1
  34. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  36. package/lib/chain/blocks/utils/chainSegment.js +26 -11
  37. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  38. package/lib/chain/chain.d.ts +9 -4
  39. package/lib/chain/chain.d.ts.map +1 -1
  40. package/lib/chain/chain.js +82 -51
  41. package/lib/chain/chain.js.map +1 -1
  42. package/lib/chain/emitter.d.ts +1 -1
  43. package/lib/chain/emitter.js +1 -1
  44. package/lib/chain/errors/blockError.d.ts +3 -5
  45. package/lib/chain/errors/blockError.d.ts.map +1 -1
  46. package/lib/chain/errors/blockError.js +2 -2
  47. package/lib/chain/errors/blockError.js.map +1 -1
  48. package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
  49. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  50. package/lib/chain/errors/executionPayloadBid.js +1 -0
  51. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  52. package/lib/chain/errors/executionPayloadEnvelope.d.ts +0 -11
  53. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  54. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  55. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  56. package/lib/chain/errors/gossipValidation.d.ts +2 -1
  57. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  58. package/lib/chain/errors/gossipValidation.js +1 -0
  59. package/lib/chain/errors/gossipValidation.js.map +1 -1
  60. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  61. package/lib/chain/forkChoice/index.js +2 -0
  62. package/lib/chain/forkChoice/index.js.map +1 -1
  63. package/lib/chain/interface.d.ts +4 -4
  64. package/lib/chain/interface.d.ts.map +1 -1
  65. package/lib/chain/lightClient/index.d.ts +3 -3
  66. package/lib/chain/lightClient/index.d.ts.map +1 -1
  67. package/lib/chain/lightClient/index.js +21 -23
  68. package/lib/chain/lightClient/index.js.map +1 -1
  69. package/lib/chain/lightClient/proofs.d.ts +2 -1
  70. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  71. package/lib/chain/lightClient/proofs.js +5 -1
  72. package/lib/chain/lightClient/proofs.js.map +1 -1
  73. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  74. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  75. package/lib/chain/prepareNextSlot.js +13 -13
  76. package/lib/chain/prepareNextSlot.js.map +1 -1
  77. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  78. package/lib/chain/produceBlock/produceBlockBody.js +46 -64
  79. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  80. package/lib/chain/validation/block.d.ts.map +1 -1
  81. package/lib/chain/validation/block.js +9 -50
  82. package/lib/chain/validation/block.js.map +1 -1
  83. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  84. package/lib/chain/validation/executionPayloadBid.js +56 -40
  85. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  86. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  87. package/lib/chain/validation/executionPayloadEnvelope.js +0 -29
  88. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  89. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  90. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  91. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  92. package/lib/chain/validatorMonitor.d.ts +7 -1
  93. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  94. package/lib/chain/validatorMonitor.js +22 -5
  95. package/lib/chain/validatorMonitor.js.map +1 -1
  96. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  97. package/lib/execution/builder/validateBid.js +8 -5
  98. package/lib/execution/builder/validateBid.js.map +1 -1
  99. package/lib/execution/engine/mock.d.ts.map +1 -1
  100. package/lib/execution/engine/mock.js +4 -1
  101. package/lib/execution/engine/mock.js.map +1 -1
  102. package/lib/metrics/metrics/lodestar.d.ts +4 -3
  103. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  104. package/lib/metrics/metrics/lodestar.js +5 -5
  105. package/lib/metrics/metrics/lodestar.js.map +1 -1
  106. package/lib/network/gossip/encoding.d.ts.map +1 -1
  107. package/lib/network/gossip/encoding.js +0 -6
  108. package/lib/network/gossip/encoding.js.map +1 -1
  109. package/lib/network/gossip/topic.d.ts +2 -1
  110. package/lib/network/gossip/topic.d.ts.map +1 -1
  111. package/lib/network/gossip/topic.js +4 -20
  112. package/lib/network/gossip/topic.js.map +1 -1
  113. package/lib/network/network.js +2 -2
  114. package/lib/network/network.js.map +1 -1
  115. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  116. package/lib/network/processor/gossipHandlers.js +383 -279
  117. package/lib/network/processor/gossipHandlers.js.map +1 -1
  118. package/lib/network/processor/index.d.ts +49 -3
  119. package/lib/network/processor/index.d.ts.map +1 -1
  120. package/lib/network/processor/index.js +254 -114
  121. package/lib/network/processor/index.js.map +1 -1
  122. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  123. package/lib/network/reqresp/protocols.js +6 -22
  124. package/lib/network/reqresp/protocols.js.map +1 -1
  125. package/lib/network/reqresp/rateLimit.js +2 -1
  126. package/lib/network/reqresp/rateLimit.js.map +1 -1
  127. package/lib/sync/range/range.d.ts.map +1 -1
  128. package/lib/sync/range/range.js +24 -1
  129. package/lib/sync/range/range.js.map +1 -1
  130. package/package.json +15 -15
  131. package/src/api/impl/beacon/blocks/index.ts +8 -14
  132. package/src/api/impl/beacon/state/utils.ts +15 -0
  133. package/src/api/impl/config/index.ts +4 -1
  134. package/src/api/impl/validator/index.ts +44 -43
  135. package/src/chain/archiveStore/archiveStore.ts +6 -0
  136. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  137. package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -3
  138. package/src/chain/blocks/importBlock.ts +25 -20
  139. package/src/chain/blocks/index.ts +120 -23
  140. package/src/chain/blocks/types.ts +9 -0
  141. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  142. package/src/chain/chain.ts +100 -61
  143. package/src/chain/emitter.ts +1 -1
  144. package/src/chain/errors/blockError.ts +3 -3
  145. package/src/chain/errors/executionPayloadBid.ts +2 -0
  146. package/src/chain/errors/executionPayloadEnvelope.ts +0 -9
  147. package/src/chain/errors/gossipValidation.ts +1 -0
  148. package/src/chain/forkChoice/index.ts +2 -0
  149. package/src/chain/interface.ts +4 -4
  150. package/src/chain/lightClient/index.ts +32 -36
  151. package/src/chain/lightClient/proofs.ts +10 -0
  152. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  153. package/src/chain/prepareNextSlot.ts +16 -16
  154. package/src/chain/produceBlock/produceBlockBody.ts +50 -71
  155. package/src/chain/validation/block.ts +9 -66
  156. package/src/chain/validation/executionPayloadBid.ts +62 -44
  157. package/src/chain/validation/executionPayloadEnvelope.ts +0 -37
  158. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  159. package/src/chain/validatorMonitor.ts +24 -7
  160. package/src/execution/builder/validateBid.ts +10 -6
  161. package/src/execution/engine/mock.ts +4 -1
  162. package/src/metrics/metrics/lodestar.ts +6 -5
  163. package/src/network/gossip/encoding.ts +0 -6
  164. package/src/network/gossip/topic.ts +3 -25
  165. package/src/network/network.ts +2 -2
  166. package/src/network/processor/gossipHandlers.ts +495 -345
  167. package/src/network/processor/index.ts +287 -165
  168. package/src/network/reqresp/protocols.ts +7 -24
  169. package/src/network/reqresp/rateLimit.ts +2 -1
  170. package/src/sync/range/range.ts +23 -1
@@ -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
 
@@ -1381,10 +1387,15 @@ export class BeaconChain implements IBeaconChain {
1381
1387
  const slot = data.slot;
1382
1388
  if (isBlindedBeaconBlock(data)) {
1383
1389
  const sszType = this.config.getPostBellatrixForkTypes(slot).BlindedBeaconBlock;
1384
- void this.persistSszObject("BlindedBeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix);
1390
+ void this.persistSszObject(
1391
+ "BlindedBeaconBlock",
1392
+ sszType.serialize(data),
1393
+ toRootHex(sszType.hashTreeRoot(data)),
1394
+ suffix
1395
+ );
1385
1396
  } else {
1386
1397
  const sszType = this.config.getForkTypes(slot).BeaconBlock;
1387
- void this.persistSszObject("BeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix);
1398
+ void this.persistSszObject("BeaconBlock", sszType.serialize(data), toRootHex(sszType.hashTreeRoot(data)), suffix);
1388
1399
  }
1389
1400
  }
1390
1401
 
@@ -1405,33 +1416,39 @@ export class BeaconChain implements IBeaconChain {
1405
1416
  this.persistSszObject(
1406
1417
  `SignedBeaconBlock_slot_${blockSlot}`,
1407
1418
  blockType.serialize(block),
1408
- blockType.hashTreeRoot(block),
1419
+ toRootHex(this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message)),
1409
1420
  `${logStr}_block`
1410
1421
  ),
1411
1422
  this.persistSszObject(
1412
1423
  `preState_slot_${preState.slot}_BeaconState`,
1413
1424
  preState.serialize(),
1414
- preState.hashTreeRoot(),
1425
+ toRootHex(preState.hashTreeRoot()),
1415
1426
  `${logStr}_pre_state`
1416
1427
  ),
1417
1428
  this.persistSszObject(
1418
1429
  `postState_slot_${postState.slot}_BeaconState`,
1419
1430
  postState.serialize(),
1420
- postState.hashTreeRoot(),
1431
+ toRootHex(postStateRoot),
1421
1432
  `${logStr}_post_state`
1422
1433
  ),
1423
1434
  ]);
1424
1435
  }
1425
1436
 
1426
- persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string): void {
1437
+ persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string, rootHex?: RootHex): void {
1427
1438
  if (this.opts.persistInvalidSszObjects) {
1428
- void this.persistSszObject(type.typeName, type.serialize(sszObject), type.hashTreeRoot(sszObject), suffix);
1439
+ void this.persistSszObject(
1440
+ type.typeName,
1441
+ type.serialize(sszObject),
1442
+ // in SignedBeaconBlock case, we want to use BeaconBlock root instead
1443
+ rootHex ?? toRootHex(type.hashTreeRoot(sszObject)),
1444
+ suffix
1445
+ );
1429
1446
  }
1430
1447
  }
1431
1448
 
1432
- persistInvalidSszBytes(typeName: string, sszBytes: Uint8Array, suffix?: string): void {
1449
+ persistInvalidSszBytes(typeName: string, sszBytes: Uint8Array, rootHex: RootHex, suffix?: string): void {
1433
1450
  if (this.opts.persistInvalidSszObjects) {
1434
- void this.persistSszObject(typeName, sszBytes, sszBytes, suffix);
1451
+ void this.persistSszObject(typeName, sszBytes, rootHex, suffix);
1435
1452
  }
1436
1453
  }
1437
1454
 
@@ -1562,14 +1579,14 @@ export class BeaconChain implements IBeaconChain {
1562
1579
  return {state: blockState, stateId: "block_state_any_epoch", shouldWarn: true};
1563
1580
  }
1564
1581
 
1565
- private async persistSszObject(prefix: string, bytes: Uint8Array, root: Uint8Array, logStr?: string): Promise<void> {
1582
+ private async persistSszObject(prefix: string, bytes: Uint8Array, rootHex: RootHex, logStr?: string): Promise<void> {
1566
1583
  const now = new Date();
1567
1584
  // yyyy-MM-dd
1568
1585
  const dateStr = now.toISOString().split("T")[0];
1569
1586
 
1570
1587
  // by default store to lodestar_archive of current dir
1571
1588
  const dirpath = path.join(this.opts.persistInvalidSszObjectsDir ?? "invalid_ssz_objects", dateStr);
1572
- const filepath = path.join(dirpath, `${prefix}_${toRootHex(root)}.ssz`);
1589
+ const filepath = path.join(dirpath, `${prefix}_${rootHex}.ssz`);
1573
1590
 
1574
1591
  await ensureDir(dirpath);
1575
1592
 
@@ -1612,7 +1629,10 @@ export class BeaconChain implements IBeaconChain {
1612
1629
  this.processShutdownCallback(this.forkChoice.irrecoverableError);
1613
1630
  }
1614
1631
 
1632
+ // updateTime() recomputes the head on epoch-boundary checkpoint pull-up and under fast confirmation
1633
+ const prevHead = this.forkChoice.getHead();
1615
1634
  this.forkChoice.updateTime(slot);
1635
+ this.emitHeadEvents(prevHead, this.forkChoice.getHead());
1616
1636
  this.metrics?.clockSlot.set(slot);
1617
1637
 
1618
1638
  this.attestationPool.prune(slot);
@@ -1670,6 +1690,25 @@ export class BeaconChain implements IBeaconChain {
1670
1690
 
1671
1691
  private async onForkChoiceFinalized(this: BeaconChain, cp: CheckpointWithHex): Promise<void> {
1672
1692
  this.logger.verbose("Fork choice finalized", {epoch: cp.epoch, root: cp.rootHex});
1693
+ this.metrics?.finalizedEpoch.set(cp.epoch);
1694
+ const finalizedBlock = this.forkChoice.getBlockHexDefaultStatus(cp.rootHex);
1695
+ if (finalizedBlock) {
1696
+ // The callback runs synchronously inside fork choice checkpoint updates, defer writing to subscribers
1697
+ callInNextEventLoop(() => {
1698
+ this.emitter.emit(routes.events.EventType.finalizedCheckpoint, {
1699
+ block: cp.rootHex,
1700
+ epoch: cp.epoch,
1701
+ state: finalizedBlock.stateRoot,
1702
+ executionOptimistic: isOptimisticBlock(finalizedBlock),
1703
+ });
1704
+ });
1705
+ } else {
1706
+ this.logger.debug("Finalized block not found in fork choice, skip finalized_checkpoint event", {
1707
+ epoch: cp.epoch,
1708
+ root: cp.rootHex,
1709
+ });
1710
+ }
1711
+
1673
1712
  const finalizedSlot = computeStartSlotAtEpoch(cp.epoch);
1674
1713
  this.seenBlockProposers.prune(finalizedSlot);
1675
1714
 
@@ -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>;
@@ -290,8 +290,8 @@ export interface IBeaconChain {
290
290
  postState: IBeaconStateView,
291
291
  block: SignedBeaconBlock
292
292
  ): Promise<void>;
293
- persistInvalidSszValue<T>(type: Type<T>, sszObject: T | Uint8Array, suffix?: string): void;
294
- persistInvalidSszBytes(type: string, sszBytes: Uint8Array, suffix?: string): void;
293
+ persistInvalidSszValue<T>(type: Type<T>, sszObject: T, suffix?: string, rootHex?: RootHex): void;
294
+ persistInvalidSszBytes(type: string, sszBytes: Uint8Array, rootHex: RootHex, suffix?: string): void;
295
295
  regenStateForAttestationVerification(
296
296
  attEpoch: Epoch,
297
297
  shufflingDependentRoot: RootHex,
@@ -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 =
@@ -243,6 +243,22 @@ export class PrepareNextSlotScheduler {
243
243
  parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
244
244
  }
245
245
 
246
+ // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
247
+ // every slot. Without the flag, only emit the event if we are proposing in the next slot.
248
+ if (
249
+ (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
250
+ this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
251
+ ) {
252
+ const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
253
+ prepareState: stateAfterParentPayload,
254
+ prepareSlot,
255
+ parentBlockRoot: fromHex(updatedHead.blockRoot),
256
+ parentBlockHash,
257
+ feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
258
+ });
259
+ this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
260
+ }
261
+
246
262
  if (feeRecipient) {
247
263
  const preparationTime =
248
264
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -273,22 +289,6 @@ export class PrepareNextSlotScheduler {
273
289
  }
274
290
 
275
291
  this.computeStateHashTreeRoot(preparedState, isEpochTransition);
276
-
277
- // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
278
- // every slot. Without the flag, only emit the event if we are proposing in the next slot.
279
- if (
280
- (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
281
- this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
282
- ) {
283
- const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
284
- prepareState: stateAfterParentPayload,
285
- prepareSlot,
286
- parentBlockRoot: fromHex(updatedHead.blockRoot),
287
- parentBlockHash,
288
- feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
289
- });
290
- this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
291
- }
292
292
  } else {
293
293
  // Pre-bellatrix only reaches here at an epoch transition to precompute the next epoch state
294
294
  const preparedState = await this.chain.regen.getBlockSlotState(