@lodestar/beacon-node 1.43.0-dev.a142c56215 → 1.43.0-dev.a45ba75824

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 (165) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -3
  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/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +66 -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 +10 -21
  12. package/lib/chain/blocks/importBlock.js.map +1 -1
  13. package/lib/chain/blocks/importExecutionPayload.d.ts +5 -3
  14. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.js +23 -10
  16. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  17. package/lib/chain/blocks/index.d.ts.map +1 -1
  18. package/lib/chain/blocks/index.js +7 -5
  19. package/lib/chain/blocks/index.js.map +1 -1
  20. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
  21. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  22. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +5 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  25. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  26. package/lib/chain/blocks/verifyBlock.d.ts +2 -1
  27. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  28. package/lib/chain/blocks/verifyBlock.js +26 -7
  29. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  30. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  31. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  32. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  33. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  34. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  35. package/lib/chain/chain.d.ts +2 -1
  36. package/lib/chain/chain.d.ts.map +1 -1
  37. package/lib/chain/chain.js +5 -1
  38. package/lib/chain/chain.js.map +1 -1
  39. package/lib/chain/emitter.d.ts +0 -11
  40. package/lib/chain/emitter.d.ts.map +1 -1
  41. package/lib/chain/emitter.js +0 -4
  42. package/lib/chain/emitter.js.map +1 -1
  43. package/lib/chain/errors/index.d.ts +1 -0
  44. package/lib/chain/errors/index.d.ts.map +1 -1
  45. package/lib/chain/errors/index.js +1 -0
  46. package/lib/chain/errors/index.js.map +1 -1
  47. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  48. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  49. package/lib/chain/errors/proposerPreferences.js +13 -0
  50. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  51. package/lib/chain/interface.d.ts +2 -1
  52. package/lib/chain/interface.d.ts.map +1 -1
  53. package/lib/chain/interface.js.map +1 -1
  54. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  55. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  56. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  57. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  58. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  59. package/lib/chain/prepareNextSlot.js +15 -17
  60. package/lib/chain/prepareNextSlot.js.map +1 -1
  61. package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -3
  62. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  63. package/lib/chain/produceBlock/produceBlockBody.js +38 -21
  64. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  65. package/lib/chain/regen/interface.d.ts +1 -0
  66. package/lib/chain/regen/interface.d.ts.map +1 -1
  67. package/lib/chain/regen/interface.js +1 -0
  68. package/lib/chain/regen/interface.js.map +1 -1
  69. package/lib/chain/seenCache/index.d.ts +1 -0
  70. package/lib/chain/seenCache/index.d.ts.map +1 -1
  71. package/lib/chain/seenCache/index.js +1 -0
  72. package/lib/chain/seenCache/index.js.map +1 -1
  73. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
  74. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  75. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -4
  76. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  77. package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
  78. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  79. package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
  80. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  81. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  82. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  83. package/lib/chain/validation/proposerPreferences.js +69 -0
  84. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  85. package/lib/network/gossip/interface.d.ts +7 -1
  86. package/lib/network/gossip/interface.d.ts.map +1 -1
  87. package/lib/network/gossip/interface.js +1 -0
  88. package/lib/network/gossip/interface.js.map +1 -1
  89. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  90. package/lib/network/gossip/scoringParameters.js +12 -1
  91. package/lib/network/gossip/scoringParameters.js.map +1 -1
  92. package/lib/network/gossip/topic.d.ts +27 -766
  93. package/lib/network/gossip/topic.d.ts.map +1 -1
  94. package/lib/network/gossip/topic.js +6 -0
  95. package/lib/network/gossip/topic.js.map +1 -1
  96. package/lib/network/interface.d.ts +1 -0
  97. package/lib/network/interface.d.ts.map +1 -1
  98. package/lib/network/network.d.ts +1 -0
  99. package/lib/network/network.d.ts.map +1 -1
  100. package/lib/network/network.js +5 -0
  101. package/lib/network/network.js.map +1 -1
  102. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  103. package/lib/network/processor/gossipHandlers.js +22 -15
  104. package/lib/network/processor/gossipHandlers.js.map +1 -1
  105. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  106. package/lib/network/processor/gossipQueues/index.js +5 -0
  107. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  108. package/lib/network/processor/index.d.ts.map +1 -1
  109. package/lib/network/processor/index.js +6 -5
  110. package/lib/network/processor/index.js.map +1 -1
  111. package/lib/sync/range/batch.d.ts +11 -0
  112. package/lib/sync/range/batch.d.ts.map +1 -1
  113. package/lib/sync/range/batch.js +29 -4
  114. package/lib/sync/range/batch.js.map +1 -1
  115. package/lib/sync/range/chain.d.ts.map +1 -1
  116. package/lib/sync/range/chain.js +23 -5
  117. package/lib/sync/range/chain.js.map +1 -1
  118. package/lib/sync/unknownBlock.d.ts +0 -2
  119. package/lib/sync/unknownBlock.d.ts.map +1 -1
  120. package/lib/sync/unknownBlock.js +0 -47
  121. package/lib/sync/unknownBlock.js.map +1 -1
  122. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  123. package/lib/sync/utils/downloadByRange.js +21 -15
  124. package/lib/sync/utils/downloadByRange.js.map +1 -1
  125. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  126. package/lib/sync/utils/downloadByRoot.js +10 -0
  127. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  128. package/package.json +15 -15
  129. package/src/api/impl/beacon/blocks/index.ts +16 -3
  130. package/src/api/impl/beacon/pool/index.ts +83 -1
  131. package/src/api/impl/validator/index.ts +80 -0
  132. package/src/chain/blocks/importBlock.ts +9 -36
  133. package/src/chain/blocks/importExecutionPayload.ts +31 -10
  134. package/src/chain/blocks/index.ts +14 -6
  135. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +6 -1
  136. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  137. package/src/chain/blocks/verifyBlock.ts +39 -9
  138. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  139. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  140. package/src/chain/chain.ts +5 -0
  141. package/src/chain/emitter.ts +0 -11
  142. package/src/chain/errors/index.ts +1 -0
  143. package/src/chain/errors/proposerPreferences.ts +39 -0
  144. package/src/chain/interface.ts +2 -0
  145. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  146. package/src/chain/prepareNextSlot.ts +20 -28
  147. package/src/chain/produceBlock/produceBlockBody.ts +48 -26
  148. package/src/chain/regen/interface.ts +1 -0
  149. package/src/chain/seenCache/index.ts +1 -0
  150. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +25 -5
  151. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  152. package/src/chain/validation/proposerPreferences.ts +91 -0
  153. package/src/network/gossip/interface.ts +6 -0
  154. package/src/network/gossip/scoringParameters.ts +14 -1
  155. package/src/network/gossip/topic.ts +6 -0
  156. package/src/network/interface.ts +1 -0
  157. package/src/network/network.ts +11 -0
  158. package/src/network/processor/gossipHandlers.ts +31 -16
  159. package/src/network/processor/gossipQueues/index.ts +5 -0
  160. package/src/network/processor/index.ts +6 -5
  161. package/src/sync/range/batch.ts +54 -5
  162. package/src/sync/range/chain.ts +25 -5
  163. package/src/sync/unknownBlock.ts +0 -50
  164. package/src/sync/utils/downloadByRange.ts +21 -15
  165. package/src/sync/utils/downloadByRoot.ts +12 -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.43.0-dev.a142c56215",
14
+ "version": "1.43.0-dev.a45ba75824",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -135,18 +135,18 @@
135
135
  "@libp2p/peer-id": "^6.0.4",
136
136
  "@libp2p/prometheus-metrics": "^5.0.14",
137
137
  "@libp2p/tcp": "^11.0.13",
138
- "@lodestar/api": "^1.43.0-dev.a142c56215",
139
- "@lodestar/config": "^1.43.0-dev.a142c56215",
140
- "@lodestar/db": "^1.43.0-dev.a142c56215",
141
- "@lodestar/fork-choice": "^1.43.0-dev.a142c56215",
142
- "@lodestar/light-client": "^1.43.0-dev.a142c56215",
143
- "@lodestar/logger": "^1.43.0-dev.a142c56215",
144
- "@lodestar/params": "^1.43.0-dev.a142c56215",
145
- "@lodestar/reqresp": "^1.43.0-dev.a142c56215",
146
- "@lodestar/state-transition": "^1.43.0-dev.a142c56215",
147
- "@lodestar/types": "^1.43.0-dev.a142c56215",
148
- "@lodestar/utils": "^1.43.0-dev.a142c56215",
149
- "@lodestar/validator": "^1.43.0-dev.a142c56215",
138
+ "@lodestar/api": "^1.43.0-dev.a45ba75824",
139
+ "@lodestar/config": "^1.43.0-dev.a45ba75824",
140
+ "@lodestar/db": "^1.43.0-dev.a45ba75824",
141
+ "@lodestar/fork-choice": "^1.43.0-dev.a45ba75824",
142
+ "@lodestar/light-client": "^1.43.0-dev.a45ba75824",
143
+ "@lodestar/logger": "^1.43.0-dev.a45ba75824",
144
+ "@lodestar/params": "^1.43.0-dev.a45ba75824",
145
+ "@lodestar/reqresp": "^1.43.0-dev.a45ba75824",
146
+ "@lodestar/state-transition": "^1.43.0-dev.a45ba75824",
147
+ "@lodestar/types": "^1.43.0-dev.a45ba75824",
148
+ "@lodestar/utils": "^1.43.0-dev.a45ba75824",
149
+ "@lodestar/validator": "^1.43.0-dev.a45ba75824",
150
150
  "@multiformats/multiaddr": "^13.0.1",
151
151
  "datastore-core": "^11.0.2",
152
152
  "datastore-fs": "^11.0.2",
@@ -169,7 +169,7 @@
169
169
  "@libp2p/interface-internal": "^3.0.13",
170
170
  "@libp2p/logger": "^6.2.2",
171
171
  "@libp2p/utils": "^7.0.13",
172
- "@lodestar/spec-test-util": "^1.43.0-dev.a142c56215",
172
+ "@lodestar/spec-test-util": "^1.43.0-dev.a45ba75824",
173
173
  "@types/js-yaml": "^4.0.5",
174
174
  "@types/qs": "^6.9.7",
175
175
  "@types/tmp": "^0.2.3",
@@ -187,5 +187,5 @@
187
187
  "beacon",
188
188
  "blockchain"
189
189
  ],
190
- "gitHead": "2dac6aa64eb2ff2b549500b8f6a69706035ce100"
190
+ "gitHead": "d4569091a105a7870d042d3e3b4337916fb98823"
191
191
  }
@@ -5,6 +5,7 @@ import {
5
5
  BUILDER_INDEX_SELF_BUILD,
6
6
  ForkPostBellatrix,
7
7
  ForkPostFulu,
8
+ ForkPostGloas,
8
9
  ForkPreGloas,
9
10
  NUMBER_OF_COLUMNS,
10
11
  SLOTS_PER_HISTORICAL_ROOT,
@@ -109,6 +110,18 @@ export function getBeaconBlockApi({
109
110
  seenTimestampSec,
110
111
  blockRootHex: blockRoot,
111
112
  });
113
+
114
+ if (isForkPostGloas(fork)) {
115
+ chain.seenPayloadEnvelopeInputCache.add({
116
+ blockRootHex: blockRoot,
117
+ block: signedBlock as SignedBeaconBlock<ForkPostGloas>,
118
+ forkName: fork,
119
+ sampledColumns: chain.custodyConfig.sampledColumns,
120
+ custodyColumns: chain.custodyConfig.custodyColumns,
121
+ timeCreatedSec: seenTimestampSec,
122
+ });
123
+ }
124
+
112
125
  let blobSidecars: deneb.BlobSidecars, dataColumnSidecars: fulu.DataColumnSidecar[];
113
126
 
114
127
  if (isDenebBlockContents(signedBlockContents)) {
@@ -776,9 +789,9 @@ export function getBeaconBlockApi({
776
789
  // Track metrics for data column publishing
777
790
  if (dataColumnSidecars.length > 0) {
778
791
  let columnsPublishedWithZeroPeers = 0;
779
- // Skip first entry (envelope), track data columns
780
- for (let i = 1; i < sentPeersArr.length; i++) {
781
- const sentPeers = sentPeersArr[i] as number;
792
+ // Skip first entry (envelope); the final entry is processExecutionPayload(), which returns void.
793
+ for (let i = 0; i < dataColumnSidecars.length; i++) {
794
+ const sentPeers = sentPeersArr[i + 1] as number;
782
795
  metrics?.dataColumns.sentPeersPerSubnet.observe(sentPeers);
783
796
  if (sentPeers === 0) {
784
797
  columnsPublishedWithZeroPeers++;
@@ -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
  *
@@ -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
  *
@@ -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";
@@ -128,7 +111,6 @@ export async function importBlock(
128
111
  blockDelaySec,
129
112
  currentSlot,
130
113
  fork >= ForkSeq.gloas ? ExecutionStatus.PayloadSeparated : executionStatus,
131
- // TODO GLOAS: this is not useful post-gloas, may need to remove it?
132
114
  dataAvailabilityStatus
133
115
  );
134
116
 
@@ -136,23 +118,14 @@ export async function importBlock(
136
118
  // Some block event handlers require state being in state cache so need to do this before emitting EventType.block
137
119
  this.regen.processState(blockRootHex, postState);
138
120
 
139
- // For range sync, PayloadEnvelope is created before reaching this
140
- // we also don't need to trigger getBlobs() in that case
121
+ // For range sync we skip triggerGetBlobs because column fetching is handled in the range path.
141
122
  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
- });
123
+ const payloadInput = this.seenPayloadEnvelopeInputCache.get(blockRootHex);
124
+ // PayloadEnvelopeInput is supposed to have right after we have block
125
+ // there are 4 sources of them: gossip, by root, by range and api
126
+ if (!payloadInput) {
127
+ throw Error(`PayloadEnvelopeInput not seeded for block ${blockRootHex} before importBlock`);
128
+ }
156
129
 
157
130
  // Immediately attempt fetch of data columns from execution engine as the bid contains kzg commitments
158
131
  // which is all the information we need so there is no reason to delay until execution payload arrives
@@ -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";
@@ -85,12 +86,14 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
85
86
  * 4. Verify envelope (fields against state, signature, and EL in parallel where possible)
86
87
  * 5. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
87
88
  * 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
89
+ * 7. Queue notifyForkchoiceUpdate to engine api
90
+ * 8. Record metrics for payload envelope and column sources
91
+ * 9. Emit `execution_payload` event
90
92
  */
91
93
  export async function importExecutionPayload(
92
94
  this: BeaconChain,
93
95
  payloadInput: PayloadEnvelopeInput,
96
+ dataAvailabilityStatus: DataAvailabilityStatus,
94
97
  opts: ImportPayloadOpts = {}
95
98
  ): Promise<void> {
96
99
  const signedEnvelope = payloadInput.getPayloadEnvelope();
@@ -219,15 +222,33 @@ export async function importExecutionPayload(
219
222
 
220
223
  // 6. Update fork choice, transitions the block's PENDING variant to FULL
221
224
  const execStatus = toForkChoiceExecutionStatus(execResult.status);
222
- 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
+ }
223
244
 
224
- // 7. Record metrics for payload envelope and column sources
245
+ // 8. Record metrics for payload envelope and column sources
225
246
  this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
226
247
  for (const {source} of payloadInput.getSampledColumnsWithSource()) {
227
248
  this.metrics?.importPayload.columnsBySource.inc({source});
228
249
  }
229
250
 
230
- // 8. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
251
+ // 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
231
252
  if (this.clock.currentSlot - slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
232
253
  this.emitter.emit(routes.events.EventType.executionPayload, {
233
254
  slot,
@@ -261,6 +282,6 @@ export async function processExecutionPayload(
261
282
  signal: AbortSignal,
262
283
  opts: ImportPayloadOpts = {}
263
284
  ): Promise<void> {
264
- await verifyPayloadsDataAvailability([payloadInput], signal);
265
- await importExecutionPayload.call(this, payloadInput, opts);
285
+ const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability([payloadInput], signal);
286
+ await importExecutionPayload.call(this, payloadInput, dataAvailabilityStatuses[0], opts);
266
287
  }
@@ -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
@@ -110,7 +116,7 @@ export async function processBlocks(
110
116
  parentBlockSlot: parentSlots[i],
111
117
  executionStatus: executionStatuses[i],
112
118
  // start supporting optimistic syncing/processing
113
- dataAvailabilityStatus: dataAvailabilityStatuses[i],
119
+ dataAvailabilityStatus: blockDAStatuses[i],
114
120
  proposerBalanceDelta: proposerBalanceDeltas[i],
115
121
  indexedAttestations: indexedAttestationsByBlock[i],
116
122
  // TODO: Make this param mandatory and capture in gossip
@@ -130,9 +136,11 @@ export async function processBlocks(
130
136
  throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
131
137
  }
132
138
  // 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});
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});
136
144
  }
137
145
 
138
146
  await nextEventLoop();
@@ -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
 
@@ -152,6 +156,7 @@ export class PayloadEnvelopeInput {
152
156
  throw new Error("Payload envelope beacon_block_root mismatch");
153
157
  }
154
158
 
159
+ // TODO GLOAS: track source by metrics, maybe inside the seen cache
155
160
  const source: SourceMeta = {
156
161
  source: props.source,
157
162
  seenTimestampSec: props.seenTimestampSec,
@@ -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 & {
@@ -41,7 +41,8 @@ export async function verifyBlocksInEpoch(
41
41
  postStates: IBeaconStateView[];
42
42
  proposerBalanceDeltas: number[];
43
43
  segmentExecStatus: SegmentExecStatus;
44
- dataAvailabilityStatuses: DataAvailabilityStatus[];
44
+ blockDAStatuses: DataAvailabilityStatus[];
45
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
45
46
  indexedAttestationsByBlock: IndexedAttestation[][];
46
47
  }> {
47
48
  const blocks = blockInputs.map((blockInput) => blockInput.getBlock());
@@ -116,8 +117,12 @@ export async function verifyBlocksInEpoch(
116
117
 
117
118
  // Pick the data-availability source by fork:
118
119
  // - Pre-Gloas: blob/Fulu-column data lives in IBlockInput → verifyBlocksDataAvailability.
119
- // - Post-Gloas: verifyPayloadsDataAvailability
120
- const daAvailabilityPromise =
120
+ // - Post-Gloas: verifyPayloadsDataAvailability (payload-level DA, keyed by slot).
121
+ const daAvailabilityPromise: Promise<{
122
+ blockDAStatuses: DataAvailabilityStatus[];
123
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
124
+ availableTime: number;
125
+ }> =
121
126
  fork >= ForkSeq.gloas
122
127
  ? (async () => {
123
128
  const payloadInputsForDa: PayloadEnvelopeInput[] = [];
@@ -125,19 +130,37 @@ export async function verifyBlocksInEpoch(
125
130
  const pi = payloadEnvelopes?.get(input.slot);
126
131
  if (pi !== undefined) payloadInputsForDa.push(pi);
127
132
  }
128
- await verifyPayloadsDataAvailability(payloadInputsForDa, abortController.signal);
133
+ const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
134
+ payloadInputsForDa,
135
+ abortController.signal
136
+ );
137
+ const payloadDAStatuses = new Map<Slot, DataAvailabilityStatus>();
138
+ for (let i = 0; i < payloadInputsForDa.length; i++) {
139
+ payloadDAStatuses.set(payloadInputsForDa[i].slot, dataAvailabilityStatuses[i]);
140
+ }
129
141
  return {
130
142
  // post-gloas, DataAvailabilityStatus is NotRequired for forkChoice.onBlock() ProtoBlock
131
- dataAvailabilityStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
132
- availableTime: Date.now(),
143
+ blockDAStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
144
+ payloadDAStatuses,
145
+ availableTime,
133
146
  };
134
147
  })()
135
- : verifyBlocksDataAvailability(blockInputs, abortController.signal);
148
+ : (async () => {
149
+ const {dataAvailabilityStatuses, availableTime} = await verifyBlocksDataAvailability(
150
+ blockInputs,
151
+ abortController.signal
152
+ );
153
+ return {
154
+ blockDAStatuses: dataAvailabilityStatuses,
155
+ payloadDAStatuses: new Map<Slot, DataAvailabilityStatus>(),
156
+ availableTime,
157
+ };
158
+ })();
136
159
 
137
160
  // batch all I/O operations to reduce overhead
138
161
  const [
139
162
  segmentExecStatus,
140
- {dataAvailabilityStatuses, availableTime},
163
+ {blockDAStatuses, payloadDAStatuses, availableTime},
141
164
  {postStates, proposerBalanceDeltas, verifyStateTime},
142
165
  {verifySignaturesTime},
143
166
  ] = await Promise.all([
@@ -258,7 +281,14 @@ export async function verifyBlocksInEpoch(
258
281
  );
259
282
  }
260
283
 
261
- return {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock};
284
+ return {
285
+ postStates,
286
+ blockDAStatuses,
287
+ payloadDAStatuses,
288
+ proposerBalanceDeltas,
289
+ segmentExecStatus,
290
+ indexedAttestationsByBlock,
291
+ };
262
292
  } finally {
263
293
  abortController.abort();
264
294
  }
@@ -32,8 +32,8 @@ export function verifyExecutionPayloadEnvelope(
32
32
  const payload = envelope.payload;
33
33
 
34
34
  // Verify consistency with the beacon block.
35
- // Compute header root on a copy of latestBlockHeader to avoid mutating state.
36
- const headerValue = {...state.latestBlockHeader};
35
+ // Compute header root on a clone of latestBlockHeader to avoid mutating state.
36
+ const headerValue = ssz.phase0.BeaconBlockHeader.clone(state.latestBlockHeader);
37
37
  if (byteArrayEquals(headerValue.stateRoot, ssz.Root.defaultValue())) {
38
38
  headerValue.stateRoot = state.hashTreeRoot();
39
39
  }