@lodestar/beacon-node 1.42.0-dev.b10dfaca8d → 1.42.0-dev.b2b4af3e21

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 (67) 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 -1
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  5. package/lib/chain/archiveStore/archiveStore.js +1 -0
  6. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  7. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +3 -3
  8. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +6 -4
  10. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  11. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts +2 -2
  12. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  13. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
  14. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  15. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
  16. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  18. package/lib/chain/archiveStore/historicalState/worker.js +1 -4
  19. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  20. package/lib/chain/archiveStore/interface.d.ts +1 -0
  21. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  22. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  23. package/lib/chain/blocks/importBlock.js +17 -17
  24. package/lib/chain/blocks/importBlock.js.map +1 -1
  25. package/lib/chain/blocks/importExecutionPayload.d.ts +10 -8
  26. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  27. package/lib/chain/blocks/importExecutionPayload.js +76 -48
  28. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  29. package/lib/chain/blocks/index.d.ts.map +1 -1
  30. package/lib/chain/blocks/index.js +2 -1
  31. package/lib/chain/blocks/index.js.map +1 -1
  32. package/lib/chain/blocks/types.d.ts +20 -14
  33. package/lib/chain/blocks/types.d.ts.map +1 -1
  34. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +2 -2
  35. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  36. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  37. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  38. package/lib/chain/options.d.ts +1 -0
  39. package/lib/chain/options.d.ts.map +1 -1
  40. package/lib/chain/options.js +1 -0
  41. package/lib/chain/options.js.map +1 -1
  42. package/lib/network/libp2p/index.d.ts.map +1 -1
  43. package/lib/network/libp2p/index.js +22 -11
  44. package/lib/network/libp2p/index.js.map +1 -1
  45. package/lib/network/options.d.ts.map +1 -1
  46. package/lib/network/options.js +7 -2
  47. package/lib/network/options.js.map +1 -1
  48. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  49. package/lib/network/processor/gossipHandlers.js +15 -8
  50. package/lib/network/processor/gossipHandlers.js.map +1 -1
  51. package/package.json +16 -16
  52. package/src/api/impl/beacon/blocks/index.ts +15 -1
  53. package/src/chain/archiveStore/archiveStore.ts +1 -0
  54. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +6 -5
  55. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +2 -1
  56. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  57. package/src/chain/archiveStore/historicalState/worker.ts +1 -5
  58. package/src/chain/archiveStore/interface.ts +1 -0
  59. package/src/chain/blocks/importBlock.ts +18 -17
  60. package/src/chain/blocks/importExecutionPayload.ts +84 -53
  61. package/src/chain/blocks/index.ts +2 -1
  62. package/src/chain/blocks/types.ts +25 -14
  63. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +4 -4
  64. package/src/chain/options.ts +2 -0
  65. package/src/network/libp2p/index.ts +24 -13
  66. package/src/network/options.ts +7 -2
  67. package/src/network/processor/gossipHandlers.ts +16 -8
@@ -3,6 +3,7 @@ import {routes} from "@lodestar/api";
3
3
  import {
4
4
  AncestorStatus,
5
5
  EpochDifference,
6
+ ExecutionStatus,
6
7
  ForkChoiceError,
7
8
  ForkChoiceErrorCode,
8
9
  NotReorgedReason,
@@ -84,7 +85,7 @@ export async function importBlock(
84
85
  fullyVerifiedBlock: FullyVerifiedBlock,
85
86
  opts: ImportBlockOpts
86
87
  ): Promise<void> {
87
- const {blockInput, postState, parentBlockSlot, executionStatus, dataAvailabilityStatus, indexedAttestations} =
88
+ const {blockInput, postBlockState, parentBlockSlot, executionStatus, dataAvailabilityStatus, indexedAttestations} =
88
89
  fullyVerifiedBlock;
89
90
  const block = blockInput.getBlock();
90
91
  const source = blockInput.getBlockSource();
@@ -96,7 +97,7 @@ export async function importBlock(
96
97
  const blockEpoch = computeEpochAtSlot(blockSlot);
97
98
  const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
98
99
  const blockDelaySec =
99
- fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
100
+ fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postBlockState.genesisTime);
100
101
  const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
101
102
  const fork = this.config.getForkSeq(blockSlot);
102
103
 
@@ -119,13 +120,13 @@ export async function importBlock(
119
120
  // 2. Import block to fork choice
120
121
 
121
122
  // Should compute checkpoint balances before forkchoice.onBlock
122
- this.checkpointBalancesCache.processState(blockRootHex, postState);
123
+ this.checkpointBalancesCache.processState(blockRootHex, postBlockState);
123
124
  const blockSummary = this.forkChoice.onBlock(
124
125
  block.message,
125
- postState,
126
+ postBlockState,
126
127
  blockDelaySec,
127
128
  currentSlot,
128
- executionStatus,
129
+ fork >= ForkSeq.gloas ? ExecutionStatus.PayloadSeparated : executionStatus,
129
130
  dataAvailabilityStatus
130
131
  );
131
132
 
@@ -135,7 +136,7 @@ export async function importBlock(
135
136
  // Post-Gloas: blockSummary.payloadStatus is always PENDING, so payloadPresent = false (block state only, no payload processing yet)
136
137
  const payloadPresent = !isGloasBlock(blockSummary);
137
138
  // processState manages both block state and payload state variants together for memory/disk management
138
- this.regen.processBlockState(blockRootHex, postState);
139
+ this.regen.processBlockState(blockRootHex, postBlockState);
139
140
 
140
141
  // For Gloas blocks, create PayloadEnvelopeInput so it's available for later payload import
141
142
  if (fork >= ForkSeq.gloas) {
@@ -171,7 +172,7 @@ export async function importBlock(
171
172
  (opts.importAttestations !== AttestationImportOpt.Skip && blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT)
172
173
  ) {
173
174
  const attestations = block.message.body.attestations;
174
- const rootCache = new RootCache(postState);
175
+ const rootCache = new RootCache(postBlockState);
175
176
  const invalidAttestationErrorsByCode = new Map<string, {error: Error; count: number}>();
176
177
 
177
178
  const addAttestation = fork >= ForkSeq.electra ? addAttestationPostElectra : addAttestationPreElectra;
@@ -185,7 +186,7 @@ export async function importBlock(
185
186
  const attDataRoot = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(indexedAttestation.data));
186
187
  addAttestation.call(
187
188
  this,
188
- postState,
189
+ postBlockState,
189
190
  target,
190
191
  attDataRoot,
191
192
  attestation as Attestation<ForkPostElectra>,
@@ -300,7 +301,7 @@ export async function importBlock(
300
301
 
301
302
  if (newHead.blockRoot !== oldHead.blockRoot) {
302
303
  // Set head state as strong reference
303
- this.regen.updateHeadState(newHead, postState);
304
+ this.regen.updateHeadState(newHead, postBlockState);
304
305
 
305
306
  try {
306
307
  this.emitter.emit(routes.events.EventType.head, {
@@ -372,7 +373,7 @@ export async function importBlock(
372
373
  try {
373
374
  this.lightClientServer?.onImportBlockHead(
374
375
  block.message as BeaconBlock<ForkPostAltair>,
375
- postState,
376
+ postBlockState,
376
377
  parentBlockSlot
377
378
  );
378
379
  } catch (e) {
@@ -393,11 +394,11 @@ export async function importBlock(
393
394
  // and the block is weak and can potentially be reorged out.
394
395
  let shouldOverrideFcu = false;
395
396
 
396
- if (blockSlot >= currentSlot && postState.isExecutionStateType) {
397
+ if (blockSlot >= currentSlot && postBlockState.isExecutionStateType) {
397
398
  let notOverrideFcuReason = NotReorgedReason.Unknown;
398
399
  const proposalSlot = blockSlot + 1;
399
400
  try {
400
- const proposerIndex = postState.getBeaconProposer(proposalSlot);
401
+ const proposerIndex = postBlockState.getBeaconProposer(proposalSlot);
401
402
  const feeRecipient = this.beaconProposerCache.get(proposerIndex);
402
403
 
403
404
  if (feeRecipient) {
@@ -477,20 +478,20 @@ export async function importBlock(
477
478
  }
478
479
  }
479
480
 
480
- if (!postState.isStateValidatorsNodesPopulated()) {
481
- this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postState.slot});
481
+ if (!postBlockState.isStateValidatorsNodesPopulated()) {
482
+ this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postBlockState.slot});
482
483
  }
483
484
 
484
485
  // Cache shufflings when crossing an epoch boundary
485
486
  const parentEpoch = computeEpochAtSlot(parentBlockSlot);
486
487
  if (parentEpoch < blockEpoch) {
487
- this.shufflingCache.processState(postState);
488
+ this.shufflingCache.processState(postBlockState);
488
489
  this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot});
489
490
  }
490
491
 
491
492
  if (blockSlot % SLOTS_PER_EPOCH === 0) {
492
493
  // Cache state to preserve epoch transition work
493
- const checkpointState = postState;
494
+ const checkpointState = postBlockState;
494
495
  const cp = getCheckpointFromState(checkpointState);
495
496
  this.regen.addCheckpointState(cp, checkpointState, payloadPresent);
496
497
  // consumers should not mutate state ever
@@ -584,7 +585,7 @@ export async function importBlock(
584
585
  this.validatorMonitor?.registerSyncAggregateInBlock(
585
586
  blockEpoch,
586
587
  (block as altair.SignedBeaconBlock).message.body.syncAggregate,
587
- fullyVerifiedBlock.postState.currentSyncCommitteeIndexed.validatorIndices
588
+ fullyVerifiedBlock.postBlockState.currentSyncCommitteeIndexed.validatorIndices
588
589
  );
589
590
  }
590
591
 
@@ -1,5 +1,6 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {ForkName} from "@lodestar/params";
2
+ import {ExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
+ import {SLOTS_PER_EPOCH} from "@lodestar/params";
3
4
  import {getExecutionPayloadEnvelopeSignatureSet} from "@lodestar/state-transition";
4
5
  import {byteArrayEquals, fromHex, toRootHex} from "@lodestar/utils";
5
6
  import {ExecutionPayloadStatus} from "../../execution/index.js";
@@ -51,18 +52,33 @@ export class PayloadError extends Error {
51
52
  }
52
53
  }
53
54
 
55
+ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExecutionStatus {
56
+ switch (status) {
57
+ case ExecutionPayloadStatus.VALID:
58
+ return ExecutionStatus.Valid;
59
+ // TODO GLOAS: Handle optimistic import for payload
60
+ case ExecutionPayloadStatus.SYNCING:
61
+ case ExecutionPayloadStatus.ACCEPTED:
62
+ return ExecutionStatus.Syncing;
63
+ default:
64
+ throw new Error(`Unexpected execution payload status for fork choice: ${status}`);
65
+ }
66
+ }
67
+
54
68
  /**
55
69
  * Import an execution payload envelope after all data is available.
56
70
  *
57
71
  * This function:
58
- * 1. Gets the ProtoBlock from fork choice
59
- * 2. Applies write-queue backpressure (waitForSpace) early, before verification
60
- * 3. Regenerates the block state
61
- * 4. Runs EL verification (notifyNewPayload) in parallel with signature verification and processExecutionPayloadEnvelope
62
- * 5. Persists verified payload envelope to hot DB
63
- * 6. Updates fork choice
64
- * 7. Caches the post-execution payload state
65
- * 8. Records metrics for column sources
72
+ * 1. Emits `execution_payload_available` if payload is for current slot
73
+ * 2. Gets the ProtoBlock from fork choice
74
+ * 3. Applies write-queue backpressure (waitForSpace) early, before verification
75
+ * 4. Regenerates the block state
76
+ * 5. Runs EL verification (notifyNewPayload) in parallel with signature verification and processExecutionPayloadEnvelope
77
+ * 6. Persists verified payload envelope to hot DB
78
+ * 7. Updates fork choice
79
+ * 8. Caches the post-execution payload state
80
+ * 9. Records metrics for column sources
81
+ * 10. Emits `execution_payload` for recent enough payloads after successful import
66
82
  *
67
83
  */
68
84
  export async function importExecutionPayload(
@@ -70,10 +86,24 @@ export async function importExecutionPayload(
70
86
  payloadInput: PayloadEnvelopeInput,
71
87
  opts: ImportPayloadOpts = {}
72
88
  ): Promise<void> {
73
- const envelope = payloadInput.getPayloadEnvelope();
89
+ const signedEnvelope = payloadInput.getPayloadEnvelope();
90
+ const envelope = signedEnvelope.message;
74
91
  const blockRootHex = payloadInput.blockRootHex;
92
+ const blockHashHex = payloadInput.getBlockHashHex();
93
+ const fork = this.config.getForkName(envelope.slot);
75
94
 
76
- // 1. Get ProtoBlock for parent root lookup
95
+ // 1. Emit `execution_payload_available` event at the start of import. At this point the payload input
96
+ // is already complete, so the payload and required data are available for payload attestation.
97
+ // This event is only about availability, not validity of the execution payload, hence we can emit
98
+ // it before getting a response from the execution client on whether the payload is valid or not.
99
+ if (this.clock.currentSlot === envelope.slot) {
100
+ this.emitter.emit(routes.events.EventType.executionPayloadAvailable, {
101
+ slot: envelope.slot,
102
+ blockRoot: blockRootHex,
103
+ });
104
+ }
105
+
106
+ // 2. Get ProtoBlock for parent root lookup
77
107
  const protoBlock = this.forkChoice.getBlockHexDefaultStatus(blockRootHex);
78
108
  if (!protoBlock) {
79
109
  throw new PayloadError({
@@ -82,11 +112,11 @@ export async function importExecutionPayload(
82
112
  });
83
113
  }
84
114
 
85
- // 2. Apply backpressure from the write queue early, before doing verification work.
115
+ // 3. Apply backpressure from the write queue early, before doing verification work.
86
116
  // The actual DB write is deferred until after verification succeeds.
87
117
  await this.unfinalizedPayloadEnvelopeWrites.waitForSpace();
88
118
 
89
- // 3. Get pre-state for processExecutionPayloadEnvelope
119
+ // 4. Get pre-state for processExecutionPayloadEnvelope
90
120
  // We need the block state (post-block, pre-payload) to process the envelope
91
121
  const blockState = await this.regen.getBlockSlotState(
92
122
  protoBlock,
@@ -95,16 +125,16 @@ export async function importExecutionPayload(
95
125
  RegenCaller.processBlock
96
126
  );
97
127
 
98
- // 4. Run verification steps in parallel
128
+ // 5. Run verification steps in parallel
99
129
  // Note: No data availability check needed here - importExecutionPayload is only
100
130
  // called when payloadInput.isComplete() is true, so all data is already available.
101
131
  const [execResult, signatureValid, postPayloadResult] = await Promise.all([
102
132
  this.executionEngine.notifyNewPayload(
103
- ForkName.gloas,
104
- envelope.message.payload,
133
+ fork,
134
+ envelope.payload,
105
135
  payloadInput.getVersionedHashes(),
106
136
  fromHex(protoBlock.parentRoot),
107
- envelope.message.executionRequests
137
+ envelope.executionRequests
108
138
  ),
109
139
 
110
140
  opts.validSignature === true
@@ -114,7 +144,7 @@ export async function importExecutionPayload(
114
144
  this.config,
115
145
  this.pubkeyCache,
116
146
  blockState,
117
- envelope,
147
+ signedEnvelope,
118
148
  payloadInput.proposerIndex
119
149
  );
120
150
  return this.bls.verifySignatureSets([signatureSet]);
@@ -125,7 +155,7 @@ export async function importExecutionPayload(
125
155
  (async () => {
126
156
  try {
127
157
  return {
128
- postPayloadState: blockState.processExecutionPayloadEnvelope(envelope, {
158
+ postPayloadState: blockState.processExecutionPayloadEnvelope(signedEnvelope, {
129
159
  verifySignature: false,
130
160
  verifyStateRoot: false,
131
161
  }),
@@ -142,12 +172,12 @@ export async function importExecutionPayload(
142
172
  })(),
143
173
  ]);
144
174
 
145
- // 4b. Check signature verification result
175
+ // 5a. Check signature verification result
146
176
  if (!signatureValid) {
147
177
  throw new PayloadError({code: PayloadErrorCode.INVALID_SIGNATURE});
148
178
  }
149
179
 
150
- // 5. Handle EL response
180
+ // 5b. Handle EL response
151
181
  switch (execResult.status) {
152
182
  case ExecutionPayloadStatus.VALID:
153
183
  break;
@@ -161,12 +191,7 @@ export async function importExecutionPayload(
161
191
 
162
192
  case ExecutionPayloadStatus.ACCEPTED:
163
193
  case ExecutionPayloadStatus.SYNCING:
164
- // TODO GLOAS: Handle optimistic import for payload - for now treat as error
165
- throw new PayloadError({
166
- code: PayloadErrorCode.EXECUTION_ENGINE_ERROR,
167
- execStatus: execResult.status,
168
- errorMessage: execResult.validationError ?? "EL syncing, payload not yet validated",
169
- });
194
+ break;
170
195
 
171
196
  case ExecutionPayloadStatus.INVALID_BLOCK_HASH:
172
197
  case ExecutionPayloadStatus.ELERROR:
@@ -178,59 +203,65 @@ export async function importExecutionPayload(
178
203
  });
179
204
  }
180
205
 
181
- // 5b. Verify envelope state root matches post-state
206
+ // 5c. Verify envelope state root matches post-state
182
207
  const postPayloadState = postPayloadResult.postPayloadState;
183
208
  const postPayloadStateRoot = postPayloadState.hashTreeRoot();
184
- if (!byteArrayEquals(envelope.message.stateRoot, postPayloadStateRoot)) {
209
+ if (!byteArrayEquals(envelope.stateRoot, postPayloadStateRoot)) {
185
210
  throw new PayloadError({
186
211
  code: PayloadErrorCode.STATE_TRANSITION_ERROR,
187
- message: `Envelope state root mismatch expected=${toRootHex(envelope.message.stateRoot)} actual=${toRootHex(postPayloadStateRoot)}`,
212
+ message: `Envelope state root mismatch expected=${toRootHex(envelope.stateRoot)} actual=${toRootHex(postPayloadStateRoot)}`,
188
213
  });
189
214
  }
190
215
 
191
- // 5c. Persist payload envelope to hot DB (performed asynchronously to avoid blocking)
216
+ // 6. Persist payload envelope to hot DB (performed asynchronously to avoid blocking)
192
217
  this.unfinalizedPayloadEnvelopeWrites.push(payloadInput).catch((e) => {
193
218
  if (!isQueueErrorAborted(e)) {
194
219
  this.logger.error(
195
220
  "Error pushing payload envelope to unfinalized write queue",
196
- {slot: payloadInput.slot, root: blockRootHex},
221
+ {slot: envelope.slot, root: blockRootHex},
197
222
  e as Error
198
223
  );
199
224
  }
200
225
  });
201
226
 
202
- // 6. Update fork choice
227
+ // 7. Update fork choice
203
228
  this.forkChoice.onExecutionPayload(
204
229
  blockRootHex,
205
- payloadInput.getBlockHashHex(),
206
- envelope.message.payload.blockNumber,
207
- toRootHex(postPayloadStateRoot)
230
+ blockHashHex,
231
+ envelope.payload.blockNumber,
232
+ toRootHex(postPayloadStateRoot),
233
+ toForkChoiceExecutionStatus(execResult.status)
208
234
  );
209
235
 
210
- // 7. Cache payload state
211
- // TODO GLOAS: Enable when PR #8868 merged (adds processPayloadState)
212
- // this.regen.processPayloadState(postPayloadState);
213
- // if epoch boundary also call
214
- // this.regen.addCheckpointState(cp, checkpointState, true);
236
+ // 8. Cache payload state
237
+ this.regen.processPayloadState(postPayloadState);
238
+ if (postPayloadState.slot % SLOTS_PER_EPOCH === 0) {
239
+ const {checkpoint} = postPayloadState.computeAnchorCheckpoint();
240
+ this.regen.addCheckpointState(checkpoint, postPayloadState, true);
241
+ }
215
242
 
216
- // 8. Record metrics for payload envelope and column sources
243
+ // 9. Record metrics for payload envelope and column sources
217
244
  this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
218
245
  for (const {source} of payloadInput.getSampledColumnsWithSource()) {
219
246
  this.metrics?.importPayload.columnsBySource.inc({source});
220
247
  }
221
248
 
222
- this.logger.verbose("Execution payload imported", {
223
- slot: payloadInput.slot,
224
- root: blockRootHex,
225
- blockHash: payloadInput.getBlockHashHex(),
226
- });
227
-
228
- // 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
229
- const currentSlot = this.clock.currentSlot;
230
- if (currentSlot - payloadInput.slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
231
- this.emitter.emit(routes.events.EventType.executionPayloadAvailable, {
232
- slot: payloadInput.slot,
249
+ // 10. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
250
+ if (this.clock.currentSlot - envelope.slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
251
+ this.emitter.emit(routes.events.EventType.executionPayload, {
252
+ slot: envelope.slot,
253
+ builderIndex: envelope.builderIndex,
254
+ blockHash: blockHashHex,
233
255
  blockRoot: blockRootHex,
256
+ stateRoot: toRootHex(envelope.stateRoot),
257
+ // TODO GLOAS: revisit once we support optimistic import
258
+ executionOptimistic: false,
234
259
  });
235
260
  }
261
+
262
+ this.logger.verbose("Execution payload imported", {
263
+ slot: envelope.slot,
264
+ root: blockRootHex,
265
+ blockHash: blockHashHex,
266
+ });
236
267
  }
@@ -88,7 +88,8 @@ export async function processBlocks(
88
88
  const fullyVerifiedBlocks = relevantBlocks.map(
89
89
  (block, i): FullyVerifiedBlock => ({
90
90
  blockInput: block,
91
- postState: postStates[i],
91
+ postBlockState: postStates[i],
92
+ postEnvelopeState: null,
92
93
  parentBlockSlot: parentSlots[i],
93
94
  executionStatus: executionStatuses[i],
94
95
  // start supporting optimistic syncing/processing
@@ -1,5 +1,5 @@
1
1
  import type {ChainForkConfig} from "@lodestar/config";
2
- import {MaybeValidExecutionStatus} from "@lodestar/fork-choice";
2
+ import {BlockExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
3
  import {ForkSeq} from "@lodestar/params";
4
4
  import {DataAvailabilityStatus, IBeaconStateView, computeEpochAtSlot} from "@lodestar/state-transition";
5
5
  import type {IndexedAttestation, Slot, fulu} from "@lodestar/types";
@@ -88,24 +88,35 @@ export type ImportBlockOpts = {
88
88
  seenTimestampSec?: number;
89
89
  };
90
90
 
91
- /**
92
- * A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and ready to import
93
- */
94
- export type FullyVerifiedBlock = {
91
+ type FullyVerifiedBlockBase = {
95
92
  blockInput: IBlockInput;
96
- postState: IBeaconStateView;
93
+ postBlockState: IBeaconStateView;
97
94
  parentBlockSlot: Slot;
98
95
  proposerBalanceDelta: number;
99
- /**
100
- * If the execution payload couldnt be verified because of EL syncing status,
101
- * used in optimistic sync or for merge block
102
- */
103
- executionStatus: MaybeValidExecutionStatus;
104
96
  dataAvailabilityStatus: DataAvailabilityStatus;
105
- /**
106
- * Pre-computed indexed attestations from signature verification to avoid duplicate work
107
- */
97
+ /** Pre-computed indexed attestations from signature verification to avoid duplicate work */
108
98
  indexedAttestations: IndexedAttestation[];
109
99
  /** Seen timestamp seconds */
110
100
  seenTimestampSec: number;
111
101
  };
102
+
103
+ /**
104
+ * A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and ready to import.
105
+ *
106
+ * Discriminated union on `postEnvelopeState`:
107
+ * - `null` → block has no pre-verified envelope; `executionStatus` is any `BlockExecutionStatus`
108
+ * - non-null → envelope was pre-verified during state transition; `executionStatus` is narrowed to
109
+ * `Valid | Syncing` (matching what `forkChoice.onExecutionPayload` expects)
110
+ */
111
+ export type FullyVerifiedBlock = FullyVerifiedBlockBase &
112
+ (
113
+ | {
114
+ postEnvelopeState: null;
115
+ /** If the execution payload couldn't be verified because of EL syncing status, used in optimistic sync or for merge block */
116
+ executionStatus: BlockExecutionStatus;
117
+ }
118
+ | {
119
+ postEnvelopeState: IBeaconStateView;
120
+ executionStatus: PayloadExecutionStatus;
121
+ }
122
+ );
@@ -1,10 +1,10 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {
3
+ BlockExecutionStatus,
3
4
  ExecutionStatus,
4
5
  IForkChoice,
5
6
  LVHInvalidResponse,
6
7
  LVHValidResponse,
7
- MaybeValidExecutionStatus,
8
8
  ProtoBlock,
9
9
  } from "@lodestar/fork-choice";
10
10
  import {ForkSeq} from "@lodestar/params";
@@ -33,7 +33,7 @@ type ExecAbortType = {blockIndex: number; execError: BlockError};
33
33
  export type SegmentExecStatus =
34
34
  | {
35
35
  execAborted: null;
36
- executionStatuses: MaybeValidExecutionStatus[];
36
+ executionStatuses: BlockExecutionStatus[];
37
37
  executionTime: number;
38
38
  }
39
39
  | {execAborted: ExecAbortType; invalidSegmentLVH?: LVHInvalidResponse};
@@ -62,7 +62,7 @@ export async function verifyBlocksExecutionPayload(
62
62
  signal: AbortSignal,
63
63
  opts: BlockProcessOpts & ImportBlockOpts
64
64
  ): Promise<SegmentExecStatus> {
65
- const executionStatuses: MaybeValidExecutionStatus[] = [];
65
+ const executionStatuses: BlockExecutionStatus[] = [];
66
66
  const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
67
67
  const lastBlock = blockInputs.at(-1);
68
68
 
@@ -103,7 +103,7 @@ export async function verifyBlocksExecutionPayload(
103
103
  return getSegmentErrorResponse({verifyResponse, blockIndex}, parentBlock, blockInputs);
104
104
  }
105
105
 
106
- // If we are here then its because executionStatus is one of MaybeValidExecutionStatus
106
+ // If we are here then its because executionStatus is one of BlockExecutionStatus
107
107
  const {executionStatus} = verifyResponse;
108
108
  executionStatuses.push(executionStatus);
109
109
  }
@@ -47,6 +47,7 @@ export type IChainOptions = BlockProcessOpts &
47
47
  minSameMessageSignatureSetsToBatch: number;
48
48
  archiveDateEpochs?: number;
49
49
  nHistoricalStatesFileDataStore?: boolean;
50
+ nativeStateView?: boolean;
50
51
  };
51
52
 
52
53
  export type BlockProcessOpts = {
@@ -124,6 +125,7 @@ export const defaultChainOptions: IChainOptions = {
124
125
  // - users can prune the persisted checkpoint state files manually to save disc space
125
126
  // - it helps debug easier when network is unfinalized
126
127
  nHistoricalStatesFileDataStore: true,
128
+ nativeStateView: false,
127
129
  maxBlockStates: DEFAULT_MAX_BLOCK_STATES,
128
130
  maxCPStateEpochsInMemory: DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY,
129
131
  maxCPStateEpochsOnDisk: DEFAULT_MAX_CP_STATE_ON_DISK,
@@ -44,6 +44,8 @@ export async function createNodeJsLibp2p(
44
44
  ): Promise<Libp2p> {
45
45
  const localMultiaddrs = networkOpts.localMultiaddrs || defaultNetworkOptions.localMultiaddrs;
46
46
  const disconnectThreshold = networkOpts.disconnectThreshold ?? defaultNetworkOptions.disconnectThreshold;
47
+ const tcpEnabled = networkOpts.tcp ?? defaultNetworkOptions.tcp;
48
+ const quicEnabled = networkOpts.quic ?? defaultNetworkOptions.quic;
47
49
  const {peerStoreDir, disablePeerDiscovery} = nodeJsLibp2pOpts;
48
50
 
49
51
  let datastore: undefined | Eth2PeerDataStore = undefined;
@@ -58,7 +60,7 @@ export async function createNodeJsLibp2p(
58
60
  ...(networkOpts.bootMultiaddrs ?? defaultNetworkOptions.bootMultiaddrs ?? []),
59
61
  // Append discv5.bootEnrs to bootMultiaddrs if requested
60
62
  ...(networkOpts.connectToDiscv5Bootnodes
61
- ? await getDiscv5Multiaddrs(networkOpts.discv5?.bootEnrs ?? [], networkOpts.quic)
63
+ ? await getDiscv5Multiaddrs(networkOpts.discv5?.bootEnrs ?? [], quicEnabled)
62
64
  : []),
63
65
  ];
64
66
 
@@ -71,7 +73,7 @@ export async function createNodeJsLibp2p(
71
73
  }
72
74
  }
73
75
  const transports: Libp2pInit["transports"] = [];
74
- if (networkOpts.tcp ?? true) {
76
+ if (tcpEnabled) {
75
77
  transports.unshift(
76
78
  tcp({
77
79
  // Reject connections when the server's connection count gets high
@@ -87,17 +89,26 @@ export async function createNodeJsLibp2p(
87
89
  })
88
90
  );
89
91
  }
90
- if (networkOpts.quic) {
91
- transports.unshift(
92
- quic({
93
- handshakeTimeout: 5_000,
94
- maxIdleTimeout: 10_000,
95
- keepAliveInterval: 5_000,
96
- maxConcurrentStreamLimit: 256,
97
- maxStreamData: 10_000_000,
98
- maxConnectionData: 15_000_000,
99
- })
100
- );
92
+ if (quicEnabled) {
93
+ const quicMultiaddrs = localMultiaddrs.filter((ma) => ma.includes("/quic-v1"));
94
+ const hasIpv4Quic = quicMultiaddrs.some((ma) => ma.includes("/ip4/"));
95
+ const hasIpv6Quic = quicMultiaddrs.some((ma) => ma.includes("/ip6/"));
96
+ // Only add QUIC transport if at least one QUIC listen address is configured,
97
+ // otherwise the transport constructor will throw
98
+ if (hasIpv4Quic || hasIpv6Quic) {
99
+ transports.unshift(
100
+ quic({
101
+ handshakeTimeout: 5_000,
102
+ maxIdleTimeout: 10_000,
103
+ keepAliveInterval: 5_000,
104
+ maxConcurrentStreamLimit: 256,
105
+ maxStreamData: 10_000_000,
106
+ maxConnectionData: 15_000_000,
107
+ ipv4: hasIpv4Quic,
108
+ ipv6: hasIpv6Quic,
109
+ })
110
+ );
111
+ }
101
112
  }
102
113
 
103
114
  const noiseCrypto = {
@@ -55,7 +55,12 @@ export const defaultNetworkOptions: NetworkOptions = {
55
55
  maxPeers: 210, // Allow some room above targetPeers for new inbound peers
56
56
  targetPeers: 200,
57
57
  // In CLI usage this is typically overridden; when unset it serves as a fallback default (e.g. programmatic usage/tests)
58
- localMultiaddrs: ["/ip4/0.0.0.0/tcp/9000", "/ip6/::/tcp/9000"],
58
+ localMultiaddrs: [
59
+ "/ip4/0.0.0.0/udp/9001/quic-v1",
60
+ "/ip6/::/udp/9001/quic-v1",
61
+ "/ip4/0.0.0.0/tcp/9000",
62
+ "/ip6/::/tcp/9000",
63
+ ],
59
64
  bootMultiaddrs: [],
60
65
  /** disabled by default */
61
66
  discv5: null,
@@ -69,7 +74,7 @@ export const defaultNetworkOptions: NetworkOptions = {
69
74
  slotsToSubscribeBeforeAggregatorDuty: 2,
70
75
  // This will enable the light client server by default
71
76
  disableLightClientServer: false,
72
- quic: false,
77
+ quic: true,
73
78
  tcp: true,
74
79
  // specific option for fulu
75
80
  // - this is the same to TARGET_SUBNET_PEERS
@@ -841,16 +841,17 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
841
841
  seenTimestampSec,
842
842
  }: GossipHandlerParamGeneric<GossipType.execution_payload>) => {
843
843
  const {serializedData} = gossipData;
844
- const executionPayloadEnvelope = sszDeserialize(topic, serializedData);
844
+ const signedEnvelope = sszDeserialize(topic, serializedData);
845
+ const envelope = signedEnvelope.message;
845
846
  // TODO GLOAS: handle BLOCK_ROOT_UNKNOWN error to trigger sync
846
- await validateGossipExecutionPayloadEnvelope(chain, executionPayloadEnvelope);
847
+ await validateGossipExecutionPayloadEnvelope(chain, signedEnvelope);
847
848
 
848
- const slot = executionPayloadEnvelope.message.slot;
849
+ const slot = envelope.slot;
849
850
  const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
850
851
  metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
851
- chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, executionPayloadEnvelope);
852
+ chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
852
853
 
853
- const blockRootHex = toRootHex(executionPayloadEnvelope.message.beaconBlockRoot);
854
+ const blockRootHex = toRootHex(envelope.beaconBlockRoot);
854
855
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
855
856
 
856
857
  if (!payloadInput) {
@@ -861,16 +862,23 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
861
862
  });
862
863
  }
863
864
 
864
- chain.serializedCache.set(executionPayloadEnvelope, serializedData);
865
+ chain.serializedCache.set(signedEnvelope, serializedData);
865
866
 
866
867
  payloadInput.addPayloadEnvelope({
867
- envelope: executionPayloadEnvelope,
868
+ envelope: signedEnvelope,
868
869
  source: PayloadEnvelopeInputSource.gossip,
869
870
  seenTimestampSec,
870
871
  peerIdStr,
871
872
  });
872
873
 
873
- // TODO GLOAS: Emit execution_payload_gossip event for gossip receipt.
874
+ chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
875
+ slot,
876
+ builderIndex: envelope.builderIndex,
877
+ blockHash: toRootHex(envelope.payload.blockHash),
878
+ blockRoot: blockRootHex,
879
+ stateRoot: toRootHex(envelope.stateRoot),
880
+ });
881
+
874
882
  chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
875
883
  chain.logger.debug("Error processing execution payload from gossip", {slot, root: blockRootHex}, e as Error);
876
884
  });