@lodestar/beacon-node 1.44.0-dev.b506aab66d → 1.44.0-dev.f507c14622

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 (55) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +30 -0
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +77 -37
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  8. package/lib/chain/blocks/importBlock.js +4 -1
  9. package/lib/chain/blocks/importBlock.js.map +1 -1
  10. package/lib/chain/blocks/importExecutionPayload.js +1 -1
  11. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  12. package/lib/chain/chain.d.ts +1 -1
  13. package/lib/chain/chain.d.ts.map +1 -1
  14. package/lib/chain/chain.js +2 -1
  15. package/lib/chain/chain.js.map +1 -1
  16. package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
  17. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  18. package/lib/chain/errors/executionPayloadBid.js +1 -0
  19. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  20. package/lib/chain/opPools/executionPayloadBidPool.d.ts +4 -4
  21. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  22. package/lib/chain/opPools/executionPayloadBidPool.js +6 -4
  23. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  24. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
  25. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  26. package/lib/chain/produceBlock/produceBlockBody.js +54 -14
  27. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  28. package/lib/chain/regen/interface.d.ts +1 -0
  29. package/lib/chain/regen/interface.d.ts.map +1 -1
  30. package/lib/chain/regen/interface.js +1 -0
  31. package/lib/chain/regen/interface.js.map +1 -1
  32. package/lib/chain/validation/executionPayloadBid.js +12 -2
  33. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  34. package/lib/network/interface.d.ts +1 -0
  35. package/lib/network/interface.d.ts.map +1 -1
  36. package/lib/network/network.d.ts +1 -0
  37. package/lib/network/network.d.ts.map +1 -1
  38. package/lib/network/network.js +5 -0
  39. package/lib/network/network.js.map +1 -1
  40. package/lib/network/processor/gossipHandlers.js +1 -1
  41. package/lib/network/processor/gossipHandlers.js.map +1 -1
  42. package/package.json +14 -14
  43. package/src/api/impl/beacon/blocks/index.ts +36 -0
  44. package/src/api/impl/validator/index.ts +90 -38
  45. package/src/chain/blocks/importBlock.ts +7 -1
  46. package/src/chain/blocks/importExecutionPayload.ts +1 -1
  47. package/src/chain/chain.ts +2 -0
  48. package/src/chain/errors/executionPayloadBid.ts +2 -0
  49. package/src/chain/opPools/executionPayloadBidPool.ts +10 -9
  50. package/src/chain/produceBlock/produceBlockBody.ts +78 -16
  51. package/src/chain/regen/interface.ts +1 -0
  52. package/src/chain/validation/executionPayloadBid.ts +13 -2
  53. package/src/network/interface.ts +1 -0
  54. package/src/network/network.ts +11 -0
  55. package/src/network/processor/gossipHandlers.ts +1 -1
@@ -50,7 +50,7 @@ import {
50
50
  gloas,
51
51
  ssz,
52
52
  } from "@lodestar/types";
53
- import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
53
+ import {GWEI_TO_WEI, Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
54
54
  import {ZERO_HASH_HEX} from "../../constants/index.js";
55
55
  import {numToQuantity} from "../../execution/engine/utils.js";
56
56
  import {IExecutionBuilder, IExecutionEngine, PayloadAttributes, PayloadId} from "../../execution/index.js";
@@ -91,6 +91,8 @@ export type BlockAttributes = {
91
91
  slot: Slot;
92
92
  parentBlock: ProtoBlock;
93
93
  feeRecipient?: string;
94
+ /** When provided, build block with this builder bid instead of a self-build bid */
95
+ builderBid?: gloas.SignedExecutionPayloadBid;
94
96
  };
95
97
 
96
98
  export enum BlockType {
@@ -150,6 +152,28 @@ export type ProduceResult =
150
152
  | ProduceFullPhase0
151
153
  | ProduceBlinded;
152
154
 
155
+ /**
156
+ * Drop voluntary exits that `parent_execution_requests` have invalidated (e.g. a withdrawal
157
+ * request initiating an exit on the same validator). Op pool selected against the unapplied
158
+ * state, so re-validate against the post-apply state to avoid producing an invalid block.
159
+ *
160
+ * `getStateAfterParentPayload` is a thunk so the post-apply state is only materialized when
161
+ * actually needed (i.e. when extending the parent payload and there are exits to filter).
162
+ */
163
+ function maybeFilterInvalidatedVoluntaryExits(
164
+ commonBlockBody: CommonBlockBody,
165
+ isExtendingPayload: boolean,
166
+ getStateAfterParentPayload: () => IBeaconStateViewBellatrix
167
+ ): CommonBlockBody["voluntaryExits"] {
168
+ if (!isExtendingPayload || commonBlockBody.voluntaryExits.length === 0) {
169
+ return commonBlockBody.voluntaryExits;
170
+ }
171
+ const state = getStateAfterParentPayload();
172
+ return commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) =>
173
+ state.isValidVoluntaryExit(signedVoluntaryExit, false)
174
+ );
175
+ }
176
+
153
177
  export async function produceBlockBody<T extends BlockType>(
154
178
  this: BeaconChain,
155
179
  blockType: T,
@@ -172,6 +196,7 @@ export async function produceBlockBody<T extends BlockType>(
172
196
  proposerIndex,
173
197
  proposerPubKey,
174
198
  commonBlockBodyPromise,
199
+ builderBid,
175
200
  } = blockAttr;
176
201
  let executionPayloadValue: Wei;
177
202
  let blockBody: AssembledBodyType<T>;
@@ -192,7 +217,43 @@ export async function produceBlockBody<T extends BlockType>(
192
217
  };
193
218
  this.logger.verbose("Producing beacon block body", logMeta);
194
219
 
195
- if (isForkPostGloas(fork)) {
220
+ if (builderBid !== undefined) {
221
+ if (!isStatePostGloas(currentState)) {
222
+ throw new Error("Expected Gloas state for builder bid block production");
223
+ }
224
+
225
+ const isExtendingPayload = byteArrayEquals(
226
+ builderBid.message.parentBlockHash,
227
+ currentState.latestExecutionPayloadBid.blockHash
228
+ );
229
+ const parentExecutionRequests = isExtendingPayload
230
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
231
+ : ssz.electra.ExecutionRequests.defaultValue();
232
+ executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;
233
+
234
+ const commonBlockBody = await commonBlockBodyPromise;
235
+ const gloasBody = Object.assign({}, commonBlockBody) as gloas.BeaconBlockBody;
236
+ gloasBody.signedExecutionPayloadBid = builderBid;
237
+ gloasBody.payloadAttestations = this.payloadAttestationPool.getPayloadAttestationsForBlock(
238
+ parentBlock.blockRoot,
239
+ blockSlot - 1
240
+ );
241
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
242
+ gloasBody.voluntaryExits = maybeFilterInvalidatedVoluntaryExits(commonBlockBody, isExtendingPayload, () =>
243
+ currentState.withParentPayloadApplied(parentExecutionRequests)
244
+ );
245
+ blockBody = gloasBody as AssembledBodyType<T>;
246
+
247
+ this.logger.verbose("Produced block with builder bid", {
248
+ slot: blockSlot,
249
+ builderIndex: builderBid.message.builderIndex,
250
+ bidValue: builderBid.message.value,
251
+ parentBlockHash: toRootHex(builderBid.message.parentBlockHash),
252
+ parentBlockRoot: toRootHex(builderBid.message.parentBlockRoot),
253
+ blockHash: toRootHex(builderBid.message.blockHash),
254
+ isExtendingPayload,
255
+ });
256
+ } else if (isForkPostGloas(fork)) {
196
257
  if (!isStatePostGloas(currentState)) {
197
258
  throw new Error("Expected Gloas state for Gloas block production");
198
259
  }
@@ -209,12 +270,6 @@ export async function produceBlockBody<T extends BlockType>(
209
270
 
210
271
  const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer();
211
272
 
212
- this.logger.verbose("Preparing execution payload from engine", {
213
- slot: blockSlot,
214
- parentBlockRoot: toRootHex(parentBlockRoot),
215
- feeRecipient,
216
- });
217
-
218
273
  // Get execution payload from EL
219
274
  let parentBlockHash: Bytes32;
220
275
  let parentExecutionRequests: electra.ExecutionRequests;
@@ -247,6 +302,16 @@ export async function produceBlockBody<T extends BlockType>(
247
302
  const {prepType, payloadId} = prepareRes;
248
303
  Object.assign(logMeta, {executionPayloadPrepType: prepType});
249
304
 
305
+ this.logger.verbose("Prepared execution payload from engine", {
306
+ slot: blockSlot,
307
+ parentBlockRoot: toRootHex(parentBlockRoot),
308
+ parentBlockHash: toRootHex(parentBlockHash),
309
+ feeRecipient,
310
+ prepType,
311
+ payloadId,
312
+ isBuildingOnFull,
313
+ });
314
+
250
315
  if (prepType !== PayloadPreparationType.Cached) {
251
316
  await sleep(PAYLOAD_GENERATION_TIME_MS);
252
317
  }
@@ -300,14 +365,11 @@ export async function produceBlockBody<T extends BlockType>(
300
365
  blockSlot - 1
301
366
  );
302
367
  gloasBody.parentExecutionRequests = parentExecutionRequests;
303
- // Drop voluntary exits that parent_execution_requests have invalidated (e.g. a withdrawal
304
- // request initiating an exit on the same validator). Op pool selected against the unapplied
305
- // state, so re-validate against the post-apply state to avoid producing an invalid block.
306
- if (isBuildingOnFull && commonBlockBody.voluntaryExits.length > 0) {
307
- gloasBody.voluntaryExits = commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) =>
308
- stateAfterParentPayload.isValidVoluntaryExit(signedVoluntaryExit, false)
309
- );
310
- }
368
+ gloasBody.voluntaryExits = maybeFilterInvalidatedVoluntaryExits(
369
+ commonBlockBody,
370
+ isBuildingOnFull,
371
+ () => stateAfterParentPayload
372
+ );
311
373
  blockBody = gloasBody as AssembledBodyType<T>;
312
374
 
313
375
  // Store execution payload data required to construct execution payload envelope later
@@ -17,6 +17,7 @@ export enum RegenCaller {
17
17
  predictProposerHead = "predictProposerHead",
18
18
  produceAttestationData = "produceAttestationData",
19
19
  processBlocksInEpoch = "processBlocksInEpoch",
20
+ importExecutionPayload = "importExecutionPayload",
20
21
  validateGossipAggregateAndProof = "validateGossipAggregateAndProof",
21
22
  validateGossipAttestation = "validateGossipAttestation",
22
23
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
@@ -61,6 +61,17 @@ async function validateExecutionPayloadBid(
61
61
  });
62
62
  }
63
63
 
64
+ // [REJECT] The bid is for a higher slot than its parent block -- i.e.
65
+ // validate that `bid.slot` is greater than the slot of the block with root
66
+ // `bid.parent_block_root`.
67
+ if (bid.slot <= parentBlock.slot) {
68
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
69
+ code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT,
70
+ parentSlot: parentBlock.slot,
71
+ slot: bid.slot,
72
+ });
73
+ }
74
+
64
75
  // [IGNORE] A `SignedProposerPreferences` matching `bid.slot` and the bid's branch has been
65
76
  // seen — i.e. `proposal_slot == bid.slot` AND `dependent_root ==
66
77
  // get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`.
@@ -186,11 +197,11 @@ async function validateExecutionPayloadBid(
186
197
  // [IGNORE] this bid is the highest value bid seen for the tuple
187
198
  // `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
188
199
  const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
189
- if (bestBid !== null && bestBid.value >= bid.value) {
200
+ if (bestBid !== null && bestBid.message.value >= bid.value) {
190
201
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
191
202
  code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
192
203
  bidValue: bid.value,
193
- currentHighestBid: bestBid.value,
204
+ currentHighestBid: bestBid.message.value,
194
205
  });
195
206
  }
196
207
  // [IGNORE] `bid.value` is less or equal than the builder's excess balance --
@@ -113,6 +113,7 @@ export interface INetwork extends INetworkCorePublic {
113
113
  publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise<number>;
114
114
  publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise<number>;
115
115
  publishSignedExecutionPayloadEnvelope(signedEnvelope: gloas.SignedExecutionPayloadEnvelope): Promise<number>;
116
+ publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number>;
116
117
  publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number>;
117
118
  publishProposerPreferences(signedProposerPreferences: gloas.SignedProposerPreferences): Promise<number>;
118
119
 
@@ -515,6 +515,17 @@ export class Network implements INetwork {
515
515
  );
516
516
  }
517
517
 
518
+ async publishSignedExecutionPayloadBid(signedBid: gloas.SignedExecutionPayloadBid): Promise<number> {
519
+ const epoch = computeEpochAtSlot(signedBid.message.slot);
520
+ const boundary = this.config.getForkBoundaryAtEpoch(epoch);
521
+
522
+ return this.publishGossip<GossipType.execution_payload_bid>(
523
+ {type: GossipType.execution_payload_bid, boundary},
524
+ signedBid,
525
+ {ignoreDuplicatePublishError: true}
526
+ );
527
+ }
528
+
518
529
  async publishPayloadAttestationMessage(payloadAttestationMessage: gloas.PayloadAttestationMessage): Promise<number> {
519
530
  const epoch = computeEpochAtSlot(payloadAttestationMessage.data.slot);
520
531
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
@@ -1229,7 +1229,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1229
1229
 
1230
1230
  // Handle valid payload bid by storing in a bid pool
1231
1231
  try {
1232
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid.message);
1232
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
1233
1233
  metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1234
1234
  } catch (e) {
1235
1235
  logger.error("Error adding to executionPayloadBid pool", {}, e as Error);