@lodestar/beacon-node 1.44.0-dev.985999b30c → 1.44.0-dev.edb7c53fbc

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 (70) 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/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +1 -1
  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 +77 -37
  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 +5 -2
  12. package/lib/chain/blocks/importBlock.js.map +1 -1
  13. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  14. package/lib/chain/blocks/importExecutionPayload.js +4 -2
  15. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  16. package/lib/chain/chain.d.ts +1 -1
  17. package/lib/chain/chain.d.ts.map +1 -1
  18. package/lib/chain/chain.js +2 -1
  19. package/lib/chain/chain.js.map +1 -1
  20. package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
  21. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  22. package/lib/chain/errors/executionPayloadBid.js +1 -0
  23. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  24. package/lib/chain/opPools/executionPayloadBidPool.d.ts +4 -4
  25. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  26. package/lib/chain/opPools/executionPayloadBidPool.js +6 -4
  27. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  28. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  29. package/lib/chain/prepareNextSlot.js +2 -1
  30. package/lib/chain/prepareNextSlot.js.map +1 -1
  31. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
  32. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  33. package/lib/chain/produceBlock/produceBlockBody.js +59 -16
  34. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  35. package/lib/chain/regen/interface.d.ts +1 -0
  36. package/lib/chain/regen/interface.d.ts.map +1 -1
  37. package/lib/chain/regen/interface.js +1 -0
  38. package/lib/chain/regen/interface.js.map +1 -1
  39. package/lib/chain/validation/executionPayloadBid.js +12 -2
  40. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  41. package/lib/metrics/metrics/lodestar.d.ts +1 -1
  42. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  43. package/lib/metrics/metrics/lodestar.js +4 -3
  44. package/lib/metrics/metrics/lodestar.js.map +1 -1
  45. package/lib/network/interface.d.ts +1 -0
  46. package/lib/network/interface.d.ts.map +1 -1
  47. package/lib/network/network.d.ts +1 -0
  48. package/lib/network/network.d.ts.map +1 -1
  49. package/lib/network/network.js +5 -0
  50. package/lib/network/network.js.map +1 -1
  51. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  52. package/lib/network/processor/gossipHandlers.js +10 -3
  53. package/lib/network/processor/gossipHandlers.js.map +1 -1
  54. package/package.json +14 -14
  55. package/src/api/impl/beacon/blocks/index.ts +36 -0
  56. package/src/api/impl/beacon/pool/index.ts +2 -1
  57. package/src/api/impl/validator/index.ts +90 -38
  58. package/src/chain/blocks/importBlock.ts +9 -2
  59. package/src/chain/blocks/importExecutionPayload.ts +7 -2
  60. package/src/chain/chain.ts +2 -0
  61. package/src/chain/errors/executionPayloadBid.ts +2 -0
  62. package/src/chain/opPools/executionPayloadBidPool.ts +10 -9
  63. package/src/chain/prepareNextSlot.ts +2 -1
  64. package/src/chain/produceBlock/produceBlockBody.ts +83 -18
  65. package/src/chain/regen/interface.ts +1 -0
  66. package/src/chain/validation/executionPayloadBid.ts +13 -2
  67. package/src/metrics/metrics/lodestar.ts +4 -3
  68. package/src/network/interface.ts +1 -0
  69. package/src/network/network.ts +11 -0
  70. package/src/network/processor/gossipHandlers.ts +13 -3
@@ -116,13 +116,19 @@ export async function importBlock(
116
116
  }
117
117
  executionStatus = parentBlock.executionStatus;
118
118
  }
119
+
120
+ // getBeaconProposerOrNull will return null if head state is more than one epoch away
121
+ // from block slot. We skip proposer boost canonical check as we cannot determine the canonical proposer
122
+ const expectedProposerIndex: number | null = this.getHeadState().getBeaconProposerOrNull(blockSlot);
123
+
119
124
  const blockSummary = this.forkChoice.onBlock(
120
125
  block.message,
121
126
  postState,
122
127
  blockDelaySec,
123
128
  currentSlot,
124
129
  executionStatus,
125
- dataAvailabilityStatus
130
+ dataAvailabilityStatus,
131
+ expectedProposerIndex
126
132
  );
127
133
 
128
134
  // This adds the state necessary to process the next block
@@ -258,7 +264,8 @@ export async function importBlock(
258
264
  this.forkChoice.notifyPtcMessages(
259
265
  toRootHex(payloadAttestation.data.beaconBlockRoot),
260
266
  ptcIndices,
261
- payloadAttestation.data.payloadPresent
267
+ payloadAttestation.data.payloadPresent,
268
+ payloadAttestation.data.blobDataAvailable
262
269
  );
263
270
  }
264
271
  } catch (e) {
@@ -129,7 +129,7 @@ export async function importExecutionPayload(
129
129
 
130
130
  // 3. Regenerate state for envelope verification
131
131
  const blockState = await this.regen
132
- .getBlockSlotState(protoBlock, protoBlock.slot, {dontTransferCache: true}, RegenCaller.processBlock)
132
+ .getBlockSlotState(protoBlock, protoBlock.slot, {dontTransferCache: true}, RegenCaller.importExecutionPayload)
133
133
  .catch(() =>
134
134
  // only happen at the 1st batch of skipped slot checkpoint sync
135
135
  this.regen.getClosestHeadState(protoBlock)
@@ -255,7 +255,11 @@ export async function importExecutionPayload(
255
255
  }
256
256
 
257
257
  // 8. Record metrics for payload envelope and column sources
258
- this.metrics?.importPayload.bySource.inc({source: payloadInput.getPayloadEnvelopeSource().source});
258
+ const delaySec = this.clock.secFromSlot(slot);
259
+ this.metrics?.importPayload.elapsedTimeTillImported.observe(
260
+ {source: payloadInput.getPayloadEnvelopeSource().source},
261
+ delaySec
262
+ );
259
263
  for (const {source} of payloadInput.getSampledColumnsWithSource()) {
260
264
  this.metrics?.importPayload.columnsBySource.inc({source});
261
265
  }
@@ -276,6 +280,7 @@ export async function importExecutionPayload(
276
280
  builderIndex: envelope.builderIndex,
277
281
  blockRoot: blockRootHex,
278
282
  blockHash: blockHashHex,
283
+ delaySec,
279
284
  });
280
285
  }
281
286
 
@@ -1049,6 +1049,7 @@ export class BeaconChain implements IBeaconChain {
1049
1049
  feeRecipient,
1050
1050
  commonBlockBodyPromise,
1051
1051
  parentBlock,
1052
+ builderBid,
1052
1053
  }: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}
1053
1054
  ): Promise<{
1054
1055
  block: AssembledBlockType<T>;
@@ -1078,6 +1079,7 @@ export class BeaconChain implements IBeaconChain {
1078
1079
  proposerIndex,
1079
1080
  proposerPubKey,
1080
1081
  commonBlockBodyPromise,
1082
+ builderBid,
1081
1083
  }
1082
1084
  );
1083
1085
 
@@ -11,6 +11,7 @@ export enum ExecutionPayloadBidErrorCode {
11
11
  UNKNOWN_BLOCK_ROOT = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_BLOCK_ROOT",
12
12
  UNKNOWN_PARENT_BLOCK_HASH = "EXECUTION_PAYLOAD_BID_ERROR_UNKNOWN_PARENT_BLOCK_HASH",
13
13
  INVALID_SLOT = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SLOT",
14
+ NOT_LATER_THAN_PARENT = "EXECUTION_PAYLOAD_BID_ERROR_NOT_LATER_THAN_PARENT",
14
15
  INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
15
16
  NO_MATCHING_PROPOSER_PREFERENCES = "EXECUTION_PAYLOAD_BID_ERROR_NO_MATCHING_PROPOSER_PREFERENCES",
16
17
  PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH",
@@ -41,6 +42,7 @@ export type ExecutionPayloadBidErrorType =
41
42
  | {code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT; parentBlockRoot: RootHex}
42
43
  | {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
43
44
  | {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
45
+ | {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
44
46
  | {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
45
47
  | {
46
48
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES;
@@ -12,13 +12,13 @@ type BlockRootHex = string;
12
12
  type BlockHashHex = string;
13
13
 
14
14
  /**
15
- * Store the best execution payload bid per slot / (parent block root, parent block hash).
15
+ * Store the best signed execution payload bid per slot / (parent block root, parent block hash).
16
16
  */
17
17
  export class ExecutionPayloadBidPool {
18
18
  private readonly bidByParentHashByParentRootBySlot = new MapDef<
19
19
  Slot,
20
- MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>
21
- >(() => new MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>(() => new Map()));
20
+ MapDef<BlockRootHex, Map<BlockHashHex, gloas.SignedExecutionPayloadBid>>
21
+ >(() => new MapDef<BlockRootHex, Map<BlockHashHex, gloas.SignedExecutionPayloadBid>>(() => new Map()));
22
22
  private lowestPermissibleSlot = 0;
23
23
 
24
24
  get size(): number {
@@ -31,8 +31,8 @@ export class ExecutionPayloadBidPool {
31
31
  return count;
32
32
  }
33
33
 
34
- add(bid: gloas.ExecutionPayloadBid): InsertOutcome {
35
- const {slot, parentBlockRoot, parentBlockHash, value} = bid;
34
+ add(bid: gloas.SignedExecutionPayloadBid): InsertOutcome {
35
+ const {slot, parentBlockRoot, parentBlockHash, value} = bid.message;
36
36
  const lowestPermissibleSlot = this.lowestPermissibleSlot;
37
37
 
38
38
  if (slot < lowestPermissibleSlot) {
@@ -45,7 +45,7 @@ export class ExecutionPayloadBidPool {
45
45
  const existing = bidByParentHash.get(parentHashHex);
46
46
 
47
47
  if (existing) {
48
- const existingValue = existing.value;
48
+ const existingValue = existing.message.value;
49
49
  const newValue = value;
50
50
  if (newValue > existingValue) {
51
51
  bidByParentHash.set(parentHashHex, bid);
@@ -59,14 +59,15 @@ export class ExecutionPayloadBidPool {
59
59
  }
60
60
 
61
61
  /**
62
- * Return the highest-value bid matching slot, parent block hash, and parent block root.
62
+ * Return the highest-value signed bid matching slot, parent block hash, and parent block root.
63
63
  * Used for gossip validation and block production.
64
64
  */
65
65
  getBestBid(
66
66
  slot: Slot,
67
- parentBlockHash: BlockHashHex,
67
+ parentBlockHash: BlockHashHex | null,
68
68
  parentBlockRoot: BlockRootHex
69
- ): gloas.ExecutionPayloadBid | null {
69
+ ): gloas.SignedExecutionPayloadBid | null {
70
+ if (parentBlockHash === null) return null;
70
71
  const bidByParentHash = this.bidByParentHashByParentRootBySlot.get(slot)?.get(parentBlockRoot);
71
72
  return bidByParentHash?.get(parentBlockHash) ?? null;
72
73
  }
@@ -169,7 +169,8 @@ export class PrepareNextSlotScheduler {
169
169
  // Apply parent payload once here as it's reused by EL prep and SSE emit below
170
170
  let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
171
171
  if (isStatePostGloas(updatedPrepareState)) {
172
- if (this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)) {
172
+ // Spec: should_build_on_full(store, head) — see produceBlockBody.ts for context.
173
+ if (this.chain.forkChoice.shouldBuildOnFull(updatedHead)) {
173
174
  parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
174
175
  // Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
175
176
  if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
@@ -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,19 +270,16 @@ 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;
221
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
222
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
223
- const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
224
- if (isExtendingPayload) {
278
+ // Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
279
+ // (set by chain.getProposerHead(slot)). Returns false when the PTC majority
280
+ // signalled the blob data is not available, forcing a build on EMPTY (reorg).
281
+ const isBuildingOnFull = this.forkChoice.shouldBuildOnFull(parentBlock);
282
+ if (isBuildingOnFull) {
225
283
  parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
226
284
  parentExecutionRequests = await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot);
227
285
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
@@ -244,6 +302,16 @@ export async function produceBlockBody<T extends BlockType>(
244
302
  const {prepType, payloadId} = prepareRes;
245
303
  Object.assign(logMeta, {executionPayloadPrepType: prepType});
246
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
+
247
315
  if (prepType !== PayloadPreparationType.Cached) {
248
316
  await sleep(PAYLOAD_GENERATION_TIME_MS);
249
317
  }
@@ -297,14 +365,11 @@ export async function produceBlockBody<T extends BlockType>(
297
365
  blockSlot - 1
298
366
  );
299
367
  gloasBody.parentExecutionRequests = parentExecutionRequests;
300
- // Drop voluntary exits that parent_execution_requests have invalidated (e.g. a withdrawal
301
- // request initiating an exit on the same validator). Op pool selected against the unapplied
302
- // state, so re-validate against the post-apply state to avoid producing an invalid block.
303
- if (isExtendingPayload && commonBlockBody.voluntaryExits.length > 0) {
304
- gloasBody.voluntaryExits = commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) =>
305
- stateAfterParentPayload.isValidVoluntaryExit(signedVoluntaryExit, false)
306
- );
307
- }
368
+ gloasBody.voluntaryExits = maybeFilterInvalidatedVoluntaryExits(
369
+ commonBlockBody,
370
+ isBuildingOnFull,
371
+ () => stateAfterParentPayload
372
+ );
308
373
  blockBody = gloasBody as AssembledBodyType<T>;
309
374
 
310
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 --
@@ -987,10 +987,11 @@ export function createLodestarMetrics(
987
987
  }),
988
988
  },
989
989
  importPayload: {
990
- bySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
991
- name: "lodestar_import_payload_by_source_total",
992
- help: "Total number of imported execution payload envelopes by source",
990
+ elapsedTimeTillImported: register.histogram<{source: PayloadEnvelopeInputSource}>({
991
+ name: "lodestar_import_payload_elapsed_time_till_imported_seconds",
992
+ help: "Time elapsed between slot time and the time execution payload envelope is imported (added to fork choice)",
993
993
  labelNames: ["source"],
994
+ buckets: [1, 2, 3, 6, 9, 12],
994
995
  }),
995
996
  columnsBySource: register.gauge<{source: PayloadEnvelopeInputSource}>({
996
997
  name: "lodestar_import_payload_columns_by_source_total",
@@ -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);
@@ -1117,7 +1117,16 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1117
1117
  }
1118
1118
 
1119
1119
  const slot = envelope.payload.slotNumber;
1120
- const delaySec = seenTimestampSec - computeTimeAtSlot(config, slot, chain.genesisTime);
1120
+ const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
1121
+
1122
+ logger.debug("Received gossip payload envelope", {
1123
+ currentSlot: chain.clock.currentSlot,
1124
+ peerId: peerIdStr,
1125
+ slot,
1126
+ blockRoot: toRootHex(envelope.beaconBlockRoot),
1127
+ delaySec,
1128
+ });
1129
+
1121
1130
  metrics?.gossipExecutionPayloadEnvelope.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1122
1131
  chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
1123
1132
 
@@ -1206,7 +1215,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1206
1215
  chain.forkChoice.notifyPtcMessages(
1207
1216
  toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1208
1217
  validationResult.validatorCommitteeIndices,
1209
- payloadAttestationMessage.data.payloadPresent
1218
+ payloadAttestationMessage.data.payloadPresent,
1219
+ payloadAttestationMessage.data.blobDataAvailable
1210
1220
  );
1211
1221
  },
1212
1222
  [GossipType.execution_payload_bid]: async ({
@@ -1219,7 +1229,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1219
1229
 
1220
1230
  // Handle valid payload bid by storing in a bid pool
1221
1231
  try {
1222
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid.message);
1232
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
1223
1233
  metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1224
1234
  } catch (e) {
1225
1235
  logger.error("Error adding to executionPayloadBid pool", {}, e as Error);