@lodestar/beacon-node 1.43.0-dev.180030f946 → 1.43.0-dev.1d4e5f45ec

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 (248) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +15 -4
  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/debug/index.d.ts.map +1 -1
  8. package/lib/api/impl/debug/index.js +0 -1
  9. package/lib/api/impl/debug/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 +68 -2
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  14. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  16. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  17. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  18. package/lib/chain/blocks/importBlock.js +16 -31
  19. package/lib/chain/blocks/importBlock.js.map +1 -1
  20. package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
  21. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.js +37 -15
  23. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts.map +1 -1
  25. package/lib/chain/blocks/index.js +35 -21
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
  32. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/types.d.ts +2 -1
  34. package/lib/chain/blocks/types.d.ts.map +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  36. package/lib/chain/blocks/utils/chainSegment.js +8 -0
  37. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  38. package/lib/chain/blocks/verifyBlock.d.ts +2 -1
  39. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  40. package/lib/chain/blocks/verifyBlock.js +30 -12
  41. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  43. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  45. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  47. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlocksSanityChecks.js +25 -5
  49. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  51. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  52. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +12 -8
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  54. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  55. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  56. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  57. package/lib/chain/chain.d.ts +2 -1
  58. package/lib/chain/chain.d.ts.map +1 -1
  59. package/lib/chain/chain.js +28 -9
  60. package/lib/chain/chain.js.map +1 -1
  61. package/lib/chain/emitter.d.ts +0 -11
  62. package/lib/chain/emitter.d.ts.map +1 -1
  63. package/lib/chain/emitter.js +0 -4
  64. package/lib/chain/emitter.js.map +1 -1
  65. package/lib/chain/errors/index.d.ts +1 -0
  66. package/lib/chain/errors/index.d.ts.map +1 -1
  67. package/lib/chain/errors/index.js +1 -0
  68. package/lib/chain/errors/index.js.map +1 -1
  69. package/lib/chain/errors/proposerPreferences.d.ts +40 -0
  70. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  71. package/lib/chain/errors/proposerPreferences.js +14 -0
  72. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  73. package/lib/chain/initState.d.ts.map +1 -1
  74. package/lib/chain/initState.js +6 -1
  75. package/lib/chain/initState.js.map +1 -1
  76. package/lib/chain/interface.d.ts +2 -1
  77. package/lib/chain/interface.d.ts.map +1 -1
  78. package/lib/chain/interface.js.map +1 -1
  79. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  80. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  81. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  82. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  83. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  84. package/lib/chain/prepareNextSlot.js +16 -18
  85. package/lib/chain/prepareNextSlot.js.map +1 -1
  86. package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
  87. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  88. package/lib/chain/produceBlock/produceBlockBody.js +35 -17
  89. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  90. package/lib/chain/regen/interface.d.ts +1 -0
  91. package/lib/chain/regen/interface.d.ts.map +1 -1
  92. package/lib/chain/regen/interface.js +1 -0
  93. package/lib/chain/regen/interface.js.map +1 -1
  94. package/lib/chain/regen/queued.d.ts.map +1 -1
  95. package/lib/chain/regen/queued.js +1 -4
  96. package/lib/chain/regen/queued.js.map +1 -1
  97. package/lib/chain/regen/regen.d.ts.map +1 -1
  98. package/lib/chain/regen/regen.js +1 -4
  99. package/lib/chain/regen/regen.js.map +1 -1
  100. package/lib/chain/seenCache/index.d.ts +1 -0
  101. package/lib/chain/seenCache/index.d.ts.map +1 -1
  102. package/lib/chain/seenCache/index.js +1 -0
  103. package/lib/chain/seenCache/index.js.map +1 -1
  104. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +21 -11
  105. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  106. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
  107. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  108. package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
  109. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  110. package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
  111. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  112. package/lib/chain/validation/executionPayloadBid.js +11 -8
  113. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  114. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  115. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  116. package/lib/chain/validation/proposerPreferences.js +91 -0
  117. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  118. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  119. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  120. package/lib/metrics/metrics/lodestar.js +4 -0
  121. package/lib/metrics/metrics/lodestar.js.map +1 -1
  122. package/lib/network/gossip/interface.d.ts +7 -1
  123. package/lib/network/gossip/interface.d.ts.map +1 -1
  124. package/lib/network/gossip/interface.js +1 -0
  125. package/lib/network/gossip/interface.js.map +1 -1
  126. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  127. package/lib/network/gossip/scoringParameters.js +12 -1
  128. package/lib/network/gossip/scoringParameters.js.map +1 -1
  129. package/lib/network/gossip/topic.d.ts +29 -766
  130. package/lib/network/gossip/topic.d.ts.map +1 -1
  131. package/lib/network/gossip/topic.js +6 -0
  132. package/lib/network/gossip/topic.js.map +1 -1
  133. package/lib/network/interface.d.ts +1 -0
  134. package/lib/network/interface.d.ts.map +1 -1
  135. package/lib/network/network.d.ts +1 -0
  136. package/lib/network/network.d.ts.map +1 -1
  137. package/lib/network/network.js +5 -0
  138. package/lib/network/network.js.map +1 -1
  139. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  140. package/lib/network/processor/gossipHandlers.js +38 -16
  141. package/lib/network/processor/gossipHandlers.js.map +1 -1
  142. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  143. package/lib/network/processor/gossipQueues/index.js +5 -0
  144. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  145. package/lib/network/processor/index.d.ts.map +1 -1
  146. package/lib/network/processor/index.js +6 -5
  147. package/lib/network/processor/index.js.map +1 -1
  148. package/lib/node/nodejs.js +2 -2
  149. package/lib/node/nodejs.js.map +1 -1
  150. package/lib/node/notifier.js +1 -7
  151. package/lib/node/notifier.js.map +1 -1
  152. package/lib/sync/constants.d.ts +3 -1
  153. package/lib/sync/constants.d.ts.map +1 -1
  154. package/lib/sync/constants.js +3 -4
  155. package/lib/sync/constants.js.map +1 -1
  156. package/lib/sync/range/batch.d.ts +23 -3
  157. package/lib/sync/range/batch.d.ts.map +1 -1
  158. package/lib/sync/range/batch.js +191 -36
  159. package/lib/sync/range/batch.js.map +1 -1
  160. package/lib/sync/range/chain.d.ts +13 -2
  161. package/lib/sync/range/chain.d.ts.map +1 -1
  162. package/lib/sync/range/chain.js +61 -9
  163. package/lib/sync/range/chain.js.map +1 -1
  164. package/lib/sync/range/range.d.ts.map +1 -1
  165. package/lib/sync/range/range.js +14 -3
  166. package/lib/sync/range/range.js.map +1 -1
  167. package/lib/sync/sync.d.ts.map +1 -1
  168. package/lib/sync/sync.js +13 -0
  169. package/lib/sync/sync.js.map +1 -1
  170. package/lib/sync/types.d.ts +34 -0
  171. package/lib/sync/types.d.ts.map +1 -1
  172. package/lib/sync/types.js +34 -0
  173. package/lib/sync/types.js.map +1 -1
  174. package/lib/sync/unknownBlock.d.ts +29 -1
  175. package/lib/sync/unknownBlock.d.ts.map +1 -1
  176. package/lib/sync/unknownBlock.js +738 -61
  177. package/lib/sync/unknownBlock.js.map +1 -1
  178. package/lib/sync/utils/downloadByRange.d.ts +29 -8
  179. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  180. package/lib/sync/utils/downloadByRange.js +104 -42
  181. package/lib/sync/utils/downloadByRange.js.map +1 -1
  182. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  183. package/lib/sync/utils/downloadByRoot.js +16 -2
  184. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  185. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  186. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  187. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  188. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  189. package/lib/util/sszBytes.d.ts.map +1 -1
  190. package/lib/util/sszBytes.js +8 -6
  191. package/lib/util/sszBytes.js.map +1 -1
  192. package/package.json +16 -16
  193. package/src/api/impl/beacon/blocks/index.ts +20 -4
  194. package/src/api/impl/beacon/pool/index.ts +83 -1
  195. package/src/api/impl/debug/index.ts +0 -1
  196. package/src/api/impl/validator/index.ts +82 -1
  197. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  198. package/src/chain/blocks/importBlock.ts +16 -50
  199. package/src/chain/blocks/importExecutionPayload.ts +51 -20
  200. package/src/chain/blocks/index.ts +32 -15
  201. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
  202. package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
  203. package/src/chain/blocks/types.ts +2 -1
  204. package/src/chain/blocks/utils/chainSegment.ts +8 -0
  205. package/src/chain/blocks/verifyBlock.ts +45 -13
  206. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  207. package/src/chain/blocks/verifyBlocksSanityChecks.ts +26 -7
  208. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +16 -8
  209. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  210. package/src/chain/chain.ts +32 -7
  211. package/src/chain/emitter.ts +0 -11
  212. package/src/chain/errors/index.ts +1 -0
  213. package/src/chain/errors/proposerPreferences.ts +47 -0
  214. package/src/chain/initState.ts +9 -1
  215. package/src/chain/interface.ts +2 -0
  216. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  217. package/src/chain/prepareNextSlot.ts +21 -29
  218. package/src/chain/produceBlock/produceBlockBody.ts +46 -22
  219. package/src/chain/regen/interface.ts +1 -0
  220. package/src/chain/regen/queued.ts +2 -7
  221. package/src/chain/regen/regen.ts +2 -7
  222. package/src/chain/seenCache/index.ts +1 -0
  223. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
  224. package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
  225. package/src/chain/validation/executionPayloadBid.ts +11 -8
  226. package/src/chain/validation/proposerPreferences.ts +110 -0
  227. package/src/metrics/metrics/lodestar.ts +4 -0
  228. package/src/network/gossip/interface.ts +6 -0
  229. package/src/network/gossip/scoringParameters.ts +14 -1
  230. package/src/network/gossip/topic.ts +6 -0
  231. package/src/network/interface.ts +1 -0
  232. package/src/network/network.ts +11 -0
  233. package/src/network/processor/gossipHandlers.ts +53 -17
  234. package/src/network/processor/gossipQueues/index.ts +5 -0
  235. package/src/network/processor/index.ts +6 -5
  236. package/src/node/nodejs.ts +2 -2
  237. package/src/node/notifier.ts +1 -8
  238. package/src/sync/constants.ts +4 -4
  239. package/src/sync/range/batch.ts +240 -42
  240. package/src/sync/range/chain.ts +77 -10
  241. package/src/sync/range/range.ts +16 -3
  242. package/src/sync/sync.ts +13 -1
  243. package/src/sync/types.ts +72 -0
  244. package/src/sync/unknownBlock.ts +928 -65
  245. package/src/sync/utils/downloadByRange.ts +166 -44
  246. package/src/sync/utils/downloadByRoot.ts +24 -2
  247. package/src/sync/utils/pendingBlocksTree.ts +0 -15
  248. package/src/util/sszBytes.ts +8 -6
@@ -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
  *
@@ -43,7 +43,6 @@ export function getDebugApi({
43
43
  validity: (() => {
44
44
  switch (node.executionStatus) {
45
45
  case ExecutionStatus.Valid:
46
- case ExecutionStatus.PayloadSeparated:
47
46
  return "valid";
48
47
  case ExecutionStatus.Invalid:
49
48
  return "invalid";
@@ -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
  *
@@ -1641,13 +1721,14 @@ export function getValidatorApi(
1641
1721
  throw Error("Cached block production result is not full block");
1642
1722
  }
1643
1723
 
1644
- const {executionPayload, executionRequests} = produceResult as ProduceFullGloas;
1724
+ const {executionPayload, executionRequests, parentBlockRoot} = produceResult as ProduceFullGloas;
1645
1725
 
1646
1726
  const envelope: gloas.ExecutionPayloadEnvelope = {
1647
1727
  payload: executionPayload,
1648
1728
  executionRequests: executionRequests,
1649
1729
  builderIndex: BUILDER_INDEX_SELF_BUILD,
1650
1730
  beaconBlockRoot,
1731
+ parentBeaconBlockRoot: parentBlockRoot,
1651
1732
  };
1652
1733
 
1653
1734
  logger.info("Produced execution payload envelope", {
@@ -895,8 +895,11 @@ export class BlockInputColumns extends AbstractBlockInput<ForkColumnsDA, fulu.Da
895
895
  return this.getAllColumnsWithSource().map(({columnSidecar}) => columnSidecar);
896
896
  }
897
897
 
898
+ /**
899
+ * Strictly checks missing sampled columns. Does NOT short-circuit on `state.hasAllData`.
900
+ */
898
901
  getMissingSampledColumnMeta(): MissingColumnMeta {
899
- if (this.state.hasAllData) {
902
+ if (this.state.hasComputedAllData) {
900
903
  return {
901
904
  missing: [],
902
905
  versionedHashes: this.state.versionedHashes,
@@ -9,14 +9,7 @@ import {
9
9
  NotReorgedReason,
10
10
  getSafeExecutionBlockHash,
11
11
  } from "@lodestar/fork-choice";
12
- import {
13
- ForkPostAltair,
14
- ForkPostElectra,
15
- ForkPostGloas,
16
- ForkSeq,
17
- MAX_SEED_LOOKAHEAD,
18
- SLOTS_PER_EPOCH,
19
- } from "@lodestar/params";
12
+ import {ForkPostAltair, ForkPostElectra, ForkSeq, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
20
13
  import {
21
14
  IBeaconStateView,
22
15
  RootCache,
@@ -27,17 +20,7 @@ import {
27
20
  isStatePostAltair,
28
21
  isStatePostBellatrix,
29
22
  } from "@lodestar/state-transition";
30
- import {
31
- Attestation,
32
- BeaconBlock,
33
- SignedBeaconBlock,
34
- altair,
35
- capella,
36
- electra,
37
- isGloasBeaconBlock,
38
- phase0,
39
- ssz,
40
- } from "@lodestar/types";
23
+ import {Attestation, BeaconBlock, altair, capella, electra, isGloasBeaconBlock, phase0, ssz} from "@lodestar/types";
41
24
  import {isErrorAborted, toRootHex} from "@lodestar/utils";
42
25
  import {ZERO_HASH_HEX} from "../../constants/index.js";
43
26
  import {callInNextEventLoop} from "../../util/eventLoop.js";
@@ -86,8 +69,8 @@ export async function importBlock(
86
69
  fullyVerifiedBlock: FullyVerifiedBlock,
87
70
  opts: ImportBlockOpts
88
71
  ): Promise<void> {
89
- const {blockInput, postState, parentBlockSlot, executionStatus, dataAvailabilityStatus, indexedAttestations} =
90
- fullyVerifiedBlock;
72
+ const {blockInput, postState, parentBlockSlot, dataAvailabilityStatus, indexedAttestations} = fullyVerifiedBlock;
73
+ let {executionStatus} = fullyVerifiedBlock;
91
74
  const block = blockInput.getBlock();
92
75
  const source = blockInput.getBlockSource();
93
76
  const {slot: blockSlot} = block.message;
@@ -122,13 +105,23 @@ export async function importBlock(
122
105
 
123
106
  // Should compute checkpoint balances before forkchoice.onBlock
124
107
  this.checkpointBalancesCache.processState(blockRootHex, postState);
108
+ if (fork >= ForkSeq.gloas) {
109
+ const parentRootHex = toRootHex(block.message.parentRoot);
110
+ const parentBlock = this.forkChoice.getBlockHexDefaultStatus(parentRootHex);
111
+ if (parentBlock === null) {
112
+ throw Error(`Parent block not found in forkChoice, parentRoot=${parentRootHex}`);
113
+ }
114
+ if (parentBlock.executionStatus === ExecutionStatus.Invalid) {
115
+ throw Error(`Parent block has invalid execution status, parentRoot=${parentRootHex}`);
116
+ }
117
+ executionStatus = parentBlock.executionStatus;
118
+ }
125
119
  const blockSummary = this.forkChoice.onBlock(
126
120
  block.message,
127
121
  postState,
128
122
  blockDelaySec,
129
123
  currentSlot,
130
- fork >= ForkSeq.gloas ? ExecutionStatus.PayloadSeparated : executionStatus,
131
- // TODO GLOAS: this is not useful post-gloas, may need to remove it?
124
+ executionStatus,
132
125
  dataAvailabilityStatus
133
126
  );
134
127
 
@@ -136,33 +129,6 @@ export async function importBlock(
136
129
  // Some block event handlers require state being in state cache so need to do this before emitting EventType.block
137
130
  this.regen.processState(blockRootHex, postState);
138
131
 
139
- // For range sync, PayloadEnvelope is created before reaching this
140
- // we also don't need to trigger getBlobs() in that case
141
- if (fork >= ForkSeq.gloas && !opts.fromRangeSync) {
142
- const payloadInput = this.seenPayloadEnvelopeInputCache.add({
143
- blockRootHex,
144
- block: block as SignedBeaconBlock<ForkPostGloas>,
145
- forkName: blockInput.forkName,
146
- sampledColumns: this.custodyConfig.sampledColumns,
147
- custodyColumns: this.custodyConfig.custodyColumns,
148
- timeCreatedSec: fullyVerifiedBlock.seenTimestampSec,
149
- });
150
- this.logger.debug("Created PayloadEnvelopeInput for block", {
151
- slot: blockSlot,
152
- root: blockRootHex,
153
- source: source.source,
154
- ...(opts.seenTimestampSec !== undefined ? {recvToImport: Date.now() / 1000 - opts.seenTimestampSec} : {}),
155
- });
156
-
157
- // Immediately attempt fetch of data columns from execution engine as the bid contains kzg commitments
158
- // which is all the information we need so there is no reason to delay until execution payload arrives
159
- // TODO GLOAS: If we want EL retries after this initial attempt, add an explicit retry policy here
160
- // (for example later in the slot). Do not couple retries to incoming gossip columns.
161
- // Columns fetched here feed payloadInput.addColumn, which resolves waitForAllData for any
162
- // in-flight importExecutionPayload. No processExecutionPayload trigger needed from this path.
163
- this.getBlobsTracker.triggerGetBlobs(payloadInput);
164
- }
165
-
166
132
  this.metrics?.importBlock.bySource.inc({source: source.source});
167
133
  this.logger.verbose("Added block to forkchoice and state cache", {slot: blockSlot, root: blockRootHex});
168
134
 
@@ -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 {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";
@@ -20,6 +21,7 @@ export enum PayloadErrorCode {
20
21
  EXECUTION_ENGINE_INVALID = "PAYLOAD_ERROR_EXECUTION_ENGINE_INVALID",
21
22
  EXECUTION_ENGINE_ERROR = "PAYLOAD_ERROR_EXECUTION_ENGINE_ERROR",
22
23
  BLOCK_NOT_IN_FORK_CHOICE = "PAYLOAD_ERROR_BLOCK_NOT_IN_FORK_CHOICE",
24
+ MISS_BLOCK_STATE = "PAYLOAD_ERROR_MISS_BLOCK_STATE",
23
25
  ENVELOPE_VERIFICATION_ERROR = "PAYLOAD_ERROR_ENVELOPE_VERIFICATION_ERROR",
24
26
  INVALID_SIGNATURE = "PAYLOAD_ERROR_INVALID_SIGNATURE",
25
27
  }
@@ -39,6 +41,10 @@ export type PayloadErrorType =
39
41
  code: PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE;
40
42
  blockRootHex: string;
41
43
  }
44
+ | {
45
+ code: PayloadErrorCode.MISS_BLOCK_STATE;
46
+ blockRootHex: string;
47
+ }
42
48
  | {
43
49
  code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR;
44
50
  message: string;
@@ -60,7 +66,6 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
60
66
  switch (status) {
61
67
  case ExecutionPayloadStatus.VALID:
62
68
  return ExecutionStatus.Valid;
63
- // TODO GLOAS: Handle optimistic import for payload
64
69
  case ExecutionPayloadStatus.SYNCING:
65
70
  case ExecutionPayloadStatus.ACCEPTED:
66
71
  return ExecutionStatus.Syncing;
@@ -85,12 +90,14 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
85
90
  * 4. Verify envelope (fields against state, signature, and EL in parallel where possible)
86
91
  * 5. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
87
92
  * 6. Update fork choice (transitions the block's PENDING variant to FULL)
88
- * 7. Record metrics for payload envelope and column sources
89
- * 8. Emit `execution_payload` event
93
+ * 7. Queue notifyForkchoiceUpdate to engine api
94
+ * 8. Record metrics for payload envelope and column sources
95
+ * 9. Emit `execution_payload` event
90
96
  */
91
97
  export async function importExecutionPayload(
92
98
  this: BeaconChain,
93
99
  payloadInput: PayloadEnvelopeInput,
100
+ dataAvailabilityStatus: DataAvailabilityStatus,
94
101
  opts: ImportPayloadOpts = {}
95
102
  ): Promise<void> {
96
103
  const signedEnvelope = payloadInput.getPayloadEnvelope();
@@ -121,12 +128,19 @@ export async function importExecutionPayload(
121
128
  }
122
129
 
123
130
  // 3. Regenerate state for envelope verification
124
- const blockState = await this.regen.getBlockSlotState(
125
- protoBlock,
126
- protoBlock.slot,
127
- {dontTransferCache: true},
128
- RegenCaller.processBlock
129
- );
131
+ const blockState = await this.regen
132
+ .getBlockSlotState(protoBlock, protoBlock.slot, {dontTransferCache: true}, RegenCaller.processBlock)
133
+ .catch(() =>
134
+ // only happen at the 1st batch of skipped slot checkpoint sync
135
+ this.regen.getClosestHeadState(protoBlock)
136
+ );
137
+
138
+ if (blockState == null) {
139
+ throw new PayloadError({
140
+ code: PayloadErrorCode.MISS_BLOCK_STATE,
141
+ blockRootHex: protoBlock.blockRoot,
142
+ });
143
+ }
130
144
  if (!isStatePostGloas(blockState)) {
131
145
  throw new PayloadError({
132
146
  code: PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR,
@@ -157,7 +171,7 @@ export async function importExecutionPayload(
157
171
  fork,
158
172
  envelope.payload,
159
173
  payloadInput.getVersionedHashes(),
160
- fromHex(protoBlock.parentRoot),
174
+ envelope.parentBeaconBlockRoot,
161
175
  envelope.executionRequests
162
176
  ),
163
177
 
@@ -219,23 +233,40 @@ export async function importExecutionPayload(
219
233
 
220
234
  // 6. Update fork choice, transitions the block's PENDING variant to FULL
221
235
  const execStatus = toForkChoiceExecutionStatus(execResult.status);
222
- this.forkChoice.onExecutionPayload(blockRootHex, blockHashHex, envelope.payload.blockNumber, execStatus);
236
+ this.forkChoice.onExecutionPayload(
237
+ blockRootHex,
238
+ blockHashHex,
239
+ envelope.payload.blockNumber,
240
+ execStatus,
241
+ dataAvailabilityStatus
242
+ );
243
+
244
+ // 7. Queue notifyForkchoiceUpdate to engine api
245
+ const head = this.forkChoice.getHead();
246
+ if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
247
+ const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
248
+ const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
249
+ this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
250
+ if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
251
+ this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
252
+ }
253
+ });
254
+ }
223
255
 
224
- // 7. Record metrics for payload envelope and column sources
256
+ // 8. Record metrics for payload envelope and column sources
225
257
  this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
226
258
  for (const {source} of payloadInput.getSampledColumnsWithSource()) {
227
259
  this.metrics?.importPayload.columnsBySource.inc({source});
228
260
  }
229
261
 
230
- // 8. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
262
+ // 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
231
263
  if (this.clock.currentSlot - slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
232
264
  this.emitter.emit(routes.events.EventType.executionPayload, {
233
265
  slot,
234
266
  builderIndex: envelope.builderIndex,
235
267
  blockHash: blockHashHex,
236
268
  blockRoot: blockRootHex,
237
- // TODO GLOAS: revisit once we support optimistic import
238
- executionOptimistic: false,
269
+ executionOptimistic: execStatus === ExecutionStatus.Syncing,
239
270
  });
240
271
  }
241
272
 
@@ -261,6 +292,6 @@ export async function processExecutionPayload(
261
292
  signal: AbortSignal,
262
293
  opts: ImportPayloadOpts = {}
263
294
  ): Promise<void> {
264
- await verifyPayloadsDataAvailability([payloadInput], signal);
265
- await importExecutionPayload.call(this, payloadInput, opts);
295
+ const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability([payloadInput], signal);
296
+ await importExecutionPayload.call(this, payloadInput, dataAvailabilityStatuses[0], opts);
266
297
  }
@@ -65,7 +65,7 @@ export async function processBlocks(
65
65
  }
66
66
 
67
67
  try {
68
- const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksSanityChecks(this, blocks, opts);
68
+ const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksSanityChecks(this, blocks, payloadEnvelopes, opts);
69
69
 
70
70
  // No relevant blocks, skip verifyBlocksInEpoch()
71
71
  if (relevantBlocks.length === 0 || parentBlock === null) {
@@ -90,8 +90,14 @@ export async function processBlocks(
90
90
 
91
91
  // Fully verify a block to be imported immediately after. Does not produce any side-effects besides adding intermediate
92
92
  // states in the state cache through regen.
93
- const {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock} =
94
- await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
93
+ const {
94
+ postStates,
95
+ blockDAStatuses,
96
+ payloadDAStatuses,
97
+ proposerBalanceDeltas,
98
+ segmentExecStatus,
99
+ indexedAttestationsByBlock,
100
+ } = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
95
101
 
96
102
  // If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
97
103
  // and we need to further propagate
@@ -103,26 +109,35 @@ export async function processBlocks(
103
109
  }
104
110
 
105
111
  const {executionStatuses} = segmentExecStatus;
106
- const fullyVerifiedBlocks = relevantBlocks.map(
107
- (block, i): FullyVerifiedBlock => ({
112
+ const verifiedBlocksBySlot = new Map<Slot, FullyVerifiedBlock>();
113
+ for (let i = 0; i < relevantBlocks.length; i++) {
114
+ const block = relevantBlocks[i];
115
+ verifiedBlocksBySlot.set(block.getBlock().message.slot, {
108
116
  blockInput: block,
109
117
  postState: postStates[i],
110
118
  parentBlockSlot: parentSlots[i],
111
119
  executionStatus: executionStatuses[i],
112
120
  // start supporting optimistic syncing/processing
113
- dataAvailabilityStatus: dataAvailabilityStatuses[i],
121
+ dataAvailabilityStatus: blockDAStatuses[i],
114
122
  proposerBalanceDelta: proposerBalanceDeltas[i],
115
123
  indexedAttestations: indexedAttestationsByBlock[i],
116
124
  // TODO: Make this param mandatory and capture in gossip
117
125
  seenTimestampSec: opts.seenTimestampSec ?? Math.floor(Date.now() / 1000),
118
- })
119
- );
126
+ });
127
+ }
120
128
 
121
- for (const fullyVerifiedBlock of fullyVerifiedBlocks) {
122
- // TODO: Consider batching importBlock too if it takes significant time
123
- await importBlock.call(this, fullyVerifiedBlock, opts);
129
+ const slotSet = new Set<Slot>(blocks.map((b) => b.getBlock().message.slot));
130
+ if (payloadEnvelopes) {
131
+ for (const slot of payloadEnvelopes.keys()) slotSet.add(slot);
132
+ }
133
+ const slots = Array.from(slotSet).sort((a, b) => a - b);
134
+ for (const slot of slots) {
135
+ const fullyVerifiedBlock = verifiedBlocksBySlot.get(slot);
136
+ if (fullyVerifiedBlock !== undefined) {
137
+ // TODO: Consider batching importBlock too if it takes significant time
138
+ await importBlock.call(this, fullyVerifiedBlock, opts);
139
+ }
124
140
 
125
- const slot = fullyVerifiedBlock.blockInput.getBlock().message.slot;
126
141
  const payloadInput = payloadEnvelopes?.get(slot);
127
142
  if (payloadInput?.hasPayloadEnvelope()) {
128
143
  if (!payloadInput.isComplete()) {
@@ -130,9 +145,11 @@ export async function processBlocks(
130
145
  throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
131
146
  }
132
147
  // we already awaited DA in verifyBlocksInEpoch for this segment
133
- // TODO GLOAS: may need FullyVerifiedPayload here with DatAvailabilityStatus added from here
134
- // the current flow use that data from the forkchoice pending node which is not correct
135
- await importExecutionPayload.call(this, payloadInput, {validSignature: false});
148
+ const payloadDA = payloadDAStatuses.get(slot);
149
+ if (payloadDA === undefined) {
150
+ throw new Error(`Missing payload DA status for slot ${slot}`);
151
+ }
152
+ await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
136
153
  }
137
154
 
138
155
  await nextEventLoop();