@lodestar/beacon-node 1.43.0-dev.38479366cc → 1.43.0-dev.3bcc6d0ad5

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 (162) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -3
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +66 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  11. package/lib/chain/blocks/importBlock.js +10 -21
  12. package/lib/chain/blocks/importBlock.js.map +1 -1
  13. package/lib/chain/blocks/importExecutionPayload.d.ts +5 -3
  14. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.js +23 -10
  16. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  17. package/lib/chain/blocks/index.d.ts.map +1 -1
  18. package/lib/chain/blocks/index.js +7 -5
  19. package/lib/chain/blocks/index.js.map +1 -1
  20. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +1 -0
  21. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  22. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +5 -1
  23. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  25. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  26. package/lib/chain/blocks/verifyBlock.d.ts +2 -1
  27. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  28. package/lib/chain/blocks/verifyBlock.js +26 -7
  29. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  30. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  31. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  32. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  33. package/lib/chain/chain.d.ts +2 -1
  34. package/lib/chain/chain.d.ts.map +1 -1
  35. package/lib/chain/chain.js +5 -1
  36. package/lib/chain/chain.js.map +1 -1
  37. package/lib/chain/emitter.d.ts +0 -11
  38. package/lib/chain/emitter.d.ts.map +1 -1
  39. package/lib/chain/emitter.js +0 -4
  40. package/lib/chain/emitter.js.map +1 -1
  41. package/lib/chain/errors/index.d.ts +1 -0
  42. package/lib/chain/errors/index.d.ts.map +1 -1
  43. package/lib/chain/errors/index.js +1 -0
  44. package/lib/chain/errors/index.js.map +1 -1
  45. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  46. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  47. package/lib/chain/errors/proposerPreferences.js +13 -0
  48. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  49. package/lib/chain/interface.d.ts +2 -1
  50. package/lib/chain/interface.d.ts.map +1 -1
  51. package/lib/chain/interface.js.map +1 -1
  52. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  53. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  54. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  55. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  56. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  57. package/lib/chain/prepareNextSlot.js +15 -17
  58. package/lib/chain/prepareNextSlot.js.map +1 -1
  59. package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -3
  60. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  61. package/lib/chain/produceBlock/produceBlockBody.js +33 -22
  62. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  63. package/lib/chain/regen/interface.d.ts +1 -0
  64. package/lib/chain/regen/interface.d.ts.map +1 -1
  65. package/lib/chain/regen/interface.js +1 -0
  66. package/lib/chain/regen/interface.js.map +1 -1
  67. package/lib/chain/seenCache/index.d.ts +1 -0
  68. package/lib/chain/seenCache/index.d.ts.map +1 -1
  69. package/lib/chain/seenCache/index.js +1 -0
  70. package/lib/chain/seenCache/index.js.map +1 -1
  71. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +8 -2
  72. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  73. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -4
  74. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  75. package/lib/chain/seenCache/seenProposerPreferences.d.ts +15 -0
  76. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  77. package/lib/chain/seenCache/seenProposerPreferences.js +25 -0
  78. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  79. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  80. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  81. package/lib/chain/validation/proposerPreferences.js +69 -0
  82. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  83. package/lib/network/gossip/interface.d.ts +7 -1
  84. package/lib/network/gossip/interface.d.ts.map +1 -1
  85. package/lib/network/gossip/interface.js +1 -0
  86. package/lib/network/gossip/interface.js.map +1 -1
  87. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  88. package/lib/network/gossip/scoringParameters.js +12 -1
  89. package/lib/network/gossip/scoringParameters.js.map +1 -1
  90. package/lib/network/gossip/topic.d.ts +8 -0
  91. package/lib/network/gossip/topic.d.ts.map +1 -1
  92. package/lib/network/gossip/topic.js +6 -0
  93. package/lib/network/gossip/topic.js.map +1 -1
  94. package/lib/network/interface.d.ts +1 -0
  95. package/lib/network/interface.d.ts.map +1 -1
  96. package/lib/network/network.d.ts +1 -0
  97. package/lib/network/network.d.ts.map +1 -1
  98. package/lib/network/network.js +5 -0
  99. package/lib/network/network.js.map +1 -1
  100. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  101. package/lib/network/processor/gossipHandlers.js +22 -15
  102. package/lib/network/processor/gossipHandlers.js.map +1 -1
  103. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  104. package/lib/network/processor/gossipQueues/index.js +5 -0
  105. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  106. package/lib/network/processor/index.d.ts.map +1 -1
  107. package/lib/network/processor/index.js +6 -5
  108. package/lib/network/processor/index.js.map +1 -1
  109. package/lib/sync/range/batch.d.ts +11 -0
  110. package/lib/sync/range/batch.d.ts.map +1 -1
  111. package/lib/sync/range/batch.js +29 -4
  112. package/lib/sync/range/batch.js.map +1 -1
  113. package/lib/sync/range/chain.d.ts.map +1 -1
  114. package/lib/sync/range/chain.js +23 -5
  115. package/lib/sync/range/chain.js.map +1 -1
  116. package/lib/sync/unknownBlock.d.ts +0 -2
  117. package/lib/sync/unknownBlock.d.ts.map +1 -1
  118. package/lib/sync/unknownBlock.js +0 -47
  119. package/lib/sync/unknownBlock.js.map +1 -1
  120. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  121. package/lib/sync/utils/downloadByRange.js +21 -15
  122. package/lib/sync/utils/downloadByRange.js.map +1 -1
  123. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  124. package/lib/sync/utils/downloadByRoot.js +10 -0
  125. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  126. package/package.json +15 -15
  127. package/src/api/impl/beacon/blocks/index.ts +16 -3
  128. package/src/api/impl/beacon/pool/index.ts +83 -1
  129. package/src/api/impl/validator/index.ts +80 -0
  130. package/src/chain/blocks/importBlock.ts +9 -36
  131. package/src/chain/blocks/importExecutionPayload.ts +31 -10
  132. package/src/chain/blocks/index.ts +14 -6
  133. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +6 -1
  134. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  135. package/src/chain/blocks/verifyBlock.ts +39 -9
  136. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  137. package/src/chain/chain.ts +5 -0
  138. package/src/chain/emitter.ts +0 -11
  139. package/src/chain/errors/index.ts +1 -0
  140. package/src/chain/errors/proposerPreferences.ts +39 -0
  141. package/src/chain/interface.ts +2 -0
  142. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  143. package/src/chain/prepareNextSlot.ts +20 -28
  144. package/src/chain/produceBlock/produceBlockBody.ts +43 -27
  145. package/src/chain/regen/interface.ts +1 -0
  146. package/src/chain/seenCache/index.ts +1 -0
  147. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +25 -5
  148. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  149. package/src/chain/validation/proposerPreferences.ts +91 -0
  150. package/src/network/gossip/interface.ts +6 -0
  151. package/src/network/gossip/scoringParameters.ts +14 -1
  152. package/src/network/gossip/topic.ts +6 -0
  153. package/src/network/interface.ts +1 -0
  154. package/src/network/network.ts +11 -0
  155. package/src/network/processor/gossipHandlers.ts +31 -16
  156. package/src/network/processor/gossipQueues/index.ts +5 -0
  157. package/src/network/processor/index.ts +6 -5
  158. package/src/sync/range/batch.ts +54 -5
  159. package/src/sync/range/chain.ts +25 -5
  160. package/src/sync/unknownBlock.ts +0 -50
  161. package/src/sync/utils/downloadByRange.ts +21 -15
  162. package/src/sync/utils/downloadByRoot.ts +12 -0
@@ -1,7 +1,8 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {ExecutionStatus, PayloadExecutionStatus} from "@lodestar/fork-choice";
3
- import {isStatePostGloas} from "@lodestar/state-transition";
4
- import {fromHex} from "@lodestar/utils";
2
+ import {ExecutionStatus, PayloadExecutionStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
3
+ import {DataAvailabilityStatus, isStatePostGloas} from "@lodestar/state-transition";
4
+ import {fromHex, isErrorAborted} from "@lodestar/utils";
5
+ import {ZERO_HASH_HEX} from "../../constants/index.js";
5
6
  import {ExecutionPayloadStatus} from "../../execution/index.js";
6
7
  import {isQueueErrorAborted} from "../../util/queue/index.js";
7
8
  import {BeaconChain} from "../chain.js";
@@ -85,12 +86,14 @@ function toForkChoiceExecutionStatus(status: ExecutionPayloadStatus): PayloadExe
85
86
  * 4. Verify envelope (fields against state, signature, and EL in parallel where possible)
86
87
  * 5. Persist verified payload envelope to hot DB (waits for write-queue space for backpressure)
87
88
  * 6. Update fork choice (transitions the block's PENDING variant to FULL)
88
- * 7. Record metrics for payload envelope and column sources
89
- * 8. Emit `execution_payload` event
89
+ * 7. Queue notifyForkchoiceUpdate to engine api
90
+ * 8. Record metrics for payload envelope and column sources
91
+ * 9. Emit `execution_payload` event
90
92
  */
91
93
  export async function importExecutionPayload(
92
94
  this: BeaconChain,
93
95
  payloadInput: PayloadEnvelopeInput,
96
+ dataAvailabilityStatus: DataAvailabilityStatus,
94
97
  opts: ImportPayloadOpts = {}
95
98
  ): Promise<void> {
96
99
  const signedEnvelope = payloadInput.getPayloadEnvelope();
@@ -219,15 +222,33 @@ export async function importExecutionPayload(
219
222
 
220
223
  // 6. Update fork choice, transitions the block's PENDING variant to FULL
221
224
  const execStatus = toForkChoiceExecutionStatus(execResult.status);
222
- this.forkChoice.onExecutionPayload(blockRootHex, blockHashHex, envelope.payload.blockNumber, execStatus);
225
+ this.forkChoice.onExecutionPayload(
226
+ blockRootHex,
227
+ blockHashHex,
228
+ envelope.payload.blockNumber,
229
+ execStatus,
230
+ dataAvailabilityStatus
231
+ );
232
+
233
+ // 7. Queue notifyForkchoiceUpdate to engine api
234
+ const head = this.forkChoice.getHead();
235
+ if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
236
+ const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
237
+ const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
238
+ this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
239
+ if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
240
+ this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
241
+ }
242
+ });
243
+ }
223
244
 
224
- // 7. Record metrics for payload envelope and column sources
245
+ // 8. Record metrics for payload envelope and column sources
225
246
  this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
226
247
  for (const {source} of payloadInput.getSampledColumnsWithSource()) {
227
248
  this.metrics?.importPayload.columnsBySource.inc({source});
228
249
  }
229
250
 
230
- // 8. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
251
+ // 9. Emit event after payload is fully verified and imported to fork choice, only for recent enough payloads
231
252
  if (this.clock.currentSlot - slot < EVENTSTREAM_EMIT_RECENT_EXECUTION_PAYLOAD_SLOTS) {
232
253
  this.emitter.emit(routes.events.EventType.executionPayload, {
233
254
  slot,
@@ -261,6 +282,6 @@ export async function processExecutionPayload(
261
282
  signal: AbortSignal,
262
283
  opts: ImportPayloadOpts = {}
263
284
  ): Promise<void> {
264
- await verifyPayloadsDataAvailability([payloadInput], signal);
265
- await importExecutionPayload.call(this, payloadInput, opts);
285
+ const {dataAvailabilityStatuses} = await verifyPayloadsDataAvailability([payloadInput], signal);
286
+ await importExecutionPayload.call(this, payloadInput, dataAvailabilityStatuses[0], opts);
266
287
  }
@@ -90,8 +90,14 @@ export async function processBlocks(
90
90
 
91
91
  // Fully verify a block to be imported immediately after. Does not produce any side-effects besides adding intermediate
92
92
  // states in the state cache through regen.
93
- const {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock} =
94
- await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
93
+ const {
94
+ postStates,
95
+ blockDAStatuses,
96
+ payloadDAStatuses,
97
+ proposerBalanceDeltas,
98
+ segmentExecStatus,
99
+ indexedAttestationsByBlock,
100
+ } = await verifyBlocksInEpoch.call(this, parentBlock, relevantBlocks, payloadEnvelopes, opts);
95
101
 
96
102
  // If segmentExecStatus has lvhForkchoice then, the entire segment should be invalid
97
103
  // and we need to further propagate
@@ -110,7 +116,7 @@ export async function processBlocks(
110
116
  parentBlockSlot: parentSlots[i],
111
117
  executionStatus: executionStatuses[i],
112
118
  // start supporting optimistic syncing/processing
113
- dataAvailabilityStatus: dataAvailabilityStatuses[i],
119
+ dataAvailabilityStatus: blockDAStatuses[i],
114
120
  proposerBalanceDelta: proposerBalanceDeltas[i],
115
121
  indexedAttestations: indexedAttestationsByBlock[i],
116
122
  // TODO: Make this param mandatory and capture in gossip
@@ -130,9 +136,11 @@ export async function processBlocks(
130
136
  throw new Error(`Payload envelope for slot ${slot} not complete after DA verification`);
131
137
  }
132
138
  // we already awaited DA in verifyBlocksInEpoch for this segment
133
- // TODO GLOAS: may need FullyVerifiedPayload here with DatAvailabilityStatus added from here
134
- // the current flow use that data from the forkchoice pending node which is not correct
135
- await importExecutionPayload.call(this, payloadInput, {validSignature: false});
139
+ const payloadDA = payloadDAStatuses.get(slot);
140
+ if (payloadDA === undefined) {
141
+ throw new Error(`Missing payload DA status for slot ${slot}`);
142
+ }
143
+ await importExecutionPayload.call(this, payloadInput, payloadDA, {validSignature: false});
136
144
  }
137
145
 
138
146
  await nextEventLoop();
@@ -64,6 +64,7 @@ export class PayloadEnvelopeInput {
64
64
  readonly proposerIndex: ValidatorIndex;
65
65
  readonly bid: gloas.ExecutionPayloadBid;
66
66
  readonly versionedHashes: VersionedHashes;
67
+ readonly daOutOfRange: boolean;
67
68
 
68
69
  private columnsCache = new Map<ColumnIndex, ColumnWithSource>();
69
70
 
@@ -87,6 +88,7 @@ export class PayloadEnvelopeInput {
87
88
  sampledColumns: ColumnIndex[];
88
89
  custodyColumns: ColumnIndex[];
89
90
  timeCreatedSec: number;
91
+ daOutOfRange: boolean;
90
92
  }) {
91
93
  this.blockRootHex = props.blockRootHex;
92
94
  this.slot = props.slot;
@@ -97,13 +99,14 @@ export class PayloadEnvelopeInput {
97
99
  this.sampledColumns = props.sampledColumns;
98
100
  this.custodyColumns = props.custodyColumns;
99
101
  this.timeCreatedSec = props.timeCreatedSec;
102
+ this.daOutOfRange = props.daOutOfRange;
100
103
  this.payloadEnvelopeDataPromise = createPromise();
101
104
  this.allDataPromise = createPromise();
102
105
  this.columnsDataPromise = createPromise();
103
106
 
104
107
  const noBlobs = props.bid.blobKzgCommitments.length === 0;
105
108
  const noSampledColumns = props.sampledColumns.length === 0;
106
- const hasAllData = noBlobs || noSampledColumns;
109
+ const hasAllData = props.daOutOfRange || noBlobs || noSampledColumns;
107
110
 
108
111
  if (hasAllData) {
109
112
  this.state = {hasPayload: false, hasAllData: true, hasComputedAllData: true};
@@ -125,6 +128,7 @@ export class PayloadEnvelopeInput {
125
128
  sampledColumns: props.sampledColumns,
126
129
  custodyColumns: props.custodyColumns,
127
130
  timeCreatedSec: props.timeCreatedSec,
131
+ daOutOfRange: props.daOutOfRange,
128
132
  });
129
133
  }
130
134
 
@@ -152,6 +156,7 @@ export class PayloadEnvelopeInput {
152
156
  throw new Error("Payload envelope beacon_block_root mismatch");
153
157
  }
154
158
 
159
+ // TODO GLOAS: track source by metrics, maybe inside the seen cache
155
160
  const source: SourceMeta = {
156
161
  source: props.source,
157
162
  seenTimestampSec: props.seenTimestampSec,
@@ -27,6 +27,7 @@ export type CreateFromBlockProps = {
27
27
  sampledColumns: ColumnIndex[];
28
28
  custodyColumns: ColumnIndex[];
29
29
  timeCreatedSec: number;
30
+ daOutOfRange: boolean;
30
31
  };
31
32
 
32
33
  export type AddPayloadEnvelopeProps = SourceMeta & {
@@ -41,7 +41,8 @@ export async function verifyBlocksInEpoch(
41
41
  postStates: IBeaconStateView[];
42
42
  proposerBalanceDeltas: number[];
43
43
  segmentExecStatus: SegmentExecStatus;
44
- dataAvailabilityStatuses: DataAvailabilityStatus[];
44
+ blockDAStatuses: DataAvailabilityStatus[];
45
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
45
46
  indexedAttestationsByBlock: IndexedAttestation[][];
46
47
  }> {
47
48
  const blocks = blockInputs.map((blockInput) => blockInput.getBlock());
@@ -116,8 +117,12 @@ export async function verifyBlocksInEpoch(
116
117
 
117
118
  // Pick the data-availability source by fork:
118
119
  // - Pre-Gloas: blob/Fulu-column data lives in IBlockInput → verifyBlocksDataAvailability.
119
- // - Post-Gloas: verifyPayloadsDataAvailability
120
- const daAvailabilityPromise =
120
+ // - Post-Gloas: verifyPayloadsDataAvailability (payload-level DA, keyed by slot).
121
+ const daAvailabilityPromise: Promise<{
122
+ blockDAStatuses: DataAvailabilityStatus[];
123
+ payloadDAStatuses: Map<Slot, DataAvailabilityStatus>;
124
+ availableTime: number;
125
+ }> =
121
126
  fork >= ForkSeq.gloas
122
127
  ? (async () => {
123
128
  const payloadInputsForDa: PayloadEnvelopeInput[] = [];
@@ -125,19 +130,37 @@ export async function verifyBlocksInEpoch(
125
130
  const pi = payloadEnvelopes?.get(input.slot);
126
131
  if (pi !== undefined) payloadInputsForDa.push(pi);
127
132
  }
128
- await verifyPayloadsDataAvailability(payloadInputsForDa, abortController.signal);
133
+ const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
134
+ payloadInputsForDa,
135
+ abortController.signal
136
+ );
137
+ const payloadDAStatuses = new Map<Slot, DataAvailabilityStatus>();
138
+ for (let i = 0; i < payloadInputsForDa.length; i++) {
139
+ payloadDAStatuses.set(payloadInputsForDa[i].slot, dataAvailabilityStatuses[i]);
140
+ }
129
141
  return {
130
142
  // post-gloas, DataAvailabilityStatus is NotRequired for forkChoice.onBlock() ProtoBlock
131
- dataAvailabilityStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
132
- availableTime: Date.now(),
143
+ blockDAStatuses: blockInputs.map(() => DataAvailabilityStatus.NotRequired),
144
+ payloadDAStatuses,
145
+ availableTime,
133
146
  };
134
147
  })()
135
- : verifyBlocksDataAvailability(blockInputs, abortController.signal);
148
+ : (async () => {
149
+ const {dataAvailabilityStatuses, availableTime} = await verifyBlocksDataAvailability(
150
+ blockInputs,
151
+ abortController.signal
152
+ );
153
+ return {
154
+ blockDAStatuses: dataAvailabilityStatuses,
155
+ payloadDAStatuses: new Map<Slot, DataAvailabilityStatus>(),
156
+ availableTime,
157
+ };
158
+ })();
136
159
 
137
160
  // batch all I/O operations to reduce overhead
138
161
  const [
139
162
  segmentExecStatus,
140
- {dataAvailabilityStatuses, availableTime},
163
+ {blockDAStatuses, payloadDAStatuses, availableTime},
141
164
  {postStates, proposerBalanceDeltas, verifyStateTime},
142
165
  {verifySignaturesTime},
143
166
  ] = await Promise.all([
@@ -258,7 +281,14 @@ export async function verifyBlocksInEpoch(
258
281
  );
259
282
  }
260
283
 
261
- return {postStates, dataAvailabilityStatuses, proposerBalanceDeltas, segmentExecStatus, indexedAttestationsByBlock};
284
+ return {
285
+ postStates,
286
+ blockDAStatuses,
287
+ payloadDAStatuses,
288
+ proposerBalanceDeltas,
289
+ segmentExecStatus,
290
+ indexedAttestationsByBlock,
291
+ };
262
292
  } finally {
263
293
  abortController.abort();
264
294
  }
@@ -28,11 +28,14 @@ export async function verifyPayloadsDataAvailability(
28
28
  await Promise.all(promises);
29
29
 
30
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
31
+ const dataAvailabilityStatuses: DataAvailabilityStatus[] = payloadInputs.map((payloadInput) => {
32
+ if (payloadInput.daOutOfRange) {
33
+ return DataAvailabilityStatus.OutOfRange;
34
+ }
35
+ return payloadInput.getBlobKzgCommitments().length === 0
33
36
  ? DataAvailabilityStatus.NotRequired
34
- : DataAvailabilityStatus.Available
35
- );
37
+ : DataAvailabilityStatus.Available;
38
+ });
36
39
 
37
40
  return {dataAvailabilityStatuses, availableTime};
38
41
  }
@@ -106,6 +106,7 @@ import {
106
106
  SeenExecutionPayloadBids,
107
107
  SeenPayloadAttesters,
108
108
  SeenPayloadEnvelopeInput,
109
+ SeenProposerPreferences,
109
110
  SeenSyncCommitteeMessages,
110
111
  } from "./seenCache/index.js";
111
112
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -186,6 +187,7 @@ export class BeaconChain implements IBeaconChain {
186
187
  readonly seenPayloadAttesters = new SeenPayloadAttesters();
187
188
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
188
189
  readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
190
+ readonly seenProposerPreferences = new SeenProposerPreferences();
189
191
  readonly seenBlockProposers = new SeenBlockProposers();
190
192
  readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
191
193
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -334,6 +336,8 @@ export class BeaconChain implements IBeaconChain {
334
336
  logger,
335
337
  });
336
338
  this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
339
+ config,
340
+ clock,
337
341
  chainEvents: emitter,
338
342
  signal,
339
343
  serializedCache: this.serializedCache,
@@ -1437,6 +1441,7 @@ export class BeaconChain implements IBeaconChain {
1437
1441
  this.payloadAttestationPool.prune(slot);
1438
1442
  this.executionPayloadBidPool.prune(slot);
1439
1443
  this.seenExecutionPayloadBids.prune(slot);
1444
+ this.seenProposerPreferences.prune(slot);
1440
1445
  this.seenAttestationDatas.onSlot(slot);
1441
1446
  this.reprocessController.onSlot(slot);
1442
1447
 
@@ -4,7 +4,6 @@ import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {IBeaconStateView} from "@lodestar/state-transition";
6
6
  import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
7
- import {SignedExecutionPayloadEnvelope} from "@lodestar/types/gloas";
8
7
  import {PeerIdStr} from "../util/peerId.js";
9
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
10
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
@@ -60,10 +59,6 @@ export enum ChainEvent {
60
59
  * Post-gloas, missing parent could be a SignedBeaconBlock and/or a SignedExecutionPayloadEnvelope
61
60
  */
62
61
  blockUnknownParent = "blockUnknownParent",
63
- /**
64
- * Trigger BlockInputSync to find a SignedBeaconBlock given a SignedExecutionPayloadEnvelop received
65
- */
66
- envelopeUnknownBlock = "envelopeUnknownBlock",
67
62
  /**
68
63
  * Trigger BlockInputSync to find a SignedBeaconBlock with specified block root.
69
64
  */
@@ -92,11 +87,6 @@ type ApiEvents = {[K in routes.events.EventType]: (data: routes.events.EventData
92
87
 
93
88
  export type ChainEventData = {
94
89
  [ChainEvent.blockUnknownParent]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
95
- [ChainEvent.envelopeUnknownBlock]: {
96
- envelope: SignedExecutionPayloadEnvelope;
97
- peer?: PeerIdStr;
98
- source: BlockInputSource;
99
- };
100
90
  [ChainEvent.unknownBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
101
91
  [ChainEvent.incompleteBlockInput]: {blockInput: IBlockInput; peer: PeerIdStr; source: BlockInputSource};
102
92
  [ChainEvent.incompletePayloadEnvelope]: {
@@ -124,7 +114,6 @@ export type IChainEvents = ApiEvents & {
124
114
  // Sync events that are chain->chain. Initiated from network requests but do not cross the network
125
115
  // barrier so are considered ChainEvent(s).
126
116
  [ChainEvent.blockUnknownParent]: (data: ChainEventData[ChainEvent.blockUnknownParent]) => void;
127
- [ChainEvent.envelopeUnknownBlock]: (data: ChainEventData[ChainEvent.envelopeUnknownBlock]) => void;
128
117
  [ChainEvent.unknownBlockRoot]: (data: ChainEventData[ChainEvent.unknownBlockRoot]) => void;
129
118
  [ChainEvent.incompleteBlockInput]: (data: ChainEventData[ChainEvent.incompleteBlockInput]) => void;
130
119
  [ChainEvent.incompletePayloadEnvelope]: (data: ChainEventData[ChainEvent.incompletePayloadEnvelope]) => void;
@@ -8,6 +8,7 @@ export * from "./executionPayloadBid.js";
8
8
  export * from "./executionPayloadEnvelope.js";
9
9
  export * from "./gossipValidation.js";
10
10
  export * from "./payloadAttestation.js";
11
+ export * from "./proposerPreferences.js";
11
12
  export * from "./proposerSlashingError.js";
12
13
  export * from "./syncCommitteeError.js";
13
14
  export * from "./voluntaryExitError.js";
@@ -0,0 +1,39 @@
1
+ import {Slot, ValidatorIndex} from "@lodestar/types";
2
+ import {GossipActionError} from "./gossipValidation.js";
3
+
4
+ export enum ProposerPreferencesErrorCode {
5
+ INVALID_EPOCH = "PROPOSER_PREFERENCES_ERROR_INVALID_EPOCH",
6
+ PROPOSAL_SLOT_PASSED = "PROPOSER_PREFERENCES_ERROR_PROPOSAL_SLOT_PASSED",
7
+ INVALID_PROPOSER = "PROPOSER_PREFERENCES_ERROR_INVALID_PROPOSER",
8
+ ALREADY_KNOWN = "PROPOSER_PREFERENCES_ERROR_ALREADY_KNOWN",
9
+ INVALID_SIGNATURE = "PROPOSER_PREFERENCES_ERROR_INVALID_SIGNATURE",
10
+ }
11
+
12
+ export type ProposerPreferencesErrorType =
13
+ | {
14
+ code: ProposerPreferencesErrorCode.INVALID_EPOCH;
15
+ proposalSlot: Slot;
16
+ currentEpoch: number;
17
+ }
18
+ | {
19
+ code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED;
20
+ proposalSlot: Slot;
21
+ currentSlot: Slot;
22
+ }
23
+ | {
24
+ code: ProposerPreferencesErrorCode.INVALID_PROPOSER;
25
+ proposalSlot: Slot;
26
+ validatorIndex: ValidatorIndex;
27
+ }
28
+ | {
29
+ code: ProposerPreferencesErrorCode.ALREADY_KNOWN;
30
+ proposalSlot: Slot;
31
+ validatorIndex: ValidatorIndex;
32
+ }
33
+ | {
34
+ code: ProposerPreferencesErrorCode.INVALID_SIGNATURE;
35
+ proposalSlot: Slot;
36
+ validatorIndex: ValidatorIndex;
37
+ };
38
+
39
+ export class ProposerPreferencesError extends GossipActionError<ProposerPreferencesErrorType> {}
@@ -61,6 +61,7 @@ import {
61
61
  SeenContributionAndProof,
62
62
  SeenExecutionPayloadBids,
63
63
  SeenPayloadAttesters,
64
+ SeenProposerPreferences,
64
65
  SeenSyncCommitteeMessages,
65
66
  } from "./seenCache/index.js";
66
67
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -131,6 +132,7 @@ export interface IBeaconChain {
131
132
  readonly seenPayloadAttesters: SeenPayloadAttesters;
132
133
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
133
134
  readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
135
+ readonly seenProposerPreferences: SeenProposerPreferences;
134
136
  readonly seenBlockProposers: SeenBlockProposers;
135
137
  readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
136
138
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -1,7 +1,7 @@
1
1
  import {Signature, aggregateSignatures} from "@chainsafe/blst";
2
2
  import {BitArray} from "@chainsafe/ssz";
3
3
  import {ChainForkConfig} from "@lodestar/config";
4
- import {MAX_COMMITTEES_PER_SLOT, PTC_SIZE} from "@lodestar/params";
4
+ import {MAX_COMMITTEES_PER_SLOT, MAX_PAYLOAD_ATTESTATIONS, PTC_SIZE} from "@lodestar/params";
5
5
  import {RootHex, Slot, gloas} from "@lodestar/types";
6
6
  import {MapDef, toRootHex} from "@lodestar/utils";
7
7
  import {Metrics} from "../../metrics/metrics.js";
@@ -95,13 +95,9 @@ export class PayloadAttestationPool {
95
95
 
96
96
  /**
97
97
  * Get payload attestations to be included in a block.
98
- * Pick the top `maxAttestation` number of attestations with the most votes
98
+ * Pick the top `MAX_PAYLOAD_ATTESTATIONS` aggregates with the most votes.
99
99
  */
100
- getPayloadAttestationsForBlock(
101
- beaconBlockRoot: BlockRootHex,
102
- slot: Slot,
103
- maxAttestation: number
104
- ): gloas.PayloadAttestation[] {
100
+ getPayloadAttestationsForBlock(beaconBlockRoot: BlockRootHex, slot: Slot): gloas.PayloadAttestation[] {
105
101
  const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
106
102
 
107
103
  if (!aggregateByDataRootByBlockRoot) {
@@ -119,7 +115,32 @@ export class PayloadAttestationPool {
119
115
  return Array.from(aggregateByDataRoot.values())
120
116
  .slice()
121
117
  .sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
122
- .slice(0, maxAttestation)
118
+ .slice(0, MAX_PAYLOAD_ATTESTATIONS)
119
+ .map(fastToPayloadAttestation);
120
+ }
121
+
122
+ getAll(slot?: Slot): gloas.PayloadAttestation[] {
123
+ const aggregates: AggregateFast[] = [];
124
+
125
+ const addAggregates = (aggregateByDataRootByBlockRoot: Map<BlockRootHex, Map<DataRootHex, AggregateFast>>) => {
126
+ for (const aggregateByDataRoot of aggregateByDataRootByBlockRoot.values()) {
127
+ aggregates.push(...aggregateByDataRoot.values());
128
+ }
129
+ };
130
+
131
+ if (slot !== undefined) {
132
+ const aggregateByDataRootByBlockRoot = this.aggregateByDataRootByBlockRootBySlot.get(slot);
133
+ if (aggregateByDataRootByBlockRoot) {
134
+ addAggregates(aggregateByDataRootByBlockRoot);
135
+ }
136
+ } else {
137
+ for (const aggregateByDataRootByBlockRoot of this.aggregateByDataRootByBlockRootBySlot.values()) {
138
+ addAggregates(aggregateByDataRootByBlockRoot);
139
+ }
140
+ }
141
+
142
+ return aggregates
143
+ .sort((a, b) => b.aggregationBits.getTrueBitIndexes().length - a.aggregationBits.getTrueBitIndexes().length)
123
144
  .map(fastToPayloadAttestation);
124
145
  }
125
146
 
@@ -4,13 +4,14 @@ import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
4
4
  import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
5
5
  import {
6
6
  IBeaconStateView,
7
+ IBeaconStateViewBellatrix,
7
8
  StateHashTreeRootSource,
8
9
  computeEpochAtSlot,
9
10
  computeTimeAtSlot,
10
11
  isStatePostBellatrix,
11
12
  isStatePostGloas,
12
13
  } from "@lodestar/state-transition";
13
- import {Bytes32, Slot, electra} from "@lodestar/types";
14
+ import {Bytes32, Slot} from "@lodestar/types";
14
15
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
15
16
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
16
17
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -165,19 +166,26 @@ export class PrepareNextSlotScheduler {
165
166
  }
166
167
 
167
168
  let parentBlockHash: Bytes32;
168
- let isExtendingPayload = false;
169
+ // Apply parent payload once here as it's reused by EL prep and SSE emit below
170
+ let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
169
171
  if (isStatePostGloas(updatedPrepareState)) {
170
- isExtendingPayload = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot);
171
- parentBlockHash = isExtendingPayload
172
- ? updatedPrepareState.latestExecutionPayloadBid.blockHash
173
- : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
+ if (this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)) {
173
+ parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
174
+ // Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
175
+ if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
176
+ const parentExecutionRequests = await this.chain.getParentExecutionRequests(
177
+ updatedHead.slot,
178
+ updatedHead.blockRoot
179
+ );
180
+ stateAfterParentPayload = updatedPrepareState.withParentPayloadApplied(parentExecutionRequests);
181
+ }
182
+ } else {
183
+ parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
184
+ }
174
185
  } else {
175
186
  parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
176
187
  }
177
188
 
178
- // Reused by the SSE emit below to avoid a second DB lookup on cache miss
179
- let parentExecutionRequests: electra.ExecutionRequests | undefined;
180
-
181
189
  if (feeRecipient) {
182
190
  const preparationTime =
183
191
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -187,13 +195,6 @@ export class PrepareNextSlotScheduler {
187
195
  const finalizedBlockHash =
188
196
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
189
197
 
190
- if (isExtendingPayload) {
191
- parentExecutionRequests = await this.chain.getParentExecutionRequests(
192
- updatedHead.slot,
193
- updatedHead.blockRoot
194
- );
195
- }
196
-
197
198
  // awaiting here instead of throwing an async call because there is no other task
198
199
  // left for scheduler and this gives nice semantics to catch and log errors in the
199
200
  // try/catch wrapper here.
@@ -205,9 +206,8 @@ export class PrepareNextSlotScheduler {
205
206
  parentBlockHash,
206
207
  safeBlockHash,
207
208
  finalizedBlockHash,
208
- updatedPrepareState,
209
- feeRecipient,
210
- parentExecutionRequests
209
+ stateAfterParentPayload,
210
+ feeRecipient
211
211
  );
212
212
  this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
213
213
  prepareSlot,
@@ -234,20 +234,12 @@ export class PrepareNextSlotScheduler {
234
234
  (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
235
235
  this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
236
236
  ) {
237
- // if we didn't fetch above (not proposing), SSE still needs it here
238
- if (!parentExecutionRequests && isExtendingPayload) {
239
- parentExecutionRequests = await this.chain.getParentExecutionRequests(
240
- updatedHead.slot,
241
- updatedHead.blockRoot
242
- );
243
- }
244
237
  const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
245
- prepareState: updatedPrepareState,
238
+ prepareState: stateAfterParentPayload,
246
239
  prepareSlot,
247
240
  parentBlockRoot: fromHex(updatedHead.blockRoot),
248
241
  parentBlockHash,
249
242
  feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
250
- parentExecutionRequests,
251
243
  });
252
244
  this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
253
245
  }