@lodestar/beacon-node 1.47.0-dev.36fc832983 → 1.47.0-dev.5ba1393504

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 (49) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +0 -6
  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 +12 -22
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/api/impl/validator/utils.d.ts +5 -0
  8. package/lib/api/impl/validator/utils.d.ts.map +1 -1
  9. package/lib/api/impl/validator/utils.js +18 -12
  10. package/lib/api/impl/validator/utils.js.map +1 -1
  11. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  12. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  13. package/lib/chain/builderCircuitBreaker.js +28 -15
  14. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  15. package/lib/chain/chain.d.ts +1 -1
  16. package/lib/chain/chain.d.ts.map +1 -1
  17. package/lib/chain/chain.js +2 -1
  18. package/lib/chain/chain.js.map +1 -1
  19. package/lib/chain/options.d.ts +1 -1
  20. package/lib/chain/options.d.ts.map +1 -1
  21. package/lib/chain/prepareNextSlot.js +1 -1
  22. package/lib/chain/prepareNextSlot.js.map +1 -1
  23. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -0
  24. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  25. package/lib/chain/produceBlock/produceBlockBody.js +6 -1
  26. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  27. package/lib/execution/builder/http.d.ts +10 -7
  28. package/lib/execution/builder/http.d.ts.map +1 -1
  29. package/lib/execution/builder/http.js +11 -8
  30. package/lib/execution/builder/http.js.map +1 -1
  31. package/lib/metrics/metrics/beacon.d.ts +3 -3
  32. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  33. package/lib/metrics/metrics/beacon.js +9 -9
  34. package/lib/metrics/metrics/beacon.js.map +1 -1
  35. package/lib/sync/unknownBlock.d.ts.map +1 -1
  36. package/lib/sync/unknownBlock.js +17 -5
  37. package/lib/sync/unknownBlock.js.map +1 -1
  38. package/package.json +14 -14
  39. package/src/api/impl/beacon/blocks/index.ts +0 -6
  40. package/src/api/impl/validator/index.ts +22 -27
  41. package/src/api/impl/validator/utils.ts +30 -15
  42. package/src/chain/builderCircuitBreaker.ts +31 -17
  43. package/src/chain/chain.ts +2 -0
  44. package/src/chain/options.ts +1 -1
  45. package/src/chain/prepareNextSlot.ts +1 -1
  46. package/src/chain/produceBlock/produceBlockBody.ts +13 -0
  47. package/src/execution/builder/http.ts +11 -11
  48. package/src/metrics/metrics/beacon.ts +9 -9
  49. package/src/sync/unknownBlock.ts +18 -5
@@ -100,6 +100,8 @@ export type BlockAttributes = {
100
100
  slot: Slot;
101
101
  parentBlock: ProtoBlock;
102
102
  feeRecipient?: string;
103
+ /** Verify that a locally produced execution payload uses `feeRecipient`. */
104
+ strictFeeRecipientCheck?: boolean;
103
105
  /** When provided, build block with this builder bid instead of a self-build bid */
104
106
  builderBid?: gloas.SignedExecutionPayloadBid;
105
107
  };
@@ -201,6 +203,7 @@ export async function produceBlockBody<T extends BlockType>(
201
203
  const {
202
204
  slot: blockSlot,
203
205
  feeRecipient: requestedFeeRecipient,
206
+ strictFeeRecipientCheck,
204
207
  parentBlock,
205
208
  proposerIndex,
206
209
  proposerPubKey,
@@ -334,6 +337,16 @@ export async function produceBlockBody<T extends BlockType>(
334
337
  executionPayloadValue = payloadRes.executionPayloadValue;
335
338
  shouldOverrideBuilder = payloadRes.shouldOverrideBuilder;
336
339
 
340
+ if (
341
+ strictFeeRecipientCheck &&
342
+ requestedFeeRecipient &&
343
+ !byteArrayEquals(executionPayload.feeRecipient, fromHex(requestedFeeRecipient))
344
+ ) {
345
+ throw Error(
346
+ `Invalid feeRecipient set in engine payload expected=${requestedFeeRecipient} actual=${toHex(executionPayload.feeRecipient)}`
347
+ );
348
+ }
349
+
337
350
  if (blobsBundle === undefined) {
338
351
  throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`);
339
352
  }
@@ -71,15 +71,18 @@ export class NoBidReceived extends Error {
71
71
  }
72
72
 
73
73
  /**
74
- * Beacon clients select randomized values from the following ranges when initializing
75
- * the circuit breaker (so at boot time and once for each unique boot).
74
+ * Default circuit breaker parameters:
76
75
  *
77
- * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
78
- * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
76
+ * SLOTS_PER_EPOCH <= FAULT_INSPECTION_WINDOW < 2 * SLOTS_PER_EPOCH (randomized at initialization)
77
+ * ALLOWED_FAULTS: FAULT_INSPECTION_WINDOW // 4
79
78
  *
80
- * The values are randomized per node so builders cannot predict when a given proposer will
81
- * fall back to local blocks. With fixed thresholds a builder could withhold payloads right up
82
- * to the limit without ever tripping the breaker.
79
+ * e.g. on mainnet SLOTS_PER_EPOCH is 32, so FAULT_INSPECTION_WINDOW is between 32 and 63,
80
+ * and a window of 40 results in ALLOWED_FAULTS = 10.
81
+ *
82
+ * The randomized default window keeps the exact slots under inspection unpredictable per node,
83
+ * so a builder cannot tell when past faults age out of a given proposer's window and time
84
+ * withholding around trip or recovery points. Explicitly configured windows are clamped to at
85
+ * least SLOTS_PER_EPOCH, and configured allowedFaults is capped at the default ~25% budget.
83
86
  */
84
87
  export function getFaultInspectionParams(opts: {faultInspectionWindow?: number; allowedFaults?: number}): {
85
88
  faultInspectionWindow: number;
@@ -90,10 +93,7 @@ export function getFaultInspectionParams(opts: {faultInspectionWindow?: number;
90
93
  SLOTS_PER_EPOCH
91
94
  );
92
95
  // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
93
- const allowedFaults = Math.min(
94
- opts.allowedFaults ?? Math.floor(faultInspectionWindow / 4),
95
- Math.floor(faultInspectionWindow / 4)
96
- );
96
+ const allowedFaults = Math.min(opts.allowedFaults ?? Infinity, Math.floor(faultInspectionWindow / 4));
97
97
  return {faultInspectionWindow, allowedFaults};
98
98
  }
99
99
 
@@ -143,17 +143,17 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
143
143
  name: "beacon_builder_circuit_breaker_active",
144
144
  help: "Whether the builder circuit breaker is active (1) causing builder bids to be ignored",
145
145
  }),
146
- faults: register.gauge({
147
- name: "beacon_builder_circuit_breaker_faults",
148
- help: "Count of blocks with unrevealed payloads in the fault inspection window",
146
+ canonicalBlocks: register.gauge({
147
+ name: "beacon_builder_circuit_breaker_canonical_blocks",
148
+ help: "Count of canonical FULL and EMPTY blocks in the fault inspection window",
149
149
  }),
150
- blocksPresent: register.gauge({
151
- name: "beacon_builder_circuit_breaker_blocks_present",
152
- help: "Count of blocks present in the fault inspection window",
150
+ fullBlocks: register.gauge({
151
+ name: "beacon_builder_circuit_breaker_full_blocks",
152
+ help: "Count of canonical FULL blocks in the fault inspection window",
153
153
  }),
154
- payloadsRevealed: register.gauge({
155
- name: "beacon_builder_circuit_breaker_payloads_revealed",
156
- help: "Count of blocks with revealed payloads in the fault inspection window",
154
+ emptyBlocks: register.gauge({
155
+ name: "beacon_builder_circuit_breaker_empty_blocks",
156
+ help: "Count of canonical EMPTY blocks in the fault inspection window",
157
157
  }),
158
158
  },
159
159
 
@@ -1,6 +1,6 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {ForkSeq} from "@lodestar/params";
3
+ import {ForkSeq, isForkPostGloas} from "@lodestar/params";
4
4
  import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
5
5
  import {computeTimeAtSlot} from "@lodestar/state-transition";
6
6
  import {RootHex, Slot, gloas} from "@lodestar/types";
@@ -764,9 +764,10 @@ export class BlockInputSync {
764
764
  // this prevents unbundling attack
765
765
  // see https://lighthouse-blog.sigmaprime.io/mev-unbundling-rpc.html
766
766
  const {slot: blockSlot, proposerIndex} = pendingBlock.blockInput.getBlock().message;
767
+ const blockRootHex = pendingBlock.blockInput.blockRootHex;
767
768
  const logCtx = {
768
769
  slot: blockSlot,
769
- root: pendingBlock.blockInput.blockRootHex,
770
+ root: blockRootHex,
770
771
  delaySec: this.chain.clock.secFromSlot(blockSlot),
771
772
  };
772
773
  this.logger.verbose("Processing downloaded block", logCtx);
@@ -780,7 +781,7 @@ export class BlockInputSync {
780
781
  // eligible for proposer boost to prevent unbundling attack
781
782
  this.logger.verbose("Avoid proposer boost for this block of known proposer", {
782
783
  slot: blockSlot,
783
- root: pendingBlock.blockInput.blockRootHex,
784
+ root: blockRootHex,
784
785
  proposerIndex,
785
786
  });
786
787
  await sleep(proposerBoostWindowMs);
@@ -807,12 +808,24 @@ export class BlockInputSync {
807
808
  if (!res.err) {
808
809
  this.logger.verbose("Processed block from unknown sync", logCtx);
809
810
  // no need to update status to "processed", delete anyway
810
- this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
811
+ this.pendingBlocks.delete(blockRootHex);
812
+
813
+ if (isForkPostGloas(fork)) {
814
+ const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
815
+ if (!payloadInput) {
816
+ this.logger.warn("PayloadEnvelopeInput not seeded during unknown sync processReadyBlock()", logCtx);
817
+ } else {
818
+ // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
819
+ // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
820
+ this.chain.getBlobsTracker.triggerGetBlobs(payloadInput);
821
+ }
822
+ }
823
+
811
824
  // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
812
825
  // are advanced through the same dependency checks as every other pending item.
813
826
  this.triggerUnknownBlockSearch();
814
827
  } else {
815
- const errorData = {slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex};
828
+ const errorData = {slot: pendingBlock.blockInput.slot, root: blockRootHex};
816
829
  if (res.err instanceof BlockError) {
817
830
  switch (res.err.type.code) {
818
831
  // This cases are already handled with `{ignoreIfKnown: true}`