@lodestar/beacon-node 1.44.0-dev.a879adb124 → 1.44.0-dev.aa2d3a82a3

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 (80) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +23 -28
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +0 -25
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +20 -27
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  11. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  12. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  13. package/lib/chain/emitter.d.ts +2 -1
  14. package/lib/chain/emitter.d.ts.map +1 -1
  15. package/lib/chain/emitter.js.map +1 -1
  16. package/lib/chain/errors/blockError.d.ts +0 -7
  17. package/lib/chain/errors/blockError.d.ts.map +1 -1
  18. package/lib/chain/errors/blockError.js +0 -3
  19. package/lib/chain/errors/blockError.js.map +1 -1
  20. package/lib/chain/errors/executionPayloadBid.d.ts +6 -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/forkChoice/index.d.ts.map +1 -1
  25. package/lib/chain/forkChoice/index.js +3 -0
  26. package/lib/chain/forkChoice/index.js.map +1 -1
  27. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  28. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  29. package/lib/chain/validation/block.d.ts +5 -1
  30. package/lib/chain/validation/block.d.ts.map +1 -1
  31. package/lib/chain/validation/block.js +4 -14
  32. package/lib/chain/validation/block.js.map +1 -1
  33. package/lib/chain/validation/executionPayloadBid.js +12 -1
  34. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  35. package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
  36. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  37. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  38. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  39. package/lib/metrics/metrics/lodestar.js +10 -0
  40. package/lib/metrics/metrics/lodestar.js.map +1 -1
  41. package/lib/network/gossip/topic.d.ts +750 -3
  42. package/lib/network/gossip/topic.d.ts.map +1 -1
  43. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  44. package/lib/network/processor/gossipHandlers.js +9 -2
  45. package/lib/network/processor/gossipHandlers.js.map +1 -1
  46. package/lib/network/processor/index.d.ts +2 -2
  47. package/lib/network/processor/index.d.ts.map +1 -1
  48. package/lib/network/processor/index.js +25 -23
  49. package/lib/network/processor/index.js.map +1 -1
  50. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  51. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +1 -2
  52. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  53. package/lib/sync/types.d.ts +9 -1
  54. package/lib/sync/types.d.ts.map +1 -1
  55. package/lib/sync/types.js +9 -2
  56. package/lib/sync/types.js.map +1 -1
  57. package/lib/sync/unknownBlock.d.ts.map +1 -1
  58. package/lib/sync/unknownBlock.js +64 -30
  59. package/lib/sync/unknownBlock.js.map +1 -1
  60. package/lib/util/sszBytes.js +1 -1
  61. package/package.json +15 -15
  62. package/src/api/impl/beacon/blocks/index.ts +27 -37
  63. package/src/api/impl/beacon/pool/index.ts +1 -41
  64. package/src/api/impl/validator/index.ts +29 -39
  65. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -4
  66. package/src/chain/emitter.ts +3 -2
  67. package/src/chain/errors/blockError.ts +0 -4
  68. package/src/chain/errors/executionPayloadBid.ts +7 -0
  69. package/src/chain/forkChoice/index.ts +5 -0
  70. package/src/chain/produceBlock/produceBlockBody.ts +1 -1
  71. package/src/chain/validation/block.ts +12 -16
  72. package/src/chain/validation/executionPayloadBid.ts +13 -1
  73. package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
  74. package/src/metrics/metrics/lodestar.ts +11 -0
  75. package/src/network/processor/gossipHandlers.ts +9 -2
  76. package/src/network/processor/index.ts +27 -27
  77. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +1 -2
  78. package/src/sync/types.ts +11 -2
  79. package/src/sync/unknownBlock.ts +70 -31
  80. package/src/util/sszBytes.ts +1 -1
@@ -11,7 +11,6 @@ import {
11
11
  SLOTS_PER_HISTORICAL_ROOT,
12
12
  isForkPostBellatrix,
13
13
  isForkPostDeneb,
14
- isForkPostElectra,
15
14
  isForkPostFulu,
16
15
  isForkPostGloas,
17
16
  } from "@lodestar/params";
@@ -94,7 +93,7 @@ export function getBeaconBlockApi({
94
93
  ApiModules,
95
94
  "chain" | "config" | "metrics" | "network" | "db"
96
95
  >): ApplicationMethods<routes.beacon.block.Endpoints> {
97
- const publishBlock: ApplicationMethods<routes.beacon.block.Endpoints>["publishBlockV2"] = async (
96
+ const publishBlockV2: ApplicationMethods<routes.beacon.block.Endpoints>["publishBlockV2"] = async (
98
97
  {signedBlockContents, broadcastValidation},
99
98
  _context,
100
99
  opts: PublishBlockOpts = {}
@@ -406,8 +405,8 @@ export function getBeaconBlockApi({
406
405
  }
407
406
  };
408
407
 
409
- const publishBlindedBlock: ApplicationMethods<routes.beacon.block.Endpoints>["publishBlindedBlock"] = async (
410
- {signedBlindedBlock},
408
+ const publishBlindedBlockV2: ApplicationMethods<routes.beacon.block.Endpoints>["publishBlindedBlockV2"] = async (
409
+ {signedBlindedBlock, broadcastValidation},
411
410
  context,
412
411
  opts: PublishBlockOpts = {}
413
412
  ) => {
@@ -437,7 +436,7 @@ export function getBeaconBlockApi({
437
436
  );
438
437
 
439
438
  chain.logger.info("Publishing assembled block", {slot, blockRoot, source});
440
- return publishBlock({signedBlockContents}, {...context, sszBytes: null}, opts);
439
+ return publishBlockV2({signedBlockContents, broadcastValidation}, {...context, sszBytes: null}, opts);
441
440
  }
442
441
 
443
442
  const source = ProducedBlockSource.builder;
@@ -463,7 +462,11 @@ export function getBeaconBlockApi({
463
462
  //
464
463
  // see: https://github.com/ChainSafe/lodestar/issues/5404
465
464
  chain.logger.info("Publishing assembled block", {slot, blockRoot, source});
466
- return publishBlock({signedBlockContents}, {...context, sszBytes: null}, {...opts, ignoreIfKnown: true});
465
+ return publishBlockV2(
466
+ {signedBlockContents, broadcastValidation},
467
+ {...context, sszBytes: null},
468
+ {...opts, ignoreIfKnown: true}
469
+ );
467
470
  }
468
471
  };
469
472
 
@@ -598,23 +601,6 @@ export function getBeaconBlockApi({
598
601
  };
599
602
  },
600
603
 
601
- async getBlockAttestations({blockId}) {
602
- const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId);
603
- const fork = config.getForkName(block.message.slot);
604
-
605
- if (isForkPostElectra(fork)) {
606
- throw new ApiError(
607
- 400,
608
- `Use getBlockAttestationsV2 to retrieve block attestations for post-electra fork=${fork}`
609
- );
610
- }
611
-
612
- return {
613
- data: block.message.body.attestations,
614
- meta: {executionOptimistic, finalized},
615
- };
616
- },
617
-
618
604
  async getBlockAttestationsV2({blockId}) {
619
605
  const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId);
620
606
  return {
@@ -662,16 +648,8 @@ export function getBeaconBlockApi({
662
648
  };
663
649
  },
664
650
 
665
- publishBlock,
666
- publishBlindedBlock,
667
-
668
- async publishBlindedBlockV2(args, context, opts) {
669
- await publishBlindedBlock(args, context, opts);
670
- },
671
-
672
- async publishBlockV2(args, context, opts) {
673
- await publishBlock(args, context, opts);
674
- },
651
+ publishBlockV2,
652
+ publishBlindedBlockV2,
675
653
 
676
654
  async publishExecutionPayloadEnvelope({signedExecutionPayloadEnvelope}) {
677
655
  const seenTimestampSec = Date.now() / 1000;
@@ -686,9 +664,20 @@ export function getBeaconBlockApi({
686
664
  }
687
665
 
688
666
  // TODO GLOAS: review checks, do we want to implement `broadcast_validation`?
689
- const block = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.EMPTY);
667
+ let block = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.EMPTY);
690
668
  if (block === null) {
691
- throw new ApiError(404, `Block not found for beacon block root ${blockRootHex}`);
669
+ // Only wait if the envelope is for the current slot
670
+ if (chain.clock.isCurrentSlotGivenGossipDisparity(slot)) {
671
+ chain.logger.debug("Execution payload envelope received before block, waiting for block to be imported", {
672
+ blockRoot: blockRootHex,
673
+ slot,
674
+ });
675
+ await chain.waitForBlock(slot, blockRootHex);
676
+ block = chain.forkChoice.getBlockHex(blockRootHex, PayloadStatus.EMPTY);
677
+ }
678
+ if (block === null) {
679
+ throw new ApiError(404, `Block not found for beacon block root ${blockRootHex}`);
680
+ }
692
681
  }
693
682
  if (block.slot !== slot) {
694
683
  throw new ApiError(400, `Envelope slot ${slot} does not match block slot ${block.slot}`);
@@ -735,10 +724,11 @@ export function getBeaconBlockApi({
735
724
  await sleep(msToBlockSlot);
736
725
  }
737
726
 
738
- // TODO GLOAS: if block and payload are submitted in parallel, payloadInput may not yet exist.
739
- // A queuing mechanism is needed to handle this case. See https://github.com/ChainSafe/lodestar/issues/8915
740
727
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
741
728
  if (!payloadInput) {
729
+ // The block is awaited above (queuing if the envelope arrived first), and both the API and
730
+ // gossip import paths seed the PayloadEnvelopeInput before importing the block, so the input
731
+ // should exist here.
742
732
  throw new ApiError(404, `PayloadEnvelopeInput not found for block root ${blockRootHex}`);
743
733
  }
744
734
 
@@ -9,7 +9,7 @@ import {
9
9
  isForkPostGloas,
10
10
  } from "@lodestar/params";
11
11
  import {isStatePostAltair} from "@lodestar/state-transition";
12
- import {Attestation, Epoch, SingleAttestation, isElectraAttestation, ssz, sszTypesFor} from "@lodestar/types";
12
+ import {Epoch, SingleAttestation, isElectraAttestation, ssz, sszTypesFor} from "@lodestar/types";
13
13
  import {toRootHex} from "@lodestar/utils";
14
14
  import {
15
15
  AttestationError,
@@ -40,25 +40,6 @@ export function getBeaconPoolApi({
40
40
  network,
41
41
  }: Pick<ApiModules, "chain" | "logger" | "metrics" | "network">): ApplicationMethods<routes.beacon.pool.Endpoints> {
42
42
  return {
43
- async getPoolAttestations({slot, committeeIndex}) {
44
- // Already filtered by slot
45
- let attestations: Attestation[] = chain.aggregatedAttestationPool.getAll(slot);
46
- const fork = chain.config.getForkName(slot ?? chain.clock.currentSlot);
47
-
48
- if (isForkPostElectra(fork)) {
49
- throw new ApiError(
50
- 400,
51
- `Use getPoolAttestationsV2 to retrieve pool attestations for post-electra fork=${fork}`
52
- );
53
- }
54
-
55
- if (committeeIndex !== undefined) {
56
- attestations = attestations.filter((attestation) => committeeIndex === attestation.data.index);
57
- }
58
-
59
- return {data: attestations};
60
- },
61
-
62
43
  async getPoolAttestationsV2({slot, committeeIndex}) {
63
44
  // Already filtered by slot
64
45
  let attestations = chain.aggregatedAttestationPool.getAll(slot);
@@ -134,19 +115,6 @@ export function getBeaconPoolApi({
134
115
  }
135
116
  },
136
117
 
137
- async getPoolAttesterSlashings() {
138
- const fork = chain.config.getForkName(chain.clock.currentSlot);
139
-
140
- if (isForkPostElectra(fork)) {
141
- throw new ApiError(
142
- 400,
143
- `Use getPoolAttesterSlashingsV2 to retrieve pool attester slashings for post-electra fork=${fork}`
144
- );
145
- }
146
-
147
- return {data: chain.opPool.getAllAttesterSlashings()};
148
- },
149
-
150
118
  async getPoolAttesterSlashingsV2() {
151
119
  const fork = chain.config.getForkName(chain.clock.currentSlot);
152
120
  return {data: chain.opPool.getAllAttesterSlashings(), meta: {version: fork}};
@@ -164,10 +132,6 @@ export function getBeaconPoolApi({
164
132
  return {data: chain.opPool.getAllBlsToExecutionChanges().map(({data}) => data)};
165
133
  },
166
134
 
167
- async submitPoolAttestations({signedAttestations}) {
168
- await this.submitPoolAttestationsV2({signedAttestations});
169
- },
170
-
171
135
  async submitPoolAttestationsV2({signedAttestations}) {
172
136
  const fork = chain.config.getForkName(chain.clock.currentSlot);
173
137
  const seenTimestampSec = Date.now() / 1000;
@@ -247,10 +211,6 @@ export function getBeaconPoolApi({
247
211
  }
248
212
  },
249
213
 
250
- async submitPoolAttesterSlashings({attesterSlashing}) {
251
- await this.submitPoolAttesterSlashingsV2({attesterSlashing});
252
- },
253
-
254
214
  async submitPoolAttesterSlashingsV2({attesterSlashing}) {
255
215
  await validateApiAttesterSlashing(chain, attesterSlashing);
256
216
  const fork = chain.config.getForkName(Number(attesterSlashing.attestation1.data.slot));
@@ -1207,7 +1207,10 @@ export function getValidatorApi(
1207
1207
  const isPostFulu = isForkPostFulu(config.getForkName(startSlot));
1208
1208
  const maxFutureEpoch = isPostFulu && nearNextEpoch && opts?.v2 ? nextEpoch + 1 : nextEpoch;
1209
1209
  if (currentEpoch >= 0 && epoch > maxFutureEpoch) {
1210
- throw new ApiError(400, `Requested epoch ${epoch} must not be more than one epoch in the future`);
1210
+ throw new ApiError(
1211
+ 400,
1212
+ `Requested epoch ${epoch} must not be more than ${maxFutureEpoch}, currentEpoch=${currentEpoch}, v2=${opts?.v2 ?? false}`
1213
+ );
1211
1214
  }
1212
1215
 
1213
1216
  const head = chain.forkChoice.getHead();
@@ -1291,17 +1294,35 @@ export function getValidatorApi(
1291
1294
  duties.push({slot: startSlot + i, validatorIndex: indexes[i], pubkey: pubkeys[i]});
1292
1295
  }
1293
1296
 
1294
- // Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
1295
- // It should be set to the latest block applied to `self` or the genesis block root.
1296
- const dependentRoot =
1297
- // In v2 the dependent root is different after fulu due to deterministic proposer lookahead
1298
- proposerShufflingDecisionRoot(opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0, state, epoch) ||
1299
- (await getGenesisBlockRoot(state));
1297
+ // In v2 the dependent root is different after fulu due to deterministic proposer lookahead
1298
+ let dependentRoot = proposerShufflingDecisionRoot(
1299
+ opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0,
1300
+ state,
1301
+ epoch
1302
+ );
1303
+ const logCtx = {
1304
+ epoch,
1305
+ stateSlot: state.slot,
1306
+ stateEpoch: state.epoch,
1307
+ v2: opts?.v2 ?? false,
1308
+ };
1309
+ if (dependentRoot === null) {
1310
+ // fallback to get_proposer_duties() v1, also in lodestar v1.43
1311
+ logger.verbose("Proposer duties decision root not in state, falling back to state epoch", logCtx);
1312
+ dependentRoot = proposerShufflingDecisionRoot(ForkName.phase0, state, state.epoch);
1313
+ }
1314
+ if (dependentRoot === null) {
1315
+ logger.verbose("Proposer duties decision root not in state, falling back to genesis block root", logCtx);
1316
+ dependentRoot = await getGenesisBlockRoot(state);
1317
+ }
1318
+
1319
+ const dependentRootHex = toRootHex(dependentRoot);
1320
+ logger.verbose("Computed proposer duties decision root", {...logCtx, dependentRoot: dependentRootHex});
1300
1321
 
1301
1322
  return {
1302
1323
  data: duties,
1303
1324
  meta: {
1304
- dependentRoot: toRootHex(dependentRoot),
1325
+ dependentRoot: dependentRootHex,
1305
1326
  executionOptimistic: isOptimisticBlock(head),
1306
1327
  },
1307
1328
  };
@@ -1480,33 +1501,6 @@ export function getValidatorApi(
1480
1501
  };
1481
1502
  },
1482
1503
 
1483
- async getAggregatedAttestation({attestationDataRoot, slot}) {
1484
- notWhileSyncing();
1485
-
1486
- await waitForSlot(slot); // Must never request for a future slot > currentSlot
1487
-
1488
- const dataRootHex = toRootHex(attestationDataRoot);
1489
- const aggregate = chain.attestationPool.getAggregate(slot, dataRootHex, null);
1490
- const fork = chain.config.getForkName(slot);
1491
-
1492
- if (isForkPostElectra(fork)) {
1493
- throw new ApiError(
1494
- 400,
1495
- `Use getAggregatedAttestationV2 to retrieve aggregated attestations for post-electra fork=${fork}`
1496
- );
1497
- }
1498
-
1499
- if (!aggregate) {
1500
- throw new ApiError(404, `No aggregated attestation for slot=${slot}, dataRoot=${dataRootHex}`);
1501
- }
1502
-
1503
- metrics?.production.producedAggregateParticipants.observe(aggregate.aggregationBits.getTrueBitIndexes().length);
1504
-
1505
- return {
1506
- data: aggregate,
1507
- };
1508
- },
1509
-
1510
1504
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1511
1505
  notWhileSyncing();
1512
1506
 
@@ -1530,10 +1524,6 @@ export function getValidatorApi(
1530
1524
  };
1531
1525
  },
1532
1526
 
1533
- async publishAggregateAndProofs({signedAggregateAndProofs}) {
1534
- await this.publishAggregateAndProofsV2({signedAggregateAndProofs});
1535
- },
1536
-
1537
1527
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1538
1528
  notWhileSyncing();
1539
1529
 
@@ -61,10 +61,8 @@ export function verifyExecutionPayloadEnvelope(
61
61
  `Prev randao mismatch between bid and payload bid=${toHex(bid.prevRandao)} payload=${toHex(payload.prevRandao)}`
62
62
  );
63
63
  }
64
- if (Number(bid.gasLimit) !== payload.gasLimit) {
65
- throw new Error(
66
- `Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${Number(bid.gasLimit)}`
67
- );
64
+ if (bid.gasLimit !== payload.gasLimit) {
65
+ throw new Error(`Gas limit mismatch between payload and bid payload=${payload.gasLimit} bid=${bid.gasLimit}`);
68
66
  }
69
67
  if (!byteArrayEquals(bid.blockHash, payload.blockHash)) {
70
68
  throw new Error(
@@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {routes} from "@lodestar/api";
4
4
  import {CheckpointWithHex} from "@lodestar/fork-choice";
5
5
  import {IBeaconStateView} from "@lodestar/state-transition";
6
- import {DataColumnSidecar, RootHex, deneb, phase0} from "@lodestar/types";
6
+ import {DataColumnSidecar, RootHex, Slot, deneb, phase0} from "@lodestar/types";
7
7
  import {PeerIdStr} from "../util/peerId.js";
8
8
  import {BlockInputSource, IBlockInput} from "./blocks/blockInput/types.js";
9
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
@@ -94,7 +94,8 @@ export type ChainEventData = {
94
94
  peer: PeerIdStr;
95
95
  source: BlockInputSource;
96
96
  };
97
- [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; peer?: PeerIdStr; source: BlockInputSource};
97
+ // slot is the message slot, not necessarily the envelope's slot, but useful as a logging/prune hint
98
+ [ChainEvent.unknownEnvelopeBlockRoot]: {rootHex: RootHex; slot: Slot; peer?: PeerIdStr; source: BlockInputSource};
98
99
  };
99
100
 
100
101
  export type IChainEvents = ApiEvents & {
@@ -61,9 +61,6 @@ export enum BlockErrorCode {
61
61
  TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG",
62
62
  /** Execution engine is unavailable, syncing, or api call errored. Peers must not be downscored on this code */
63
63
  EXECUTION_ENGINE_ERROR = "BLOCK_ERROR_EXECUTION_ERROR",
64
- /** The attestation head block is too far behind the attestation slot, causing many skip slots.
65
- This is deemed a DoS risk */
66
- TOO_MANY_SKIPPED_SLOTS = "TOO_MANY_SKIPPED_SLOTS",
67
64
  /** The blobs are unavailable */
68
65
  DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
69
66
  /** Block contains too many kzg commitments */
@@ -89,7 +86,6 @@ export type BlockErrorType =
89
86
  | {code: BlockErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
90
87
  | {code: BlockErrorCode.STATE_ROOT_MISMATCH}
91
88
  | {code: BlockErrorCode.GENESIS_BLOCK}
92
- | {code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS; parentSlot: Slot; blockSlot: Slot}
93
89
  | {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
94
90
  | {code: BlockErrorCode.ALREADY_KNOWN; root: RootHex}
95
91
  | {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex}
@@ -12,6 +12,7 @@ export enum ExecutionPayloadBidErrorCode {
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
14
  NOT_LATER_THAN_PARENT = "EXECUTION_PAYLOAD_BID_ERROR_NOT_LATER_THAN_PARENT",
15
+ INVALID_PREV_RANDAO = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_PREV_RANDAO",
15
16
  INVALID_SIGNATURE = "EXECUTION_PAYLOAD_BID_ERROR_INVALID_SIGNATURE",
16
17
  NO_MATCHING_PROPOSER_PREFERENCES = "EXECUTION_PAYLOAD_BID_ERROR_NO_MATCHING_PROPOSER_PREFERENCES",
17
18
  PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH = "EXECUTION_PAYLOAD_BID_ERROR_PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH",
@@ -43,6 +44,12 @@ export type ExecutionPayloadBidErrorType =
43
44
  | {code: ExecutionPayloadBidErrorCode.UNKNOWN_PARENT_BLOCK_HASH; parentBlockHash: RootHex}
44
45
  | {code: ExecutionPayloadBidErrorCode.INVALID_SLOT; builderIndex: BuilderIndex; slot: Slot}
45
46
  | {code: ExecutionPayloadBidErrorCode.NOT_LATER_THAN_PARENT; parentSlot: Slot; slot: Slot}
47
+ | {
48
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO;
49
+ builderIndex: BuilderIndex;
50
+ bidPrevRandao: string;
51
+ expectedPrevRandao: string;
52
+ }
46
53
  | {code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE; builderIndex: BuilderIndex; slot: Slot}
47
54
  | {
48
55
  code: ExecutionPayloadBidErrorCode.NO_MATCHING_PROPOSER_PREFERENCES;
@@ -1,3 +1,4 @@
1
+ import {routes} from "@lodestar/api";
1
2
  import {ChainForkConfig} from "@lodestar/config";
2
3
  import {
3
4
  ExecutionStatus,
@@ -121,6 +122,8 @@ export function initializeForkChoiceFromFinalizedState(
121
122
  {
122
123
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
123
124
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
125
+ onFastConfirmation: ({block, slot, currentSlot}) =>
126
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
124
127
  }
125
128
  ),
126
129
 
@@ -214,6 +217,8 @@ export function initializeForkChoiceFromUnfinalizedState(
214
217
  {
215
218
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
216
219
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
220
+ onFastConfirmation: ({block, slot, currentSlot}) =>
221
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
217
222
  }
218
223
  );
219
224
 
@@ -344,7 +344,7 @@ export async function produceBlockBody<T extends BlockType>(
344
344
  blockHash: executionPayload.blockHash,
345
345
  prevRandao: currentState.getRandaoMix(currentState.epoch),
346
346
  feeRecipient: executionPayload.feeRecipient,
347
- gasLimit: BigInt(executionPayload.gasLimit),
347
+ gasLimit: executionPayload.gasLimit,
348
348
  builderIndex: BUILDER_INDEX_SELF_BUILD,
349
349
  slot: blockSlot,
350
350
  value: 0,
@@ -15,12 +15,17 @@ import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js
15
15
  import {IBeaconChain} from "../interface.js";
16
16
  import {RegenCaller} from "../regen/index.js";
17
17
 
18
+ export type GossipBlockValidationResult = {
19
+ /** Number of skipped slots between the block and its parent (blockSlot - parentSlot - 1) */
20
+ skippedSlots: number;
21
+ };
22
+
18
23
  export async function validateGossipBlock(
19
24
  config: ChainForkConfig,
20
25
  chain: IBeaconChain,
21
26
  signedBlock: SignedBeaconBlock,
22
27
  fork: ForkName
23
- ): Promise<void> {
28
+ ): Promise<GossipBlockValidationResult> {
24
29
  const block = signedBlock.message;
25
30
  const blockSlot = block.slot;
26
31
  const blockEpoch = computeEpochAtSlot(blockSlot);
@@ -109,21 +114,6 @@ export async function validateGossipBlock(
109
114
  }
110
115
  }
111
116
 
112
- // [IGNORE] The attestation head block is too far behind the attestation slot, causing many skip slots.
113
- // This is deemed a DoS risk because we need to get the proposerShuffling. To get the shuffling we have
114
- // to do a bunch of epoch transitions, the longer the distance between the parent and block,
115
- // the more we have to do. epochTransitions are expensive ~750ms, so we must limit how many a
116
- // single bad block can trigger
117
- // Note: Ensure this check is done before calling chain.regen.getBlockSlotStat as this is the function that does various epoch transitions.
118
- // Note: This validation check is not part of the spec.
119
- if (chain.opts.maxSkipSlots != null && parentBlock.slot + chain.opts.maxSkipSlots < blockSlot) {
120
- throw new BlockGossipError(GossipAction.IGNORE, {
121
- code: BlockErrorCode.TOO_MANY_SKIPPED_SLOTS,
122
- parentSlot: parentBlock.slot,
123
- blockSlot,
124
- });
125
- }
126
-
127
117
  // [REJECT] The block is from a higher slot than its parent.
128
118
  if (parentBlock.slot >= blockSlot) {
129
119
  throw new BlockGossipError(GossipAction.REJECT, {
@@ -133,6 +123,10 @@ export async function validateGossipBlock(
133
123
  });
134
124
  }
135
125
 
126
+ // Number of skipped slots between block and parent (non-spec). Previously this gated blocks via
127
+ // maxSkipSlots; now the caller only observes it so legitimate post-skip blocks are no longer ignored.
128
+ const skippedSlots = blockSlot - parentBlock.slot - 1;
129
+
136
130
  // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
137
131
  if (isForkPostDeneb(fork) && !isForkPostGloas(fork)) {
138
132
  const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
@@ -247,4 +241,6 @@ export async function validateGossipBlock(
247
241
  }
248
242
 
249
243
  chain.seenBlockProposers.add(blockSlot, proposerIndex);
244
+
245
+ return {skippedSlots};
250
246
  }
@@ -175,7 +175,7 @@ async function validateExecutionPayloadBid(
175
175
  // [IGNORE] `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`,
176
176
  // where `parent_gas_limit` is the `gas_limit` of the parent execution payload and
177
177
  // `target_gas_limit` is `proposer_preferences.target_gas_limit`.
178
- const bidGasLimit = Number(bid.gasLimit);
178
+ const bidGasLimit = bid.gasLimit;
179
179
  const parentGasLimit = parentPayloadVariant.executionPayloadGasLimit;
180
180
  const targetGasLimit = proposerPreferences.message.targetGasLimit;
181
181
  if (!isGasLimitTargetCompatible(parentGasLimit, bidGasLimit, targetGasLimit)) {
@@ -232,6 +232,18 @@ async function validateExecutionPayloadBid(
232
232
  });
233
233
  }
234
234
 
235
+ // [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that
236
+ // `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))`.
237
+ const randaoMix = state.getRandaoMix(computeEpochAtSlot(state.slot));
238
+ if (!byteArrayEquals(bid.prevRandao, randaoMix)) {
239
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
240
+ code: ExecutionPayloadBidErrorCode.INVALID_PREV_RANDAO,
241
+ builderIndex: bid.builderIndex,
242
+ bidPrevRandao: toHex(bid.prevRandao),
243
+ expectedPrevRandao: toHex(randaoMix),
244
+ });
245
+ }
246
+
235
247
  // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
236
248
  const signatureSet = createSingleSignatureSetFromComponents(
237
249
  PublicKey.fromBytes(builder.pubkey),
@@ -35,8 +35,6 @@ async function validateExecutionPayloadEnvelope(
35
35
  // [IGNORE] The envelope's block root `envelope.beacon_block_root` has been seen (via
36
36
  // gossip or non-gossip sources) (a client MAY queue payload for processing once
37
37
  // the block is retrieved).
38
- // TODO GLOAS: Need to review this, we should queue the envelope for later
39
- // processing if the block is not yet known, otherwise we would ignore it here
40
38
  const block = chain.forkChoice.getBlockDefaultStatus(envelope.beaconBlockRoot);
41
39
  if (block === null) {
42
40
  throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
@@ -610,6 +610,11 @@ export function createLodestarMetrics(
610
610
  help: "The origination source of one of the BlockInputSync triggers",
611
611
  labelNames: ["source"],
612
612
  }),
613
+ payloadSource: register.counter<{source: BlockInputSource}>({
614
+ name: "lodestar_payload_input_sync_source_total",
615
+ help: "Count of payload (execution payload envelope) sync triggers, labeled by their source",
616
+ labelNames: ["source"],
617
+ }),
613
618
  pendingBlocks: register.gauge({
614
619
  name: "lodestar_sync_unknown_block_pending_blocks_size",
615
620
  help: "Current size of UnknownBlockSync pending blocks cache",
@@ -861,6 +866,12 @@ export function createLodestarMetrics(
861
866
  labelNames: ["numBlobs"],
862
867
  }),
863
868
 
869
+ skippedSlots: register.histogram({
870
+ name: "lodestar_gossip_block_skipped_slots",
871
+ help: "Number of skipped slots between a gossip block and its parent (blockSlot - parentSlot - 1)",
872
+ buckets: [0, 1, 2, 4, 8, 16, 32],
873
+ }),
874
+
864
875
  processBlockErrors: register.gauge<{error: BlockErrorCode | "NOT_BLOCK_ERROR"}>({
865
876
  name: "lodestar_gossip_block_process_block_errors",
866
877
  help: "Count of errors, by error type, while processing blocks",
@@ -185,7 +185,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
185
185
  peerIdStr,
186
186
  });
187
187
  try {
188
- await validateGossipBlock(config, chain, signedBlock, fork);
188
+ const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
189
189
 
190
190
  if (isForkPostGloas(fork)) {
191
191
  chain.seenPayloadEnvelopeInputCache.add({
@@ -205,8 +205,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
205
205
 
206
206
  metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation);
207
207
  metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime);
208
+ metrics?.gossipBlock.skippedSlots.observe(skippedSlots);
208
209
 
209
- logger.debug("Validated gossip block", {...blockInputMeta, ...logCtx, recvToValidation, validationTime});
210
+ logger.debug("Validated gossip block", {
211
+ ...blockInputMeta,
212
+ ...logCtx,
213
+ recvToValidation,
214
+ validationTime,
215
+ skippedSlots,
216
+ });
210
217
 
211
218
  chain.emitter.emit(routes.events.EventType.blockGossip, {slot, block: blockRootHex});
212
219