@lodestar/beacon-node 1.43.0-dev.6641fd750e → 1.43.0-dev.9fa9f08ef6

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 (148) hide show
  1. package/lib/api/impl/lodestar/attesterSlashing.d.ts +8 -0
  2. package/lib/api/impl/lodestar/attesterSlashing.d.ts.map +1 -0
  3. package/lib/api/impl/lodestar/attesterSlashing.js +29 -0
  4. package/lib/api/impl/lodestar/attesterSlashing.js.map +1 -0
  5. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  6. package/lib/api/impl/lodestar/index.js +36 -1
  7. package/lib/api/impl/lodestar/index.js.map +1 -1
  8. package/lib/api/impl/validator/index.d.ts.map +1 -1
  9. package/lib/api/impl/validator/index.js +4 -3
  10. package/lib/api/impl/validator/index.js.map +1 -1
  11. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  12. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  13. package/lib/chain/GetBlobsTracker.js +1 -2
  14. package/lib/chain/GetBlobsTracker.js.map +1 -1
  15. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
  17. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  18. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  19. package/lib/chain/blocks/importBlock.js +27 -35
  20. package/lib/chain/blocks/importBlock.js.map +1 -1
  21. package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  23. package/lib/chain/blocks/importExecutionPayload.js +10 -8
  24. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  25. package/lib/chain/blocks/index.js +1 -1
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  32. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  33. package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
  34. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  35. package/lib/chain/blocks/types.d.ts +1 -1
  36. package/lib/chain/blocks/types.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  38. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  39. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
  40. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  41. package/lib/chain/chain.d.ts.map +1 -1
  42. package/lib/chain/chain.js +17 -37
  43. package/lib/chain/chain.js.map +1 -1
  44. package/lib/chain/emitter.d.ts +13 -1
  45. package/lib/chain/emitter.d.ts.map +1 -1
  46. package/lib/chain/emitter.js +5 -0
  47. package/lib/chain/emitter.js.map +1 -1
  48. package/lib/chain/errors/attestationError.d.ts +8 -1
  49. package/lib/chain/errors/attestationError.d.ts.map +1 -1
  50. package/lib/chain/errors/attestationError.js +4 -0
  51. package/lib/chain/errors/attestationError.js.map +1 -1
  52. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  53. package/lib/chain/forkChoice/index.js +12 -4
  54. package/lib/chain/forkChoice/index.js.map +1 -1
  55. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  56. package/lib/chain/prepareNextSlot.js +22 -16
  57. package/lib/chain/prepareNextSlot.js.map +1 -1
  58. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
  59. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  60. package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
  61. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  62. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -8
  63. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  64. package/lib/chain/produceBlock/produceBlockBody.js +24 -19
  65. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  66. package/lib/chain/regen/errors.d.ts +1 -11
  67. package/lib/chain/regen/errors.d.ts.map +1 -1
  68. package/lib/chain/regen/errors.js +0 -2
  69. package/lib/chain/regen/errors.js.map +1 -1
  70. package/lib/chain/regen/interface.d.ts +6 -12
  71. package/lib/chain/regen/interface.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.d.ts +6 -11
  73. package/lib/chain/regen/queued.d.ts.map +1 -1
  74. package/lib/chain/regen/queued.js +8 -40
  75. package/lib/chain/regen/queued.js.map +1 -1
  76. package/lib/chain/regen/regen.d.ts +0 -5
  77. package/lib/chain/regen/regen.d.ts.map +1 -1
  78. package/lib/chain/regen/regen.js +7 -34
  79. package/lib/chain/regen/regen.js.map +1 -1
  80. package/lib/chain/stateCache/datastore/db.d.ts +5 -4
  81. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  82. package/lib/chain/stateCache/datastore/db.js +10 -32
  83. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  84. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  85. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  86. package/lib/chain/stateCache/datastore/file.js +5 -5
  87. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  88. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  89. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  90. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
  91. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  92. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
  93. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  94. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
  95. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  96. package/lib/chain/stateCache/persistentCheckpointsCache.js +116 -215
  97. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  98. package/lib/chain/stateCache/types.d.ts +8 -15
  99. package/lib/chain/stateCache/types.d.ts.map +1 -1
  100. package/lib/chain/stateCache/types.js.map +1 -1
  101. package/lib/chain/validation/aggregateAndProof.js +12 -0
  102. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  103. package/lib/chain/validation/attestation.d.ts.map +1 -1
  104. package/lib/chain/validation/attestation.js +12 -0
  105. package/lib/chain/validation/attestation.js.map +1 -1
  106. package/lib/chain/validation/executionPayloadEnvelope.js +2 -2
  107. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  108. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  109. package/lib/network/processor/gossipHandlers.js +19 -3
  110. package/lib/network/processor/gossipHandlers.js.map +1 -1
  111. package/lib/node/nodejs.d.ts.map +1 -1
  112. package/lib/node/nodejs.js +4 -2
  113. package/lib/node/nodejs.js.map +1 -1
  114. package/package.json +15 -15
  115. package/src/api/impl/lodestar/attesterSlashing.ts +43 -0
  116. package/src/api/impl/lodestar/index.ts +48 -2
  117. package/src/api/impl/validator/index.ts +6 -5
  118. package/src/chain/GetBlobsTracker.ts +1 -2
  119. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +2 -4
  120. package/src/chain/blocks/importBlock.ts +26 -39
  121. package/src/chain/blocks/importExecutionPayload.ts +11 -7
  122. package/src/chain/blocks/index.ts +1 -1
  123. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
  124. package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
  125. package/src/chain/blocks/types.ts +1 -1
  126. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
  127. package/src/chain/chain.ts +16 -47
  128. package/src/chain/emitter.ts +12 -0
  129. package/src/chain/errors/attestationError.ts +6 -1
  130. package/src/chain/forkChoice/index.ts +12 -4
  131. package/src/chain/prepareNextSlot.ts +25 -16
  132. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
  133. package/src/chain/produceBlock/produceBlockBody.ts +34 -20
  134. package/src/chain/regen/errors.ts +1 -6
  135. package/src/chain/regen/interface.ts +6 -12
  136. package/src/chain/regen/queued.ts +12 -48
  137. package/src/chain/regen/regen.ts +8 -36
  138. package/src/chain/stateCache/datastore/db.ts +10 -33
  139. package/src/chain/stateCache/datastore/file.ts +5 -6
  140. package/src/chain/stateCache/datastore/types.ts +2 -3
  141. package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
  142. package/src/chain/stateCache/persistentCheckpointsCache.ts +135 -246
  143. package/src/chain/stateCache/types.ts +8 -14
  144. package/src/chain/validation/aggregateAndProof.ts +13 -0
  145. package/src/chain/validation/attestation.ts +13 -0
  146. package/src/chain/validation/executionPayloadEnvelope.ts +2 -2
  147. package/src/network/processor/gossipHandlers.ts +23 -7
  148. package/src/node/nodejs.ts +4 -2
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.6641fd750e",
14
+ "version": "1.43.0-dev.9fa9f08ef6",
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.6641fd750e",
139
- "@lodestar/config": "^1.43.0-dev.6641fd750e",
140
- "@lodestar/db": "^1.43.0-dev.6641fd750e",
141
- "@lodestar/fork-choice": "^1.43.0-dev.6641fd750e",
142
- "@lodestar/light-client": "^1.43.0-dev.6641fd750e",
143
- "@lodestar/logger": "^1.43.0-dev.6641fd750e",
144
- "@lodestar/params": "^1.43.0-dev.6641fd750e",
145
- "@lodestar/reqresp": "^1.43.0-dev.6641fd750e",
146
- "@lodestar/state-transition": "^1.43.0-dev.6641fd750e",
147
- "@lodestar/types": "^1.43.0-dev.6641fd750e",
148
- "@lodestar/utils": "^1.43.0-dev.6641fd750e",
149
- "@lodestar/validator": "^1.43.0-dev.6641fd750e",
138
+ "@lodestar/api": "^1.43.0-dev.9fa9f08ef6",
139
+ "@lodestar/config": "^1.43.0-dev.9fa9f08ef6",
140
+ "@lodestar/db": "^1.43.0-dev.9fa9f08ef6",
141
+ "@lodestar/fork-choice": "^1.43.0-dev.9fa9f08ef6",
142
+ "@lodestar/light-client": "^1.43.0-dev.9fa9f08ef6",
143
+ "@lodestar/logger": "^1.43.0-dev.9fa9f08ef6",
144
+ "@lodestar/params": "^1.43.0-dev.9fa9f08ef6",
145
+ "@lodestar/reqresp": "^1.43.0-dev.9fa9f08ef6",
146
+ "@lodestar/state-transition": "^1.43.0-dev.9fa9f08ef6",
147
+ "@lodestar/types": "^1.43.0-dev.9fa9f08ef6",
148
+ "@lodestar/utils": "^1.43.0-dev.9fa9f08ef6",
149
+ "@lodestar/validator": "^1.43.0-dev.9fa9f08ef6",
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.6641fd750e",
172
+ "@lodestar/spec-test-util": "^1.43.0-dev.9fa9f08ef6",
173
173
  "@types/js-yaml": "^4.0.5",
174
174
  "@types/qs": "^6.9.7",
175
175
  "@types/tmp": "^0.2.3",
@@ -186,5 +186,5 @@
186
186
  "beacon",
187
187
  "blockchain"
188
188
  ],
189
- "gitHead": "9791abe98717579326dbf4c9b55e8dd637a6f778"
189
+ "gitHead": "d7a61eccedb2f5fa84991eaa2aa81fbcdf21c85e"
190
190
  }
@@ -0,0 +1,43 @@
1
+ import {ForkSeq} from "@lodestar/params";
2
+ import {
3
+ getIntersectingIndices,
4
+ isSlashableAttestationData,
5
+ toIndexedAttestationBigint,
6
+ } from "@lodestar/state-transition";
7
+ import {AttesterSlashing, IndexedAttestation} from "@lodestar/types";
8
+
9
+ /**
10
+ * Find all slashable pairs within a list of IndexedAttestations and
11
+ * construct AttesterSlashing objects for each.
12
+ */
13
+ export function getAttesterSlashingsFromIndexedAttestations(
14
+ fork: ForkSeq,
15
+ indexedAttestations: IndexedAttestation[]
16
+ ): AttesterSlashing[] {
17
+ const slashings: AttesterSlashing[] = [];
18
+
19
+ for (let i = 0; i < indexedAttestations.length; i++) {
20
+ for (let j = i + 1; j < indexedAttestations.length; j++) {
21
+ // Order by source epoch so the surrounding attestation is always first,
22
+ // matching what isSlashableAttestationData expects (one-directional check).
23
+ const [first, second] =
24
+ indexedAttestations[j].data.source.epoch < indexedAttestations[i].data.source.epoch
25
+ ? [indexedAttestations[j], indexedAttestations[i]]
26
+ : [indexedAttestations[i], indexedAttestations[j]];
27
+
28
+ if (getIntersectingIndices(first.attestingIndices, second.attestingIndices).length === 0) continue;
29
+
30
+ const firstBigint = toIndexedAttestationBigint(first, fork);
31
+ const secondBigint = toIndexedAttestationBigint(second, fork);
32
+
33
+ if (!isSlashableAttestationData(firstBigint.data, secondBigint.data)) continue;
34
+
35
+ slashings.push({
36
+ attestation1: firstBigint,
37
+ attestation2: secondBigint,
38
+ });
39
+ }
40
+ }
41
+
42
+ return slashings;
43
+ }
@@ -3,8 +3,13 @@ import {ApplicationMethods} from "@lodestar/api/server";
3
3
  import {ChainForkConfig} from "@lodestar/config";
4
4
  import {Repository} from "@lodestar/db";
5
5
  import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
6
- import {isStatePostCapella} from "@lodestar/state-transition";
7
- import {ssz} from "@lodestar/types";
6
+ import {
7
+ computeEpochAtSlot,
8
+ computeStartSlotAtEpoch,
9
+ getIndexedAttestation,
10
+ isStatePostCapella,
11
+ } from "@lodestar/state-transition";
12
+ import {Attestation, Epoch, IndexedAttestation, ssz} from "@lodestar/types";
8
13
  import {Checkpoint} from "@lodestar/types/phase0";
9
14
  import {fromHex, toHex, toRootHex} from "@lodestar/utils";
10
15
  import {BeaconChain} from "../../../chain/index.js";
@@ -16,6 +21,7 @@ import {ProfileThread, profileThread, writeHeapSnapshot} from "../../../util/pro
16
21
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
17
22
  import {ApiError} from "../errors.js";
18
23
  import {ApiModules} from "../types.js";
24
+ import {getAttesterSlashingsFromIndexedAttestations} from "./attesterSlashing.js";
19
25
 
20
26
  export function getLodestarApi({
21
27
  chain,
@@ -273,6 +279,46 @@ export function getLodestarApi({
273
279
  },
274
280
  };
275
281
  },
282
+
283
+ async getAttesterSlashingsFromBlocks({signedBlocks}) {
284
+ const attestations = new Map<Epoch, Attestation[]>();
285
+
286
+ for (const block of signedBlocks) {
287
+ const attestationsOfABlock = block.message.body.attestations;
288
+ for (const attestation of attestationsOfABlock) {
289
+ const epoch = computeEpochAtSlot(attestation.data.slot);
290
+ let attestationsPerEpoch = attestations.get(epoch);
291
+ if (!attestationsPerEpoch) {
292
+ attestationsPerEpoch = [];
293
+ attestations.set(epoch, attestationsPerEpoch);
294
+ }
295
+ attestationsPerEpoch.push(attestation);
296
+ }
297
+ }
298
+
299
+ const indexedAttestations: IndexedAttestation[] = [];
300
+ // Assume all blocks are from the same fork
301
+ const forkSeq = config.getForkSeq(signedBlocks[0].message.slot);
302
+
303
+ for (const [epoch, attestationsPerEpoch] of attestations) {
304
+ const slot = computeStartSlotAtEpoch(epoch);
305
+ const {state} = await getStateResponseWithRegen(chain, slot);
306
+ const stateView = state instanceof Uint8Array ? chain.getHeadState().loadOtherState(state) : state;
307
+ const shuffling = stateView.getShufflingAtEpoch(epoch);
308
+ for (const attestation of attestationsPerEpoch) {
309
+ indexedAttestations.push(getIndexedAttestation(shuffling, forkSeq, attestation));
310
+ }
311
+ }
312
+
313
+ const result = getAttesterSlashingsFromIndexedAttestations(forkSeq, indexedAttestations);
314
+
315
+ return {
316
+ data: result,
317
+ meta: {
318
+ version: config.getForkName(signedBlocks[0].message.slot),
319
+ },
320
+ };
321
+ },
276
322
  };
277
323
  }
278
324
 
@@ -70,7 +70,7 @@ import {ChainEvent, CommonBlockBody} from "../../../chain/index.js";
70
70
  import {PREPARE_NEXT_SLOT_BPS} from "../../../chain/prepareNextSlot.js";
71
71
  import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/produceBlock/index.js";
72
72
  import {RegenCaller} from "../../../chain/regen/index.js";
73
- import {CheckpointHexPayload} from "../../../chain/stateCache/types.js";
73
+ import {CheckpointHex} from "../../../chain/stateCache/types.js";
74
74
  import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
75
75
  import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
76
76
  import {ZERO_HASH} from "../../../constants/index.js";
@@ -301,7 +301,7 @@ export function getValidatorApi(
301
301
  * |
302
302
  * prepareNextSlot (4s before next slot)
303
303
  */
304
- async function waitForCheckpointState(cpHex: CheckpointHexPayload): Promise<IBeaconStateView | null> {
304
+ async function waitForCheckpointState(cpHex: CheckpointHex): Promise<IBeaconStateView | null> {
305
305
  const cpState = chain.regen.getCheckpointStateSync(cpHex);
306
306
  if (cpState) {
307
307
  return cpState;
@@ -1113,7 +1113,6 @@ export function getValidatorApi(
1113
1113
  const cpState = await waitForCheckpointState({
1114
1114
  rootHex: head.blockRoot,
1115
1115
  epoch,
1116
- payloadPresent: head.payloadStatus === PayloadStatus.FULL,
1117
1116
  });
1118
1117
  if (cpState) {
1119
1118
  state = cpState;
@@ -1642,7 +1641,7 @@ export function getValidatorApi(
1642
1641
  throw Error("Cached block production result is not full block");
1643
1642
  }
1644
1643
 
1645
- const {executionPayload, executionRequests, payloadEnvelopeStateRoot} = produceResult as ProduceFullGloas;
1644
+ const {executionPayload, executionRequests} = produceResult as ProduceFullGloas;
1646
1645
 
1647
1646
  const envelope: gloas.ExecutionPayloadEnvelope = {
1648
1647
  payload: executionPayload,
@@ -1650,7 +1649,9 @@ export function getValidatorApi(
1650
1649
  builderIndex: BUILDER_INDEX_SELF_BUILD,
1651
1650
  beaconBlockRoot,
1652
1651
  slot,
1653
- stateRoot: payloadEnvelopeStateRoot,
1652
+ // TODO GLOAS: stateRoot is no longer computed during block production.
1653
+ // This field will be removed when we implement defer payload processing
1654
+ stateRoot: ZERO_HASH,
1654
1655
  };
1655
1656
 
1656
1657
  logger.info("Produced execution payload envelope", {
@@ -44,7 +44,7 @@ export class GetBlobsTracker {
44
44
  this.config = init.config;
45
45
  }
46
46
 
47
- triggerGetBlobs(input: IBlockInput | PayloadEnvelopeInput, onComplete?: () => void): void {
47
+ triggerGetBlobs(input: IBlockInput | PayloadEnvelopeInput): void {
48
48
  if (this.activeReconstructions.has(input.blockRootHex)) {
49
49
  return;
50
50
  }
@@ -101,7 +101,6 @@ export class GetBlobsTracker {
101
101
  .then((result) => {
102
102
  this.logger.debug("getBlobsV2 result for block", {...logCtx, result});
103
103
  this.metrics?.dataColumns.dataColumnEngineResult.inc({result});
104
- onComplete?.();
105
104
  })
106
105
  .catch((error) => {
107
106
  this.logger.debug("Error during getBlobsV2 for block", logCtx, error as Error);
@@ -9,7 +9,6 @@ import {AllocSource, BufferPool} from "../../../util/bufferPool.js";
9
9
  import {getStateSlotFromBytes} from "../../../util/multifork.js";
10
10
  import {IStateRegenerator} from "../../regen/interface.js";
11
11
  import {serializeState} from "../../serializeState.js";
12
- import {fcCheckpointToHexPayload} from "../../stateCache/persistentCheckpointsCache.js";
13
12
  import {StateArchiveStrategy, StatesArchiveOpts} from "../interface.js";
14
13
 
15
14
  /**
@@ -108,9 +107,8 @@ export class FrequencyStateArchiveStrategy implements StateArchiveStrategy {
108
107
  async archiveState(finalized: CheckpointWithPayloadStatus, metrics?: Metrics | null): Promise<void> {
109
108
  // starting from Mar 2024, the finalized state could be from disk or in memory
110
109
  let timer = metrics?.processFinalizedCheckpoint.frequencyStateArchive.startTimer();
111
- // Convert fork-choice checkpoint to beacon-node checkpoint with payloadPresent
112
- const finalizedHexPayload = fcCheckpointToHexPayload(finalized);
113
- const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalizedHexPayload);
110
+ const finalizedHex = {epoch: finalized.epoch, rootHex: finalized.rootHex};
111
+ const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalizedHex);
114
112
  timer?.({step: FrequencyStateArchiveStep.GetFinalizedState});
115
113
 
116
114
  const {rootHex} = finalized;
@@ -8,7 +8,6 @@ import {
8
8
  ForkChoiceErrorCode,
9
9
  NotReorgedReason,
10
10
  getSafeExecutionBlockHash,
11
- isGloasBlock,
12
11
  } from "@lodestar/fork-choice";
13
12
  import {
14
13
  ForkPostAltair,
@@ -48,7 +47,7 @@ import type {BeaconChain} from "../chain.js";
48
47
  import {ChainEvent, ReorgEventData} from "../emitter.js";
49
48
  import {ForkchoiceCaller} from "../forkChoice/index.js";
50
49
  import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC} from "../reprocess.js";
51
- import {toCheckpointHexPayload} from "../stateCache/persistentCheckpointsCache.js";
50
+ import {toCheckpointHex} from "../stateCache/persistentCheckpointsCache.js";
52
51
  import {isBlockInputBlobs, isBlockInputColumns} from "./blockInput/blockInput.js";
53
52
  import {AttestationImportOpt, FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
54
53
  import {getCheckpointFromState} from "./utils/checkpoint.js";
@@ -87,7 +86,7 @@ export async function importBlock(
87
86
  fullyVerifiedBlock: FullyVerifiedBlock,
88
87
  opts: ImportBlockOpts
89
88
  ): Promise<void> {
90
- const {blockInput, postBlockState, parentBlockSlot, executionStatus, dataAvailabilityStatus, indexedAttestations} =
89
+ const {blockInput, postState, parentBlockSlot, executionStatus, dataAvailabilityStatus, indexedAttestations} =
91
90
  fullyVerifiedBlock;
92
91
  const block = blockInput.getBlock();
93
92
  const source = blockInput.getBlockSource();
@@ -99,7 +98,7 @@ export async function importBlock(
99
98
  const blockEpoch = computeEpochAtSlot(blockSlot);
100
99
  const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
101
100
  const blockDelaySec =
102
- fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postBlockState.genesisTime);
101
+ fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
103
102
  const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
104
103
  const fork = this.config.getForkSeq(blockSlot);
105
104
 
@@ -122,10 +121,10 @@ export async function importBlock(
122
121
  // 2. Import block to fork choice
123
122
 
124
123
  // Should compute checkpoint balances before forkchoice.onBlock
125
- this.checkpointBalancesCache.processState(blockRootHex, postBlockState);
124
+ this.checkpointBalancesCache.processState(blockRootHex, postState);
126
125
  const blockSummary = this.forkChoice.onBlock(
127
126
  block.message,
128
- postBlockState,
127
+ postState,
129
128
  blockDelaySec,
130
129
  currentSlot,
131
130
  fork >= ForkSeq.gloas ? ExecutionStatus.PayloadSeparated : executionStatus,
@@ -134,11 +133,7 @@ export async function importBlock(
134
133
 
135
134
  // This adds the state necessary to process the next block
136
135
  // Some block event handlers require state being in state cache so need to do this before emitting EventType.block
137
- // Pre-Gloas: blockSummary.payloadStatus is always FULL, payloadPresent = true
138
- // Post-Gloas: blockSummary.payloadStatus is always PENDING, so payloadPresent = false (block state only, no payload processing yet)
139
- const payloadPresent = !isGloasBlock(blockSummary);
140
- // processState manages both block state and payload state variants together for memory/disk management
141
- this.regen.processBlockState(blockRootHex, postBlockState);
136
+ this.regen.processState(blockRootHex, postState);
142
137
 
143
138
  // For Gloas blocks, create PayloadEnvelopeInput so it's available for later payload import
144
139
  if (fork >= ForkSeq.gloas) {
@@ -161,17 +156,9 @@ export async function importBlock(
161
156
  // which is all the information we need so there is no reason to delay until execution payload arrives
162
157
  // TODO GLOAS: If we want EL retries after this initial attempt, add an explicit retry policy here
163
158
  // (for example later in the slot). Do not couple retries to incoming gossip columns.
164
- this.getBlobsTracker.triggerGetBlobs(payloadInput, () => {
165
- // TODO GLOAS: come up with a better mechanism to trigger processExecutionPayload after data becomes available,
166
- // similar to how pre-gloas uses waitForBlockAndAllData with a cutoff timeout and incompleteBlockInput event
167
- this.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
168
- this.logger.debug(
169
- "Error processing execution payload after getBlobs",
170
- {slot: blockSlot, root: blockRootHex},
171
- e as Error
172
- );
173
- });
174
- });
159
+ // Columns fetched here feed payloadInput.addColumn, which resolves waitForAllData for any
160
+ // in-flight importExecutionPayload. No processExecutionPayload trigger needed from this path.
161
+ this.getBlobsTracker.triggerGetBlobs(payloadInput);
175
162
  }
176
163
 
177
164
  this.metrics?.importBlock.bySource.inc({source: source.source});
@@ -191,7 +178,7 @@ export async function importBlock(
191
178
  (opts.importAttestations !== AttestationImportOpt.Skip && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)
192
179
  ) {
193
180
  const attestations = block.message.body.attestations;
194
- const rootCache = new RootCache(postBlockState);
181
+ const rootCache = new RootCache(postState);
195
182
  const invalidAttestationErrorsByCode = new Map<string, {error: Error; count: number}>();
196
183
 
197
184
  const addAttestation = fork >= ForkSeq.electra ? addAttestationPostElectra : addAttestationPreElectra;
@@ -205,7 +192,7 @@ export async function importBlock(
205
192
  const attDataRoot = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data));
206
193
  addAttestation.call(
207
194
  this,
208
- postBlockState,
195
+ postState,
209
196
  target,
210
197
  attDataRoot,
211
198
  attestation as Attestation<ForkPostElectra>,
@@ -320,7 +307,7 @@ export async function importBlock(
320
307
 
321
308
  if (newHead.blockRoot !== oldHead.blockRoot) {
322
309
  // Set head state as strong reference
323
- this.regen.updateHeadState(newHead, postBlockState);
310
+ this.regen.updateHeadState(newHead, postState);
324
311
 
325
312
  try {
326
313
  this.emitter.emit(routes.events.EventType.head, {
@@ -390,10 +377,10 @@ export async function importBlock(
390
377
  // we want to import block asap so do this in the next event loop
391
378
  callInNextEventLoop(() => {
392
379
  try {
393
- if (isStatePostAltair(postBlockState)) {
380
+ if (isStatePostAltair(postState)) {
394
381
  this.lightClientServer?.onImportBlockHead(
395
382
  block.message as BeaconBlock<ForkPostAltair>,
396
- postBlockState,
383
+ postState,
397
384
  parentBlockSlot
398
385
  );
399
386
  }
@@ -415,11 +402,11 @@ export async function importBlock(
415
402
  // and the block is weak and can potentially be reorged out.
416
403
  let shouldOverrideFcu = false;
417
404
 
418
- if (blockSlot >= currentSlot && isStatePostBellatrix(postBlockState) && postBlockState.isExecutionStateType) {
405
+ if (blockSlot >= currentSlot && isStatePostBellatrix(postState) && postState.isExecutionStateType) {
419
406
  let notOverrideFcuReason = NotReorgedReason.Unknown;
420
407
  const proposalSlot = blockSlot + 1;
421
408
  try {
422
- const proposerIndex = postBlockState.getBeaconProposer(proposalSlot);
409
+ const proposerIndex = postState.getBeaconProposer(proposalSlot);
423
410
  const feeRecipient = this.beaconProposerCache.get(proposerIndex);
424
411
 
425
412
  if (feeRecipient) {
@@ -499,27 +486,27 @@ export async function importBlock(
499
486
  }
500
487
  }
501
488
 
502
- if (!postBlockState.isStateValidatorsNodesPopulated()) {
503
- this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postBlockState.slot});
489
+ if (!postState.isStateValidatorsNodesPopulated()) {
490
+ this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postState.slot});
504
491
  }
505
492
 
506
493
  // Cache shufflings when crossing an epoch boundary
507
494
  const parentEpoch = computeEpochAtSlot(parentBlockSlot);
508
495
  if (parentEpoch < blockEpoch) {
509
- this.shufflingCache.processState(postBlockState);
496
+ this.shufflingCache.processState(postState);
510
497
  this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot});
511
498
  }
512
499
 
513
500
  if (blockSlot % SLOTS_PER_EPOCH === 0) {
514
501
  // Cache state to preserve epoch transition work
515
- const checkpointState = postBlockState;
502
+ const checkpointState = postState;
516
503
  const cp = getCheckpointFromState(checkpointState);
517
- this.regen.addCheckpointState(cp, checkpointState, payloadPresent);
504
+ this.regen.addCheckpointState(cp, checkpointState);
518
505
  // consumers should not mutate state ever
519
506
  this.emitter.emit(ChainEvent.checkpoint, cp, checkpointState);
520
507
 
521
508
  // Note: in-lined code from previos handler of ChainEvent.checkpoint
522
- this.logger.verbose("Checkpoint processed", toCheckpointHexPayload(cp, payloadPresent));
509
+ this.logger.verbose("Checkpoint processed", toCheckpointHex(cp));
523
510
 
524
511
  const activeValidatorsCount = checkpointState.activeValidatorCount;
525
512
  this.metrics?.currentActiveValidators.set(activeValidatorsCount);
@@ -537,7 +524,7 @@ export async function importBlock(
537
524
  const justifiedEpoch = justifiedCheckpoint.epoch;
538
525
  const preJustifiedEpoch = parentBlockSummary.justifiedEpoch;
539
526
  if (justifiedEpoch > preJustifiedEpoch) {
540
- this.logger.verbose("Checkpoint justified", toCheckpointHexPayload(justifiedCheckpoint, payloadPresent));
527
+ this.logger.verbose("Checkpoint justified", toCheckpointHex(justifiedCheckpoint));
541
528
  this.metrics?.previousJustifiedEpoch.set(checkpointState.previousJustifiedCheckpoint.epoch);
542
529
  this.metrics?.currentJustifiedEpoch.set(justifiedCheckpoint.epoch);
543
530
  }
@@ -551,7 +538,7 @@ export async function importBlock(
551
538
  state: toRootHex(checkpointState.hashTreeRoot()),
552
539
  executionOptimistic: false,
553
540
  });
554
- this.logger.verbose("Checkpoint finalized", toCheckpointHexPayload(finalizedCheckpoint, payloadPresent));
541
+ this.logger.verbose("Checkpoint finalized", toCheckpointHex(finalizedCheckpoint));
555
542
  this.metrics?.finalizedEpoch.set(finalizedCheckpoint.epoch);
556
543
  }
557
544
  }
@@ -602,11 +589,11 @@ export async function importBlock(
602
589
  this.metrics?.parentBlockDistance.observe(blockSlot - parentBlockSlot);
603
590
  this.metrics?.proposerBalanceDeltaAny.observe(fullyVerifiedBlock.proposerBalanceDelta);
604
591
  this.validatorMonitor?.registerImportedBlock(block.message, fullyVerifiedBlock);
605
- if (isStatePostAltair(fullyVerifiedBlock.postBlockState)) {
592
+ if (isStatePostAltair(fullyVerifiedBlock.postState)) {
606
593
  this.validatorMonitor?.registerSyncAggregateInBlock(
607
594
  blockEpoch,
608
595
  (block as altair.SignedBeaconBlock).message.body.syncAggregate,
609
- fullyVerifiedBlock.postBlockState.currentSyncCommitteeIndexed.validatorIndices
596
+ fullyVerifiedBlock.postState.currentSyncCommitteeIndexed.validatorIndices
610
597
  );
611
598
  }
612
599
 
@@ -9,6 +9,7 @@ import {BeaconChain} from "../chain.js";
9
9
  import {RegenCaller} from "../regen/interface.js";
10
10
  import {PayloadEnvelopeInput} from "../seenCache/seenPayloadEnvelopeInput.js";
11
11
  import {ImportPayloadOpts} from "./types.js";
12
+ import {verifyPayloadsDataAvailability} from "./verifyPayloadsDataAvailability.js";
12
13
 
13
14
  const EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS = 64;
14
15
 
@@ -84,6 +85,7 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
84
85
  export async function importExecutionPayload(
85
86
  this: BeaconChain,
86
87
  payloadInput: PayloadEnvelopeInput,
88
+ signal: AbortSignal,
87
89
  opts: ImportPayloadOpts = {}
88
90
  ): Promise<void> {
89
91
  const signedEnvelope = payloadInput.getPayloadEnvelope();
@@ -112,11 +114,15 @@ export async function importExecutionPayload(
112
114
  });
113
115
  }
114
116
 
115
- // 3. Apply backpressure from the write queue early, before doing verification work.
117
+ // 3. Wait for data columns to be available before claiming a write-queue slot.
118
+ // The helper is shared with future gloas sync services; take the single-item batch form here.
119
+ await verifyPayloadsDataAvailability([payloadInput], signal);
120
+
121
+ // 4. Apply backpressure from the write queue, before doing verification work.
116
122
  // The actual DB write is deferred until after verification succeeds.
117
123
  await this.unfinalizedPayloadEnvelopeWrites.waitForSpace();
118
124
 
119
- // 4. Get pre-state for processExecutionPayloadEnvelope
125
+ // 5. Get pre-state for processExecutionPayloadEnvelope
120
126
  // We need the block state (post-block, pre-payload) to process the envelope
121
127
  const blockState = await this.regen.getBlockSlotState(
122
128
  protoBlock,
@@ -131,9 +137,7 @@ export async function importExecutionPayload(
131
137
  });
132
138
  }
133
139
 
134
- // 5. Run verification steps in parallel
135
- // Note: No data availability check needed here - importExecutionPayload is only
136
- // called when payloadInput.isComplete() is true, so all data is already available.
140
+ // 6. Run verification steps in parallel
137
141
  const [execResult, signatureValid, postPayloadResult] = await Promise.all([
138
142
  this.executionEngine.notifyNewPayload(
139
143
  fork,
@@ -240,10 +244,10 @@ export async function importExecutionPayload(
240
244
  );
241
245
 
242
246
  // 8. Cache payload state
243
- this.regen.processPayloadState(postPayloadState);
247
+ this.regen.processState(blockRootHex, postPayloadState);
244
248
  if (postPayloadState.slot % SLOTS_PER_EPOCH === 0) {
245
249
  const {checkpoint} = postPayloadState.computeAnchorCheckpoint();
246
- this.regen.addCheckpointState(checkpoint, postPayloadState, true);
250
+ this.regen.addCheckpointState(checkpoint, postPayloadState);
247
251
  }
248
252
 
249
253
  // 9. Record metrics for payload envelope and column sources
@@ -88,7 +88,7 @@ export async function processBlocks(
88
88
  const fullyVerifiedBlocks = relevantBlocks.map(
89
89
  (block, i): FullyVerifiedBlock => ({
90
90
  blockInput: block,
91
- postBlockState: postStates[i],
91
+ postState: postStates[i],
92
92
  postPayloadState: null,
93
93
  parentBlockSlot: parentSlots[i],
94
94
  executionStatus: executionStatuses[i],
@@ -73,6 +73,7 @@ export class PayloadEnvelopeInput {
73
73
  private timeCreatedSec: number;
74
74
 
75
75
  private readonly payloadEnvelopeDataPromise: PromiseParts<gloas.SignedExecutionPayloadEnvelope>;
76
+ private readonly allDataPromise: PromiseParts<gloas.DataColumnSidecar[]>;
76
77
  private readonly columnsDataPromise: PromiseParts<gloas.DataColumnSidecar[]>;
77
78
 
78
79
  state: PayloadEnvelopeInputState;
@@ -97,6 +98,7 @@ export class PayloadEnvelopeInput {
97
98
  this.custodyColumns = props.custodyColumns;
98
99
  this.timeCreatedSec = props.timeCreatedSec;
99
100
  this.payloadEnvelopeDataPromise = createPromise();
101
+ this.allDataPromise = createPromise();
100
102
  this.columnsDataPromise = createPromise();
101
103
 
102
104
  const noBlobs = props.bid.blobKzgCommitments.length === 0;
@@ -105,6 +107,7 @@ export class PayloadEnvelopeInput {
105
107
 
106
108
  if (hasAllData) {
107
109
  this.state = {hasPayload: false, hasAllData: true, hasComputedAllData: true};
110
+ this.allDataPromise.resolve(this.getSampledColumns());
108
111
  this.columnsDataPromise.resolve(this.getSampledColumns());
109
112
  } else {
110
113
  this.state = {hasPayload: false, hasAllData: false, hasComputedAllData: false};
@@ -203,6 +206,12 @@ export class PayloadEnvelopeInput {
203
206
  return true;
204
207
  }
205
208
 
209
+ // Resolve allDataPromise on the first transition to hasAllData (either sampled-complete or
210
+ // reconstruction-threshold branch). Guarded so it fires exactly once.
211
+ if (!this.state.hasAllData && hasAllData) {
212
+ this.allDataPromise.resolve(sampledColumns);
213
+ }
214
+
206
215
  if (hasComputedAllData) {
207
216
  this.columnsDataPromise.resolve(sampledColumns);
208
217
  }
@@ -315,6 +324,24 @@ export class PayloadEnvelopeInput {
315
324
  return this.state.hasComputedAllData;
316
325
  }
317
326
 
327
+ waitForAllData(timeout: number, signal?: AbortSignal): Promise<gloas.DataColumnSidecar[]> {
328
+ if (this.state.hasAllData) {
329
+ return Promise.resolve(this.getSampledColumns());
330
+ }
331
+ return withTimeout(() => this.allDataPromise.promise, timeout, signal);
332
+ }
333
+
334
+ async waitForEnvelopeAndAllData(timeout: number, signal?: AbortSignal): Promise<this> {
335
+ if (!this.state.hasPayload || !this.state.hasAllData) {
336
+ await withTimeout(
337
+ () => Promise.all([this.payloadEnvelopeDataPromise.promise, this.allDataPromise.promise]),
338
+ timeout,
339
+ signal
340
+ );
341
+ }
342
+ return this;
343
+ }
344
+
318
345
  waitForComputedAllData(timeout: number, signal?: AbortSignal): Promise<gloas.DataColumnSidecar[]> {
319
346
  if (this.state.hasComputedAllData) {
320
347
  return Promise.resolve(this.getSampledColumns());
@@ -16,6 +16,11 @@ enum PayloadEnvelopeImportStatus {
16
16
 
17
17
  /**
18
18
  * PayloadEnvelopeProcessor processes payload envelope jobs in a queued fashion, one after the other.
19
+ *
20
+ * Jobs are enqueued only on envelope arrival (gossip or API). The envelope may reach us before
21
+ * the sampled data columns; importExecutionPayload awaits `verifyPayloadsDataAvailability`
22
+ * internally, so a queued job can pend for up to `PAYLOAD_DATA_AVAILABILITY_TIMEOUT` while
23
+ * waiting for columns. Duplicate triggers for the same payloadInput are deduped via `importStatus`.
19
24
  */
20
25
  export class PayloadEnvelopeProcessor {
21
26
  readonly jobQueue: JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>;
@@ -25,7 +30,7 @@ export class PayloadEnvelopeProcessor {
25
30
  this.jobQueue = new JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>(
26
31
  (payloadInput, opts) => {
27
32
  this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.importing);
28
- return importExecutionPayload.call(chain, payloadInput, opts);
33
+ return importExecutionPayload.call(chain, payloadInput, signal, opts);
29
34
  },
30
35
  {maxLength: QUEUE_MAX_LENGTH, noYieldIfOneItem: true, signal},
31
36
  metrics?.payloadEnvelopeProcessorQueue ?? undefined
@@ -33,10 +38,6 @@ export class PayloadEnvelopeProcessor {
33
38
  }
34
39
 
35
40
  async processPayloadEnvelopeJob(payloadInput: PayloadEnvelopeInput, opts: ImportPayloadOpts = {}): Promise<void> {
36
- if (!payloadInput.isComplete()) {
37
- return;
38
- }
39
-
40
41
  if (this.importStatus.get(payloadInput) !== undefined) {
41
42
  return;
42
43
  }
@@ -90,7 +90,7 @@ export type ImportBlockOpts = {
90
90
 
91
91
  type FullyVerifiedBlockBase = {
92
92
  blockInput: IBlockInput;
93
- postBlockState: IBeaconStateView;
93
+ postState: IBeaconStateView;
94
94
  parentBlockSlot: Slot;
95
95
  proposerBalanceDelta: number;
96
96
  dataAvailabilityStatus: DataAvailabilityStatus;