@lodestar/beacon-node 1.44.0-rc.1 → 1.44.0-rc.3

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 (85) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +23 -28
  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 +0 -25
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/config/constants.d.ts +4 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +5 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/validator/index.d.ts.map +1 -1
  12. package/lib/api/impl/validator/index.js +0 -20
  13. package/lib/api/impl/validator/index.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  15. package/lib/chain/blocks/importBlock.js +14 -2
  16. package/lib/chain/blocks/importBlock.js.map +1 -1
  17. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  18. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  19. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  20. package/lib/chain/chain.d.ts +2 -2
  21. package/lib/chain/chain.d.ts.map +1 -1
  22. package/lib/chain/chain.js +1 -1
  23. package/lib/chain/chain.js.map +1 -1
  24. package/lib/chain/errors/executionPayloadBid.d.ts +12 -0
  25. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  26. package/lib/chain/errors/executionPayloadBid.js +2 -0
  27. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  28. package/lib/chain/interface.d.ts +2 -2
  29. package/lib/chain/interface.d.ts.map +1 -1
  30. package/lib/chain/interface.js.map +1 -1
  31. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  32. package/lib/chain/prepareNextSlot.js +0 -9
  33. package/lib/chain/prepareNextSlot.js.map +1 -1
  34. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -2
  35. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  36. package/lib/chain/produceBlock/produceBlockBody.js +4 -4
  37. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  38. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  39. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +8 -2
  40. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  41. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  42. package/lib/chain/validation/executionPayloadBid.js +23 -1
  43. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  44. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  45. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  46. package/lib/execution/engine/http.js +1 -1
  47. package/lib/execution/engine/http.js.map +1 -1
  48. package/lib/execution/engine/interface.d.ts +2 -2
  49. package/lib/execution/engine/interface.d.ts.map +1 -1
  50. package/lib/execution/engine/interface.js +6 -2
  51. package/lib/execution/engine/interface.js.map +1 -1
  52. package/lib/execution/engine/mock.d.ts.map +1 -1
  53. package/lib/execution/engine/mock.js +3 -5
  54. package/lib/execution/engine/mock.js.map +1 -1
  55. package/lib/execution/engine/types.d.ts +15 -7
  56. package/lib/execution/engine/types.d.ts.map +1 -1
  57. package/lib/execution/engine/types.js +47 -9
  58. package/lib/execution/engine/types.js.map +1 -1
  59. package/lib/network/gossip/topic.d.ts +22 -2
  60. package/lib/network/gossip/topic.d.ts.map +1 -1
  61. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  62. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +1 -2
  63. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  64. package/lib/util/sszBytes.js +1 -1
  65. package/package.json +14 -14
  66. package/src/api/impl/beacon/blocks/index.ts +27 -37
  67. package/src/api/impl/beacon/pool/index.ts +1 -41
  68. package/src/api/impl/config/constants.ts +8 -0
  69. package/src/api/impl/validator/index.ts +0 -31
  70. package/src/chain/blocks/importBlock.ts +19 -5
  71. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -5
  72. package/src/chain/chain.ts +2 -3
  73. package/src/chain/errors/executionPayloadBid.ts +14 -0
  74. package/src/chain/interface.ts +1 -2
  75. package/src/chain/prepareNextSlot.ts +0 -10
  76. package/src/chain/produceBlock/produceBlockBody.ts +7 -7
  77. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -3
  78. package/src/chain/validation/executionPayloadBid.ts +25 -1
  79. package/src/chain/validation/executionPayloadEnvelope.ts +1 -1
  80. package/src/execution/engine/http.ts +1 -1
  81. package/src/execution/engine/interface.ts +12 -2
  82. package/src/execution/engine/mock.ts +6 -5
  83. package/src/execution/engine/types.ts +75 -14
  84. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +1 -2
  85. package/src/util/sszBytes.ts +1 -1
@@ -1501,33 +1501,6 @@ export function getValidatorApi(
1501
1501
  };
1502
1502
  },
1503
1503
 
1504
- async getAggregatedAttestation({attestationDataRoot, slot}) {
1505
- notWhileSyncing();
1506
-
1507
- await waitForSlot(slot); // Must never request for a future slot > currentSlot
1508
-
1509
- const dataRootHex = toRootHex(attestationDataRoot);
1510
- const aggregate = chain.attestationPool.getAggregate(slot, dataRootHex, null);
1511
- const fork = chain.config.getForkName(slot);
1512
-
1513
- if (isForkPostElectra(fork)) {
1514
- throw new ApiError(
1515
- 400,
1516
- `Use getAggregatedAttestationV2 to retrieve aggregated attestations for post-electra fork=${fork}`
1517
- );
1518
- }
1519
-
1520
- if (!aggregate) {
1521
- throw new ApiError(404, `No aggregated attestation for slot=${slot}, dataRoot=${dataRootHex}`);
1522
- }
1523
-
1524
- metrics?.production.producedAggregateParticipants.observe(aggregate.aggregationBits.getTrueBitIndexes().length);
1525
-
1526
- return {
1527
- data: aggregate,
1528
- };
1529
- },
1530
-
1531
1504
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1532
1505
  notWhileSyncing();
1533
1506
 
@@ -1551,10 +1524,6 @@ export function getValidatorApi(
1551
1524
  };
1552
1525
  },
1553
1526
 
1554
- async publishAggregateAndProofs({signedAggregateAndProofs}) {
1555
- await this.publishAggregateAndProofsV2({signedAggregateAndProofs});
1556
- },
1557
-
1558
1527
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1559
1528
  notWhileSyncing();
1560
1529
 
@@ -22,7 +22,7 @@ import {
22
22
  } from "@lodestar/state-transition";
23
23
  import {Attestation, BeaconBlock, altair, capella, electra, isGloasBeaconBlock, phase0, ssz} from "@lodestar/types";
24
24
  import {isErrorAborted, toRootHex} from "@lodestar/utils";
25
- import {ZERO_HASH_HEX} from "../../constants/index.js";
25
+ import {GENESIS_SLOT, ZERO_HASH_HEX} from "../../constants/index.js";
26
26
  import {callInNextEventLoop} from "../../util/eventLoop.js";
27
27
  import {isOptimisticBlock} from "../../util/forkChoice.js";
28
28
  import {isQueueErrorAborted} from "../../util/queue/index.js";
@@ -189,10 +189,12 @@ export async function importBlock(
189
189
  this.seenBlockAttesters.addIndices(blockEpoch, indexedAttestation.attestingIndices);
190
190
 
191
191
  const correctHead = ssz.Root.equals(rootCache.getBlockRootAtSlot(attestation.data.slot), beaconBlockRoot);
192
- const missedSlotVote = ssz.Root.equals(
193
- rootCache.getBlockRootAtSlot(attestation.data.slot - 1),
194
- rootCache.getBlockRootAtSlot(attestation.data.slot)
195
- );
192
+ const missedSlotVote =
193
+ attestation.data.slot > GENESIS_SLOT &&
194
+ ssz.Root.equals(
195
+ rootCache.getBlockRootAtSlot(attestation.data.slot - 1),
196
+ rootCache.getBlockRootAtSlot(attestation.data.slot)
197
+ );
196
198
  this.validatorMonitor?.registerAttestationInBlock(
197
199
  indexedAttestation,
198
200
  parentBlockSlot,
@@ -281,6 +283,18 @@ export async function importBlock(
281
283
  const newHead = this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock);
282
284
  const currFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
283
285
 
286
+ // Prune the gloas payload-envelope cache below the new head's parent so it stays bounded during
287
+ // syncing. On a synced node, cache holds just 2 entries — head (parent for
288
+ // next-slot production) and head.parent (proposer-boost-reorg fallback)
289
+ if (fork >= ForkSeq.gloas) {
290
+ callInNextEventLoop(() => {
291
+ const newHeadParent = this.forkChoice.getBlockHexDefaultStatus(newHead.parentRoot);
292
+ if (newHeadParent) {
293
+ this.seenPayloadEnvelopeInputCache.pruneBelowParent(newHeadParent);
294
+ }
295
+ });
296
+ }
297
+
284
298
  if (newHead.blockRoot !== oldHead.blockRoot) {
285
299
  // Set head state as strong reference
286
300
  this.regen.updateHeadState(newHead, postState);
@@ -61,10 +61,8 @@ export function verifyExecutionPayloadEnvelope(
61
61
  `Prev randao mismatch between bid and payload bid=${toHex(bid.prevRandao)} payload=${toHex(payload.prevRandao)}`
62
62
  );
63
63
  }
64
- if (Number(bid.gasLimit) !== payload.gasLimit) {
65
- throw new Error(
66
- `Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${Number(bid.gasLimit)}`
67
- );
64
+ if (bid.gasLimit !== payload.gasLimit) {
65
+ throw new Error(`Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${bid.gasLimit}`);
68
66
  }
69
67
  if (!byteArrayEquals(bid.blockHash, payload.blockHash)) {
70
68
  throw new Error(
@@ -74,7 +72,7 @@ export function verifyExecutionPayloadEnvelope(
74
72
  // Verify execution_requests_root matches bid commitment.
75
73
  // Can be skipped if already verified during gossip validation.
76
74
  if (verifyExecutionRequestsRoot) {
77
- const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
75
+ const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
78
76
  if (!byteArrayEquals(requestsRoot, bid.executionRequestsRoot)) {
79
77
  throw new Error(
80
78
  `Execution requests root mismatch envelope=${toRootHex(requestsRoot)} bid=${toRootHex(bid.executionRequestsRoot)}`
@@ -40,7 +40,6 @@ import {
40
40
  ValidatorIndex,
41
41
  Wei,
42
42
  deneb,
43
- electra,
44
43
  gloas,
45
44
  isBlindedBeaconBlock,
46
45
  phase0,
@@ -925,10 +924,10 @@ export class BeaconChain implements IBeaconChain {
925
924
  async getParentExecutionRequests(
926
925
  parentBlockSlot: Slot,
927
926
  parentBlockRootHex: RootHex
928
- ): Promise<electra.ExecutionRequests> {
927
+ ): Promise<gloas.ExecutionRequests> {
929
928
  // at the fork boundary, parent is pre-gloas
930
929
  if (!isForkPostGloas(this.config.getForkName(parentBlockSlot))) {
931
- return ssz.electra.ExecutionRequests.defaultValue();
930
+ return ssz.gloas.ExecutionRequests.defaultValue();
932
931
  }
933
932
  const envelope = await this.getExecutionPayloadEnvelope(parentBlockSlot, parentBlockRootHex);
934
933
  if (envelope === null) {
@@ -3,6 +3,7 @@ import {GossipActionError} from "./gossipValidation.js";
3
3
 
4
4
  export enum ExecutionPayloadBidErrorCode {
5
5
  BUILDER_NOT_ELIGIBLE = "EXECUTION_PAYLOAD_BID_ERROR_BUILDER_NOT_ELIGIBLE",
6
+ INVALID_BUILDER_VERSION = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_BUILDER_VERSION",
6
7
  NON_ZERO_EXECUTION_PAYMENT = "EXECUTION_PAYLOAD_BID_ERROR_NON_ZERO_EXECUTION_PAYMENT",
7
8
  BID_ALREADY_KNOWN = "EXECUTION_PAYLOAD_BID_ERROR_BID_ALREADY_KNOWN",
8
9
  BID_TOO_LOW = "EXECUTION_PAYLOAD_BID_ERROR_BID_TOO_LOW",
@@ -12,6 +13,7 @@ export enum ExecutionPayloadBidErrorCode {
12
13
  UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
13
14
  INVALID_SLOT = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SLOT",
14
15
  NOT_LATER_THAN_PARENT = "EXECUTION_PAYLOAD_BID_ERROR_NOT_LATER_THAN_PARENT",
16
+ INVALID_PREV_RANDAO = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_PREV_RANDAO",
15
17
  INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
16
18
  NO_MATCHING_PROPOSER_PREFERENCES = "EXECUTION_PAYLOAD_BID_ERROR_NO_MATCHING_PROPOSER_PREFERENCES",
17
19
  PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH",
@@ -20,6 +22,12 @@ export enum ExecutionPayloadBidErrorCode {
20
22
 
21
23
  export type ExecutionPayloadBidErrorType =
22
24
  | {code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE; builderIndex: BuilderIndex}
25
+ | {
26
+ code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION;
27
+ builderIndex: BuilderIndex;
28
+ version: number;
29
+ expectedVersion: number;
30
+ }
23
31
  | {
24
32
  code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT;
25
33
  builderIndex: BuilderIndex;
@@ -43,6 +51,12 @@ export type ExecutionPayloadBidErrorType =
43
51
  | {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
44
52
  | {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
45
53
  | {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
54
+ | {
55
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO;
56
+ builderIndex: BuilderIndex;
57
+ bidPrevRandao: string;
58
+ expectedPrevRandao: string;
59
+ }
46
60
  | {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
47
61
  | {
48
62
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES;
@@ -18,7 +18,6 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
- electra,
22
21
  gloas,
23
22
  phase0,
24
23
  rewards,
@@ -236,7 +235,7 @@ export interface IBeaconChain {
236
235
  blockSlot: Slot,
237
236
  blockRootHex: string
238
237
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
239
- getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
238
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<gloas.ExecutionRequests>;
240
239
 
241
240
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
242
241
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -217,16 +217,6 @@ export class PrepareNextSlotScheduler {
217
217
  });
218
218
  }
219
219
 
220
- if (ForkSeq[fork] >= ForkSeq.gloas) {
221
- // Cutoff = slot of the parent of the block we'll actually build on (post-reorg).
222
- // Steady state: cache holds just 2 entries — head (parent for next-slot production)
223
- // and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
224
- const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
225
- if (updatedHeadParent) {
226
- this.chain.seenPayloadEnvelopeInputCache.pruneBelowParent(updatedHeadParent);
227
- }
228
- }
229
-
230
220
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
231
221
 
232
222
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
@@ -108,7 +108,7 @@ export type ProduceFullGloas = {
108
108
  type: BlockType.Full;
109
109
  fork: ForkPostGloas;
110
110
  executionPayload: ExecutionPayload<ForkPostGloas>;
111
- executionRequests: electra.ExecutionRequests;
111
+ executionRequests: gloas.ExecutionRequests;
112
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
113
113
  cells: fulu.Cell[][];
114
114
  parentBlockRoot: Root;
@@ -228,7 +228,7 @@ export async function produceBlockBody<T extends BlockType>(
228
228
  );
229
229
  const parentExecutionRequests = isExtendingPayload
230
230
  ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
231
- : ssz.electra.ExecutionRequests.defaultValue();
231
+ : ssz.gloas.ExecutionRequests.defaultValue();
232
232
  executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;
233
233
 
234
234
  const commonBlockBody = await commonBlockBodyPromise;
@@ -272,7 +272,7 @@ export async function produceBlockBody<T extends BlockType>(
272
272
 
273
273
  // Get execution payload from EL
274
274
  let parentBlockHash: Bytes32;
275
- let parentExecutionRequests: electra.ExecutionRequests;
275
+ let parentExecutionRequests: gloas.ExecutionRequests;
276
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
277
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
278
278
  // Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
@@ -285,7 +285,7 @@ export async function produceBlockBody<T extends BlockType>(
285
285
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
286
286
  } else {
287
287
  parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
288
- parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
288
+ parentExecutionRequests = ssz.gloas.ExecutionRequests.defaultValue();
289
289
  }
290
290
  const prepareRes = await prepareExecutionPayload(
291
291
  this,
@@ -344,13 +344,13 @@ export async function produceBlockBody<T extends BlockType>(
344
344
  blockHash: executionPayload.blockHash,
345
345
  prevRandao: currentState.getRandaoMix(currentState.epoch),
346
346
  feeRecipient: executionPayload.feeRecipient,
347
- gasLimit: BigInt(executionPayload.gasLimit),
347
+ gasLimit: executionPayload.gasLimit,
348
348
  builderIndex: BUILDER_INDEX_SELF_BUILD,
349
349
  slot: blockSlot,
350
350
  value: 0,
351
351
  executionPayment: 0,
352
352
  blobKzgCommitments: blobsBundle.commitments,
353
- executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
353
+ executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests),
354
354
  };
355
355
  const signedBid: gloas.SignedExecutionPayloadBid = {
356
356
  message: bid,
@@ -375,7 +375,7 @@ export async function produceBlockBody<T extends BlockType>(
375
375
  // Store execution payload data required to construct execution payload envelope later
376
376
  const gloasResult = produceResult as ProduceFullGloas;
377
377
  gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
378
- gloasResult.executionRequests = executionRequests;
378
+ gloasResult.executionRequests = executionRequests as gloas.ExecutionRequests;
379
379
  gloasResult.blobsBundle = blobsBundle;
380
380
  gloasResult.cells = cells;
381
381
  gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {CheckpointWithHex, IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
4
  import {RootHex} from "@lodestar/types";
5
5
  import {Logger} from "@lodestar/utils";
@@ -154,9 +154,14 @@ export class SeenPayloadEnvelopeInput {
154
154
 
155
155
  pruneBelowParent(parentBlock: ProtoBlock): void {
156
156
  for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
157
- if (block.slot < parentBlock.slot) {
157
+ // Only evict once the payload is FULL (revealed/imported) — on an EMPTY/PENDING branch we may
158
+ // still need to download the FULL envelope (see #9475), and evicting would make payload-by-root
159
+ // sync throw "Missing PayloadEnvelopeInput for known block".
160
+ if (block.slot < parentBlock.slot && block.payloadStatus === PayloadStatus.FULL) {
158
161
  const input = this.payloadInputs.get(block.blockRoot);
159
- if (input) {
162
+ // ...and don't evict while columns are still being gathered: writeDataColumnsToDb awaits the
163
+ // same hasComputedAllData() before persisting. Such entries are pruned by a later call.
164
+ if (input?.hasComputedAllData()) {
160
165
  this.evictPayloadInput(input);
161
166
  this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
162
167
  slot: block.slot,
@@ -1,4 +1,5 @@
1
1
  import {PublicKey} from "@chainsafe/blst";
2
+ import {PAYLOAD_BUILDER_VERSION} from "@lodestar/params";
2
3
  import {
3
4
  computeEpochAtSlot,
4
5
  createSingleSignatureSetFromComponents,
@@ -139,6 +140,17 @@ async function validateExecutionPayloadBid(
139
140
  });
140
141
  }
141
142
 
143
+ // [REJECT] The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e.
144
+ // `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`.
145
+ if (builder.version !== PAYLOAD_BUILDER_VERSION) {
146
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
147
+ code: ExecutionPayloadBidErrorCode.INVALID_BUILDER_VERSION,
148
+ builderIndex: bid.builderIndex,
149
+ version: builder.version,
150
+ expectedVersion: PAYLOAD_BUILDER_VERSION,
151
+ });
152
+ }
153
+
142
154
  // [REJECT] `bid.execution_payment` is zero.
143
155
  if (bid.executionPayment !== 0) {
144
156
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
@@ -175,7 +187,7 @@ async function validateExecutionPayloadBid(
175
187
  // [IGNORE] `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`,
176
188
  // where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
177
189
  // `target_gas_limit` is `proposer_preferences.target_gas_limit`.
178
- const bidGasLimit = Number(bid.gasLimit);
190
+ const bidGasLimit = bid.gasLimit;
179
191
  const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
180
192
  const targetGasLimit = proposerPreferences.message.targetGasLimit;
181
193
  if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
@@ -232,6 +244,18 @@ async function validateExecutionPayloadBid(
232
244
  });
233
245
  }
234
246
 
247
+ // [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
248
+ // `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
249
+ const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
250
+ if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
251
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
252
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
253
+ builderIndex: bid.builderIndex,
254
+ bidPrevRandao: toHex(bid.prevRandao),
255
+ expectedPrevRandao: toHex(randaoMix),
256
+ });
257
+ }
258
+
235
259
  // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
236
260
  const signatureSet = createSingleSignatureSetFromComponents(
237
261
  PublicKey.fromBytes(builder.pubkey),
@@ -106,7 +106,7 @@ async function validateExecutionPayloadEnvelope(
106
106
  }
107
107
 
108
108
  // [REJECT] `hash_tree_root(envelope.execution_requests) == bid.execution_requests_root`
109
- const requestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
109
+ const requestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(envelope.executionRequests);
110
110
  if (!byteArrayEquals(requestsRoot, payloadInput.getBid().executionRequestsRoot)) {
111
111
  throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
112
112
  code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_ROOT_MISMATCH,
@@ -244,7 +244,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
244
244
  if (executionRequests === undefined) {
245
245
  throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
246
246
  }
247
- const serializedExecutionRequests = serializeExecutionRequests(executionRequests);
247
+ const serializedExecutionRequests = serializeExecutionRequests(fork, executionRequests);
248
248
  engineRequest = {
249
249
  method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
250
250
  params: [
@@ -1,5 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {
3
+ BUILDER_DEPOSIT_REQUEST_TYPE,
4
+ BUILDER_EXIT_REQUEST_TYPE,
3
5
  CONSOLIDATION_REQUEST_TYPE,
4
6
  DEPOSIT_REQUEST_TYPE,
5
7
  ForkName,
@@ -51,10 +53,18 @@ export enum ExecutionEngineState {
51
53
  export type ExecutionRequestType =
52
54
  | typeof DEPOSIT_REQUEST_TYPE
53
55
  | typeof WITHDRAWAL_REQUEST_TYPE
54
- | typeof CONSOLIDATION_REQUEST_TYPE;
56
+ | typeof CONSOLIDATION_REQUEST_TYPE
57
+ | typeof BUILDER_DEPOSIT_REQUEST_TYPE
58
+ | typeof BUILDER_EXIT_REQUEST_TYPE;
55
59
 
56
60
  export function isExecutionRequestType(type: number): type is ExecutionRequestType {
57
- return type === DEPOSIT_REQUEST_TYPE || type === WITHDRAWAL_REQUEST_TYPE || type === CONSOLIDATION_REQUEST_TYPE;
61
+ return (
62
+ type === DEPOSIT_REQUEST_TYPE ||
63
+ type === WITHDRAWAL_REQUEST_TYPE ||
64
+ type === CONSOLIDATION_REQUEST_TYPE ||
65
+ type === BUILDER_DEPOSIT_REQUEST_TYPE ||
66
+ type === BUILDER_EXIT_REQUEST_TYPE
67
+ );
58
68
  }
59
69
 
60
70
  export type ExecutePayloadResponse =
@@ -393,11 +393,12 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend {
393
393
  blobs,
394
394
  proofs,
395
395
  }),
396
- executionRequests: serializeExecutionRequests({
397
- deposits: ssz.electra.DepositRequests.defaultValue(),
398
- withdrawals: ssz.electra.WithdrawalRequests.defaultValue(),
399
- consolidations: ssz.electra.ConsolidationRequests.defaultValue(),
400
- }),
396
+ executionRequests: serializeExecutionRequests(
397
+ fork,
398
+ ForkSeq[fork] >= ForkSeq.gloas
399
+ ? ssz.gloas.ExecutionRequests.defaultValue()
400
+ : ssz.electra.ExecutionRequests.defaultValue()
401
+ ),
401
402
  });
402
403
 
403
404
  // IF the payload is deemed VALID and the build process has begun
@@ -1,4 +1,6 @@
1
1
  import {
2
+ BUILDER_DEPOSIT_REQUEST_TYPE,
3
+ BUILDER_EXIT_REQUEST_TYPE,
2
4
  BYTES_PER_FIELD_ELEMENT,
3
5
  BYTES_PER_LOGS_BLOOM,
4
6
  CELLS_PER_EXT_BLOB,
@@ -205,16 +207,27 @@ export type WithdrawalRpc = {
205
207
  };
206
208
 
207
209
  /**
208
- * ExecutionRequestsRpc only holds at most 3 elements and no repeated type:
209
- * - ssz'ed DepositRequests
210
- * - ssz'ed WithdrawalRequests
211
- * - ssz'ed ConsolidationRequests
210
+ * ExecutionRequestsRpc holds at most 5 elements and no repeated type, ordered by
211
+ * the single-byte request type prefix:
212
+ * - ssz'ed DepositRequests (0x00)
213
+ * - ssz'ed WithdrawalRequests (0x01)
214
+ * - ssz'ed ConsolidationRequests (0x02)
215
+ * - ssz'ed BuilderDepositRequests (0x03)
216
+ * - ssz'ed BuilderExitRequests (0x04)
212
217
  */
213
- export type ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[];
218
+ export type ExecutionRequestsRpc = (
219
+ | DepositRequestsRpc
220
+ | WithdrawalRequestsRpc
221
+ | ConsolidationRequestsRpc
222
+ | BuilderDepositRequestsRpc
223
+ | BuilderExitRequestsRpc
224
+ )[];
214
225
 
215
226
  export type DepositRequestsRpc = DATA;
216
227
  export type WithdrawalRequestsRpc = DATA;
217
228
  export type ConsolidationRequestsRpc = DATA;
229
+ export type BuilderDepositRequestsRpc = DATA;
230
+ export type BuilderExitRequestsRpc = DATA;
218
231
 
219
232
  export type BlobAndProofRpc = {
220
233
  blob: DATA;
@@ -339,7 +352,7 @@ export function parseExecutionPayload(
339
352
  data = response.executionPayload;
340
353
  blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined;
341
354
  executionRequests = response.executionRequests
342
- ? deserializeExecutionRequests(response.executionRequests)
355
+ ? deserializeExecutionRequests(fork, response.executionRequests)
343
356
  : undefined;
344
357
  shouldOverrideBuilder = response.shouldOverrideBuilder ?? false;
345
358
  } else {
@@ -526,13 +539,36 @@ function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc):
526
539
  return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null));
527
540
  }
528
541
 
542
+ function serializeBuilderDepositRequests(
543
+ builderDepositRequests: gloas.BuilderDepositRequests
544
+ ): BuilderDepositRequestsRpc {
545
+ const requestsBytes = ssz.gloas.BuilderDepositRequests.serialize(builderDepositRequests);
546
+ return bytesToData(prefixRequests(requestsBytes, BUILDER_DEPOSIT_REQUEST_TYPE));
547
+ }
548
+
549
+ function deserializeBuilderDepositRequests(serialized: BuilderDepositRequestsRpc): gloas.BuilderDepositRequests {
550
+ return ssz.gloas.BuilderDepositRequests.deserialize(dataToBytes(serialized, null));
551
+ }
552
+
553
+ function serializeBuilderExitRequests(builderExitRequests: gloas.BuilderExitRequests): BuilderExitRequestsRpc {
554
+ const requestsBytes = ssz.gloas.BuilderExitRequests.serialize(builderExitRequests);
555
+ return bytesToData(prefixRequests(requestsBytes, BUILDER_EXIT_REQUEST_TYPE));
556
+ }
557
+
558
+ function deserializeBuilderExitRequests(serialized: BuilderExitRequestsRpc): gloas.BuilderExitRequests {
559
+ return ssz.gloas.BuilderExitRequests.deserialize(dataToBytes(serialized, null));
560
+ }
561
+
529
562
  /**
530
563
  * This is identical to get_execution_requests_list in
531
564
  * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list
565
+ *
566
+ * Gloas extends the list with builder deposits (0x03) and builder exits (0x04) per
567
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#modified-get_execution_requests_list
532
568
  */
533
- export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc {
569
+ export function serializeExecutionRequests(fork: ForkName, executionRequests: ExecutionRequests): ExecutionRequestsRpc {
534
570
  const {deposits, withdrawals, consolidations} = executionRequests;
535
- const result = [];
571
+ const result: ExecutionRequestsRpc = [];
536
572
 
537
573
  if (deposits.length !== 0) {
538
574
  result.push(serializeDepositRequests(deposits));
@@ -546,15 +582,26 @@ export function serializeExecutionRequests(executionRequests: ExecutionRequests)
546
582
  result.push(serializeConsolidationRequests(consolidations));
547
583
  }
548
584
 
585
+ if (ForkSeq[fork] >= ForkSeq.gloas) {
586
+ const {builderDeposits, builderExits} = executionRequests as gloas.ExecutionRequests;
587
+
588
+ if (builderDeposits.length !== 0) {
589
+ result.push(serializeBuilderDepositRequests(builderDeposits));
590
+ }
591
+
592
+ if (builderExits.length !== 0) {
593
+ result.push(serializeBuilderExitRequests(builderExits));
594
+ }
595
+ }
596
+
549
597
  return result;
550
598
  }
551
599
 
552
- export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests {
553
- const result: ExecutionRequests = {
554
- deposits: [],
555
- withdrawals: [],
556
- consolidations: [],
557
- };
600
+ export function deserializeExecutionRequests(fork: ForkName, serialized: ExecutionRequestsRpc): ExecutionRequests {
601
+ const result: ExecutionRequests =
602
+ ForkSeq[fork] >= ForkSeq.gloas
603
+ ? {deposits: [], withdrawals: [], consolidations: [], builderDeposits: [], builderExits: []}
604
+ : {deposits: [], withdrawals: [], consolidations: []};
558
605
 
559
606
  if (serialized.length === 0) {
560
607
  return result;
@@ -601,6 +648,20 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc):
601
648
  result.consolidations = deserializeConsolidationRequests(requests);
602
649
  break;
603
650
  }
651
+ case BUILDER_DEPOSIT_REQUEST_TYPE: {
652
+ if (ForkSeq[fork] < ForkSeq.gloas) {
653
+ throw Error(`Builder deposit request is not supported pre-gloas fork=${fork}`);
654
+ }
655
+ (result as gloas.ExecutionRequests).builderDeposits = deserializeBuilderDepositRequests(requests);
656
+ break;
657
+ }
658
+ case BUILDER_EXIT_REQUEST_TYPE: {
659
+ if (ForkSeq[fork] < ForkSeq.gloas) {
660
+ throw Error(`Builder exit request is not supported pre-gloas fork=${fork}`);
661
+ }
662
+ (result as gloas.ExecutionRequests).builderExits = deserializeBuilderExitRequests(requests);
663
+ break;
664
+ }
604
665
  }
605
666
  prevRequestType = currentRequestType;
606
667
  }
@@ -1,6 +1,5 @@
1
1
  import {LogData} from "@lodestar/logger";
2
2
  import {ForkSeq} from "@lodestar/params";
3
- import {RespStatus, ResponseError} from "@lodestar/reqresp";
4
3
  import {ColumnIndex, Slot} from "@lodestar/types";
5
4
  import {prettyBytes, prettyPrintIndices, toRootHex} from "@lodestar/utils";
6
5
  import {IBeaconChain} from "../../../chain/interface.js";
@@ -76,7 +75,7 @@ export async function handleColumnSidecarUnavailability({
76
75
 
77
76
  export function validateRequestedDataColumns(chain: IBeaconChain, requestedColumns: ColumnIndex[]): ColumnIndex[] {
78
77
  if (requestedColumns.length === 0) {
79
- throw new ResponseError(RespStatus.INVALID_REQUEST, "dataColumnSidecar requested without column indices");
78
+ return [];
80
79
  }
81
80
 
82
81
  const {custodyColumns, custodyColumnsIndex} = chain.custodyConfig;
@@ -706,7 +706,7 @@ export function getBlockRootFromPayloadAttestationMessageSerialized(data: Uint8A
706
706
  * blockHash: Bytes32 (32 bytes)
707
707
  * prevRandao: Bytes32 (32 bytes)
708
708
  * feeRecipient: ExecutionAddress(20 bytes)
709
- * gasLimit: UintBn64 (8 bytes)
709
+ * gasLimit: UintNum64 (8 bytes)
710
710
  * builderIndex: BuilderIndex (8 bytes)
711
711
  * slot: Slot (8 bytes) ← absolute offset 264
712
712
  */