@lodestar/beacon-node 1.50.0 → 1.51.0-dev.4157a0dec4

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 (142) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +7 -2
  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 +31 -17
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/ColumnReconstructionTracker.d.ts +11 -13
  8. package/lib/chain/ColumnReconstructionTracker.d.ts.map +1 -1
  9. package/lib/chain/ColumnReconstructionTracker.js +49 -44
  10. package/lib/chain/ColumnReconstructionTracker.js.map +1 -1
  11. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/utils/archiveBlocks.js +4 -5
  13. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  14. package/lib/chain/blocks/blockInput/blockInput.d.ts +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  16. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  17. package/lib/chain/blocks/blockInput/utils.js +2 -2
  18. package/lib/chain/blocks/blockInput/utils.js.map +1 -1
  19. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  20. package/lib/chain/blocks/importBlock.js +7 -4
  21. package/lib/chain/blocks/importBlock.js.map +1 -1
  22. package/lib/chain/chain.d.ts.map +1 -1
  23. package/lib/chain/chain.js +4 -2
  24. package/lib/chain/chain.js.map +1 -1
  25. package/lib/chain/interface.d.ts +1 -1
  26. package/lib/chain/interface.d.ts.map +1 -1
  27. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  28. package/lib/chain/prepareNextSlot.js +4 -3
  29. package/lib/chain/prepareNextSlot.js.map +1 -1
  30. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  31. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  32. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  33. package/lib/chain/produceBlock/produceBlockBody.js +8 -6
  34. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  35. package/lib/chain/reprocess.d.ts +2 -1
  36. package/lib/chain/reprocess.d.ts.map +1 -1
  37. package/lib/chain/reprocess.js +2 -1
  38. package/lib/chain/reprocess.js.map +1 -1
  39. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  40. package/lib/chain/seenCache/seenGossipBlockInput.js +1 -1
  41. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  42. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  43. package/lib/chain/stateCache/persistentCheckpointsCache.js +1 -1
  44. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  45. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  46. package/lib/chain/validatorMonitor.js +6 -4
  47. package/lib/chain/validatorMonitor.js.map +1 -1
  48. package/lib/constants/constants.d.ts +1 -0
  49. package/lib/constants/constants.d.ts.map +1 -1
  50. package/lib/constants/constants.js +1 -0
  51. package/lib/constants/constants.js.map +1 -1
  52. package/lib/db/repositories/blockArchive.js +2 -2
  53. package/lib/db/repositories/blockArchive.js.map +1 -1
  54. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -1
  55. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  56. package/lib/db/repositories/blockArchiveIndex.js +1 -2
  57. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  58. package/lib/execution/engine/index.d.ts.map +1 -1
  59. package/lib/execution/engine/index.js +1 -1
  60. package/lib/execution/engine/index.js.map +1 -1
  61. package/lib/execution/engine/jsonRpcHttpClient.d.ts.map +1 -1
  62. package/lib/execution/engine/jsonRpcHttpClient.js +14 -4
  63. package/lib/execution/engine/jsonRpcHttpClient.js.map +1 -1
  64. package/lib/execution/engine/restTransport.d.ts +3 -1
  65. package/lib/execution/engine/restTransport.d.ts.map +1 -1
  66. package/lib/execution/engine/restTransport.js +33 -21
  67. package/lib/execution/engine/restTransport.js.map +1 -1
  68. package/lib/execution/engine/transportMetrics.d.ts +5 -1
  69. package/lib/execution/engine/transportMetrics.d.ts.map +1 -1
  70. package/lib/execution/engine/types.d.ts.map +1 -1
  71. package/lib/execution/engine/types.js +12 -11
  72. package/lib/execution/engine/types.js.map +1 -1
  73. package/lib/metrics/metrics/lodestar.d.ts +11 -0
  74. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  75. package/lib/metrics/metrics/lodestar.js +31 -1
  76. package/lib/metrics/metrics/lodestar.js.map +1 -1
  77. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  78. package/lib/network/gossip/gossipsub.js +3 -1
  79. package/lib/network/gossip/gossipsub.js.map +1 -1
  80. package/lib/network/gossip/topic.d.ts +0 -14
  81. package/lib/network/gossip/topic.d.ts.map +1 -1
  82. package/lib/network/processor/index.js +3 -3
  83. package/lib/network/processor/index.js.map +1 -1
  84. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  85. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +4 -4
  86. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  87. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +3 -3
  88. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  89. package/lib/network/reqresp/rateLimit.js +2 -2
  90. package/lib/network/reqresp/rateLimit.js.map +1 -1
  91. package/lib/network/subnets/attnetsService.d.ts.map +1 -1
  92. package/lib/network/subnets/attnetsService.js +3 -1
  93. package/lib/network/subnets/attnetsService.js.map +1 -1
  94. package/lib/node/notifier.d.ts.map +1 -1
  95. package/lib/node/notifier.js +12 -13
  96. package/lib/node/notifier.js.map +1 -1
  97. package/lib/sync/unknownBlock.d.ts.map +1 -1
  98. package/lib/sync/unknownBlock.js +3 -2
  99. package/lib/sync/unknownBlock.js.map +1 -1
  100. package/lib/util/clock.d.ts.map +1 -1
  101. package/lib/util/clock.js +3 -4
  102. package/lib/util/clock.js.map +1 -1
  103. package/lib/util/sszBytes.d.ts +2 -2
  104. package/lib/util/sszBytes.d.ts.map +1 -1
  105. package/lib/util/sszBytes.js +22 -14
  106. package/lib/util/sszBytes.js.map +1 -1
  107. package/package.json +15 -15
  108. package/src/api/impl/beacon/blocks/index.ts +10 -3
  109. package/src/api/impl/validator/index.ts +41 -19
  110. package/src/chain/ColumnReconstructionTracker.ts +63 -46
  111. package/src/chain/archiveStore/utils/archiveBlocks.ts +6 -7
  112. package/src/chain/blocks/blockInput/blockInput.ts +1 -1
  113. package/src/chain/blocks/blockInput/utils.ts +2 -2
  114. package/src/chain/blocks/importBlock.ts +8 -4
  115. package/src/chain/chain.ts +4 -2
  116. package/src/chain/interface.ts +2 -1
  117. package/src/chain/prepareNextSlot.ts +7 -3
  118. package/src/chain/produceBlock/computeNewStateRoot.ts +9 -2
  119. package/src/chain/produceBlock/produceBlockBody.ts +10 -5
  120. package/src/chain/reprocess.ts +2 -1
  121. package/src/chain/seenCache/seenGossipBlockInput.ts +2 -1
  122. package/src/chain/stateCache/persistentCheckpointsCache.ts +4 -1
  123. package/src/chain/validatorMonitor.ts +7 -4
  124. package/src/constants/constants.ts +1 -0
  125. package/src/db/repositories/blockArchive.ts +2 -2
  126. package/src/db/repositories/blockArchiveIndex.ts +2 -3
  127. package/src/execution/engine/index.ts +2 -1
  128. package/src/execution/engine/jsonRpcHttpClient.ts +12 -5
  129. package/src/execution/engine/restTransport.ts +53 -22
  130. package/src/execution/engine/transportMetrics.ts +2 -1
  131. package/src/execution/engine/types.ts +13 -11
  132. package/src/metrics/metrics/lodestar.ts +32 -1
  133. package/src/network/gossip/gossipsub.ts +3 -1
  134. package/src/network/processor/index.ts +3 -3
  135. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +4 -10
  136. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +3 -3
  137. package/src/network/reqresp/rateLimit.ts +2 -2
  138. package/src/network/subnets/attnetsService.ts +8 -3
  139. package/src/node/notifier.ts +14 -13
  140. package/src/sync/unknownBlock.ts +3 -2
  141. package/src/util/clock.ts +3 -4
  142. package/src/util/sszBytes.ts +25 -12
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.50.0",
14
+ "version": "1.51.0-dev.4157a0dec4",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -127,17 +127,17 @@
127
127
  "@libp2p/prometheus-metrics": "^5.0.28",
128
128
  "@libp2p/tcp": "^11.0.27",
129
129
  "@libp2p/utils": "^7.4.0",
130
- "@lodestar/api": "^1.50.0",
131
- "@lodestar/config": "^1.50.0",
132
- "@lodestar/db": "^1.50.0",
133
- "@lodestar/fork-choice": "^1.50.0",
134
- "@lodestar/logger": "^1.50.0",
135
- "@lodestar/params": "^1.50.0",
136
- "@lodestar/reqresp": "^1.50.0",
137
- "@lodestar/state-transition": "^1.50.0",
138
- "@lodestar/types": "^1.50.0",
139
- "@lodestar/utils": "^1.50.0",
140
- "@lodestar/validator": "^1.50.0",
130
+ "@lodestar/api": "^1.51.0-dev.4157a0dec4",
131
+ "@lodestar/config": "^1.51.0-dev.4157a0dec4",
132
+ "@lodestar/db": "^1.51.0-dev.4157a0dec4",
133
+ "@lodestar/fork-choice": "^1.51.0-dev.4157a0dec4",
134
+ "@lodestar/logger": "^1.51.0-dev.4157a0dec4",
135
+ "@lodestar/params": "^1.51.0-dev.4157a0dec4",
136
+ "@lodestar/reqresp": "^1.51.0-dev.4157a0dec4",
137
+ "@lodestar/state-transition": "^1.51.0-dev.4157a0dec4",
138
+ "@lodestar/types": "^1.51.0-dev.4157a0dec4",
139
+ "@lodestar/utils": "^1.51.0-dev.4157a0dec4",
140
+ "@lodestar/validator": "^1.51.0-dev.4157a0dec4",
141
141
  "@multiformats/multiaddr": "^13.0.1",
142
142
  "datastore-core": "^12.0.1",
143
143
  "datastore-level": "^13.0.1",
@@ -159,8 +159,8 @@
159
159
  "@ethereumjs/rlp": "^4.0.1",
160
160
  "@libp2p/interface-internal": "^3.1.12",
161
161
  "@libp2p/logger": "^6.2.13",
162
- "@lodestar/spec-test-util": "^1.50.0",
163
- "@lodestar/test-utils": "^1.50.0",
162
+ "@lodestar/spec-test-util": "^1.51.0-dev.4157a0dec4",
163
+ "@lodestar/test-utils": "^1.51.0-dev.4157a0dec4",
164
164
  "@types/js-yaml": "^4.0.5",
165
165
  "@types/qs": "^6.9.7",
166
166
  "@types/tmp": "^0.2.3",
@@ -175,5 +175,5 @@
175
175
  "beacon",
176
176
  "blockchain"
177
177
  ],
178
- "gitHead": "003bd66543fa65b5c1e954eec9e8077054c226dc"
178
+ "gitHead": "5153cc3cb9c3ec948602370f5aff659205e100bf"
179
179
  }
@@ -6,6 +6,7 @@ import {
6
6
  ForkPostBellatrix,
7
7
  ForkPostFulu,
8
8
  ForkPostGloas,
9
+ ForkPreBellatrix,
9
10
  ForkPreGloas,
10
11
  NUMBER_OF_COLUMNS,
11
12
  SLOTS_PER_HISTORICAL_ROOT,
@@ -674,7 +675,7 @@ export function getBeaconBlockApi({
674
675
  return {
675
676
  data: isForkPostBellatrix(fork)
676
677
  ? signedBeaconBlockToBlinded(config, block as SignedBeaconBlock<ForkPostBellatrix & ForkPreGloas>)
677
- : block,
678
+ : (block as SignedBeaconBlock<ForkPreBellatrix>),
678
679
  meta: {
679
680
  executionOptimistic,
680
681
  finalized,
@@ -692,8 +693,14 @@ export function getBeaconBlockApi({
692
693
  },
693
694
 
694
695
  async getBlockRoot({blockId}) {
695
- // Fast path: From head state already available in memory get historical blockRoot
696
- const slot = typeof blockId === "string" ? parseInt(blockId) : blockId;
696
+ // Fast path: From head state already available in memory get historical blockRoot.
697
+ // A 0x id is a root. parseInt() accepts that prefix as hex and can return a slot.
698
+ const slot =
699
+ typeof blockId === "number"
700
+ ? blockId
701
+ : blockId.toLowerCase().startsWith("0x")
702
+ ? Number.NaN
703
+ : parseInt(blockId, 10);
697
704
  if (!Number.isNaN(slot)) {
698
705
  const head = chain.forkChoice.getHead();
699
706
 
@@ -5,6 +5,8 @@ import {
5
5
  BUILDER_INDEX_SELF_BUILD,
6
6
  ForkName,
7
7
  ForkPostBellatrix,
8
+ ForkPostDeneb,
9
+ ForkPreDeneb,
8
10
  ForkPreGloas,
9
11
  ForkSeq,
10
12
  GENESIS_SLOT,
@@ -111,14 +113,13 @@ import {
111
113
  * Post this time, race execution and builder to pick whatever resolves first.
112
114
  *
113
115
  * Empirically the builder block resolves in ~1 second, and execution block resolves in <500 ms.
114
- * A cutoff of 2 seconds gives enough time and if there are unexpected delays it ensures we publish
115
- * in time as proposals post 4 seconds into the slot will likely be orphaned due to proposer boost reorg.
116
+ * A cutoff of 1667 BPS (2 seconds for 12s slots) gives enough time and if there are unexpected delays
117
+ * it ensures we publish in time as proposals after the attestation deadline will likely be orphaned
118
+ * due to proposer boost reorg.
116
119
  *
117
120
  * TODO GLOAS: re-evaluate cutoff timing due to attestation deadline changes in gloas
118
121
  */
119
- const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
120
- /** Overall timeout for execution and block production apis */
121
- const BLOCK_PRODUCTION_RACE_TIMEOUT_MS = 12_000;
122
+ const BLOCK_PRODUCTION_RACE_CUTOFF_BPS = 1667;
122
123
  /** Rejection message of the bid block branch when there is no viable bid to commit to */
123
124
  const NO_BID_AVAILABLE = "No builder bid available";
124
125
 
@@ -167,8 +168,8 @@ function compareBidCandidates(a: BidCandidate, b: BidCandidate): number {
167
168
  }
168
169
 
169
170
  type ProduceBlockContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & {
170
- data: BlockContents;
171
- version: ForkName;
171
+ data: BlockContents<ForkPreGloas>;
172
+ version: ForkPreGloas;
172
173
  };
173
174
  type ProduceBlindedBlockRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & {
174
175
  data: BlindedBeaconBlock;
@@ -301,8 +302,8 @@ export function getValidatorApi(
301
302
  */
302
303
  function msToNextEpoch(): number {
303
304
  const nextEpoch = chain.clock.currentEpoch + 1;
304
- const secPerEpoch = (SLOTS_PER_EPOCH * config.SLOT_DURATION_MS) / 1000;
305
- const nextEpochStartSec = chain.genesisTime + nextEpoch * secPerEpoch;
305
+ const nextEpochSlot = computeStartSlotAtEpoch(nextEpoch);
306
+ const nextEpochStartSec = computeTimeAtSlot(config, nextEpochSlot, chain.genesisTime);
306
307
  return nextEpochStartSec * 1000 - Date.now();
307
308
  }
308
309
 
@@ -511,6 +512,10 @@ export function getValidatorApi(
511
512
  parentBlock: ProtoBlock;
512
513
  }
513
514
  ): Promise<ProduceBlockContentsRes & {shouldOverrideBuilder?: boolean}> {
515
+ const version = config.getForkName(slot);
516
+ if (isForkPostGloas(version)) {
517
+ throw Error(`produceEngineBlockContents called for post-gloas fork=${version}`);
518
+ }
514
519
  const source = ProducedBlockSource.engine;
515
520
  metrics?.blockProductionRequests.inc({source});
516
521
 
@@ -525,7 +530,6 @@ export function getValidatorApi(
525
530
  feeRecipient,
526
531
  commonBlockBodyPromise,
527
532
  });
528
- const version = config.getForkName(block.slot);
529
533
  if (strictFeeRecipientCheck && feeRecipient && isForkPostBellatrix(version)) {
530
534
  const blockFeeRecipient = toHex((block as bellatrix.BeaconBlock).body.executionPayload.feeRecipient);
531
535
  if (blockFeeRecipient !== feeRecipient) {
@@ -563,7 +567,7 @@ export function getValidatorApi(
563
567
 
564
568
  return {
565
569
  data: {
566
- block,
570
+ block: block as BeaconBlock<ForkPostDeneb & ForkPreGloas>,
567
571
  blobs: blobsBundle.blobs,
568
572
  kzgProofs: blobsBundle.proofs,
569
573
  },
@@ -574,7 +578,13 @@ export function getValidatorApi(
574
578
  };
575
579
  }
576
580
 
577
- return {data: {block}, version, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder};
581
+ return {
582
+ data: {block: block as BeaconBlock<ForkPreDeneb>},
583
+ version,
584
+ executionPayloadValue,
585
+ consensusBlockValue,
586
+ shouldOverrideBuilder,
587
+ };
578
588
  } finally {
579
589
  if (timer) timer({source});
580
590
  }
@@ -680,17 +690,21 @@ export function getValidatorApi(
680
690
  });
681
691
 
682
692
  // Calculate cutoff time based on start of the slot
683
- const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
693
+ const cutoffMs = Math.max(
694
+ 0,
695
+ config.getSlotComponentDurationMs(fork, BLOCK_PRODUCTION_RACE_CUTOFF_BPS) - chain.clock.msFromSlot(slot)
696
+ );
697
+ const blockProductionTimeoutMs = config.getSlotDurationMs(fork);
684
698
 
685
699
  logger.verbose("Block production race (builder vs execution) starting", {
686
700
  ...loggerContext,
687
701
  cutoffMs,
688
- timeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
702
+ timeoutMs: blockProductionTimeoutMs,
689
703
  });
690
704
 
691
705
  const blockProductionRacePromise = resolveOrRacePromises([builderPromise, enginePromise], {
692
706
  resolveTimeoutMs: cutoffMs,
693
- raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
707
+ raceTimeoutMs: blockProductionTimeoutMs,
694
708
  signal: controller.signal,
695
709
  });
696
710
 
@@ -878,6 +892,10 @@ export function getValidatorApi(
878
892
 
879
893
  return {
880
894
  async produceBlockV3({slot, randaoReveal, graffiti, skipRandaoVerification, builderBoostFactor, ...opts}) {
895
+ const forkName = config.getForkName(slot);
896
+ if (isForkPostGloas(forkName)) {
897
+ throw new ApiError(400, `produceBlockV3 not supported for post-gloas fork=${forkName}, use produceBlockV4`);
898
+ }
881
899
  const {data, ...meta} = await produceEngineOrBuilderBlock(
882
900
  slot,
883
901
  randaoReveal,
@@ -1095,7 +1113,10 @@ export function getValidatorApi(
1095
1113
 
1096
1114
  // Calculate cutoff time based on start of the slot, ensures a slow local payload build does
1097
1115
  // not delay the proposal when a builder bid block is available (and vice versa)
1098
- const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
1116
+ const cutoffMs = Math.max(
1117
+ 0,
1118
+ config.getSlotComponentDurationMs(fork, BLOCK_PRODUCTION_RACE_CUTOFF_BPS) - chain.clock.msFromSlot(slot)
1119
+ );
1099
1120
 
1100
1121
  // use abort controller to stop waiting for the bid block if the engine block will be selected
1101
1122
  const controller = new AbortController();
@@ -1123,7 +1144,7 @@ export function getValidatorApi(
1123
1144
 
1124
1145
  const [engineResult, bidBlockResult] = await resolveOrRacePromises([enginePromise, bidBlockPromise], {
1125
1146
  resolveTimeoutMs: cutoffMs,
1126
- raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
1147
+ raceTimeoutMs: config.getSlotDurationMs(fork),
1127
1148
  signal: controller.signal,
1128
1149
  });
1129
1150
 
@@ -1417,7 +1438,7 @@ export function getValidatorApi(
1417
1438
  // Spec: set payload_present only if the envelope was seen before get_payload_due_ms()
1418
1439
  // into the slot. Use the envelope's own arrival time (getPayloadEnvelopeSource), not
1419
1440
  // the input's creation time.
1420
- const payloadDueSec = config.getPayloadDueMs() / 1000;
1441
+ const payloadDueSec = config.getPayloadDueMs(fork) / 1000;
1421
1442
  const payloadSeenSec =
1422
1443
  payloadInput?.hasPayloadEnvelope() === true
1423
1444
  ? chain.clock.secFromSlot(slot, payloadInput.getPayloadEnvelopeSource().seenTimestampSec)
@@ -1494,8 +1515,9 @@ export function getValidatorApi(
1494
1515
  const currentEpoch = currentEpochWithDisparity();
1495
1516
  const nextEpoch = currentEpoch + 1;
1496
1517
  const startSlot = computeStartSlotAtEpoch(epoch);
1518
+ const prepareFork = config.getForkName(computeStartSlotAtEpoch(nextEpoch) - 1);
1497
1519
  const prepareNextSlotLookAheadMs =
1498
- config.SLOT_DURATION_MS - config.getSlotComponentDurationMs(PREPARE_NEXT_SLOT_BPS);
1520
+ config.getSlotDurationMs(prepareFork) - config.getSlotComponentDurationMs(prepareFork, PREPARE_NEXT_SLOT_BPS);
1499
1521
  const toNextEpochMs = msToNextEpoch();
1500
1522
  const nearNextEpoch = toNextEpochMs < prepareNextSlotLookAheadMs;
1501
1523
  // Post-Fulu the proposer lookahead is deterministic and known a full epoch ahead, so
@@ -1,7 +1,10 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
+ import {NUMBER_OF_COLUMNS} from "@lodestar/params";
3
+ import {RootHex} from "@lodestar/types";
2
4
  import {Logger, sleep} from "@lodestar/utils";
3
5
  import {Metrics} from "../metrics/metrics.js";
4
6
  import {DataColumnReconstructionCode, recoverDataColumnSidecars} from "../util/dataColumns.js";
7
+ import {JobItemQueue} from "../util/queue/index.js";
5
8
  import {BlockInputColumns} from "./blocks/blockInput/index.js";
6
9
  import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/index.js";
7
10
  import {ChainEventEmitter} from "./emitter.js";
@@ -16,15 +19,22 @@ const RECONSTRUCTION_DELAY_MIN_BPS = 667;
16
19
  */
17
20
  const RECONSTRUCTION_DELAY_MAX_BPS = 1000;
18
21
 
22
+ const QUEUE_MAX_LENGTH = 16;
23
+
24
+ export type ColumnReconstructionInput = BlockInputColumns | PayloadEnvelopeInput;
25
+
19
26
  export type ColumnReconstructionTrackerInit = {
20
27
  logger: Logger;
21
28
  emitter: ChainEventEmitter;
22
29
  metrics: Metrics | null;
23
30
  config: ChainForkConfig;
31
+ signal: AbortSignal;
24
32
  };
25
33
 
26
34
  /**
27
- * Tracks column reconstruction attempts to avoid duplicate and multiple in-flight calls
35
+ * Runs column reconstruction one block root at a time, in trigger order. A trigger for a root that is
36
+ * queued or in flight is a no-op. A root is removed once its attempt finishes so a later column can
37
+ * retry a failed attempt.
28
38
  */
29
39
  export class ColumnReconstructionTracker {
30
40
  logger: Logger;
@@ -32,64 +42,71 @@ export class ColumnReconstructionTracker {
32
42
  metrics: Metrics | null;
33
43
  config: ChainForkConfig;
34
44
 
35
- /**
36
- * Track last attempted block root
37
- *
38
- * This is sufficient to avoid duplicate calls since we only call this
39
- * function when we see a new data column sidecar from gossip.
40
- */
41
- lastBlockRootHex: string | null = null;
42
- /** Track if a reconstruction attempt is in-flight */
43
- running = false;
44
-
45
- private readonly minDelayMs: number;
46
- private readonly maxDelayMs: number;
45
+ private readonly signal: AbortSignal;
46
+ private readonly pending = new Set<RootHex>();
47
+ private readonly queue: JobItemQueue<[ColumnReconstructionInput, number], void>;
47
48
 
48
49
  constructor(init: ColumnReconstructionTrackerInit) {
49
50
  this.logger = init.logger;
50
51
  this.emitter = init.emitter;
51
52
  this.metrics = init.metrics;
52
53
  this.config = init.config;
53
- this.minDelayMs = this.config.getSlotComponentDurationMs(RECONSTRUCTION_DELAY_MIN_BPS);
54
- this.maxDelayMs = this.config.getSlotComponentDurationMs(RECONSTRUCTION_DELAY_MAX_BPS);
54
+ this.signal = init.signal;
55
+ this.queue = new JobItemQueue(
56
+ this.reconstruct,
57
+ {maxLength: QUEUE_MAX_LENGTH, signal: init.signal},
58
+ init.metrics?.columnReconstructionQueue
59
+ );
55
60
  }
56
61
 
57
- triggerColumnReconstruction(input: BlockInputColumns | PayloadEnvelopeInput): void {
58
- if (this.running) {
62
+ triggerColumnReconstruction(input: ColumnReconstructionInput): void {
63
+ if (this.pending.has(input.blockRootHex)) {
59
64
  return;
60
65
  }
61
66
 
62
- if (this.lastBlockRootHex === input.blockRootHex) {
67
+ if (input.getAllColumns().length < NUMBER_OF_COLUMNS / 2) {
63
68
  return;
64
69
  }
65
70
 
66
- // We don't care about the outcome of this call,
67
- // just that it has been triggered for this block root.
68
- this.running = true;
69
- this.lastBlockRootHex = input.blockRootHex;
70
- const delay = this.minDelayMs + Math.random() * (this.maxDelayMs - this.minDelayMs);
71
- sleep(delay)
72
- .then(() => {
73
- const logCtx = {slot: input.slot, root: input.blockRootHex};
74
- this.logger.debug("Attempting data column sidecar reconstruction", logCtx);
75
- recoverDataColumnSidecars(input, this.emitter, this.metrics)
76
- .then((result) => {
77
- this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result});
78
- this.logger.debug("Data column sidecar reconstruction complete", {...logCtx, result});
79
- })
80
- .catch((e) => {
81
- this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({
82
- result: DataColumnReconstructionCode.Failed,
83
- });
84
- this.logger.debug("Error during data column sidecar reconstruction", logCtx, e as Error);
85
- })
86
- .finally(() => {
87
- this.logger.debug("Data column sidecar reconstruction attempt finished", logCtx);
88
- this.running = false;
89
- });
90
- })
91
- .catch((err) => {
92
- this.logger.debug("ColumnReconstructionTracker unreachable error", {}, err);
93
- });
71
+ this.pending.add(input.blockRootHex);
72
+ void this.enqueue(input);
73
+ }
74
+
75
+ private async enqueue(input: ColumnReconstructionInput): Promise<void> {
76
+ try {
77
+ await this.queue.push(input, Date.now());
78
+ } catch (e) {
79
+ this.logger.debug(
80
+ "Data column sidecar reconstruction dropped",
81
+ {slot: input.slot, root: input.blockRootHex},
82
+ e as Error
83
+ );
84
+ } finally {
85
+ this.pending.delete(input.blockRootHex);
86
+ }
94
87
  }
88
+
89
+ private reconstruct = async (input: ColumnReconstructionInput, queuedAtMs: number): Promise<void> => {
90
+ const fork = this.config.getForkName(input.slot);
91
+ const minDelayMs = this.config.getSlotComponentDurationMs(fork, RECONSTRUCTION_DELAY_MIN_BPS);
92
+ const maxDelayMs = this.config.getSlotComponentDurationMs(fork, RECONSTRUCTION_DELAY_MAX_BPS);
93
+ const delay = minDelayMs + Math.random() * (maxDelayMs - minDelayMs);
94
+
95
+ // The delay gives gossip a chance to deliver the remaining columns, so time already spent in the
96
+ // queue behind another root counts towards it
97
+ await sleep(delay - (Date.now() - queuedAtMs), this.signal);
98
+
99
+ const logCtx = {slot: input.slot, root: input.blockRootHex};
100
+ this.logger.debug("Attempting data column sidecar reconstruction", logCtx);
101
+ try {
102
+ const result = await recoverDataColumnSidecars(input, this.emitter, this.metrics);
103
+ this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result});
104
+ this.logger.debug("Data column sidecar reconstruction complete", {...logCtx, result});
105
+ } catch (e) {
106
+ this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result: DataColumnReconstructionCode.Failed});
107
+ this.logger.debug("Error during data column sidecar reconstruction", logCtx, e as Error);
108
+ } finally {
109
+ this.logger.debug("Data column sidecar reconstruction attempt finished", logCtx);
110
+ }
111
+ };
95
112
  }
@@ -3,7 +3,7 @@ import {ChainForkConfig} from "@lodestar/config";
3
3
  import {KeyValue} from "@lodestar/db";
4
4
  import {CheckpointWithHex, ExecutionStatus, IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
5
5
  import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
6
- import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
6
+ import {computeEpochAtSlot, computeStartSlotAtEpoch, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
7
7
  import {Epoch, Slot} from "@lodestar/types";
8
8
  import {Logger, fromAsync, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
9
9
  import {IBeaconDb} from "../../../db/index.js";
@@ -253,14 +253,13 @@ export async function archiveBlocks(
253
253
  }
254
254
 
255
255
  // Delete expired data column sidecars
256
- // Keep only `[current_epoch - max(MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, archiveDataEpochs)]`
256
+ // Keep only `[min(compute_blob_data_retention_start_epoch(current_epoch), current_epoch - archiveDataEpochs)]`
257
257
  if (finalizedPostFulu) {
258
258
  if (archiveDataEpochs !== Infinity) {
259
- const dataColumnSidecarsArchiveWindow = Math.max(
260
- config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS,
261
- archiveDataEpochs ?? 0
259
+ const dataColumnSidecarsMinEpoch = Math.min(
260
+ getBlobDataRetentionStartEpoch(config, currentEpoch),
261
+ currentEpoch - (archiveDataEpochs ?? 0)
262
262
  );
263
- const dataColumnSidecarsMinEpoch = currentEpoch - dataColumnSidecarsArchiveWindow;
264
263
  if (dataColumnSidecarsMinEpoch >= config.FULU_FORK_EPOCH) {
265
264
  const columnsPruneSlot = computeStartSlotAtEpoch(dataColumnSidecarsMinEpoch);
266
265
  const prunedColumnSlots = await db.dataColumns.pruneBefore(columnsPruneSlot);
@@ -439,7 +438,7 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
439
438
  const blockEpoch = computeEpochAtSlot(block.slot);
440
439
  if (
441
440
  config.getForkSeq(block.slot) < ForkSeq.fulu ||
442
- blockEpoch < currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
441
+ blockEpoch < getBlobDataRetentionStartEpoch(config, currentEpoch)
443
442
  ) {
444
443
  continue;
445
444
  }
@@ -210,7 +210,7 @@ export class BlockInputPreData extends AbstractBlockInput<ForkPreDeneb, null> {
210
210
  this.blockPromise.resolve(state.block);
211
211
  }
212
212
 
213
- static createFromBlock(props: AddBlock & CreateBlockInputMeta): BlockInputPreData {
213
+ static createFromBlock(props: AddBlock<ForkPreDeneb> & CreateBlockInputMeta): BlockInputPreData {
214
214
  const init: BlockInputInit = {
215
215
  daOutOfRange: props.daOutOfRange,
216
216
  timeCreated: props.seenTimestampSec,
@@ -1,6 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ForkName, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
- import {computeEpochAtSlot} from "@lodestar/state-transition";
3
+ import {computeEpochAtSlot, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
4
4
  import {Epoch, Slot} from "@lodestar/types";
5
5
 
6
6
  export function isDaOutOfRange(
@@ -10,7 +10,7 @@ export function isDaOutOfRange(
10
10
  currentEpoch: Epoch
11
11
  ): boolean {
12
12
  if (isForkPostFulu(forkName)) {
13
- return computeEpochAtSlot(blockSlot) < currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS;
13
+ return computeEpochAtSlot(blockSlot) < getBlobDataRetentionStartEpoch(config, currentEpoch);
14
14
  }
15
15
 
16
16
  if (isForkPostDeneb(forkName)) {
@@ -45,7 +45,7 @@ import {isQueueErrorAborted} from "../../util/queue/index.js";
45
45
  import type {BeaconChain} from "../chain.js";
46
46
  import {ChainEvent, ReorgEventData} from "../emitter.js";
47
47
  import {ForkchoiceCaller} from "../forkChoice/index.js";
48
- import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC} from "../reprocess.js";
48
+ import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_BPS} from "../reprocess.js";
49
49
  import {toCheckpointHex} from "../stateCache/persistentCheckpointsCache.js";
50
50
  import {isBlockInputBlobs, isBlockInputColumns} from "./blockInput/blockInput.js";
51
51
  import {AttestationImportOpt, FullyVerifiedBlock, ImportBlockOpts} from "./types.js";
@@ -335,7 +335,8 @@ export async function importBlock(
335
335
  this.metrics.headSlot.set(newHead.slot);
336
336
  // Only track "recent" blocks. Otherwise sync can distort this metrics heavily.
337
337
  // We want to track recent blocks coming from gossip, unknown block sync, and API.
338
- if (delaySec < (SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000) {
338
+ const headFork = this.config.getForkName(newHead.slot);
339
+ if (delaySec < (SLOTS_PER_EPOCH * this.config.getSlotDurationMs(headFork)) / 1000) {
339
340
  this.metrics.importBlock.elapsedTimeTillBecomeHead.observe(delaySec);
340
341
  const cutOffSec = this.config.getAttestationDueMs(this.config.getForkName(blockSlot)) / 1000;
341
342
  if (delaySec > cutOffSec) {
@@ -586,7 +587,7 @@ export async function importBlock(
586
587
  this.validatorMonitor?.registerSyncAggregateInBlock(
587
588
  blockEpoch,
588
589
  (block as altair.SignedBeaconBlock).message.body.syncAggregate,
589
- fullyVerifiedBlock.postState.currentSyncCommitteeIndexed.validatorIndices
590
+ fullyVerifiedBlock.postState.currentSyncCommitteeValidatorIndices
590
591
  );
591
592
  }
592
593
 
@@ -600,7 +601,10 @@ export async function importBlock(
600
601
  }
601
602
  }
602
603
 
603
- const advancedSlot = this.clock.slotWithFutureTolerance(REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC);
604
+ const reprocessGuardSec =
605
+ this.config.getSlotComponentDurationMs(this.config.getForkName(blockSlot), REPROCESS_MIN_TIME_TO_NEXT_SLOT_BPS) /
606
+ 1000;
607
+ const advancedSlot = this.clock.slotWithFutureTolerance(reprocessGuardSec);
604
608
 
605
609
  // Gossip blocks need to be imported as soon as possible, waiting attestations could be processed
606
610
  // in the next event loop. See https://github.com/ChainSafe/lodestar/issues/4789
@@ -58,7 +58,7 @@ import {
58
58
  } from "@lodestar/types";
59
59
  import {Logger, fromHex, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
60
60
  import {ProcessShutdownCallback} from "@lodestar/validator";
61
- import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js";
61
+ import {GENESIS_EPOCH, HALF_SLOT_BPS, ZERO_HASH} from "../constants/index.js";
62
62
  import {IBeaconDb} from "../db/index.js";
63
63
  import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../db/repositories/blobSidecars.js";
64
64
  import {decodeArchivedEnvelope} from "../db/repositories/index.js";
@@ -509,6 +509,7 @@ export class BeaconChain implements IBeaconChain {
509
509
  emitter,
510
510
  metrics,
511
511
  config,
512
+ signal,
512
513
  });
513
514
 
514
515
  this.archiveStore = new ArchiveStore(
@@ -1841,7 +1842,8 @@ export class BeaconChain implements IBeaconChain {
1841
1842
  const metrics = this.metrics;
1842
1843
  if (metrics && (slot + 1) % SLOTS_PER_EPOCH === 0) {
1843
1844
  // On the last slot of the epoch
1844
- sleep(this.config.SLOT_DURATION_MS / 2)
1845
+ const fork = this.config.getForkName(slot);
1846
+ sleep(this.config.getSlotComponentDurationMs(fork, HALF_SLOT_BPS))
1845
1847
  .then(() => this.validatorMonitor?.onceEveryEndOfEpoch(this.getHeadState()))
1846
1848
  .catch((e) => {
1847
1849
  if (!isErrorAborted(e)) this.logger.error("Error on validator monitor onceEveryEndOfEpoch", {slot}, e);
@@ -331,6 +331,7 @@ export type SSZObjectType =
331
331
  | "syncCommittee"
332
332
  | "contributionAndProof";
333
333
 
334
- export type CommonBlockBody = phase0.BeaconBlockBody &
334
+ export type CommonBlockBody = Omit<phase0.BeaconBlockBody, "eth1Data" | "deposits"> &
335
+ Partial<Pick<phase0.BeaconBlockBody, "eth1Data" | "deposits">> &
335
336
  Pick<capella.BeaconBlockBody, "blsToExecutionChanges"> &
336
337
  Pick<altair.BeaconBlockBody, "syncAggregate">;
@@ -96,6 +96,7 @@ export class PrepareNextSlotScheduler {
96
96
  const nextEpoch = computeEpochAtSlot(clockSlot) + 1;
97
97
  const isEpochTransition = prepareEpoch === nextEpoch;
98
98
  const fork = this.config.getForkName(prepareSlot);
99
+ const currentFork = this.config.getForkName(clockSlot);
99
100
 
100
101
  // Early return if we are pre-genesis
101
102
  // or we are pre-bellatrix and this is not an epoch transition
@@ -106,7 +107,7 @@ export class PrepareNextSlotScheduler {
106
107
  try {
107
108
  // At PREPARE_NEXT_SLOT_BPS (~67%) of the current slot we prepare payload for the next slot
108
109
  // or precompute epoch transition
109
- await sleep(this.config.getSlotComponentDurationMs(PREPARE_NEXT_SLOT_BPS), this.signal);
110
+ await sleep(this.config.getSlotComponentDurationMs(currentFork, PREPARE_NEXT_SLOT_BPS), this.signal);
110
111
 
111
112
  // calling updateHead() here before we produce a block to reduce reorg possibility
112
113
  const headBlock = this.chain.recomputeForkChoiceHead(ForkchoiceCaller.prepareNextSlot);
@@ -369,7 +370,7 @@ export class PrepareNextSlotScheduler {
369
370
  // skip when we're the next-slot proposer — don't compete with block-production prep.
370
371
  feeRecipient === undefined
371
372
  ) {
372
- const maxDurationMs = this.config.getSlotComponentDurationMs(BUILDER_PREVERIFY_LIMIT_BPS);
373
+ const maxDurationMs = this.config.getSlotComponentDurationMs(currentFork, BUILDER_PREVERIFY_LIMIT_BPS);
373
374
  const preVerifyTimer = this.metrics?.builderDepositPreVerify.duration.startTimer();
374
375
  const result = headState.preVerifyBuilderDepositsPreGloas(MAX_BUILDER_DEPOSITS_PER_SLOT, maxDurationMs);
375
376
  preVerifyTimer?.();
@@ -423,7 +424,10 @@ export class PrepareNextSlotScheduler {
423
424
  * later in the slot, connecting at PREPARE_NEXT_SLOT_BPS would go cold before bids are requested.
424
425
  */
425
426
  private checkBuilderStatusBeforeSlot(prepareSlot: Slot): void {
426
- const msBeforeSlot = this.config.getSlotComponentDurationMs(BUILDER_STATUS_CHECK_BEFORE_SLOT_BPS);
427
+ const msBeforeSlot = this.config.getSlotComponentDurationMs(
428
+ this.config.getForkName(prepareSlot - 1),
429
+ BUILDER_STATUS_CHECK_BEFORE_SLOT_BPS
430
+ );
427
431
  const msUntilCheck = -this.chain.clock.msFromSlot(prepareSlot) - msBeforeSlot;
428
432
  sleep(Math.max(0, msUntilCheck), this.signal)
429
433
  .then(() => this.chain.builderApiClient.checkStatus())
@@ -1,5 +1,12 @@
1
1
  import {EMPTY_SIGNATURE, IBeaconStateView, StateHashTreeRootSource} from "@lodestar/state-transition";
2
- import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types";
2
+ import {
3
+ BeaconBlock,
4
+ BlindedBeaconBlock,
5
+ Gwei,
6
+ Root,
7
+ SignedBeaconBlock,
8
+ SignedBlindedBeaconBlock,
9
+ } from "@lodestar/types";
3
10
  import {Metrics} from "../../metrics/index.js";
4
11
 
5
12
  /**
@@ -13,7 +20,7 @@ export function computeNewStateRoot(
13
20
  block: BeaconBlock | BlindedBeaconBlock
14
21
  ): {newStateRoot: Root; proposerReward: Gwei; postState: IBeaconStateView} {
15
22
  // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type
16
- const signedBlock = {message: block, signature: EMPTY_SIGNATURE};
23
+ const signedBlock = {message: block, signature: EMPTY_SIGNATURE} as SignedBeaconBlock | SignedBlindedBeaconBlock;
17
24
  const {newStateRoot, proposerReward, postState, hashTreeRootTime} = state.computeNewStateRoot(
18
25
  {block: signedBlock},
19
26
  {metrics: metrics?.stateTransition}
@@ -20,6 +20,7 @@ import {
20
20
  isForkPostAltair,
21
21
  isForkPostBellatrix,
22
22
  isForkPostGloas,
23
+ isForkPostHeze,
23
24
  } from "@lodestar/params";
24
25
  import {
25
26
  G2_POINT_AT_INFINITY,
@@ -684,12 +685,11 @@ export async function produceBlockBody<T extends BlockType>(
684
685
  executionPayloadValue = BigInt(0);
685
686
  }
686
687
 
687
- const {graffiti, attestations, deposits, voluntaryExits, attesterSlashings, proposerSlashings} = blockBody;
688
+ const {graffiti, attestations, voluntaryExits, attesterSlashings, proposerSlashings} = blockBody;
688
689
 
689
690
  Object.assign(logMeta, {
690
691
  graffiti: fromGraffitiBytes(graffiti),
691
692
  attestations: attestations.length,
692
- deposits: deposits.length,
693
693
  voluntaryExits: voluntaryExits.length,
694
694
  attesterSlashings: attesterSlashings.length,
695
695
  proposerSlashings: proposerSlashings.length,
@@ -1106,16 +1106,21 @@ export async function produceCommonBlockBody<T extends BlockType>(
1106
1106
  });
1107
1107
 
1108
1108
  // Live proposal production is supported from Fulu onward.
1109
- return {
1109
+ const commonBlockBody: CommonBlockBody = {
1110
1110
  randaoReveal,
1111
1111
  graffiti,
1112
- eth1Data: currentState.eth1Data,
1113
1112
  proposerSlashings: this.opts.disableProposerSlashings === true ? [] : proposerSlashings,
1114
1113
  attesterSlashings,
1115
1114
  attestations,
1116
- deposits: [],
1117
1115
  voluntaryExits,
1118
1116
  blsToExecutionChanges,
1119
1117
  syncAggregate,
1120
1118
  };
1119
+
1120
+ if (!isForkPostHeze(fork)) {
1121
+ commonBlockBody.eth1Data = currentState.eth1Data;
1122
+ commonBlockBody.deposits = [];
1123
+ }
1124
+
1125
+ return commonBlockBody;
1121
1126
  }