@lodestar/beacon-node 1.43.0-dev.aef3645690 → 1.43.0-dev.e341cdc614

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 (136) hide show
  1. package/lib/api/impl/validator/index.d.ts.map +1 -1
  2. package/lib/api/impl/validator/index.js +4 -3
  3. package/lib/api/impl/validator/index.js.map +1 -1
  4. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  5. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  6. package/lib/chain/GetBlobsTracker.js +1 -2
  7. package/lib/chain/GetBlobsTracker.js.map +1 -1
  8. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
  10. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  11. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  12. package/lib/chain/blocks/importBlock.js +27 -35
  13. package/lib/chain/blocks/importBlock.js.map +1 -1
  14. package/lib/chain/blocks/importExecutionPayload.d.ts +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  16. package/lib/chain/blocks/importExecutionPayload.js +10 -8
  17. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  18. package/lib/chain/blocks/index.js +1 -1
  19. package/lib/chain/blocks/index.js.map +1 -1
  20. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +3 -0
  21. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  22. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +20 -0
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  25. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  26. package/lib/chain/blocks/payloadEnvelopeProcessor.js +6 -4
  27. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  28. package/lib/chain/blocks/types.d.ts +1 -1
  29. package/lib/chain/blocks/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  31. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  32. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +25 -0
  33. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  34. package/lib/chain/chain.d.ts.map +1 -1
  35. package/lib/chain/chain.js +17 -37
  36. package/lib/chain/chain.js.map +1 -1
  37. package/lib/chain/emitter.d.ts +13 -1
  38. package/lib/chain/emitter.d.ts.map +1 -1
  39. package/lib/chain/emitter.js +5 -0
  40. package/lib/chain/emitter.js.map +1 -1
  41. package/lib/chain/errors/attestationError.d.ts +8 -1
  42. package/lib/chain/errors/attestationError.d.ts.map +1 -1
  43. package/lib/chain/errors/attestationError.js +4 -0
  44. package/lib/chain/errors/attestationError.js.map +1 -1
  45. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  46. package/lib/chain/forkChoice/index.js +6 -2
  47. package/lib/chain/forkChoice/index.js.map +1 -1
  48. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  49. package/lib/chain/prepareNextSlot.js +2 -6
  50. package/lib/chain/prepareNextSlot.js.map +1 -1
  51. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
  52. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  53. package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
  54. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  55. package/lib/chain/produceBlock/produceBlockBody.d.ts +0 -6
  56. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  57. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  58. package/lib/chain/regen/errors.d.ts +1 -11
  59. package/lib/chain/regen/errors.d.ts.map +1 -1
  60. package/lib/chain/regen/errors.js +0 -2
  61. package/lib/chain/regen/errors.js.map +1 -1
  62. package/lib/chain/regen/interface.d.ts +6 -12
  63. package/lib/chain/regen/interface.d.ts.map +1 -1
  64. package/lib/chain/regen/queued.d.ts +6 -11
  65. package/lib/chain/regen/queued.d.ts.map +1 -1
  66. package/lib/chain/regen/queued.js +8 -40
  67. package/lib/chain/regen/queued.js.map +1 -1
  68. package/lib/chain/regen/regen.d.ts +0 -5
  69. package/lib/chain/regen/regen.d.ts.map +1 -1
  70. package/lib/chain/regen/regen.js +7 -34
  71. package/lib/chain/regen/regen.js.map +1 -1
  72. package/lib/chain/stateCache/datastore/db.d.ts +5 -4
  73. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  74. package/lib/chain/stateCache/datastore/db.js +10 -32
  75. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  76. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  77. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  78. package/lib/chain/stateCache/datastore/file.js +5 -5
  79. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  80. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  81. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  82. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
  83. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  84. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
  85. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  86. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
  87. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  88. package/lib/chain/stateCache/persistentCheckpointsCache.js +116 -215
  89. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  90. package/lib/chain/stateCache/types.d.ts +8 -15
  91. package/lib/chain/stateCache/types.d.ts.map +1 -1
  92. package/lib/chain/stateCache/types.js.map +1 -1
  93. package/lib/chain/validation/aggregateAndProof.js +12 -0
  94. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  95. package/lib/chain/validation/attestation.d.ts.map +1 -1
  96. package/lib/chain/validation/attestation.js +12 -0
  97. package/lib/chain/validation/attestation.js.map +1 -1
  98. package/lib/chain/validation/executionPayloadEnvelope.js +2 -2
  99. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  100. package/lib/network/gossip/topic.d.ts +2 -729
  101. package/lib/network/gossip/topic.d.ts.map +1 -1
  102. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  103. package/lib/network/processor/gossipHandlers.js +19 -3
  104. package/lib/network/processor/gossipHandlers.js.map +1 -1
  105. package/package.json +15 -15
  106. package/src/api/impl/validator/index.ts +6 -5
  107. package/src/chain/GetBlobsTracker.ts +1 -2
  108. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +2 -4
  109. package/src/chain/blocks/importBlock.ts +26 -39
  110. package/src/chain/blocks/importExecutionPayload.ts +11 -7
  111. package/src/chain/blocks/index.ts +1 -1
  112. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -0
  113. package/src/chain/blocks/payloadEnvelopeProcessor.ts +6 -5
  114. package/src/chain/blocks/types.ts +1 -1
  115. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +38 -0
  116. package/src/chain/chain.ts +16 -47
  117. package/src/chain/emitter.ts +12 -0
  118. package/src/chain/errors/attestationError.ts +6 -1
  119. package/src/chain/forkChoice/index.ts +6 -2
  120. package/src/chain/prepareNextSlot.ts +2 -6
  121. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
  122. package/src/chain/produceBlock/produceBlockBody.ts +0 -6
  123. package/src/chain/regen/errors.ts +1 -6
  124. package/src/chain/regen/interface.ts +6 -12
  125. package/src/chain/regen/queued.ts +12 -48
  126. package/src/chain/regen/regen.ts +8 -36
  127. package/src/chain/stateCache/datastore/db.ts +10 -33
  128. package/src/chain/stateCache/datastore/file.ts +5 -6
  129. package/src/chain/stateCache/datastore/types.ts +2 -3
  130. package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
  131. package/src/chain/stateCache/persistentCheckpointsCache.ts +135 -246
  132. package/src/chain/stateCache/types.ts +8 -14
  133. package/src/chain/validation/aggregateAndProof.ts +13 -0
  134. package/src/chain/validation/attestation.ts +13 -0
  135. package/src/chain/validation/executionPayloadEnvelope.ts +2 -2
  136. package/src/network/processor/gossipHandlers.ts +23 -7
@@ -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;
@@ -0,0 +1,38 @@
1
+ import {DataAvailabilityStatus} from "@lodestar/state-transition";
2
+ import {gloas} from "@lodestar/types";
3
+ import {PayloadEnvelopeInput} from "../seenCache/seenPayloadEnvelopeInput.js";
4
+
5
+ // we can now wait for full 12 seconds because sync and reconstruction will try pulling
6
+ // the data columns from the network anyway while the envelope is being processed
7
+ export const PAYLOAD_DATA_AVAILABILITY_TIMEOUT = 12_000;
8
+
9
+ /**
10
+ * Verifies that all payload envelope inputs have their data columns available.
11
+ * - Waits a max of PAYLOAD_DATA_AVAILABILITY_TIMEOUT for all data to be available
12
+ * - Returns the time at which all data was available
13
+ * - Returns the data availability status for each payload input
14
+ */
15
+ export async function verifyPayloadsDataAvailability(
16
+ payloadInputs: PayloadEnvelopeInput[],
17
+ signal: AbortSignal
18
+ ): Promise<{
19
+ dataAvailabilityStatuses: DataAvailabilityStatus[];
20
+ availableTime: number;
21
+ }> {
22
+ const promises: Promise<gloas.DataColumnSidecar[]>[] = [];
23
+ for (const payloadInput of payloadInputs) {
24
+ if (!payloadInput.hasAllData()) {
25
+ promises.push(payloadInput.waitForAllData(PAYLOAD_DATA_AVAILABILITY_TIMEOUT, signal));
26
+ }
27
+ }
28
+ await Promise.all(promises);
29
+
30
+ const availableTime = Math.max(0, Math.max(...payloadInputs.map((payloadInput) => payloadInput.getTimeComplete())));
31
+ const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) =>
32
+ payloadInput.getBlobKzgCommitments().length === 0
33
+ ? DataAvailabilityStatus.NotRequired
34
+ : DataAvailabilityStatus.Available
35
+ );
36
+
37
+ return {dataAvailabilityStatuses, availableTime};
38
+ }
@@ -2,17 +2,9 @@ import path from "node:path";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Type} from "@chainsafe/ssz";
4
4
  import {BeaconConfig} from "@lodestar/config";
5
- import {
6
- CheckpointWithPayloadStatus,
7
- IForkChoice,
8
- PayloadStatus,
9
- ProtoBlock,
10
- UpdateHeadOpt,
11
- getCheckpointPayloadStatus,
12
- } from "@lodestar/fork-choice";
5
+ import {CheckpointWithPayloadStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
13
6
  import {LoggerNode} from "@lodestar/logger/node";
14
7
  import {
15
- BUILDER_INDEX_SELF_BUILD,
16
8
  EFFECTIVE_BALANCE_INCREMENT,
17
9
  type ForkPostFulu,
18
10
  type ForkPostGloas,
@@ -31,7 +23,6 @@ import {
31
23
  getEffectiveBalancesFromStateBytes,
32
24
  isStatePostAltair,
33
25
  isStatePostElectra,
34
- isStatePostGloas,
35
26
  } from "@lodestar/state-transition";
36
27
  import {
37
28
  BeaconBlock,
@@ -100,8 +91,8 @@ import {
100
91
  } from "./opPools/index.js";
101
92
  import {IChainOptions} from "./options.js";
102
93
  import {PrepareNextSlotScheduler} from "./prepareNextSlot.js";
103
- import {computeNewStateRoot, computePayloadEnvelopeStateRoot} from "./produceBlock/computeNewStateRoot.js";
104
- import {AssembledBlockType, BlockType, ProduceFullGloas, ProduceResult} from "./produceBlock/index.js";
94
+ import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js";
95
+ import {AssembledBlockType, BlockType, ProduceResult} from "./produceBlock/index.js";
105
96
  import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produceBlock/produceBlockBody.js";
106
97
  import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js";
107
98
  import {ReprocessController} from "./reprocess.js";
@@ -125,7 +116,7 @@ import {DbCPStateDatastore, checkpointToDatastoreKey} from "./stateCache/datasto
125
116
  import {FileCPStateDatastore} from "./stateCache/datastore/file.js";
126
117
  import {CPStateDatastore} from "./stateCache/datastore/types.js";
127
118
  import {FIFOBlockStateCache} from "./stateCache/fifoBlockStateCache.js";
128
- import {PersistentCheckpointStateCache, fcCheckpointToHexPayload} from "./stateCache/persistentCheckpointsCache.js";
119
+ import {PersistentCheckpointStateCache} from "./stateCache/persistentCheckpointsCache.js";
129
120
  import {CheckpointStateCache} from "./stateCache/types.js";
130
121
  import {ValidatorMonitor} from "./validatorMonitor.js";
131
122
 
@@ -390,8 +381,7 @@ export class BeaconChain implements IBeaconChain {
390
381
  const {checkpoint} = anchorState.computeAnchorCheckpoint();
391
382
  blockStateCache.add(anchorState);
392
383
  blockStateCache.setHeadState(anchorState);
393
- const payloadPresent = getCheckpointPayloadStatus(config, anchorState, checkpoint.epoch) === PayloadStatus.FULL;
394
- checkpointStateCache.add(checkpoint, anchorState, payloadPresent);
384
+ checkpointStateCache.add(checkpoint, anchorState);
395
385
 
396
386
  const forkChoice = initializeForkChoice(
397
387
  config,
@@ -685,7 +675,7 @@ export class BeaconChain implements IBeaconChain {
685
675
 
686
676
  // TODO GLOAS: Need to revisit the design of this api. Currently we just retrieve FULL state of the checkpoint for backwards compatibility.
687
677
  // because pre-gloas we always store FULL checkpoint state.
688
- const persistedKey = checkpointToDatastoreKey(checkpoint, true);
678
+ const persistedKey = checkpointToDatastoreKey(checkpoint);
689
679
  return this.cpStateDatastore.read(persistedKey);
690
680
  }
691
681
 
@@ -693,8 +683,8 @@ export class BeaconChain implements IBeaconChain {
693
683
  checkpoint: CheckpointWithPayloadStatus
694
684
  ): {state: IBeaconStateView; executionOptimistic: boolean; finalized: boolean} | null {
695
685
  // finalized or justified checkpoint states maynot be available with PersistentCheckpointStateCache, use getCheckpointStateOrBytes() api to get Uint8Array
696
- const checkpointHexPayload = fcCheckpointToHexPayload(checkpoint);
697
- const cachedStateCtx = this.regen.getCheckpointStateSync(checkpointHexPayload);
686
+ const checkpointHex = {epoch: checkpoint.epoch, rootHex: checkpoint.rootHex};
687
+ const cachedStateCtx = this.regen.getCheckpointStateSync(checkpointHex);
698
688
  if (cachedStateCtx) {
699
689
  const block = this.forkChoice.getBlockDefaultStatus(
700
690
  ssz.phase0.BeaconBlockHeader.hashTreeRoot(cachedStateCtx.latestBlockHeader)
@@ -713,8 +703,8 @@ export class BeaconChain implements IBeaconChain {
713
703
  async getStateOrBytesByCheckpoint(
714
704
  checkpoint: CheckpointWithPayloadStatus
715
705
  ): Promise<{state: IBeaconStateView | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> {
716
- const checkpointHexPayload = fcCheckpointToHexPayload(checkpoint);
717
- const cachedStateCtx = await this.regen.getCheckpointStateOrBytes(checkpointHexPayload);
706
+ const checkpointHex = {epoch: checkpoint.epoch, rootHex: checkpoint.rootHex};
707
+ const cachedStateCtx = await this.regen.getCheckpointStateOrBytes(checkpointHex);
718
708
  if (cachedStateCtx) {
719
709
  const block = this.forkChoice.getBlockDefaultStatus(checkpoint.root);
720
710
  const finalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
@@ -1070,7 +1060,7 @@ export class BeaconChain implements IBeaconChain {
1070
1060
  body,
1071
1061
  } as AssembledBlockType<T>;
1072
1062
 
1073
- const {newStateRoot, proposerReward, postBlockState} = computeNewStateRoot(this.metrics, state, block);
1063
+ const {newStateRoot, proposerReward} = computeNewStateRoot(this.metrics, state, block);
1074
1064
  block.stateRoot = newStateRoot;
1075
1065
  const blockRoot =
1076
1066
  produceResult.type === BlockType.Full
@@ -1079,26 +1069,9 @@ export class BeaconChain implements IBeaconChain {
1079
1069
  const blockRootHex = toRootHex(blockRoot);
1080
1070
 
1081
1071
  const fork = this.config.getForkName(slot);
1082
- if (isForkPostGloas(fork)) {
1083
- // TODO GLOAS: we should retire BlockType post-gloas, may need a new enum for self vs non-self built
1084
- if (produceResult.type !== BlockType.Full) {
1085
- throw Error(`Unexpected block type=${produceResult.type} for post-gloas fork=${fork}`);
1086
- }
1087
-
1088
- const gloasResult = produceResult as ProduceFullGloas;
1089
- const envelope: gloas.ExecutionPayloadEnvelope = {
1090
- payload: gloasResult.executionPayload,
1091
- executionRequests: gloasResult.executionRequests,
1092
- builderIndex: BUILDER_INDEX_SELF_BUILD,
1093
- beaconBlockRoot: blockRoot,
1094
- slot,
1095
- stateRoot: ZERO_HASH,
1096
- };
1097
- if (!isStatePostGloas(postBlockState)) {
1098
- throw Error(`Expected gloas+ post-state for execution payload envelope, got fork=${postBlockState.forkName}`);
1099
- }
1100
- const payloadEnvelopeStateRoot = computePayloadEnvelopeStateRoot(this.metrics, postBlockState, envelope);
1101
- gloasResult.payloadEnvelopeStateRoot = payloadEnvelopeStateRoot;
1072
+ // TODO GLOAS: we should retire BlockType post-gloas, may need a new enum for self vs non-self built
1073
+ if (isForkPostGloas(fork) && produceResult.type !== BlockType.Full) {
1074
+ throw Error(`Unexpected block type=${produceResult.type} for post-gloas fork=${fork}`);
1102
1075
  }
1103
1076
 
1104
1077
  // Track the produced block for consensus broadcast validations, later validation, etc.
@@ -1346,8 +1319,8 @@ export class BeaconChain implements IBeaconChain {
1346
1319
  checkpoint: CheckpointWithPayloadStatus,
1347
1320
  blockState: IBeaconStateView
1348
1321
  ): {state: IBeaconStateView; stateId: string; shouldWarn: boolean} {
1349
- const checkpointHexPayload = fcCheckpointToHexPayload(checkpoint);
1350
- const state = this.regen.getCheckpointStateSync(checkpointHexPayload);
1322
+ const checkpointHex = {epoch: checkpoint.epoch, rootHex: checkpoint.rootHex};
1323
+ const state = this.regen.getCheckpointStateSync(checkpointHex);
1351
1324
  if (state) {
1352
1325
  return {state, stateId: "checkpoint_state", shouldWarn: false};
1353
1326
  }
@@ -1471,10 +1444,6 @@ export class BeaconChain implements IBeaconChain {
1471
1444
  private onClockEpoch(epoch: Epoch): void {
1472
1445
  this.metrics?.clockEpoch.set(epoch);
1473
1446
 
1474
- if (epoch === this.config.GLOAS_FORK_EPOCH) {
1475
- this.regen.upgradeForGloas(epoch);
1476
- }
1477
-
1478
1447
  this.seenAttesters.prune(epoch);
1479
1448
  this.seenAggregators.prune(epoch);
1480
1449
  this.seenPayloadAttesters.prune(epoch);
@@ -7,6 +7,7 @@ import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
7
7
  import {SignedExecutionPayloadEnvelope} from "@lodestar/types/gloas";
8
8
  import {PeerIdStr} from "../util/peerId.js";
9
9
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
10
+ import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
10
11
 
11
12
  /**
12
13
  * Important chain events that occur during normal chain operation.
@@ -76,6 +77,11 @@ export enum ChainEvent {
76
77
  * cut-off window passes for waiting on gossip
77
78
  */
78
79
  incompleteBlockInput = "incompleteBlockInput",
80
+ /**
81
+ * Post-gloas: trigger BlockInputSync for payload envelopes whose envelope and/or sampled columns are partially
82
+ * received via gossip but are not complete by time the cut-off window passes for waiting on gossip
83
+ */
84
+ incompletePayloadEnvelope = "incompletePayloadEnvelope",
79
85
  }
80
86
 
81
87
  export type HeadEventData = routes.events.EventData[routes.events.EventType.head];
@@ -93,6 +99,11 @@ export type ChainEventData = {
93
99
  };
94
100
  [ChainEvent.unknownBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
95
101
  [ChainEvent.incompleteBlockInput]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
102
+ [ChainEvent.incompletePayloadEnvelope]: {
103
+ payloadInput: PayloadEnvelopeInput;
104
+ peer: PeerIdStr;
105
+ source: BlockInputSource;
106
+ };
96
107
  [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
97
108
  };
98
109
 
@@ -116,6 +127,7 @@ export type IChainEvents = ApiEvents & {
116
127
  [ChainEvent.envelopeUnknownBlock]: (data: ChainEventData[ChainEvent.envelopeUnknownBlock]) => void;
117
128
  [ChainEvent.unknownBlockRoot]: (data: ChainEventData[ChainEvent.unknownBlockRoot]) => void;
118
129
  [ChainEvent.incompleteBlockInput]: (data: ChainEventData[ChainEvent.incompleteBlockInput]) => void;
130
+ [ChainEvent.incompletePayloadEnvelope]: (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]) => void;
119
131
  [ChainEvent.unknownEnvelopeBlockRoot]: (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]) => void;
120
132
  };
121
133
 
@@ -147,6 +147,10 @@ export enum AttestationErrorCode {
147
147
  * Gloas: Current slot attestation is marking payload as present
148
148
  */
149
149
  PREMATURELY_INDICATED_PAYLOAD_PRESENT = "ATTESTATION_ERROR_PREMATURELY_INDICATED_PAYLOAD_PRESENT",
150
+ /**
151
+ * Gloas: index-1 attestation but the execution payload has not been seen yet
152
+ */
153
+ EXECUTION_PAYLOAD_NOT_SEEN = "ATTESTATION_ERROR_EXECUTION_PAYLOAD_NOT_SEEN",
150
154
  }
151
155
 
152
156
  export type AttestationErrorType =
@@ -185,7 +189,8 @@ export type AttestationErrorType =
185
189
  | {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX}
186
190
  | {code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE}
187
191
  | {code: AttestationErrorCode.INVALID_PAYLOAD_STATUS_VALUE; attDataIndex: number}
188
- | {code: AttestationErrorCode.PREMATURELY_INDICATED_PAYLOAD_PRESENT};
192
+ | {code: AttestationErrorCode.PREMATURELY_INDICATED_PAYLOAD_PRESENT}
193
+ | {code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN; beaconBlockRoot: RootHex};
189
194
 
190
195
  export class AttestationError extends GossipActionError<AttestationErrorType> {
191
196
  getMetadata(): Record<string, string | number | null> {
@@ -105,10 +105,14 @@ export function initializeForkChoiceFromFinalizedState(
105
105
  const isForkPostGloas = computeEpochAtSlot(state.slot) >= config.GLOAS_FORK_EPOCH;
106
106
 
107
107
  // Determine justified checkpoint payload status
108
- const justifiedPayloadStatus = getCheckpointPayloadStatus(config, state, justifiedCheckpoint.epoch);
108
+ const justifiedPayloadStatus = isForkPostGloas
109
+ ? PayloadStatus.PENDING
110
+ : getCheckpointPayloadStatus(config, state, justifiedCheckpoint.epoch);
109
111
 
110
112
  // Determine finalized checkpoint payload status
111
- const finalizedPayloadStatus = getCheckpointPayloadStatus(config, state, finalizedCheckpoint.epoch);
113
+ const finalizedPayloadStatus = isForkPostGloas
114
+ ? PayloadStatus.PENDING
115
+ : getCheckpointPayloadStatus(config, state, finalizedCheckpoint.epoch);
112
116
 
113
117
  return new forkchoiceConstructor(
114
118
  config,
@@ -1,6 +1,6 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {PayloadStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
+ import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
4
4
  import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
5
5
  import {
6
6
  IBeaconStateView,
@@ -217,11 +217,7 @@ export class PrepareNextSlotScheduler {
217
217
  // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations
218
218
  if (isEpochTransition) {
219
219
  this.metrics?.precomputeNextEpochTransition.count.inc({result: "success"}, 1);
220
- // Determine payloadPresent from head block's payload status
221
- // Pre-Gloas: payloadStatus is always FULL → payloadPresent = true
222
- // Post-Gloas: FULL → true, EMPTY → false, PENDING → false (conservative, treat as block state)
223
- const payloadPresent = headBlock.payloadStatus === PayloadStatus.FULL;
224
- const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch, payloadPresent);
220
+ const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch);
225
221
  if (previousHits === 0) {
226
222
  this.metrics?.precomputeNextEpochTransition.waste.inc();
227
223
  }
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  DataAvailabilityStatus,
3
3
  ExecutionPayloadStatus,
4
- G2_POINT_AT_INFINITY,
5
4
  IBeaconStateView,
6
- IBeaconStateViewGloas,
7
5
  StateHashTreeRootSource,
8
6
  } from "@lodestar/state-transition";
9
- import {BeaconBlock, BlindedBeaconBlock, Gwei, Root, gloas} from "@lodestar/types";
7
+ import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types";
10
8
  import {ZERO_HASH} from "../../constants/index.js";
11
9
  import {Metrics} from "../../metrics/index.js";
12
10
 
@@ -19,11 +17,11 @@ export function computeNewStateRoot(
19
17
  metrics: Metrics | null,
20
18
  state: IBeaconStateView,
21
19
  block: BeaconBlock | BlindedBeaconBlock
22
- ): {newStateRoot: Root; proposerReward: Gwei; postBlockState: IBeaconStateView} {
20
+ ): {newStateRoot: Root; proposerReward: Gwei; postState: IBeaconStateView} {
23
21
  // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
24
22
  const blockEmptySig = {message: block, signature: ZERO_HASH};
25
23
 
26
- const postBlockState = state.stateTransition(
24
+ const postState = state.stateTransition(
27
25
  blockEmptySig,
28
26
  {
29
27
  // ExecutionPayloadStatus.valid: Assume payload valid, it has been produced by a trusted EL
@@ -42,49 +40,14 @@ export function computeNewStateRoot(
42
40
  {metrics}
43
41
  );
44
42
 
45
- const {attestations, syncAggregate, slashing} = postBlockState.proposerRewards;
43
+ const {attestations, syncAggregate, slashing} = postState.proposerRewards;
46
44
  const proposerReward = BigInt(attestations + syncAggregate + slashing);
47
45
 
48
46
  const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
49
47
  source: StateHashTreeRootSource.computeNewStateRoot,
50
48
  });
51
- const newStateRoot = postBlockState.hashTreeRoot();
49
+ const newStateRoot = postState.hashTreeRoot();
52
50
  hashTreeRootTimer?.();
53
51
 
54
- return {newStateRoot, proposerReward, postBlockState};
55
- }
56
-
57
- /**
58
- * Compute the state root after processing an execution payload envelope.
59
- * Similar to `computeNewStateRoot` but for payload envelope processing.
60
- *
61
- */
62
- export function computePayloadEnvelopeStateRoot(
63
- metrics: Metrics | null,
64
- postBlockState: IBeaconStateViewGloas,
65
- envelope: gloas.ExecutionPayloadEnvelope
66
- ): Root {
67
- const signedEnvelope: gloas.SignedExecutionPayloadEnvelope = {
68
- message: envelope,
69
- signature: G2_POINT_AT_INFINITY,
70
- };
71
-
72
- const processEnvelopeTimer = metrics?.blockPayload.executionPayloadEnvelopeProcessingTime.startTimer();
73
- const postPayloadState = postBlockState.processExecutionPayloadEnvelope(signedEnvelope, {
74
- // Signature is zero-ed (G2_POINT_AT_INFINITY), skip verification
75
- verifySignature: false,
76
- // State root is being computed here, the envelope doesn't have it yet
77
- verifyStateRoot: false,
78
- // Preserve cache in source state, since the resulting state is not added to the state cache
79
- dontTransferCache: true,
80
- });
81
- processEnvelopeTimer?.();
82
-
83
- const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
84
- source: StateHashTreeRootSource.computePayloadEnvelopeStateRoot,
85
- });
86
- const stateRoot = postPayloadState.hashTreeRoot();
87
- hashTreeRootTimer?.();
88
-
89
- return stateRoot;
52
+ return {newStateRoot, proposerReward, postState};
90
53
  }
@@ -111,12 +111,6 @@ export type ProduceFullGloas = {
111
111
  executionRequests: electra.ExecutionRequests;
112
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
113
  cells: fulu.Cell[][];
114
- /**
115
- * Cached payload envelope state root computed during block production.
116
- * This is the state root after running `processExecutionPayloadEnvelope` on the
117
- * post-block state, and later used to construct the `ExecutionPayloadEnvelope`.
118
- */
119
- payloadEnvelopeStateRoot: Root;
120
114
  };
121
115
  export type ProduceFullFulu = {
122
116
  type: BlockType.Full;
@@ -1,4 +1,3 @@
1
- import {PayloadStatus} from "@lodestar/fork-choice";
2
1
  import {Root, RootHex, Slot} from "@lodestar/types";
3
2
 
4
3
  export enum RegenErrorCode {
@@ -10,8 +9,6 @@ export enum RegenErrorCode {
10
9
  BLOCK_NOT_IN_DB = "REGEN_ERROR_BLOCK_NOT_IN_DB",
11
10
  STATE_TRANSITION_ERROR = "REGEN_ERROR_STATE_TRANSITION_ERROR",
12
11
  INVALID_STATE_ROOT = "REGEN_ERROR_INVALID_STATE_ROOT",
13
- UNEXPECTED_PAYLOAD_STATUS = "REGEN_ERROR_UNEXPECTED_PAYLOAD_STATUS",
14
- INTERNAL_ERROR = "REGEN_ERROR_INTERNAL_ERROR",
15
12
  }
16
13
 
17
14
  export type RegenErrorType =
@@ -22,9 +19,7 @@ export type RegenErrorType =
22
19
  | {code: RegenErrorCode.TOO_MANY_BLOCK_PROCESSED; stateRoot: RootHex | Root}
23
20
  | {code: RegenErrorCode.BLOCK_NOT_IN_DB; blockRoot: RootHex | Root}
24
21
  | {code: RegenErrorCode.STATE_TRANSITION_ERROR; error: Error}
25
- | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex}
26
- | {code: RegenErrorCode.UNEXPECTED_PAYLOAD_STATUS; blockRoot: RootHex | Root; payloadStatus: PayloadStatus}
27
- | {code: RegenErrorCode.INTERNAL_ERROR; message: string};
22
+ | {code: RegenErrorCode.INVALID_STATE_ROOT; slot: Slot; expected: RootHex; actual: RootHex};
28
23
 
29
24
  export class RegenError extends Error {
30
25
  type: RegenErrorType;
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {IBeaconStateView} from "@lodestar/state-transition";
4
4
  import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
5
- import {CheckpointHexPayload} from "../stateCache/types.js";
5
+ import {CheckpointHex} from "../stateCache/types.js";
6
6
 
7
7
  export enum RegenCaller {
8
8
  getDuties = "getDuties",
@@ -40,21 +40,15 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
40
40
  dumpCacheSummary(): routes.lodestar.StateCacheItem[];
41
41
  getStateSync(stateRoot: RootHex): IBeaconStateView | null;
42
42
  getPreStateSync(block: BeaconBlock): IBeaconStateView | null;
43
- getCheckpointStateOrBytes(cp: CheckpointHexPayload): Promise<IBeaconStateView | Uint8Array | null>;
44
- getCheckpointStateSync(cp: CheckpointHexPayload): IBeaconStateView | null;
43
+ getCheckpointStateOrBytes(cp: CheckpointHex): Promise<IBeaconStateView | Uint8Array | null>;
44
+ getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
45
45
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
46
46
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
47
47
  pruneOnFinalized(finalizedEpoch: Epoch): void;
48
- processBlockState(blockRootHex: RootHex, postState: IBeaconStateView): void;
49
- processPayloadState(payloadState: IBeaconStateView): void;
50
- /**
51
- * payloadPresent is true if this is payload state, false if block state.
52
- * payloadPresent is always true for pre-gloas.
53
- */
54
- addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView, payloadPresent: boolean): void;
48
+ processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
49
+ addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
55
50
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void;
56
- updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch, payloadPresent: boolean): number | null;
57
- upgradeForGloas(epoch: Epoch): void;
51
+ updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
58
52
  }
59
53
 
60
54
  /**