@lodestar/beacon-node 1.43.0-dev.7622a1076c → 1.43.0-dev.7a6a5b0190

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 (199) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -5
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/lodestar/index.js +1 -1
  5. package/lib/api/impl/lodestar/index.js.map +1 -1
  6. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  7. package/lib/chain/blocks/importBlock.js +6 -3
  8. package/lib/chain/blocks/importBlock.js.map +1 -1
  9. package/lib/chain/blocks/importExecutionPayload.d.ts +19 -8
  10. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  11. package/lib/chain/blocks/importExecutionPayload.js +31 -20
  12. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  13. package/lib/chain/blocks/index.d.ts +5 -3
  14. package/lib/chain/blocks/index.d.ts.map +1 -1
  15. package/lib/chain/blocks/index.js +28 -9
  16. package/lib/chain/blocks/index.js.map +1 -1
  17. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  18. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  19. package/lib/chain/blocks/types.d.ts +2 -2
  20. package/lib/chain/blocks/types.d.ts.map +1 -1
  21. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  22. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  23. package/lib/chain/blocks/utils/chainSegment.js +81 -12
  24. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  25. package/lib/chain/blocks/verifyBlock.d.ts +3 -2
  26. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  27. package/lib/chain/blocks/verifyBlock.js +30 -5
  28. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  29. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  30. package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
  31. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  32. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  33. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  34. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
  35. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +1 -10
  36. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -1
  37. package/lib/chain/chain.d.ts +5 -3
  38. package/lib/chain/chain.d.ts.map +1 -1
  39. package/lib/chain/chain.js +17 -4
  40. package/lib/chain/chain.js.map +1 -1
  41. package/lib/chain/errors/blockError.d.ts +8 -1
  42. package/lib/chain/errors/blockError.d.ts.map +1 -1
  43. package/lib/chain/errors/blockError.js +2 -0
  44. package/lib/chain/errors/blockError.js.map +1 -1
  45. package/lib/chain/errors/index.d.ts +1 -0
  46. package/lib/chain/errors/index.d.ts.map +1 -1
  47. package/lib/chain/errors/index.js +1 -0
  48. package/lib/chain/errors/index.js.map +1 -1
  49. package/lib/chain/errors/proposerPreferences.d.ts +33 -0
  50. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  51. package/lib/chain/errors/proposerPreferences.js +13 -0
  52. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  53. package/lib/chain/interface.d.ts +5 -3
  54. package/lib/chain/interface.d.ts.map +1 -1
  55. package/lib/chain/interface.js.map +1 -1
  56. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  57. package/lib/chain/prepareNextSlot.js +30 -10
  58. package/lib/chain/prepareNextSlot.js.map +1 -1
  59. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -2
  60. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  61. package/lib/chain/produceBlock/produceBlockBody.js +34 -14
  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 +11 -4
  72. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  73. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +20 -18
  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/block.d.ts.map +1 -1
  80. package/lib/chain/validation/block.js +1 -0
  81. package/lib/chain/validation/block.js.map +1 -1
  82. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  83. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  84. package/lib/chain/validation/proposerPreferences.js +69 -0
  85. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  86. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  87. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  88. package/lib/metrics/metrics/lodestar.js +4 -0
  89. package/lib/metrics/metrics/lodestar.js.map +1 -1
  90. package/lib/network/gossip/interface.d.ts +7 -1
  91. package/lib/network/gossip/interface.d.ts.map +1 -1
  92. package/lib/network/gossip/interface.js +1 -0
  93. package/lib/network/gossip/interface.js.map +1 -1
  94. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  95. package/lib/network/gossip/scoringParameters.js +12 -1
  96. package/lib/network/gossip/scoringParameters.js.map +1 -1
  97. package/lib/network/gossip/topic.d.ts +8 -0
  98. package/lib/network/gossip/topic.d.ts.map +1 -1
  99. package/lib/network/gossip/topic.js +6 -0
  100. package/lib/network/gossip/topic.js.map +1 -1
  101. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  102. package/lib/network/processor/gossipHandlers.js +10 -6
  103. package/lib/network/processor/gossipHandlers.js.map +1 -1
  104. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  105. package/lib/network/processor/gossipQueues/index.js +5 -0
  106. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  107. package/lib/network/processor/index.d.ts.map +1 -1
  108. package/lib/network/processor/index.js +1 -0
  109. package/lib/network/processor/index.js.map +1 -1
  110. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  111. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  112. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  113. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  114. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  115. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  116. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  117. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  118. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  119. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  120. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  121. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  122. package/lib/node/notifier.js +7 -1
  123. package/lib/node/notifier.js.map +1 -1
  124. package/lib/sync/range/batch.d.ts +12 -2
  125. package/lib/sync/range/batch.d.ts.map +1 -1
  126. package/lib/sync/range/batch.js +56 -30
  127. package/lib/sync/range/batch.js.map +1 -1
  128. package/lib/sync/range/chain.d.ts +6 -2
  129. package/lib/sync/range/chain.d.ts.map +1 -1
  130. package/lib/sync/range/chain.js +4 -3
  131. package/lib/sync/range/chain.js.map +1 -1
  132. package/lib/sync/range/range.d.ts.map +1 -1
  133. package/lib/sync/range/range.js +17 -6
  134. package/lib/sync/range/range.js.map +1 -1
  135. package/lib/sync/types.d.ts +34 -0
  136. package/lib/sync/types.d.ts.map +1 -1
  137. package/lib/sync/types.js +34 -0
  138. package/lib/sync/types.js.map +1 -1
  139. package/lib/sync/unknownBlock.d.ts +24 -1
  140. package/lib/sync/unknownBlock.d.ts.map +1 -1
  141. package/lib/sync/unknownBlock.js +649 -53
  142. package/lib/sync/unknownBlock.js.map +1 -1
  143. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  144. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  145. package/lib/sync/utils/downloadByRange.js +147 -24
  146. package/lib/sync/utils/downloadByRange.js.map +1 -1
  147. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  148. package/lib/sync/utils/downloadByRoot.js +6 -2
  149. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  150. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  151. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  152. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  153. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  154. package/package.json +16 -15
  155. package/src/api/impl/beacon/blocks/index.ts +8 -5
  156. package/src/api/impl/lodestar/index.ts +1 -1
  157. package/src/chain/blocks/importBlock.ts +4 -2
  158. package/src/chain/blocks/importExecutionPayload.ts +36 -21
  159. package/src/chain/blocks/index.ts +44 -12
  160. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  161. package/src/chain/blocks/types.ts +2 -2
  162. package/src/chain/blocks/utils/chainSegment.ts +106 -17
  163. package/src/chain/blocks/verifyBlock.ts +35 -6
  164. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
  165. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  166. package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +8 -17
  167. package/src/chain/chain.ts +26 -3
  168. package/src/chain/errors/blockError.ts +4 -1
  169. package/src/chain/errors/index.ts +1 -0
  170. package/src/chain/errors/proposerPreferences.ts +39 -0
  171. package/src/chain/interface.ts +9 -1
  172. package/src/chain/prepareNextSlot.ts +42 -12
  173. package/src/chain/produceBlock/produceBlockBody.ts +37 -12
  174. package/src/chain/regen/interface.ts +1 -0
  175. package/src/chain/seenCache/index.ts +1 -0
  176. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +22 -20
  177. package/src/chain/seenCache/seenProposerPreferences.ts +29 -0
  178. package/src/chain/validation/block.ts +1 -0
  179. package/src/chain/validation/proposerPreferences.ts +91 -0
  180. package/src/metrics/metrics/lodestar.ts +4 -0
  181. package/src/network/gossip/interface.ts +6 -0
  182. package/src/network/gossip/scoringParameters.ts +14 -1
  183. package/src/network/gossip/topic.ts +6 -0
  184. package/src/network/processor/gossipHandlers.ts +15 -6
  185. package/src/network/processor/gossipQueues/index.ts +5 -0
  186. package/src/network/processor/index.ts +1 -0
  187. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  188. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  189. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  190. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  191. package/src/node/notifier.ts +8 -1
  192. package/src/sync/range/batch.ts +90 -35
  193. package/src/sync/range/chain.ts +13 -5
  194. package/src/sync/range/range.ts +18 -6
  195. package/src/sync/types.ts +72 -0
  196. package/src/sync/unknownBlock.ts +810 -57
  197. package/src/sync/utils/downloadByRange.ts +256 -39
  198. package/src/sync/utils/downloadByRoot.ts +12 -2
  199. package/src/sync/utils/pendingBlocksTree.ts +0 -15
@@ -39,6 +39,7 @@ import {
39
39
  ValidatorIndex,
40
40
  Wei,
41
41
  deneb,
42
+ electra,
42
43
  gloas,
43
44
  isBlindedBeaconBlock,
44
45
  phase0,
@@ -105,6 +106,7 @@ import {
105
106
  SeenExecutionPayloadBids,
106
107
  SeenPayloadAttesters,
107
108
  SeenPayloadEnvelopeInput,
109
+ SeenProposerPreferences,
108
110
  SeenSyncCommitteeMessages,
109
111
  } from "./seenCache/index.js";
110
112
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -185,6 +187,7 @@ export class BeaconChain implements IBeaconChain {
185
187
  readonly seenPayloadAttesters = new SeenPayloadAttesters();
186
188
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
187
189
  readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
190
+ readonly seenProposerPreferences = new SeenProposerPreferences();
188
191
  readonly seenBlockProposers = new SeenBlockProposers();
189
192
  readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
190
193
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -886,6 +889,21 @@ export class BeaconChain implements IBeaconChain {
886
889
  );
887
890
  }
888
891
 
892
+ async getParentExecutionRequests(
893
+ parentBlockSlot: Slot,
894
+ parentBlockRootHex: RootHex
895
+ ): Promise<electra.ExecutionRequests> {
896
+ // at the fork boundary, parent is pre-gloas
897
+ if (!isForkPostGloas(this.config.getForkName(parentBlockSlot))) {
898
+ return ssz.electra.ExecutionRequests.defaultValue();
899
+ }
900
+ const envelope = await this.getExecutionPayloadEnvelope(parentBlockSlot, parentBlockRootHex);
901
+ if (envelope === null) {
902
+ throw Error(`Parent execution payload envelope not found slot=${parentBlockSlot}, root=${parentBlockRootHex}`);
903
+ }
904
+ return envelope.message.executionRequests;
905
+ }
906
+
889
907
  async getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<DataColumnSidecar[]> {
890
908
  const fork = this.config.getForkName(blockSlot);
891
909
 
@@ -1082,11 +1100,15 @@ export class BeaconChain implements IBeaconChain {
1082
1100
  }
1083
1101
 
1084
1102
  async processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void> {
1085
- return this.blockProcessor.processBlocksJob([block], opts);
1103
+ return this.blockProcessor.processBlocksJob([block], null, opts);
1086
1104
  }
1087
1105
 
1088
- async processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void> {
1089
- return this.blockProcessor.processBlocksJob(blocks, opts);
1106
+ async processChainSegment(
1107
+ blocks: IBlockInput[],
1108
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
1109
+ opts?: ImportBlockOpts
1110
+ ): Promise<void> {
1111
+ await this.blockProcessor.processBlocksJob(blocks, payloadEnvelopes, opts);
1090
1112
  }
1091
1113
 
1092
1114
  async processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void> {
@@ -1417,6 +1439,7 @@ export class BeaconChain implements IBeaconChain {
1417
1439
  this.payloadAttestationPool.prune(slot);
1418
1440
  this.executionPayloadBidPool.prune(slot);
1419
1441
  this.seenExecutionPayloadBids.prune(slot);
1442
+ this.seenProposerPreferences.prune(slot);
1420
1443
  this.seenAttestationDatas.onSlot(slot);
1421
1444
  this.reprocessController.onSlot(slot);
1422
1445
 
@@ -74,6 +74,8 @@ export enum BlockErrorCode {
74
74
  PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
75
75
  /** The block's parent execution payload (defined by bid.parent_block_hash) has not been seen */
76
76
  PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN",
77
+ /** An execution payload envelope in the chain segment references a block root that does not match its slot's block */
78
+ ENVELOPE_BLOCK_ROOT_MISMATCH = "BLOCK_ERROR_ENVELOPE_BLOCK_ROOT_MISMATCH",
77
79
  }
78
80
 
79
81
  type ExecutionErrorStatus = Exclude<
@@ -107,6 +109,7 @@ export type BlockErrorType =
107
109
  | {code: BlockErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
108
110
  | {code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS}
109
111
  | {code: BlockErrorCode.NON_LINEAR_SLOTS}
112
+ | {code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH; envelopeBlockRoot: RootHex; blockRoot: RootHex}
110
113
  | {code: BlockErrorCode.PER_BLOCK_PROCESSING_ERROR; error: Error}
111
114
  | {code: BlockErrorCode.BEACON_CHAIN_ERROR; error: Error}
112
115
  | {code: BlockErrorCode.KNOWN_BAD_BLOCK}
@@ -120,7 +123,7 @@ export type BlockErrorType =
120
123
  | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
121
124
  | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
122
125
  | {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
123
- | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentBlockHash: RootHex};
126
+ | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex};
124
127
 
125
128
  export class BlockGossipError extends GossipActionError<BlockErrorType> {}
126
129
 
@@ -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> {}
@@ -18,6 +18,7 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
+ electra,
21
22
  gloas,
22
23
  phase0,
23
24
  rewards,
@@ -60,6 +61,7 @@ import {
60
61
  SeenContributionAndProof,
61
62
  SeenExecutionPayloadBids,
62
63
  SeenPayloadAttesters,
64
+ SeenProposerPreferences,
63
65
  SeenSyncCommitteeMessages,
64
66
  } from "./seenCache/index.js";
65
67
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -130,6 +132,7 @@ export interface IBeaconChain {
130
132
  readonly seenPayloadAttesters: SeenPayloadAttesters;
131
133
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
132
134
  readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
135
+ readonly seenProposerPreferences: SeenProposerPreferences;
133
136
  readonly seenBlockProposers: SeenBlockProposers;
134
137
  readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
135
138
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -231,6 +234,7 @@ export interface IBeaconChain {
231
234
  blockSlot: Slot,
232
235
  blockRootHex: string
233
236
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
237
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
234
238
 
235
239
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
236
240
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -248,7 +252,11 @@ export interface IBeaconChain {
248
252
  /** Process a block until complete */
249
253
  processBlock(block: IBlockInput, opts?: ImportBlockOpts): Promise<void>;
250
254
  /** Process a chain of blocks until complete */
251
- processChainSegment(blocks: IBlockInput[], opts?: ImportBlockOpts): Promise<void>;
255
+ processChainSegment(
256
+ blocks: IBlockInput[],
257
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null,
258
+ opts?: ImportBlockOpts
259
+ ): Promise<void>;
252
260
 
253
261
  /** Process execution payload envelope: verify, import to fork choice, and persist to DB */
254
262
  processExecutionPayload(payloadInput: PayloadEnvelopeInput, opts?: ImportPayloadOpts): Promise<void>;
@@ -10,7 +10,7 @@ import {
10
10
  isStatePostBellatrix,
11
11
  isStatePostGloas,
12
12
  } from "@lodestar/state-transition";
13
- import {Bytes32, Slot} from "@lodestar/types";
13
+ import {Bytes32, Slot, electra} from "@lodestar/types";
14
14
  import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
15
15
  import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
16
16
  import {BuilderStatus} from "../execution/builder/http.js";
@@ -83,7 +83,7 @@ export class PrepareNextSlotScheduler {
83
83
  const headBlock = this.chain.recomputeForkChoiceHead(ForkchoiceCaller.prepareNextSlot);
84
84
  const {slot: headSlot, blockRoot: headRoot} = headBlock;
85
85
  // may be updated below if we predict a proposer-boost-reorg
86
- let updatedHeadRoot = headRoot;
86
+ let updatedHead = headBlock;
87
87
 
88
88
  // PS: previously this was comparing slots, but that gave no leway on the skipped
89
89
  // slots on epoch bounday. Making it more fluid.
@@ -148,7 +148,7 @@ export class PrepareNextSlotScheduler {
148
148
  {dontTransferCache: !isEpochTransition},
149
149
  RegenCaller.predictProposerHead
150
150
  );
151
- updatedHeadRoot = proposerHeadRoot;
151
+ updatedHead = proposerHead;
152
152
  }
153
153
 
154
154
  // Update the builder status, if enabled shoot an api call to check status
@@ -165,14 +165,19 @@ export class PrepareNextSlotScheduler {
165
165
  }
166
166
 
167
167
  let parentBlockHash: Bytes32;
168
+ let isExtendingPayload = false;
168
169
  if (isStatePostGloas(updatedPrepareState)) {
169
- parentBlockHash = this.chain.forkChoice.shouldExtendPayload(updatedHeadRoot)
170
+ isExtendingPayload = this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot);
171
+ parentBlockHash = isExtendingPayload
170
172
  ? updatedPrepareState.latestExecutionPayloadBid.blockHash
171
173
  : updatedPrepareState.latestExecutionPayloadBid.parentBlockHash;
172
174
  } else {
173
175
  parentBlockHash = updatedPrepareState.latestExecutionPayloadHeader.blockHash;
174
176
  }
175
177
 
178
+ // Reused by the SSE emit below to avoid a second DB lookup on cache miss
179
+ let parentExecutionRequests: electra.ExecutionRequests | undefined;
180
+
176
181
  if (feeRecipient) {
177
182
  const preparationTime =
178
183
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
@@ -182,6 +187,13 @@ export class PrepareNextSlotScheduler {
182
187
  const finalizedBlockHash =
183
188
  this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
184
189
 
190
+ if (isExtendingPayload) {
191
+ parentExecutionRequests = await this.chain.getParentExecutionRequests(
192
+ updatedHead.slot,
193
+ updatedHead.blockRoot
194
+ );
195
+ }
196
+
185
197
  // awaiting here instead of throwing an async call because there is no other task
186
198
  // left for scheduler and this gives nice semantics to catch and log errors in the
187
199
  // try/catch wrapper here.
@@ -189,12 +201,13 @@ export class PrepareNextSlotScheduler {
189
201
  this.chain,
190
202
  this.logger,
191
203
  fork as ForkPostBellatrix, // State is of execution type
192
- fromHex(updatedHeadRoot),
204
+ fromHex(updatedHead.blockRoot),
193
205
  parentBlockHash,
194
206
  safeBlockHash,
195
207
  finalizedBlockHash,
196
208
  updatedPrepareState,
197
- feeRecipient
209
+ feeRecipient,
210
+ parentExecutionRequests
198
211
  );
199
212
  this.logger.verbose("PrepareNextSlotScheduler prepared new payload", {
200
213
  prepareSlot,
@@ -203,21 +216,38 @@ export class PrepareNextSlotScheduler {
203
216
  });
204
217
  }
205
218
 
219
+ if (ForkSeq[fork] >= ForkSeq.gloas) {
220
+ // Cutoff = slot of the parent of the block we'll actually build on (post-reorg).
221
+ // Steady state: cache holds just 2 entries — head (parent for next-slot production)
222
+ // and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
223
+ const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
224
+ if (updatedHeadParent) {
225
+ this.chain.seenPayloadEnvelopeInputCache.pruneBelow(updatedHeadParent.slot);
226
+ }
227
+ }
228
+
206
229
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
207
230
 
208
- // If emitPayloadAttributes is true emit a SSE payloadAttributes event
231
+ // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
232
+ // every slot. Without the flag, only emit the event if we are proposing in the next slot.
209
233
  if (
210
- this.chain.opts.emitPayloadAttributes === true &&
234
+ (feeRecipient || this.chain.opts.emitPayloadAttributes === true) &&
211
235
  this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes)
212
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
+ }
213
244
  const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, {
214
245
  prepareState: updatedPrepareState,
215
246
  prepareSlot,
216
- parentBlockRoot: fromHex(headRoot),
247
+ parentBlockRoot: fromHex(updatedHead.blockRoot),
217
248
  parentBlockHash,
218
- // The likely consumers of this API are builders and will anyway ignore the
219
- // feeRecipient, so just pass zero hash for now till a real use case arises
220
- feeRecipient: "0x0000000000000000000000000000000000000000000000000000000000000000",
249
+ feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
250
+ parentExecutionRequests,
221
251
  });
222
252
  this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
223
253
  }
@@ -49,7 +49,7 @@ import {
49
49
  ssz,
50
50
  } from "@lodestar/types";
51
51
  import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
52
- import {ZERO_HASH_HEX} from "../../constants/index.js";
52
+ import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js";
53
53
  import {numToQuantity} from "../../execution/engine/utils.js";
54
54
  import {
55
55
  IExecutionBuilder,
@@ -214,9 +214,19 @@ export async function produceBlockBody<T extends BlockType>(
214
214
  });
215
215
 
216
216
  // Get execution payload from EL
217
- const parentBlockHash = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot))
217
+ const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
218
+ let parentBlockHash = isExtendingPayload
218
219
  ? currentState.latestExecutionPayloadBid.blockHash
219
220
  : currentState.latestExecutionPayloadBid.parentBlockHash;
221
+ // At gloas genesis the committed bid has no prior EL block to reference
222
+ // (`bid.parentBlockHash` is zero). Fall back to `bid.blockHash` (= eth1 genesis hash) so the
223
+ // FCU to the EL carries a valid head. Post-genesis bids always reference a non-zero parent.
224
+ if (isStatePostGloas(currentState) && byteArrayEquals(parentBlockHash, ZERO_HASH)) {
225
+ parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
226
+ }
227
+ const parentExecutionRequests = isExtendingPayload
228
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
229
+ : ssz.electra.ExecutionRequests.defaultValue();
220
230
  const prepareRes = await prepareExecutionPayload(
221
231
  this,
222
232
  this.logger,
@@ -226,7 +236,8 @@ export async function produceBlockBody<T extends BlockType>(
226
236
  safeBlockHash,
227
237
  finalizedBlockHash ?? ZERO_HASH_HEX,
228
238
  currentState,
229
- feeRecipient
239
+ feeRecipient,
240
+ parentExecutionRequests
230
241
  );
231
242
 
232
243
  const {prepType, payloadId} = prepareRes;
@@ -282,7 +293,7 @@ export async function produceBlockBody<T extends BlockType>(
282
293
  gloasBody.signedExecutionPayloadBid = signedBid;
283
294
  // TODO GLOAS: Get payload attestations from pool for previous slot
284
295
  gloasBody.payloadAttestations = [];
285
- // TODO GLOAS: set parentExecutionRequests in the block body
296
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
286
297
  blockBody = gloasBody as AssembledBodyType<T>;
287
298
 
288
299
  // Store execution payload data required to construct execution payload envelope later
@@ -619,7 +630,8 @@ export async function prepareExecutionPayload(
619
630
  safeBlockHash: RootHex,
620
631
  finalizedBlockHash: RootHex,
621
632
  state: IBeaconStateViewBellatrix,
622
- suggestedFeeRecipient: string
633
+ suggestedFeeRecipient: string,
634
+ parentExecutionRequests?: electra.ExecutionRequests
623
635
  ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> {
624
636
  const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime);
625
637
  const prevRandao = state.getRandaoMix(state.epoch);
@@ -656,6 +668,7 @@ export async function prepareExecutionPayload(
656
668
  parentBlockRoot,
657
669
  parentBlockHash,
658
670
  feeRecipient: suggestedFeeRecipient,
671
+ parentExecutionRequests,
659
672
  });
660
673
 
661
674
  payloadId = await chain.executionEngine.notifyForkchoiceUpdate(
@@ -714,12 +727,14 @@ export function getPayloadAttributesForSSE(
714
727
  parentBlockRoot,
715
728
  parentBlockHash,
716
729
  feeRecipient,
730
+ parentExecutionRequests,
717
731
  }: {
718
732
  prepareState: IBeaconStateViewBellatrix;
719
733
  prepareSlot: Slot;
720
734
  parentBlockRoot: Root;
721
735
  parentBlockHash: Bytes32;
722
736
  feeRecipient: string;
737
+ parentExecutionRequests?: electra.ExecutionRequests;
723
738
  }
724
739
  ): SSEPayloadAttributes {
725
740
  const payloadAttributes = preparePayloadAttributes(fork, chain, {
@@ -728,6 +743,7 @@ export function getPayloadAttributesForSSE(
728
743
  parentBlockRoot,
729
744
  parentBlockHash,
730
745
  feeRecipient,
746
+ parentExecutionRequests,
731
747
  });
732
748
 
733
749
  let parentBlockNumber: number;
@@ -766,12 +782,14 @@ function preparePayloadAttributes(
766
782
  parentBlockRoot,
767
783
  parentBlockHash,
768
784
  feeRecipient,
785
+ parentExecutionRequests,
769
786
  }: {
770
787
  prepareState: IBeaconStateViewBellatrix;
771
788
  prepareSlot: Slot;
772
789
  parentBlockRoot: Root;
773
790
  parentBlockHash: Bytes32;
774
791
  feeRecipient: string;
792
+ parentExecutionRequests?: electra.ExecutionRequests;
775
793
  }
776
794
  ): SSEPayloadAttributes["payloadAttributes"] {
777
795
  const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime);
@@ -789,13 +807,20 @@ function preparePayloadAttributes(
789
807
 
790
808
  if (isStatePostGloas(prepareState)) {
791
809
  const isExtendingPayload = byteArrayEquals(parentBlockHash, prepareState.latestExecutionPayloadBid.blockHash);
792
- // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
793
- // already deducted from CL balances but never credited on the EL (the envelope
794
- // was not delivered). The next payload must carry those same withdrawals to
795
- // restore CL/EL consistency, otherwise validators permanently lose that balance.
796
- (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = isExtendingPayload
797
- ? prepareState.getExpectedWithdrawals().expectedWithdrawals
798
- : prepareState.payloadExpectedWithdrawals;
810
+ if (isExtendingPayload) {
811
+ if (parentExecutionRequests === undefined) {
812
+ throw new Error("parentExecutionRequests required when extending full parent");
813
+ }
814
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
815
+ prepareState.getExpectedWithdrawalsForFullParent(parentExecutionRequests);
816
+ } else {
817
+ // When the parent block is empty, state.payloadExpectedWithdrawals holds a batch
818
+ // already deducted from CL balances but never credited on the EL (the envelope
819
+ // was not delivered). The next payload must carry those same withdrawals to
820
+ // restore CL/EL consistency, otherwise validators permanently lose that balance.
821
+ (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
822
+ prepareState.payloadExpectedWithdrawals;
823
+ }
799
824
  } else {
800
825
  // withdrawals logic is now fork aware as it changes on electra fork post capella
801
826
  (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals =
@@ -21,6 +21,7 @@ export enum RegenCaller {
21
21
  validateGossipAttestation = "validateGossipAttestation",
22
22
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
23
23
  validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
24
+ validateGossipProposerPreferences = "validateGossipProposerPreferences",
24
25
  onForkChoiceFinalized = "onForkChoiceFinalized",
25
26
  restApi = "restApi",
26
27
  }
@@ -5,3 +5,4 @@ export {SeenContributionAndProof} from "./seenCommitteeContribution.js";
5
5
  export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js";
6
6
  export {SeenBlockInput} from "./seenGossipBlockInput.js";
7
7
  export {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenPayloadEnvelopeInput.js";
8
+ export {SeenProposerPreferences} from "./seenProposerPreferences.js";
@@ -1,6 +1,6 @@
1
1
  import {CheckpointWithHex} from "@lodestar/fork-choice";
2
2
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
3
- import {RootHex} from "@lodestar/types";
3
+ import {RootHex, Slot} from "@lodestar/types";
4
4
  import {Logger} from "@lodestar/utils";
5
5
  import {Metrics} from "../../metrics/metrics.js";
6
6
  import {SerializedCache} from "../../util/serializedCache.js";
@@ -21,8 +21,15 @@ export type SeenPayloadEnvelopeInputModules = {
21
21
  /**
22
22
  * Cache for tracking PayloadEnvelopeInput instances, keyed by beacon block root.
23
23
  *
24
- * Created during block import when a block is processed.
25
- * Pruned on finalization and after payload is written to DB.
24
+ * Created during block import when a block is processed. Two pruning paths:
25
+ * - `prepareNextSlot` calls `pruneBelow(headParentSlot)` every slot once the head we'll build
26
+ * on is known.
27
+ * - `onFinalized` calls `pruneBelow(finalizedSlot)` on every finalization for bulk cleanup.
28
+ *
29
+ * Steady state (linear chain, healthy progression): the cache holds ~2 entries — the head
30
+ * (parent for next-slot production) and its parent (proposer-boost-reorg fallback). It can
31
+ * transiently hold more during forks, range-sync bursts, or when `prepareNextSlot` skips
32
+ * ticks; subsequent ticks settle it back.
26
33
  */
27
34
  export class SeenPayloadEnvelopeInput {
28
35
  private readonly chainEvents: ChainEventEmitter;
@@ -58,16 +65,7 @@ export class SeenPayloadEnvelopeInput {
58
65
  }
59
66
 
60
67
  private onFinalized = (checkpoint: CheckpointWithHex): void => {
61
- // Prune all entries with slot < finalized slot
62
- const finalizedSlot = computeStartSlotAtEpoch(checkpoint.epoch);
63
- let deletedCount = 0;
64
- for (const [, input] of this.payloadInputs) {
65
- if (input.slot < finalizedSlot) {
66
- this.evictPayloadInput(input);
67
- deletedCount++;
68
- }
69
- }
70
- this.logger?.debug("SeenPayloadEnvelopeInput.onFinalized deleted cached entries", {deletedCount});
68
+ this.pruneBelow(computeStartSlotAtEpoch(checkpoint.epoch));
71
69
  };
72
70
 
73
71
  add(props: CreateFromBlockProps): PayloadEnvelopeInput {
@@ -88,17 +86,21 @@ export class SeenPayloadEnvelopeInput {
88
86
  return this.payloadInputs.get(blockRootHex)?.hasPayloadEnvelope() ?? false;
89
87
  }
90
88
 
91
- prune(blockRootHex: RootHex): void {
92
- const payloadInput = this.payloadInputs.get(blockRootHex);
93
- if (payloadInput) {
94
- this.evictPayloadInput(payloadInput);
95
- }
96
- }
97
-
98
89
  size(): number {
99
90
  return this.payloadInputs.size;
100
91
  }
101
92
 
93
+ pruneBelow(slot: Slot): void {
94
+ let deletedCount = 0;
95
+ for (const [, input] of this.payloadInputs) {
96
+ if (input.slot < slot) {
97
+ this.evictPayloadInput(input);
98
+ deletedCount++;
99
+ }
100
+ }
101
+ this.logger?.debug("SeenPayloadEnvelopeInput.pruneBelow deleted entries", {slot, deletedCount});
102
+ }
103
+
102
104
  private evictPayloadInput(payloadInput: PayloadEnvelopeInput): void {
103
105
  this.serializedCache.delete(payloadInput.getSerializedCacheKeys());
104
106
  this.payloadInputs.delete(payloadInput.blockRootHex);
@@ -0,0 +1,29 @@
1
+ import {Slot, ValidatorIndex} from "@lodestar/types";
2
+ import {MapDef} from "@lodestar/utils";
3
+
4
+ /**
5
+ * Tracks signed proposer preferences we've already seen per (proposal_slot, validator_index).
6
+ */
7
+ export class SeenProposerPreferences {
8
+ private readonly validatorIndexesBySlot = new MapDef<Slot, Set<ValidatorIndex>>(() => new Set<ValidatorIndex>());
9
+
10
+ isKnown(proposalSlot: Slot, validatorIndex: ValidatorIndex): boolean {
11
+ return this.validatorIndexesBySlot.get(proposalSlot)?.has(validatorIndex) === true;
12
+ }
13
+
14
+ add(proposalSlot: Slot, validatorIndex: ValidatorIndex): void {
15
+ this.validatorIndexesBySlot.getOrDefault(proposalSlot).add(validatorIndex);
16
+ }
17
+
18
+ /**
19
+ * Entries are only load-bearing while `proposal_slot > state.slot`. Once the slot has passed the
20
+ * `[IGNORE] proposal_slot > state.slot` gossip rule takes over, so drop them on each slot tick.
21
+ */
22
+ prune(currentSlot: Slot): void {
23
+ for (const slot of this.validatorIndexesBySlot.keys()) {
24
+ if (slot < currentSlot) {
25
+ this.validatorIndexesBySlot.delete(slot);
26
+ }
27
+ }
28
+ }
29
+ }
@@ -103,6 +103,7 @@ export async function validateGossipBlock(
103
103
  if (chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHashHex) === null) {
104
104
  throw new BlockGossipError(GossipAction.IGNORE, {
105
105
  code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
106
+ parentRoot,
106
107
  parentBlockHash: parentBlockHashHex,
107
108
  });
108
109
  }
@@ -0,0 +1,91 @@
1
+ import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {
3
+ computeEpochAtSlot,
4
+ createSingleSignatureSetFromComponents,
5
+ getProposerPreferencesSigningRoot,
6
+ isStatePostGloas,
7
+ } from "@lodestar/state-transition";
8
+ import {gloas} from "@lodestar/types";
9
+ import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
10
+ import {IBeaconChain} from "../index.js";
11
+ import {RegenCaller} from "../regen/index.js";
12
+
13
+ /**
14
+ * Validates a gossiped `SignedProposerPreferences` per
15
+ * https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#proposer_preferences
16
+ */
17
+ export async function validateGossipProposerPreferences(
18
+ chain: IBeaconChain,
19
+ signedProposerPreferences: gloas.SignedProposerPreferences
20
+ ): Promise<void> {
21
+ const preferences = signedProposerPreferences.message;
22
+ const {proposalSlot, validatorIndex} = preferences;
23
+ const proposalEpoch = computeEpochAtSlot(proposalSlot);
24
+
25
+ // [IGNORE] `preferences.proposal_slot` is in the current or next epoch.
26
+ const currentEpoch = chain.clock.currentEpoch;
27
+ if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + 1) {
28
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
29
+ code: ProposerPreferencesErrorCode.INVALID_EPOCH,
30
+ proposalSlot,
31
+ currentEpoch,
32
+ });
33
+ }
34
+
35
+ // [IGNORE] `preferences.proposal_slot` has not already passed.
36
+ const currentSlot = chain.clock.currentSlot;
37
+ if (proposalSlot <= currentSlot) {
38
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
39
+ code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED,
40
+ proposalSlot,
41
+ currentSlot,
42
+ });
43
+ }
44
+
45
+ const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipProposerPreferences);
46
+ if (!isStatePostGloas(state)) {
47
+ throw new Error(`Expected gloas+ state for proposer preferences validation, got fork=${state.forkName}`);
48
+ }
49
+
50
+ // [REJECT] `preferences.validator_index` is present at the correct slot in the current or next
51
+ // epoch's portion of `state.proposer_lookahead` — i.e. `is_valid_proposal_slot(state, preferences)`
52
+ // returns True.
53
+ const epochOffset = proposalEpoch - state.epoch;
54
+ const proposers = epochOffset === 0 ? state.currentProposers : state.nextProposers;
55
+ const expectedProposer = proposers[proposalSlot % SLOTS_PER_EPOCH];
56
+ if (epochOffset < 0 || epochOffset > 1 || expectedProposer !== validatorIndex) {
57
+ throw new ProposerPreferencesError(GossipAction.REJECT, {
58
+ code: ProposerPreferencesErrorCode.INVALID_PROPOSER,
59
+ proposalSlot,
60
+ validatorIndex,
61
+ });
62
+ }
63
+
64
+ // [IGNORE] The `signed_proposer_preferences` is the first valid message received from the validator
65
+ // with index `preferences.validator_index` and the given slot `preferences.proposal_slot`.
66
+ if (chain.seenProposerPreferences.isKnown(proposalSlot, validatorIndex)) {
67
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
68
+ code: ProposerPreferencesErrorCode.ALREADY_KNOWN,
69
+ proposalSlot,
70
+ validatorIndex,
71
+ });
72
+ }
73
+
74
+ // [REJECT] `signed_proposer_preferences.signature` is valid with respect to the validator's public key.
75
+ const signatureSet = createSingleSignatureSetFromComponents(
76
+ chain.pubkeyCache.getOrThrow(validatorIndex),
77
+ getProposerPreferencesSigningRoot(chain.config, preferences),
78
+ signedProposerPreferences.signature
79
+ );
80
+
81
+ if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true}))) {
82
+ throw new ProposerPreferencesError(GossipAction.REJECT, {
83
+ code: ProposerPreferencesErrorCode.INVALID_SIGNATURE,
84
+ proposalSlot,
85
+ validatorIndex,
86
+ });
87
+ }
88
+
89
+ // Valid
90
+ chain.seenProposerPreferences.add(proposalSlot, validatorIndex);
91
+ }