@lodestar/beacon-node 1.43.0-dev.a140dd987e → 1.43.0-dev.a6d8600051

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 (228) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -5
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/lodestar/index.js +1 -1
  8. package/lib/api/impl/lodestar/index.js.map +1 -1
  9. package/lib/api/impl/validator/index.d.ts.map +1 -1
  10. package/lib/api/impl/validator/index.js +66 -1
  11. package/lib/api/impl/validator/index.js.map +1 -1
  12. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  13. package/lib/chain/blocks/importBlock.js +3 -2
  14. package/lib/chain/blocks/importBlock.js.map +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.d.ts +19 -6
  16. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.js +43 -19
  18. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  19. package/lib/chain/blocks/index.d.ts +5 -3
  20. package/lib/chain/blocks/index.d.ts.map +1 -1
  21. package/lib/chain/blocks/index.js +31 -10
  22. package/lib/chain/blocks/index.js.map +1 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +4 -1
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  28. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  30. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  31. package/lib/chain/blocks/types.d.ts +2 -2
  32. package/lib/chain/blocks/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  34. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.js +81 -12
  36. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  37. package/lib/chain/blocks/verifyBlock.d.ts +5 -3
  38. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  39. package/lib/chain/blocks/verifyBlock.js +51 -7
  40. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
  43. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  44. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  45. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  46. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  47. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  48. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  49. package/lib/chain/chain.d.ts +3 -2
  50. package/lib/chain/chain.d.ts.map +1 -1
  51. package/lib/chain/chain.js +12 -4
  52. package/lib/chain/chain.js.map +1 -1
  53. package/lib/chain/errors/blockError.d.ts +8 -1
  54. package/lib/chain/errors/blockError.d.ts.map +1 -1
  55. package/lib/chain/errors/blockError.js +2 -0
  56. package/lib/chain/errors/blockError.js.map +1 -1
  57. package/lib/chain/errors/index.d.ts +1 -0
  58. package/lib/chain/errors/index.d.ts.map +1 -1
  59. package/lib/chain/errors/index.js +1 -0
  60. package/lib/chain/errors/index.js.map +1 -1
  61. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  62. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  63. package/lib/chain/errors/proposerPreferences.js +13 -0
  64. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  65. package/lib/chain/interface.d.ts +3 -2
  66. package/lib/chain/interface.d.ts.map +1 -1
  67. package/lib/chain/interface.js.map +1 -1
  68. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  69. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  70. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  71. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  72. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  73. package/lib/chain/prepareNextSlot.js +15 -17
  74. package/lib/chain/prepareNextSlot.js.map +1 -1
  75. package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -3
  76. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  77. package/lib/chain/produceBlock/produceBlockBody.js +41 -18
  78. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  79. package/lib/chain/regen/interface.d.ts +1 -0
  80. package/lib/chain/regen/interface.d.ts.map +1 -1
  81. package/lib/chain/regen/interface.js +1 -0
  82. package/lib/chain/regen/interface.js.map +1 -1
  83. package/lib/chain/seenCache/index.d.ts +1 -0
  84. package/lib/chain/seenCache/index.d.ts.map +1 -1
  85. package/lib/chain/seenCache/index.js +1 -0
  86. package/lib/chain/seenCache/index.js.map +1 -1
  87. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
  88. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  89. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +8 -2
  90. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  91. package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
  92. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  93. package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
  94. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  95. package/lib/chain/validation/block.d.ts.map +1 -1
  96. package/lib/chain/validation/block.js +1 -0
  97. package/lib/chain/validation/block.js.map +1 -1
  98. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  99. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  100. package/lib/chain/validation/proposerPreferences.js +69 -0
  101. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  102. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  103. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  104. package/lib/metrics/metrics/lodestar.js +4 -0
  105. package/lib/metrics/metrics/lodestar.js.map +1 -1
  106. package/lib/network/gossip/interface.d.ts +7 -1
  107. package/lib/network/gossip/interface.d.ts.map +1 -1
  108. package/lib/network/gossip/interface.js +1 -0
  109. package/lib/network/gossip/interface.js.map +1 -1
  110. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  111. package/lib/network/gossip/scoringParameters.js +12 -1
  112. package/lib/network/gossip/scoringParameters.js.map +1 -1
  113. package/lib/network/gossip/topic.d.ts +8 -0
  114. package/lib/network/gossip/topic.d.ts.map +1 -1
  115. package/lib/network/gossip/topic.js +6 -0
  116. package/lib/network/gossip/topic.js.map +1 -1
  117. package/lib/network/interface.d.ts +1 -0
  118. package/lib/network/interface.d.ts.map +1 -1
  119. package/lib/network/network.d.ts +1 -0
  120. package/lib/network/network.d.ts.map +1 -1
  121. package/lib/network/network.js +5 -0
  122. package/lib/network/network.js.map +1 -1
  123. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  124. package/lib/network/processor/gossipHandlers.js +14 -9
  125. package/lib/network/processor/gossipHandlers.js.map +1 -1
  126. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  127. package/lib/network/processor/gossipQueues/index.js +5 -0
  128. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  129. package/lib/network/processor/index.d.ts.map +1 -1
  130. package/lib/network/processor/index.js +1 -0
  131. package/lib/network/processor/index.js.map +1 -1
  132. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  133. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  134. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  135. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  136. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  137. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  138. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  139. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  140. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  141. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  142. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  143. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  144. package/lib/node/notifier.js +7 -1
  145. package/lib/node/notifier.js.map +1 -1
  146. package/lib/sync/range/batch.d.ts +12 -2
  147. package/lib/sync/range/batch.d.ts.map +1 -1
  148. package/lib/sync/range/batch.js +56 -30
  149. package/lib/sync/range/batch.js.map +1 -1
  150. package/lib/sync/range/chain.d.ts +6 -2
  151. package/lib/sync/range/chain.d.ts.map +1 -1
  152. package/lib/sync/range/chain.js +4 -3
  153. package/lib/sync/range/chain.js.map +1 -1
  154. package/lib/sync/range/range.d.ts.map +1 -1
  155. package/lib/sync/range/range.js +17 -6
  156. package/lib/sync/range/range.js.map +1 -1
  157. package/lib/sync/types.d.ts +34 -0
  158. package/lib/sync/types.d.ts.map +1 -1
  159. package/lib/sync/types.js +34 -0
  160. package/lib/sync/types.js.map +1 -1
  161. package/lib/sync/unknownBlock.d.ts +24 -1
  162. package/lib/sync/unknownBlock.d.ts.map +1 -1
  163. package/lib/sync/unknownBlock.js +649 -53
  164. package/lib/sync/unknownBlock.js.map +1 -1
  165. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  166. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  167. package/lib/sync/utils/downloadByRange.js +147 -24
  168. package/lib/sync/utils/downloadByRange.js.map +1 -1
  169. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  170. package/lib/sync/utils/downloadByRoot.js +6 -2
  171. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  172. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  173. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  174. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  175. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  176. package/package.json +15 -15
  177. package/src/api/impl/beacon/blocks/index.ts +8 -5
  178. package/src/api/impl/beacon/pool/index.ts +83 -1
  179. package/src/api/impl/lodestar/index.ts +1 -1
  180. package/src/api/impl/validator/index.ts +80 -0
  181. package/src/chain/blocks/importBlock.ts +3 -2
  182. package/src/chain/blocks/importExecutionPayload.ts +57 -21
  183. package/src/chain/blocks/index.ts +54 -14
  184. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +5 -1
  185. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  186. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  187. package/src/chain/blocks/types.ts +2 -2
  188. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  189. package/src/chain/blocks/verifyBlock.ts +68 -9
  190. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  191. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  192. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  193. package/src/chain/chain.ts +16 -3
  194. package/src/chain/errors/blockError.ts +4 -1
  195. package/src/chain/errors/index.ts +1 -0
  196. package/src/chain/errors/proposerPreferences.ts +39 -0
  197. package/src/chain/interface.ts +7 -1
  198. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  199. package/src/chain/prepareNextSlot.ts +20 -28
  200. package/src/chain/produceBlock/produceBlockBody.ts +51 -23
  201. package/src/chain/regen/interface.ts +1 -0
  202. package/src/chain/seenCache/index.ts +1 -0
  203. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +13 -3
  204. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  205. package/src/chain/validation/block.ts +1 -0
  206. package/src/chain/validation/proposerPreferences.ts +91 -0
  207. package/src/metrics/metrics/lodestar.ts +4 -0
  208. package/src/network/gossip/interface.ts +6 -0
  209. package/src/network/gossip/scoringParameters.ts +14 -1
  210. package/src/network/gossip/topic.ts +6 -0
  211. package/src/network/interface.ts +1 -0
  212. package/src/network/network.ts +11 -0
  213. package/src/network/processor/gossipHandlers.ts +23 -10
  214. package/src/network/processor/gossipQueues/index.ts +5 -0
  215. package/src/network/processor/index.ts +1 -0
  216. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  217. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  218. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  219. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  220. package/src/node/notifier.ts +8 -1
  221. package/src/sync/range/batch.ts +90 -35
  222. package/src/sync/range/chain.ts +13 -5
  223. package/src/sync/range/range.ts +18 -6
  224. package/src/sync/types.ts +72 -0
  225. package/src/sync/unknownBlock.ts +810 -57
  226. package/src/sync/utils/downloadByRange.ts +256 -39
  227. package/src/sync/utils/downloadByRoot.ts +12 -2
  228. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -1,17 +1,27 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ApplicationMethods} from "@lodestar/api/server";
3
- import {ForkPostElectra, ForkPreElectra, SYNC_COMMITTEE_SUBNET_SIZE, isForkPostElectra} from "@lodestar/params";
3
+ import {
4
+ ForkPostElectra,
5
+ ForkPreElectra,
6
+ SYNC_COMMITTEE_SUBNET_SIZE,
7
+ isForkPostElectra,
8
+ isForkPostGloas,
9
+ } from "@lodestar/params";
4
10
  import {isStatePostAltair} from "@lodestar/state-transition";
5
11
  import {Attestation, Epoch, SingleAttestation, isElectraAttestation, ssz, sszTypesFor} from "@lodestar/types";
12
+ import {toRootHex} from "@lodestar/utils";
6
13
  import {
7
14
  AttestationError,
8
15
  AttestationErrorCode,
9
16
  GossipAction,
17
+ PayloadAttestationError,
18
+ PayloadAttestationErrorCode,
10
19
  SyncCommitteeError,
11
20
  } from "../../../../chain/errors/index.js";
12
21
  import {validateApiAttesterSlashing} from "../../../../chain/validation/attesterSlashing.js";
13
22
  import {validateApiBlsToExecutionChange} from "../../../../chain/validation/blsToExecutionChange.js";
14
23
  import {toElectraSingleAttestation, validateApiAttestation} from "../../../../chain/validation/index.js";
24
+ import {validateApiPayloadAttestationMessage} from "../../../../chain/validation/payloadAttestationMessage.js";
15
25
  import {validateApiProposerSlashing} from "../../../../chain/validation/proposerSlashing.js";
16
26
  import {validateApiSyncCommittee} from "../../../../chain/validation/syncCommittee.js";
17
27
  import {validateApiVoluntaryExit} from "../../../../chain/validation/voluntaryExit.js";
@@ -62,6 +72,15 @@ export function getBeaconPoolApi({
62
72
  return {data: attestations, meta: {version: fork}};
63
73
  },
64
74
 
75
+ async getPoolPayloadAttestations({slot}) {
76
+ const fork = chain.config.getForkName(slot ?? chain.clock.currentSlot);
77
+ if (!isForkPostGloas(fork)) {
78
+ throw new ApiError(400, `Payload attestation pool is not supported before Gloas fork=${fork}`);
79
+ }
80
+
81
+ return {data: chain.payloadAttestationPool.getAll(slot), meta: {version: fork}};
82
+ },
83
+
65
84
  async getPoolAttesterSlashings() {
66
85
  const fork = chain.config.getForkName(chain.clock.currentSlot);
67
86
 
@@ -231,6 +250,69 @@ export function getBeaconPoolApi({
231
250
  }
232
251
  },
233
252
 
253
+ async submitPayloadAttestationMessages({payloadAttestationMessages}) {
254
+ const failures: FailureList = [];
255
+
256
+ await Promise.all(
257
+ payloadAttestationMessages.map(async (payloadAttestationMessage, i) => {
258
+ try {
259
+ const validateFn = () => validateApiPayloadAttestationMessage(chain, payloadAttestationMessage);
260
+ const {slot, beaconBlockRoot} = payloadAttestationMessage.data;
261
+ const {attDataRootHex, validatorCommitteeIndex} = await validateGossipFnRetryUnknownRoot(
262
+ validateFn,
263
+ network,
264
+ chain,
265
+ slot,
266
+ beaconBlockRoot
267
+ );
268
+
269
+ const insertOutcome = chain.payloadAttestationPool.add(
270
+ payloadAttestationMessage,
271
+ attDataRootHex,
272
+ validatorCommitteeIndex
273
+ );
274
+ metrics?.opPool.payloadAttestationPool.apiInsertOutcome.inc({insertOutcome});
275
+
276
+ chain.forkChoice.notifyPtcMessages(
277
+ toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
278
+ [validatorCommitteeIndex],
279
+ payloadAttestationMessage.data.payloadPresent
280
+ );
281
+
282
+ await network.publishPayloadAttestationMessage(payloadAttestationMessage);
283
+ } catch (e) {
284
+ const logCtx = {
285
+ slot: payloadAttestationMessage.data.slot,
286
+ validatorIndex: payloadAttestationMessage.validatorIndex,
287
+ beaconBlockRoot: toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
288
+ };
289
+
290
+ if (
291
+ e instanceof PayloadAttestationError &&
292
+ e.type.code === PayloadAttestationErrorCode.PAYLOAD_ATTESTATION_ALREADY_KNOWN
293
+ ) {
294
+ logger.debug("Ignoring known payload attestation message", logCtx);
295
+ return;
296
+ }
297
+
298
+ failures.push({index: i, message: (e as Error).message});
299
+ logger.verbose(`Error on submitPayloadAttestationMessages [${i}]`, logCtx, e as Error);
300
+ if (e instanceof PayloadAttestationError && e.action === GossipAction.REJECT) {
301
+ chain.persistInvalidSszValue(
302
+ ssz.gloas.PayloadAttestationMessage,
303
+ payloadAttestationMessage,
304
+ "api_reject"
305
+ );
306
+ }
307
+ }
308
+ })
309
+ );
310
+
311
+ if (failures.length > 0) {
312
+ throw new IndexedError("Error processing payload attestation messages", failures);
313
+ }
314
+ },
315
+
234
316
  /**
235
317
  * POST `/eth/v1/beacon/pool/sync_committees`
236
318
  *
@@ -118,7 +118,7 @@ export function getLodestarApi({
118
118
  return {
119
119
  // biome-ignore lint/complexity/useLiteralKeys: The `blockProcessor` is a protected attribute
120
120
  data: (chain as BeaconChain)["blockProcessor"].jobQueue.getItems().map((item) => {
121
- const [blockInputs, opts] = item.args;
121
+ const [blockInputs, _payloadEnvelopes, opts] = item.args;
122
122
  return {
123
123
  blockSlots: blockInputs.map((blockInput) => blockInput.slot),
124
124
  jobOpts: opts,
@@ -26,6 +26,7 @@ import {
26
26
  computeTimeAtSlot,
27
27
  getCurrentSlot,
28
28
  isStatePostAltair,
29
+ isStatePostGloas,
29
30
  proposerShufflingDecisionRoot,
30
31
  } from "@lodestar/state-transition";
31
32
  import {
@@ -1049,6 +1050,36 @@ export function getValidatorApi(
1049
1050
  };
1050
1051
  },
1051
1052
 
1053
+ async producePayloadAttestationData({slot}) {
1054
+ const fork = config.getForkName(slot);
1055
+ if (!isForkPostGloas(fork)) {
1056
+ throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
1057
+ }
1058
+
1059
+ notWhileSyncing();
1060
+ await waitForSlot(slot);
1061
+
1062
+ const block = chain.forkChoice.getCanonicalBlockClosestLteSlot(slot);
1063
+ if (!block) {
1064
+ throw new ApiError(404, `No canonical block found at or before slot=${slot}`);
1065
+ }
1066
+
1067
+ const blockIsForSlot = block.slot === slot;
1068
+ const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
1069
+ const payloadPresent = blockIsForSlot && (payloadInput?.hasPayloadEnvelope() ?? false);
1070
+ const blobDataAvailable = blockIsForSlot && (payloadInput?.hasAllData() ?? false);
1071
+
1072
+ return {
1073
+ data: {
1074
+ beaconBlockRoot: fromHex(block.blockRoot),
1075
+ slot,
1076
+ payloadPresent,
1077
+ blobDataAvailable,
1078
+ },
1079
+ meta: {version: fork},
1080
+ };
1081
+ },
1082
+
1052
1083
  /**
1053
1084
  * GET `/eth/v1/validator/sync_committee_contribution`
1054
1085
  *
@@ -1263,6 +1294,55 @@ export function getValidatorApi(
1263
1294
  };
1264
1295
  },
1265
1296
 
1297
+ async getPtcDuties({epoch, indices}) {
1298
+ notWhileSyncing();
1299
+
1300
+ if (indices.length === 0) {
1301
+ throw new ApiError(400, "No validator to get PTC duties");
1302
+ }
1303
+
1304
+ const startSlot = computeStartSlotAtEpoch(epoch);
1305
+ const fork = config.getForkName(startSlot);
1306
+ if (!isForkPostGloas(fork)) {
1307
+ throw new ApiError(400, `PTC duties are not supported before Gloas fork=${fork}`);
1308
+ }
1309
+
1310
+ await waitForNextClosestEpoch();
1311
+
1312
+ if (epoch > chain.clock.currentEpoch + 1) {
1313
+ throw new ApiError(400, "Cannot get PTC duties for epoch more than one ahead");
1314
+ }
1315
+
1316
+ const head = chain.forkChoice.getHead();
1317
+ const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
1318
+ if (!isStatePostGloas(state)) {
1319
+ throw new ApiError(400, `PTC duties are not available before Gloas fork=${state.forkName}`);
1320
+ }
1321
+
1322
+ const pubkeys = getPubkeysForIndices(state, indices);
1323
+ const ptcs = state.getEpochPTCs(epoch);
1324
+ const duties: routes.validator.PtcDuty[] = [];
1325
+ for (let i = 0, len = indices.length; i < len; i++) {
1326
+ const validatorIndex = indices[i];
1327
+ for (let j = 0; j < SLOTS_PER_EPOCH; j++) {
1328
+ if (ptcs[j].indexOf(validatorIndex) !== -1) {
1329
+ duties.push({pubkey: pubkeys[i], validatorIndex, slot: j + startSlot});
1330
+ break;
1331
+ }
1332
+ }
1333
+ }
1334
+
1335
+ const dependentRoot = fromHex(state.getShufflingDecisionRoot(epoch)) || (await getGenesisBlockRoot(state));
1336
+
1337
+ return {
1338
+ data: duties,
1339
+ meta: {
1340
+ dependentRoot: toRootHex(dependentRoot),
1341
+ executionOptimistic: isOptimisticBlock(head),
1342
+ },
1343
+ };
1344
+ },
1345
+
1266
1346
  /**
1267
1347
  * `POST /eth/v1/validator/duties/sync/{epoch}`
1268
1348
  *
@@ -135,8 +135,9 @@ export async function importBlock(
135
135
  // Some block event handlers require state being in state cache so need to do this before emitting EventType.block
136
136
  this.regen.processState(blockRootHex, postState);
137
137
 
138
- // For Gloas blocks, create PayloadEnvelopeInput so it's available for later payload import
139
- if (fork >= ForkSeq.gloas) {
138
+ // For range sync, PayloadEnvelope is created before reaching this
139
+ // we also don't need to trigger getBlobs() in that case
140
+ if (fork >= ForkSeq.gloas && !opts.fromRangeSync) {
140
141
  const payloadInput = this.seenPayloadEnvelopeInputCache.add({
141
142
  blockRootHex,
142
143
  block: block as SignedBeaconBlock<ForkPostGloas>,
@@ -1,7 +1,8 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {ExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
- import {isStatePostGloas} from "@lodestar/state-transition";
4
- import {fromHex} from "@lodestar/utils";
2
+ import {ExecutionStatus, PayloadExecutionStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
+ import {DataAvailabilityStatus, isStatePostGloas} from "@lodestar/state-transition";
4
+ import {fromHex, isErrorAborted} from "@lodestar/utils";
5
+ import {ZERO_HASH_HEX} from "../../constants/index.js";
5
6
  import {ExecutionPayloadStatus} from "../../execution/index.js";
6
7
  import {isQueueErrorAborted} from "../../util/queue/index.js";
7
8
  import {BeaconChain} from "../chain.js";
@@ -75,21 +76,24 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
75
76
  * The envelope is only verified here, no state mutation. State effects from the payload
76
77
  * are applied on the next block via processParentExecutionPayload.
77
78
  *
79
+ * The DA wait must have run upstream (range sync awaits DA in `verifyBlocksInEpoch` for the
80
+ * whole segment; gossip / API path uses the `processExecutionPayload` wrapper below).
81
+ *
78
82
  * Steps:
79
83
  * 1. Emit `execution_payload_available` event for payload attestation
80
84
  * 2. Get the ProtoBlock from fork choice
81
- * 3. Wait for data columns to be available
82
- * 4. Regenerate state for envelope verification
83
- * 5. Verify envelope (fields against state, signature, and EL in parallel where possible)
84
- * 6. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
85
- * 7. Update fork choice (transitions the block's PENDING variant to FULL)
85
+ * 3. Regenerate state for envelope verification
86
+ * 4. Verify envelope (fields against state, signature, and EL in parallel where possible)
87
+ * 5. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
88
+ * 6. Update fork choice (transitions the block's PENDING variant to FULL)
89
+ * 7. Queue notifyForkchoiceUpdate to engine api
86
90
  * 8. Record metrics for payload envelope and column sources
87
91
  * 9. Emit `execution_payload` event
88
92
  */
89
93
  export async function importExecutionPayload(
90
94
  this: BeaconChain,
91
95
  payloadInput: PayloadEnvelopeInput,
92
- signal: AbortSignal,
96
+ dataAvailabilityStatus: DataAvailabilityStatus,
93
97
  opts: ImportPayloadOpts = {}
94
98
  ): Promise<void> {
95
99
  const signedEnvelope = payloadInput.getPayloadEnvelope();
@@ -119,11 +123,7 @@ export async function importExecutionPayload(
119
123
  });
120
124
  }
121
125
 
122
- // 3. Wait for data columns to be available.
123
- // The helper is shared with future gloas sync services; take the single-item batch form here.
124
- await verifyPayloadsDataAvailability([payloadInput], signal);
125
-
126
- // 4. Regenerate state for envelope verification
126
+ // 3. Regenerate state for envelope verification
127
127
  const blockState = await this.regen.getBlockSlotState(
128
128
  protoBlock,
129
129
  protoBlock.slot,
@@ -137,7 +137,7 @@ export async function importExecutionPayload(
137
137
  });
138
138
  }
139
139
 
140
- // 5. Verify envelope fields against state first to fail fast before the EL + BLS work.
140
+ // 4. Verify envelope fields against state first to fail fast before the EL + BLS work.
141
141
  // When validSignature is true, gossip/API has already verified both the signature and the
142
142
  // executionRequestsRoot, so we skip those checks here.
143
143
  try {
@@ -154,7 +154,7 @@ export async function importExecutionPayload(
154
154
  );
155
155
  }
156
156
 
157
- // 5a. Run EL and signature verification in parallel
157
+ // 4a. Run EL and signature verification in parallel
158
158
  const [execResult, signatureValid] = await Promise.all([
159
159
  this.executionEngine.notifyNewPayload(
160
160
  fork,
@@ -176,12 +176,12 @@ export async function importExecutionPayload(
176
176
  ),
177
177
  ]);
178
178
 
179
- // 5b. Check signature verification result
179
+ // 4b. Check signature verification result
180
180
  if (!signatureValid) {
181
181
  throw new PayloadError({code: PayloadErrorCode.INVALID_SIGNATURE});
182
182
  }
183
183
 
184
- // 5c. Handle EL response
184
+ // 4c. Handle EL response
185
185
  switch (execResult.status) {
186
186
  case ExecutionPayloadStatus.VALID:
187
187
  break;
@@ -207,7 +207,7 @@ export async function importExecutionPayload(
207
207
  });
208
208
  }
209
209
 
210
- // 6. Persist payload envelope to hot DB. Wait for write-queue space here to apply backpressure
210
+ // 5. Persist payload envelope to hot DB. Wait for write-queue space here to apply backpressure
211
211
  // on the import pipeline during sync, then perform the write asynchronously to avoid blocking.
212
212
  await this.unfinalizedPayloadEnvelopeWrites.waitForSpace();
213
213
  this.unfinalizedPayloadEnvelopeWrites.push(payloadInput).catch((e) => {
@@ -220,9 +220,27 @@ export async function importExecutionPayload(
220
220
  }
221
221
  });
222
222
 
223
- // 7. Update fork choice, transitions the block's PENDING variant to FULL
223
+ // 6. Update fork choice, transitions the block's PENDING variant to FULL
224
224
  const execStatus = toForkChoiceExecutionStatus(execResult.status);
225
- this.forkChoice.onExecutionPayload(blockRootHex, blockHashHex, envelope.payload.blockNumber, execStatus);
225
+ this.forkChoice.onExecutionPayload(
226
+ blockRootHex,
227
+ blockHashHex,
228
+ envelope.payload.blockNumber,
229
+ execStatus,
230
+ dataAvailabilityStatus
231
+ );
232
+
233
+ // 7. Queue notifyForkchoiceUpdate to engine api
234
+ const head = this.forkChoice.getHead();
235
+ if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
236
+ const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
237
+ const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
238
+ this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
239
+ if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
240
+ this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
241
+ }
242
+ });
243
+ }
226
244
 
227
245
  // 8. Record metrics for payload envelope and column sources
228
246
  this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
@@ -249,3 +267,21 @@ export async function importExecutionPayload(
249
267
  blockHash: blockHashHex,
250
268
  });
251
269
  }
270
+
271
+ /**
272
+ * Process an execution payload envelope end-to-end: wait for DA, then import.
273
+ *
274
+ * Used by the PayloadEnvelopeProcessor queue (gossip / API / unknown-payload sync) — i.e.
275
+ * callers that have NOT already awaited DA themselves. Range sync's inline dispatch in
276
+ * processBlocks skips this wrapper and calls `importExecutionPayload` directly, since
277
+ * `verifyBlocksInEpoch` already awaited DA for the segment.
278
+ */
279
+ export async function processExecutionPayload(
280
+ this: BeaconChain,
281
+ payloadInput: PayloadEnvelopeInput,
282
+ signal: AbortSignal,
283
+ opts: ImportPayloadOpts = {}
284
+ ): Promise<void> {
285
+ const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability([payloadInput], signal);
286
+ await importExecutionPayload.call(this, payloadInput, dataAvailabilityStatuses[0], opts);
287
+ }
@@ -1,4 +1,4 @@
1
- import {SignedBeaconBlock} from "@lodestar/types";
1
+ import {SignedBeaconBlock, Slot} from "@lodestar/types";
2
2
  import {isErrorAborted, toRootHex} from "@lodestar/utils";
3
3
  import {Metrics} from "../../metrics/metrics.js";
4
4
  import {nextEventLoop} from "../../util/eventLoop.js";
@@ -8,6 +8,8 @@ import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.j
8
8
  import {BlockProcessOpts} from "../options.js";
9
9
  import {IBlockInput} from "./blockInput/types.js";
10
10
  import {importBlock} from "./importBlock.js";
11
+ import {importExecutionPayload} from "./importExecutionPayload.js";
12
+ import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js";
11
13
  import {FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
12
14
  import {assertLinearChainSegment} from "./utils/chainSegment.js";
13
15
  import {verifyBlocksInEpoch} from "./verifyBlock.js";
@@ -21,20 +23,24 @@ const QUEUE_MAX_LENGTH = 256;
21
23
  * BlockProcessor processes block jobs in a queued fashion, one after the other.
22
24
  */
23
25
  export class BlockProcessor {
24
- readonly jobQueue: JobItemQueue<[IBlockInput[], ImportBlockOpts], void>;
26
+ readonly jobQueue: JobItemQueue<[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts], void>;
25
27
 
26
28
  constructor(chain: BeaconChain, metrics: Metrics | null, opts: BlockProcessOpts, signal: AbortSignal) {
27
- this.jobQueue = new JobItemQueue<[IBlockInput[], ImportBlockOpts], void>(
28
- (job, importOpts) => {
29
- return processBlocks.call(chain, job, {...opts, ...importOpts});
29
+ this.jobQueue = new JobItemQueue<[IBlockInput[], Map<Slot, PayloadEnvelopeInput> | null, ImportBlockOpts], void>(
30
+ (job, payloadEnvelopes, importOpts) => {
31
+ return processBlocks.call(chain, job, payloadEnvelopes, {...opts, ...importOpts});
30
32
  },
31
33
  {maxLength: QUEUE_MAX_LENGTH, noYieldIfOneItem: true, signal},
32
34
  metrics?.blockProcessorQueue ?? undefined
33
35
  );
34
36
  }
35
37
 
36
- async processBlocksJob(job: IBlockInput[], opts: ImportBlockOpts = {}): Promise<void> {
37
- await this.jobQueue.push(job, opts);
38
+ async processBlocksJob(
39
+ job: IBlockInput[],
40
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
41
+ opts: ImportBlockOpts = {}
42
+ ): Promise<void> {
43
+ await this.jobQueue.push(job, payloadEnvelopes, opts);
38
44
  }
39
45
  }
40
46
 
@@ -51,16 +57,13 @@ export class BlockProcessor {
51
57
  export async function processBlocks(
52
58
  this: BeaconChain,
53
59
  blocks: IBlockInput[],
60
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
54
61
  opts: BlockProcessOpts & ImportBlockOpts
55
62
  ): Promise<void> {
56
63
  if (blocks.length === 0) {
57
64
  return; // TODO: or throw?
58
65
  }
59
66
 
60
- if (blocks.length > 1) {
61
- assertLinearChainSegment(this.config, blocks);
62
- }
63
-
64
67
  try {
65
68
  const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksSanityChecks(this, blocks, opts);
66
69
 
@@ -70,10 +73,31 @@ export async function processBlocks(
70
73
  return;
71
74
  }
72
75
 
76
+ const {warnings: orphanedPayloads} = assertLinearChainSegment(
77
+ this.config,
78
+ relevantBlocks,
79
+ payloadEnvelopes,
80
+ parentBlock
81
+ );
82
+ if (orphanedPayloads != null) {
83
+ for (const orphaned of orphanedPayloads) {
84
+ this.logger.debug("Orphaned payload envelope in chain segment", {
85
+ slot: orphaned.slot,
86
+ blockRoot: orphaned.payloadEnvelopeInput.blockRootHex,
87
+ });
88
+ }
89
+ }
90
+
73
91
  // Fully verify a block to be imported immediately after. Does not produce any side-effects besides adding intermediate
74
92
  // states in the state cache through regen.
75
- const {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock} =
76
- await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, opts);
93
+ const {
94
+ postStates,
95
+ blockDAStatuses,
96
+ payloadDAStatuses,
97
+ proposerBalanceDeltas,
98
+ segmentExecStatus,
99
+ indexedAttestationsByBlock,
100
+ } = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
77
101
 
78
102
  // If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
79
103
  // and we need to further propagate
@@ -92,7 +116,7 @@ export async function processBlocks(
92
116
  parentBlockSlot: parentSlots[i],
93
117
  executionStatus: executionStatuses[i],
94
118
  // start supporting optimistic syncing/processing
95
- dataAvailabilityStatus: dataAvailabilityStatuses[i],
119
+ dataAvailabilityStatus: blockDAStatuses[i],
96
120
  proposerBalanceDelta: proposerBalanceDeltas[i],
97
121
  indexedAttestations: indexedAttestationsByBlock[i],
98
122
  // TODO: Make this param mandatory and capture in gossip
@@ -103,6 +127,22 @@ export async function processBlocks(
103
127
  for (const fullyVerifiedBlock of fullyVerifiedBlocks) {
104
128
  // TODO: Consider batching importBlock too if it takes significant time
105
129
  await importBlock.call(this, fullyVerifiedBlock, opts);
130
+
131
+ const slot = fullyVerifiedBlock.blockInput.getBlock().message.slot;
132
+ const payloadInput = payloadEnvelopes?.get(slot);
133
+ if (payloadInput?.hasPayloadEnvelope()) {
134
+ if (!payloadInput.isComplete()) {
135
+ // we validated DA before reaching this
136
+ throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
137
+ }
138
+ // we already awaited DA in verifyBlocksInEpoch for this segment
139
+ const payloadDA = payloadDAStatuses.get(slot);
140
+ if (payloadDA === undefined) {
141
+ throw new Error(`Missing payload DA status for slot ${slot}`);
142
+ }
143
+ await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
144
+ }
145
+
106
146
  await nextEventLoop();
107
147
  }
108
148
  } catch (e) {
@@ -64,6 +64,7 @@ export class PayloadEnvelopeInput {
64
64
  readonly proposerIndex: ValidatorIndex;
65
65
  readonly bid: gloas.ExecutionPayloadBid;
66
66
  readonly versionedHashes: VersionedHashes;
67
+ readonly daOutOfRange: boolean;
67
68
 
68
69
  private columnsCache = new Map<ColumnIndex, ColumnWithSource>();
69
70
 
@@ -87,6 +88,7 @@ export class PayloadEnvelopeInput {
87
88
  sampledColumns: ColumnIndex[];
88
89
  custodyColumns: ColumnIndex[];
89
90
  timeCreatedSec: number;
91
+ daOutOfRange: boolean;
90
92
  }) {
91
93
  this.blockRootHex = props.blockRootHex;
92
94
  this.slot = props.slot;
@@ -97,13 +99,14 @@ export class PayloadEnvelopeInput {
97
99
  this.sampledColumns = props.sampledColumns;
98
100
  this.custodyColumns = props.custodyColumns;
99
101
  this.timeCreatedSec = props.timeCreatedSec;
102
+ this.daOutOfRange = props.daOutOfRange;
100
103
  this.payloadEnvelopeDataPromise = createPromise();
101
104
  this.allDataPromise = createPromise();
102
105
  this.columnsDataPromise = createPromise();
103
106
 
104
107
  const noBlobs = props.bid.blobKzgCommitments.length === 0;
105
108
  const noSampledColumns = props.sampledColumns.length === 0;
106
- const hasAllData = noBlobs || noSampledColumns;
109
+ const hasAllData = props.daOutOfRange || noBlobs || noSampledColumns;
107
110
 
108
111
  if (hasAllData) {
109
112
  this.state = {hasPayload: false, hasAllData: true, hasComputedAllData: true};
@@ -125,6 +128,7 @@ export class PayloadEnvelopeInput {
125
128
  sampledColumns: props.sampledColumns,
126
129
  custodyColumns: props.custodyColumns,
127
130
  timeCreatedSec: props.timeCreatedSec,
131
+ daOutOfRange: props.daOutOfRange,
128
132
  });
129
133
  }
130
134
 
@@ -27,6 +27,7 @@ export type CreateFromBlockProps = {
27
27
  sampledColumns: ColumnIndex[];
28
28
  custodyColumns: ColumnIndex[];
29
29
  timeCreatedSec: number;
30
+ daOutOfRange: boolean;
30
31
  };
31
32
 
32
33
  export type AddPayloadEnvelopeProps = SourceMeta & {
@@ -2,7 +2,7 @@ import {Metrics} from "../../metrics/metrics.js";
2
2
  import {JobItemQueue} from "../../util/queue/index.js";
3
3
  import type {BeaconChain} from "../chain.js";
4
4
  import {PayloadEnvelopeInput} from "../seenCache/seenPayloadEnvelopeInput.js";
5
- import {importExecutionPayload} from "./importExecutionPayload.js";
5
+ import {processExecutionPayload} from "./importExecutionPayload.js";
6
6
  import {ImportPayloadOpts} from "./types.js";
7
7
 
8
8
  // TODO GLOAS: Set to be equal to DEFAULT_MAX_PENDING_UNFINALIZED_PAYLOAD_ENVELOPE_WRITES for now
@@ -30,7 +30,7 @@ export class PayloadEnvelopeProcessor {
30
30
  this.jobQueue = new JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>(
31
31
  (payloadInput, opts) => {
32
32
  this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.importing);
33
- return importExecutionPayload.call(chain, payloadInput, signal, opts);
33
+ return processExecutionPayload.call(chain, payloadInput, signal, opts);
34
34
  },
35
35
  {maxLength: QUEUE_MAX_LENGTH, noYieldIfOneItem: true, signal},
36
36
  metrics?.payloadEnvelopeProcessorQueue ?? undefined
@@ -1,5 +1,5 @@
1
1
  import type {ChainForkConfig} from "@lodestar/config";
2
- import {BlockExecutionStatus} from "@lodestar/fork-choice";
2
+ import type {BlockExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
3
  import {ForkSeq} from "@lodestar/params";
4
4
  import {DataAvailabilityStatus, IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
5
5
  import type {IndexedAttestation, Slot, fulu} from "@lodestar/types";
@@ -107,5 +107,5 @@ export type FullyVerifiedBlock = {
107
107
  /** Seen timestamp seconds */
108
108
  seenTimestampSec: number;
109
109
  /** If the execution payload couldn't be verified because of EL syncing status, used in optimistic sync */
110
- executionStatus: BlockExecutionStatus;
110
+ executionStatus: BlockExecutionStatus | PayloadExecutionStatus;
111
111
  };