@lodestar/beacon-node 1.41.0-dev.9fa839a030 → 1.41.0-dev.a35cbde8b3

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 (104) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +121 -3
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lightclient/index.d.ts.map +1 -1
  5. package/lib/api/impl/lightclient/index.js +19 -2
  6. package/lib/api/impl/lightclient/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +101 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  11. package/lib/chain/blocks/importBlock.js +0 -2
  12. package/lib/chain/blocks/importBlock.js.map +1 -1
  13. package/lib/chain/blocks/index.d.ts.map +1 -1
  14. package/lib/chain/blocks/index.js +2 -1
  15. package/lib/chain/blocks/index.js.map +1 -1
  16. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  17. package/lib/chain/blocks/writeBlockInputToDb.js +3 -0
  18. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  19. package/lib/chain/chain.d.ts.map +1 -1
  20. package/lib/chain/chain.js +23 -5
  21. package/lib/chain/chain.js.map +1 -1
  22. package/lib/chain/emitter.d.ts +2 -2
  23. package/lib/chain/emitter.d.ts.map +1 -1
  24. package/lib/chain/lightClient/index.d.ts.map +1 -1
  25. package/lib/chain/lightClient/index.js +1 -1
  26. package/lib/chain/lightClient/index.js.map +1 -1
  27. package/lib/chain/options.d.ts.map +1 -1
  28. package/lib/chain/options.js.map +1 -1
  29. package/lib/chain/prepareNextSlot.js +3 -3
  30. package/lib/chain/prepareNextSlot.js.map +1 -1
  31. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +10 -2
  32. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  33. package/lib/chain/produceBlock/computeNewStateRoot.js +24 -2
  34. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  35. package/lib/chain/produceBlock/produceBlockBody.d.ts +22 -7
  36. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  37. package/lib/chain/produceBlock/produceBlockBody.js +102 -9
  38. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  39. package/lib/chain/validation/dataColumnSidecar.d.ts +2 -2
  40. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  41. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  42. package/lib/metrics/metrics/beacon.d.ts +1 -0
  43. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  44. package/lib/metrics/metrics/beacon.js +5 -0
  45. package/lib/metrics/metrics/beacon.js.map +1 -1
  46. package/lib/metrics/metrics/lodestar.d.ts +5 -0
  47. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  48. package/lib/metrics/metrics/lodestar.js +9 -0
  49. package/lib/metrics/metrics/lodestar.js.map +1 -1
  50. package/lib/metrics/metrics.d.ts.map +1 -1
  51. package/lib/metrics/metrics.js +8 -3
  52. package/lib/metrics/metrics.js.map +1 -1
  53. package/lib/network/gossip/interface.d.ts +3 -3
  54. package/lib/network/gossip/interface.d.ts.map +1 -1
  55. package/lib/network/gossip/topic.d.ts +66 -16
  56. package/lib/network/gossip/topic.d.ts.map +1 -1
  57. package/lib/network/gossip/topic.js +2 -2
  58. package/lib/network/gossip/topic.js.map +1 -1
  59. package/lib/network/interface.d.ts +3 -2
  60. package/lib/network/interface.d.ts.map +1 -1
  61. package/lib/network/network.d.ts +3 -2
  62. package/lib/network/network.d.ts.map +1 -1
  63. package/lib/network/network.js +10 -1
  64. package/lib/network/network.js.map +1 -1
  65. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  66. package/lib/network/processor/gossipHandlers.js +5 -1
  67. package/lib/network/processor/gossipHandlers.js.map +1 -1
  68. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.d.ts.map +1 -1
  69. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js +7 -1
  70. package/lib/network/reqresp/handlers/lightClientUpdatesByRange.js.map +1 -1
  71. package/lib/util/dataColumns.d.ts +11 -1
  72. package/lib/util/dataColumns.d.ts.map +1 -1
  73. package/lib/util/dataColumns.js +31 -0
  74. package/lib/util/dataColumns.js.map +1 -1
  75. package/lib/util/serializedCache.d.ts +5 -0
  76. package/lib/util/serializedCache.d.ts.map +1 -1
  77. package/lib/util/serializedCache.js +5 -0
  78. package/lib/util/serializedCache.js.map +1 -1
  79. package/package.json +15 -15
  80. package/src/api/impl/beacon/blocks/index.ts +145 -2
  81. package/src/api/impl/lightclient/index.ts +19 -2
  82. package/src/api/impl/validator/index.ts +124 -1
  83. package/src/chain/blocks/importBlock.ts +0 -3
  84. package/src/chain/blocks/index.ts +2 -1
  85. package/src/chain/blocks/writeBlockInputToDb.ts +3 -0
  86. package/src/chain/chain.ts +35 -6
  87. package/src/chain/emitter.ts +2 -2
  88. package/src/chain/lightClient/index.ts +4 -1
  89. package/src/chain/options.ts +1 -0
  90. package/src/chain/prepareNextSlot.ts +5 -5
  91. package/src/chain/produceBlock/computeNewStateRoot.ts +35 -3
  92. package/src/chain/produceBlock/produceBlockBody.ts +154 -11
  93. package/src/chain/validation/dataColumnSidecar.ts +2 -5
  94. package/src/metrics/metrics/beacon.ts +5 -0
  95. package/src/metrics/metrics/lodestar.ts +9 -0
  96. package/src/metrics/metrics.ts +8 -3
  97. package/src/network/gossip/interface.ts +3 -3
  98. package/src/network/gossip/topic.ts +2 -1
  99. package/src/network/interface.ts +4 -1
  100. package/src/network/network.ts +20 -4
  101. package/src/network/processor/gossipHandlers.ts +7 -1
  102. package/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +6 -1
  103. package/src/util/dataColumns.ts +41 -0
  104. package/src/util/serializedCache.ts +5 -0
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.41.0-dev.9fa839a030",
14
+ "version": "1.41.0-dev.a35cbde8b3",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -134,18 +134,18 @@
134
134
  "@libp2p/peer-id": "^5.1.0",
135
135
  "@libp2p/prometheus-metrics": "^4.3.15",
136
136
  "@libp2p/tcp": "^10.1.8",
137
- "@lodestar/api": "^1.41.0-dev.9fa839a030",
138
- "@lodestar/config": "^1.41.0-dev.9fa839a030",
139
- "@lodestar/db": "^1.41.0-dev.9fa839a030",
140
- "@lodestar/fork-choice": "^1.41.0-dev.9fa839a030",
141
- "@lodestar/light-client": "^1.41.0-dev.9fa839a030",
142
- "@lodestar/logger": "^1.41.0-dev.9fa839a030",
143
- "@lodestar/params": "^1.41.0-dev.9fa839a030",
144
- "@lodestar/reqresp": "^1.41.0-dev.9fa839a030",
145
- "@lodestar/state-transition": "^1.41.0-dev.9fa839a030",
146
- "@lodestar/types": "^1.41.0-dev.9fa839a030",
147
- "@lodestar/utils": "^1.41.0-dev.9fa839a030",
148
- "@lodestar/validator": "^1.41.0-dev.9fa839a030",
137
+ "@lodestar/api": "^1.41.0-dev.a35cbde8b3",
138
+ "@lodestar/config": "^1.41.0-dev.a35cbde8b3",
139
+ "@lodestar/db": "^1.41.0-dev.a35cbde8b3",
140
+ "@lodestar/fork-choice": "^1.41.0-dev.a35cbde8b3",
141
+ "@lodestar/light-client": "^1.41.0-dev.a35cbde8b3",
142
+ "@lodestar/logger": "^1.41.0-dev.a35cbde8b3",
143
+ "@lodestar/params": "^1.41.0-dev.a35cbde8b3",
144
+ "@lodestar/reqresp": "^1.41.0-dev.a35cbde8b3",
145
+ "@lodestar/state-transition": "^1.41.0-dev.a35cbde8b3",
146
+ "@lodestar/types": "^1.41.0-dev.a35cbde8b3",
147
+ "@lodestar/utils": "^1.41.0-dev.a35cbde8b3",
148
+ "@lodestar/validator": "^1.41.0-dev.a35cbde8b3",
149
149
  "@multiformats/multiaddr": "^12.1.3",
150
150
  "datastore-core": "^10.0.2",
151
151
  "datastore-fs": "^10.0.6",
@@ -169,7 +169,7 @@
169
169
  "@chainsafe/swap-or-not-shuffle": "^1.2.1",
170
170
  "@libp2p/interface-internal": "^2.3.18",
171
171
  "@libp2p/logger": "^5.1.21",
172
- "@lodestar/spec-test-util": "^1.41.0-dev.9fa839a030",
172
+ "@lodestar/spec-test-util": "^1.41.0-dev.a35cbde8b3",
173
173
  "@types/js-yaml": "^4.0.5",
174
174
  "@types/qs": "^6.9.7",
175
175
  "@types/tmp": "^0.2.3",
@@ -188,5 +188,5 @@
188
188
  "beacon",
189
189
  "blockchain"
190
190
  ],
191
- "gitHead": "1ba8fa66f8fc0890153a0ecc8cd5b3be617ce410"
191
+ "gitHead": "090cdf258f4c067ebe6b5f0bdba221c2c23b9962"
192
192
  }
@@ -1,6 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ApiError, ApplicationMethods} from "@lodestar/api/server";
3
3
  import {
4
+ BUILDER_INDEX_SELF_BUILD,
4
5
  ForkPostBellatrix,
5
6
  ForkPostFulu,
6
7
  ForkPreGloas,
@@ -27,6 +28,7 @@ import {
27
28
  WithOptionalBytes,
28
29
  deneb,
29
30
  fulu,
31
+ gloas,
30
32
  isDenebBlockContents,
31
33
  sszTypesFor,
32
34
  } from "@lodestar/types";
@@ -42,6 +44,7 @@ import {
42
44
  ProduceFullBellatrix,
43
45
  ProduceFullDeneb,
44
46
  ProduceFullFulu,
47
+ ProduceFullGloas,
45
48
  } from "../../../../chain/produceBlock/index.js";
46
49
  import {validateGossipBlock} from "../../../../chain/validation/block.js";
47
50
  import {OpSource} from "../../../../chain/validatorMonitor.js";
@@ -51,7 +54,7 @@ import {
51
54
  kzgCommitmentToVersionedHash,
52
55
  reconstructBlobs,
53
56
  } from "../../../../util/blobs.js";
54
- import {getDataColumnSidecarsFromBlock} from "../../../../util/dataColumns.js";
57
+ import {getDataColumnSidecarsForGloas, getDataColumnSidecarsFromBlock} from "../../../../util/dataColumns.js";
55
58
  import {isOptimisticBlock} from "../../../../util/forkChoice.js";
56
59
  import {kzg} from "../../../../util/kzg.js";
57
60
  import {promiseAllMaybeAsync} from "../../../../util/promises.js";
@@ -93,6 +96,7 @@ export function getBeaconBlockApi({
93
96
  const fork = config.getForkName(slot);
94
97
  const blockRoot = toRootHex(chain.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(signedBlock.message));
95
98
 
99
+ // TODO GLOAS: handle new BlockInput type
96
100
  const blockForImport = chain.seenBlockInputCache.getByBlock({
97
101
  block: signedBlock,
98
102
  source: BlockInputSource.api,
@@ -309,7 +313,9 @@ export function getBeaconBlockApi({
309
313
  ];
310
314
  const sentPeersArr = await promiseAllMaybeAsync<number | void>(publishPromises);
311
315
 
312
- if (isForkPostFulu(fork)) {
316
+ if (isForkPostGloas(fork)) {
317
+ // After gloas, data columns are not published with the block but when publishing the execution payload envelope
318
+ } else if (isForkPostFulu(fork)) {
313
319
  let columnsPublishedWithZeroPeers = 0;
314
320
  // sent peers per topic are logged in network.publishGossip(), here we only track metrics for it
315
321
  // starting from fulu, we have to push to 128 subnets so need to make sure we have enough sent peers per topic
@@ -632,6 +638,143 @@ export function getBeaconBlockApi({
632
638
  await publishBlock(args, context, opts);
633
639
  },
634
640
 
641
+ async publishExecutionPayloadEnvelope({signedExecutionPayloadEnvelope}) {
642
+ const seenTimestampSec = Date.now() / 1000;
643
+ const envelope = signedExecutionPayloadEnvelope.message;
644
+ const slot = envelope.slot;
645
+ const fork = config.getForkName(slot);
646
+ const blockRootHex = toRootHex(envelope.beaconBlockRoot);
647
+
648
+ if (!isForkPostGloas(fork)) {
649
+ throw new ApiError(400, `publishExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
650
+ }
651
+
652
+ // TODO GLOAS: review checks, do we want to implement `broadcast_validation`?
653
+ const block = chain.forkChoice.getBlockHex(blockRootHex);
654
+ if (block === null) {
655
+ throw new ApiError(404, `Block not found for beacon block root ${blockRootHex}`);
656
+ }
657
+ if (block.slot !== slot) {
658
+ throw new ApiError(400, `Envelope slot ${slot} does not match block slot ${block.slot}`);
659
+ }
660
+
661
+ const isSelfBuild = envelope.builderIndex === BUILDER_INDEX_SELF_BUILD;
662
+ let dataColumnSidecars: gloas.DataColumnSidecars = [];
663
+
664
+ if (isSelfBuild) {
665
+ // For self-builds, construct and publish data column sidecars from cached block production data
666
+ const cachedResult = chain.blockProductionCache.get(blockRootHex) as ProduceFullGloas | undefined;
667
+ if (cachedResult === undefined) {
668
+ throw new ApiError(404, `No cached block production result found for block root ${blockRootHex}`);
669
+ }
670
+ if (!isForkPostGloas(cachedResult.fork)) {
671
+ throw new ApiError(400, `Cached block production result is for pre-gloas fork=${cachedResult.fork}`);
672
+ }
673
+ if (cachedResult.type !== BlockType.Full) {
674
+ throw new ApiError(400, "Cached block production result is not full block");
675
+ }
676
+
677
+ if (cachedResult.cells && cachedResult.blobsBundle.commitments.length > 0) {
678
+ const cellsAndProofs = cachedResult.cells.map((rowCells, rowIndex) => ({
679
+ cells: rowCells,
680
+ proofs: cachedResult.blobsBundle.proofs.slice(
681
+ rowIndex * NUMBER_OF_COLUMNS,
682
+ (rowIndex + 1) * NUMBER_OF_COLUMNS
683
+ ),
684
+ }));
685
+
686
+ dataColumnSidecars = getDataColumnSidecarsForGloas(slot, envelope.beaconBlockRoot, cellsAndProofs);
687
+ }
688
+ } else {
689
+ // TODO GLOAS: will this api be used by builders or only for self-building?
690
+ }
691
+
692
+ // TODO GLOAS: Verify execution payload envelope signature
693
+ // For self-builds, the proposer signs with their own validator key
694
+ // For external builders, verify using the builder's registered pubkey
695
+ // Use verify_execution_payload_envelope_signature(state, signed_envelope)
696
+
697
+ // TODO GLOAS: Process execution payload via state transition
698
+ // Call process_execution_payload(state, signed_envelope, execution_engine)
699
+
700
+ // TODO GLOAS: Update fork choice with the execution payload
701
+ // Call on_execution_payload(store, signed_envelope) to update fork choice state
702
+
703
+ // TODO GLOAS: Add envelope and data columns to block input via seenBlockInputCache
704
+ // and trigger block import (Gloas block import requires both beacon block and envelope)
705
+
706
+ const valLogMeta = {
707
+ slot,
708
+ blockRoot: blockRootHex,
709
+ builderIndex: envelope.builderIndex,
710
+ isSelfBuild,
711
+ dataColumns: dataColumnSidecars.length,
712
+ };
713
+
714
+ // If called near a slot boundary (e.g. late in slot N-1), hold briefly so gossip aligns with slot N.
715
+ const msToBlockSlot = computeTimeAtSlot(config, slot, chain.genesisTime) * 1000 - Date.now();
716
+ if (msToBlockSlot <= MAX_API_CLOCK_DISPARITY_MS && msToBlockSlot > 0) {
717
+ await sleep(msToBlockSlot);
718
+ }
719
+
720
+ const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
721
+ metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.api}, delaySec);
722
+
723
+ chain.logger.info("Publishing execution payload envelope", valLogMeta);
724
+
725
+ // Publish envelope and data columns
726
+ const publishPromises = [
727
+ // Gossip the signed execution payload envelope first
728
+ () => network.publishSignedExecutionPayloadEnvelope(signedExecutionPayloadEnvelope),
729
+ // For self-builds, publish all data column sidecars
730
+ ...dataColumnSidecars.map((dataColumnSidecar) => () => network.publishDataColumnSidecar(dataColumnSidecar)),
731
+ ];
732
+
733
+ const sentPeersArr = await promiseAllMaybeAsync<number | void>(publishPromises);
734
+
735
+ // Track metrics for data column publishing
736
+ if (dataColumnSidecars.length > 0) {
737
+ let columnsPublishedWithZeroPeers = 0;
738
+ // Skip first entry (envelope), track data columns
739
+ for (let i = 1; i < sentPeersArr.length; i++) {
740
+ const sentPeers = sentPeersArr[i] as number;
741
+ metrics?.dataColumns.sentPeersPerSubnet.observe(sentPeers);
742
+ if (sentPeers === 0) {
743
+ columnsPublishedWithZeroPeers++;
744
+ }
745
+ }
746
+ if (columnsPublishedWithZeroPeers > 0) {
747
+ chain.logger.warn("Published data columns to 0 peers, increased risk of reorg", {
748
+ slot,
749
+ blockRoot: blockRootHex,
750
+ columns: columnsPublishedWithZeroPeers,
751
+ });
752
+ }
753
+
754
+ metrics?.dataColumns.bySource.inc({source: BlockInputSource.api}, dataColumnSidecars.length);
755
+
756
+ if (chain.emitter.listenerCount(routes.events.EventType.dataColumnSidecar)) {
757
+ // TODO GLOAS: revisit this, we likely don't wanna emit KZG commitments anymore
758
+ const cachedResult = chain.blockProductionCache.get(blockRootHex) as ProduceFullGloas | undefined;
759
+ const kzgCommitments = cachedResult?.blobsBundle.commitments.map(toHex) ?? [];
760
+ for (const dataColumnSidecar of dataColumnSidecars) {
761
+ chain.emitter.emit(routes.events.EventType.dataColumnSidecar, {
762
+ blockRoot: blockRootHex,
763
+ slot,
764
+ index: dataColumnSidecar.index,
765
+ kzgCommitments,
766
+ });
767
+ }
768
+ }
769
+ }
770
+
771
+ chain.logger.info("Published execution payload envelope", {
772
+ ...valLogMeta,
773
+ delaySec,
774
+ sentPeers: (sentPeersArr[0] as number) ?? 0,
775
+ });
776
+ },
777
+
635
778
  async getBlobSidecars({blockId, indices}) {
636
779
  assertUniqueItems(indices, "Duplicate indices provided");
637
780
 
@@ -1,7 +1,9 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ApplicationMethods} from "@lodestar/api/server";
3
3
  import {MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES, MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params";
4
+ import type {LightClientUpdate} from "@lodestar/types";
4
5
  import {fromHex} from "@lodestar/utils";
6
+ import {LightClientServerError, LightClientServerErrorCode} from "../../../chain/errors/lightClientError.js";
5
7
  import {assertLightClientServer} from "../../../node/utils/lightclient.js";
6
8
  import {ApiModules} from "../types.js";
7
9
  // TODO: Import from lightclient/server package
@@ -16,8 +18,23 @@ export function getLightclientApi({
16
18
  assertLightClientServer(lightClientServer);
17
19
 
18
20
  const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count);
19
- const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod);
20
- const updates = await Promise.all(periods.map((period) => lightClientServer.getUpdate(period)));
21
+ const updates: LightClientUpdate[] = [];
22
+ for (let i = 0; i < maxAllowedCount; i++) {
23
+ try {
24
+ const update = await lightClientServer.getUpdate(startPeriod + i);
25
+ updates.push(update);
26
+ } catch (e) {
27
+ if ((e as LightClientServerError).type?.code === LightClientServerErrorCode.RESOURCE_UNAVAILABLE) {
28
+ // Period not available, if we already have results, stop to preserve
29
+ // consecutive order. If not, skip and try the next period.
30
+ if (updates.length > 0) break;
31
+ continue;
32
+ }
33
+ // Unexpected error
34
+ throw e;
35
+ }
36
+ }
37
+
21
38
  return {
22
39
  data: updates,
23
40
  meta: {versions: updates.map((update) => config.getForkName(update.attestedHeader.beacon.slot))},
@@ -3,6 +3,7 @@ import {routes} from "@lodestar/api";
3
3
  import {ApplicationMethods} from "@lodestar/api/server";
4
4
  import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice";
5
5
  import {
6
+ BUILDER_INDEX_SELF_BUILD,
6
7
  ForkName,
7
8
  ForkPostBellatrix,
8
9
  ForkPreGloas,
@@ -14,6 +15,7 @@ import {
14
15
  isForkPostBellatrix,
15
16
  isForkPostDeneb,
16
17
  isForkPostElectra,
18
+ isForkPostGloas,
17
19
  } from "@lodestar/params";
18
20
  import {
19
21
  CachedBeaconStateAllForks,
@@ -45,6 +47,7 @@ import {
45
47
  Wei,
46
48
  bellatrix,
47
49
  getValidatorStatus,
50
+ gloas,
48
51
  phase0,
49
52
  ssz,
50
53
  } from "@lodestar/types";
@@ -69,7 +72,7 @@ import {
69
72
  } from "../../../chain/errors/index.js";
70
73
  import {ChainEvent, CommonBlockBody} from "../../../chain/index.js";
71
74
  import {PREPARE_NEXT_SLOT_BPS} from "../../../chain/prepareNextSlot.js";
72
- import {BlockType, ProduceFullDeneb} from "../../../chain/produceBlock/index.js";
75
+ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/produceBlock/index.js";
73
76
  import {RegenCaller} from "../../../chain/regen/index.js";
74
77
  import {CheckpointHex} from "../../../chain/stateCache/types.js";
75
78
  import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
@@ -901,6 +904,77 @@ export function getValidatorApi(
901
904
  return {data, meta};
902
905
  },
903
906
 
907
+ async produceBlockV4({slot, randaoReveal, graffiti, feeRecipient}) {
908
+ const fork = config.getForkName(slot);
909
+
910
+ if (!isForkPostGloas(fork)) {
911
+ throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
912
+ }
913
+
914
+ notWhileSyncing();
915
+ await waitForSlot(slot);
916
+
917
+ // TODO GLOAS: support producing blocks from builder bids
918
+ const source = ProducedBlockSource.engine;
919
+
920
+ // TODO GLOAS: needs to be updated after fork choice changes are merged
921
+ const parentBlock = chain.getProposerHead(slot);
922
+ const {blockRoot: parentBlockRootHex, slot: parentSlot} = parentBlock;
923
+ const parentBlockRoot = fromHex(parentBlockRootHex);
924
+ notOnOutOfRangeData(parentBlockRoot);
925
+ metrics?.blockProductionSlotDelta.set(slot - parentSlot);
926
+ metrics?.blockProductionRequests.inc({source});
927
+
928
+ const graffitiBytes = toGraffitiBytes(
929
+ graffiti ?? getDefaultGraffiti(getLodestarClientVersion(), chain.executionEngine.clientVersion, {})
930
+ );
931
+ const commonBlockBodyPromise = chain.produceCommonBlockBody({
932
+ slot,
933
+ parentBlock,
934
+ randaoReveal,
935
+ graffiti: graffitiBytes,
936
+ });
937
+
938
+ let timer: undefined | ((opts: {source: ProducedBlockSource}) => number);
939
+ try {
940
+ timer = metrics?.blockProductionTime.startTimer();
941
+ const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlock({
942
+ slot,
943
+ parentBlock,
944
+ randaoReveal,
945
+ graffiti: graffitiBytes,
946
+ feeRecipient,
947
+ commonBlockBodyPromise,
948
+ });
949
+
950
+ metrics?.blockProductionSuccess.inc({source});
951
+ metrics?.blockProductionNumAggregated.observe({source}, block.body.attestations.length);
952
+ metrics?.blockProductionConsensusBlockValue.observe({source}, Number(formatWeiToEth(consensusBlockValue)));
953
+ metrics?.blockProductionExecutionPayloadValue.observe({source}, Number(formatWeiToEth(executionPayloadValue)));
954
+
955
+ const blockRoot = toRootHex(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block));
956
+ logger.verbose("Produced block", {
957
+ slot,
958
+ executionPayloadValue,
959
+ consensusBlockValue,
960
+ root: blockRoot,
961
+ });
962
+ if (chain.opts.persistProducedBlocks) {
963
+ void chain.persistBlock(block, "produced_engine_block");
964
+ }
965
+
966
+ return {
967
+ data: block as gloas.BeaconBlock,
968
+ meta: {
969
+ version: fork,
970
+ consensusBlockValue,
971
+ },
972
+ };
973
+ } finally {
974
+ timer?.({source});
975
+ }
976
+ },
977
+
904
978
  async produceAttestationData({committeeIndex, slot}) {
905
979
  notWhileSyncing();
906
980
 
@@ -1532,5 +1606,54 @@ export function getValidatorApi(
1532
1606
  count: filteredRegistrations.length,
1533
1607
  });
1534
1608
  },
1609
+
1610
+ async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
1611
+ const fork = config.getForkName(slot);
1612
+
1613
+ if (!isForkPostGloas(fork)) {
1614
+ throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1615
+ }
1616
+
1617
+ notWhileSyncing();
1618
+ await waitForSlot(slot);
1619
+
1620
+ const blockRootHex = toRootHex(beaconBlockRoot);
1621
+ const produceResult = chain.blockProductionCache.get(blockRootHex);
1622
+
1623
+ if (produceResult === undefined) {
1624
+ throw new ApiError(404, `No cached block production result found for block root ${blockRootHex}`);
1625
+ }
1626
+ if (!isForkPostGloas(produceResult.fork)) {
1627
+ throw Error(`Cached block production result is for pre-gloas fork=${produceResult.fork}`);
1628
+ }
1629
+ if (produceResult.type !== BlockType.Full) {
1630
+ throw Error("Cached block production result is not full block");
1631
+ }
1632
+
1633
+ const {executionPayload, executionRequests, envelopeStateRoot} = produceResult as ProduceFullGloas;
1634
+
1635
+ const envelope: gloas.ExecutionPayloadEnvelope = {
1636
+ payload: executionPayload,
1637
+ executionRequests: executionRequests,
1638
+ builderIndex: BUILDER_INDEX_SELF_BUILD,
1639
+ beaconBlockRoot,
1640
+ slot,
1641
+ stateRoot: envelopeStateRoot,
1642
+ };
1643
+
1644
+ logger.info("Produced execution payload envelope", {
1645
+ slot,
1646
+ blockRoot: blockRootHex,
1647
+ transactions: executionPayload.transactions.length,
1648
+ blockHash: toRootHex(executionPayload.blockHash),
1649
+ });
1650
+
1651
+ return {
1652
+ data: envelope,
1653
+ meta: {
1654
+ version: fork,
1655
+ },
1656
+ };
1657
+ },
1535
1658
  };
1536
1659
  }
@@ -101,9 +101,6 @@ export async function importBlock(
101
101
  }
102
102
  });
103
103
 
104
- // Without forcefully clearing this cache, we would rely on WeakMap to evict memory which is not reliable
105
- this.serializedCache.clear();
106
-
107
104
  // 2. Import block to fork choice
108
105
 
109
106
  // Should compute checkpoint balances before forkchoice.onBlock
@@ -1,6 +1,7 @@
1
1
  import {SignedBeaconBlock} from "@lodestar/types";
2
2
  import {isErrorAborted, toRootHex} from "@lodestar/utils";
3
3
  import {Metrics} from "../../metrics/metrics.js";
4
+ import {nextEventLoop} from "../../util/eventLoop.js";
4
5
  import {JobItemQueue, isQueueErrorAborted} from "../../util/queue/index.js";
5
6
  import type {BeaconChain} from "../chain.js";
6
7
  import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.js";
@@ -100,9 +101,9 @@ export async function processBlocks(
100
101
  );
101
102
 
102
103
  for (const fullyVerifiedBlock of fullyVerifiedBlocks) {
103
- // No need to sleep(0) here since `importBlock` includes a disk write
104
104
  // TODO: Consider batching importBlock too if it takes significant time
105
105
  await importBlock.call(this, fullyVerifiedBlock, opts);
106
+ await nextEventLoop();
106
107
  }
107
108
  } catch (e) {
108
109
  if (isErrorAborted(e) || isQueueErrorAborted(e) || isBlockErrorAborted(e)) {
@@ -125,6 +125,9 @@ export async function persistBlockInputs(this: BeaconChain, blockInputs: IBlockI
125
125
  for (const blockInput of blockInputs) {
126
126
  this.seenBlockInputCache.prune(blockInput.blockRootHex);
127
127
  }
128
+ // Without forcefully clearing this cache, we would rely on WeakMap to evict memory which is not reliable.
129
+ // Clear here (after the DB write) so that writeBlockInputToDb can still use the cached serialized bytes.
130
+ this.serializedCache.clear();
128
131
  if (blockInputs.length === 1) {
129
132
  this.logger.debug("Pruned block input", {
130
133
  slot: blockInputs[0].slot,
@@ -5,11 +5,19 @@ import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz";
5
5
  import {BeaconConfig} from "@lodestar/config";
6
6
  import {CheckpointWithHex, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
7
7
  import {LoggerNode} from "@lodestar/logger/node";
8
- import {EFFECTIVE_BALANCE_INCREMENT, GENESIS_SLOT, SLOTS_PER_EPOCH, isForkPostElectra} from "@lodestar/params";
8
+ import {
9
+ BUILDER_INDEX_SELF_BUILD,
10
+ EFFECTIVE_BALANCE_INCREMENT,
11
+ GENESIS_SLOT,
12
+ SLOTS_PER_EPOCH,
13
+ isForkPostElectra,
14
+ isForkPostGloas,
15
+ } from "@lodestar/params";
9
16
  import {
10
17
  BeaconStateAllForks,
11
18
  BeaconStateElectra,
12
19
  CachedBeaconStateAllForks,
20
+ CachedBeaconStateGloas,
13
21
  EffectiveBalanceIncrements,
14
22
  EpochShuffling,
15
23
  Index2PubkeyCache,
@@ -39,6 +47,7 @@ import {
39
47
  Wei,
40
48
  deneb,
41
49
  fulu,
50
+ gloas,
42
51
  isBlindedBeaconBlock,
43
52
  phase0,
44
53
  rewards,
@@ -87,8 +96,8 @@ import {
87
96
  } from "./opPools/index.js";
88
97
  import {IChainOptions} from "./options.js";
89
98
  import {PrepareNextSlotScheduler} from "./prepareNextSlot.js";
90
- import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
91
- import {AssembledBlockType, BlockType, ProduceResult} from "./produceBlock/index.js";
99
+ import {computeEnvelopeStateRoot, computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
100
+ import {AssembledBlockType, BlockType, ProduceFullGloas, ProduceResult} from "./produceBlock/index.js";
92
101
  import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produceBlock/produceBlockBody.js";
93
102
  import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js";
94
103
  import {ReprocessController} from "./reprocess.js";
@@ -902,6 +911,7 @@ export class BeaconChain implements IBeaconChain {
902
911
  consensusBlockValue: Wei;
903
912
  shouldOverrideBuilder?: boolean;
904
913
  }> {
914
+ const fork = this.config.getForkName(slot);
905
915
  const state = await this.regen.getBlockSlotState(
906
916
  parentBlock,
907
917
  slot,
@@ -930,7 +940,7 @@ export class BeaconChain implements IBeaconChain {
930
940
  // The hashtree root computed here for debug log will get cached and hence won't introduce additional delays
931
941
  const bodyRoot =
932
942
  produceResult.type === BlockType.Full
933
- ? this.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(body)
943
+ ? sszTypesFor(fork).BeaconBlockBody.hashTreeRoot(body)
934
944
  : this.config
935
945
  .getPostBellatrixForkTypes(slot)
936
946
  .BlindedBeaconBlockBody.hashTreeRoot(body as BlindedBeaconBlockBody);
@@ -948,14 +958,33 @@ export class BeaconChain implements IBeaconChain {
948
958
  body,
949
959
  } as AssembledBlockType<T>;
950
960
 
951
- const {newStateRoot, proposerReward} = computeNewStateRoot(this.metrics, state, block);
961
+ const {newStateRoot, proposerReward, postState} = computeNewStateRoot(this.metrics, state, block);
952
962
  block.stateRoot = newStateRoot;
953
963
  const blockRoot =
954
964
  produceResult.type === BlockType.Full
955
- ? this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)
965
+ ? sszTypesFor(fork).BeaconBlock.hashTreeRoot(block)
956
966
  : this.config.getPostBellatrixForkTypes(slot).BlindedBeaconBlock.hashTreeRoot(block as BlindedBeaconBlock);
957
967
  const blockRootHex = toRootHex(blockRoot);
958
968
 
969
+ if (isForkPostGloas(fork)) {
970
+ // TODO GLOAS: we should retire BlockType post-gloas, may need a new enum for self vs non-self built
971
+ if (produceResult.type !== BlockType.Full) {
972
+ throw Error(`Unexpected block type=${produceResult.type} for post-gloas fork=${fork}`);
973
+ }
974
+
975
+ const gloasResult = produceResult as ProduceFullGloas;
976
+ const envelope: gloas.ExecutionPayloadEnvelope = {
977
+ payload: gloasResult.executionPayload,
978
+ executionRequests: gloasResult.executionRequests,
979
+ builderIndex: BUILDER_INDEX_SELF_BUILD,
980
+ beaconBlockRoot: blockRoot,
981
+ slot,
982
+ stateRoot: ZERO_HASH,
983
+ };
984
+ const envelopeStateRoot = computeEnvelopeStateRoot(this.metrics, postState as CachedBeaconStateGloas, envelope);
985
+ gloasResult.envelopeStateRoot = envelopeStateRoot;
986
+ }
987
+
959
988
  // Track the produced block for consensus broadcast validations, later validation, etc.
960
989
  this.blockProductionCache.set(blockRootHex, produceResult);
961
990
  this.metrics?.blockProductionCacheSize.set(this.blockProductionCache.size);
@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
6
- import {RootHex, deneb, fulu, phase0} from "@lodestar/types";
6
+ import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
7
7
  import {PeerIdStr} from "../util/peerId.js";
8
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
9
9
 
@@ -88,7 +88,7 @@ export type IChainEvents = ApiEvents & {
88
88
 
89
89
  [ChainEvent.updateTargetCustodyGroupCount]: (targetGroupCount: number) => void;
90
90
 
91
- [ChainEvent.publishDataColumns]: (sidecars: fulu.DataColumnSidecar[]) => void;
91
+ [ChainEvent.publishDataColumns]: (sidecars: DataColumnSidecar[]) => void;
92
92
 
93
93
  [ChainEvent.publishBlobSidecars]: (sidecars: deneb.BlobSidecar[]) => void;
94
94
 
@@ -355,7 +355,10 @@ export class LightClientServer {
355
355
  // Signature data
356
356
  const update = await this.db.bestLightClientUpdate.get(period);
357
357
  if (!update) {
358
- throw Error(`No partialUpdate available for period ${period}`);
358
+ throw new LightClientServerError(
359
+ {code: LightClientServerErrorCode.RESOURCE_UNAVAILABLE},
360
+ `No partialUpdate available for period ${period}`
361
+ );
359
362
  }
360
363
  return update;
361
364
  }
@@ -27,6 +27,7 @@ export type IChainOptions = BlockProcessOpts &
27
27
  blsVerifyAllMainThread?: boolean;
28
28
  blsVerifyAllMultiThread?: boolean;
29
29
  blacklistedBlocks?: string[];
30
+ // TODO GLOAS: add similar option for execution payload envelopes?
30
31
  persistProducedBlocks?: boolean;
31
32
  persistInvalidSszObjects?: boolean;
32
33
  persistInvalidSszObjectsDir?: string;
@@ -1,14 +1,14 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
4
- import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
4
+ import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
5
5
  import {
6
6
  CachedBeaconStateAllForks,
7
7
  CachedBeaconStateExecutions,
8
+ CachedBeaconStateGloas,
8
9
  StateHashTreeRootSource,
9
10
  computeEpochAtSlot,
10
11
  computeTimeAtSlot,
11
- isExecutionStateType,
12
12
  } from "@lodestar/state-transition";
13
13
  import {Slot} from "@lodestar/types";
14
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
@@ -120,10 +120,10 @@ export class PrepareNextSlotScheduler {
120
120
  RegenCaller.precomputeEpoch
121
121
  );
122
122
 
123
- if (isExecutionStateType(prepareState)) {
123
+ if (isForkPostBellatrix(fork)) {
124
124
  const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot);
125
125
  const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
126
- let updatedPrepareState = prepareState;
126
+ let updatedPrepareState = prepareState as CachedBeaconStateExecutions | CachedBeaconStateGloas;
127
127
  let updatedHeadRoot = headRoot;
128
128
 
129
129
  if (feeRecipient) {
@@ -146,7 +146,7 @@ export class PrepareNextSlotScheduler {
146
146
  // only transfer cache if epoch transition because that's the state we will use to stateTransition() the 1st block of epoch
147
147
  {dontTransferCache: !isEpochTransition},
148
148
  RegenCaller.predictProposerHead
149
- )) as CachedBeaconStateExecutions;
149
+ )) as CachedBeaconStateExecutions | CachedBeaconStateGloas;
150
150
  updatedHeadRoot = proposerHeadRoot;
151
151
  }
152
152