@lodestar/beacon-node 1.44.0-dev.ff43f013ea → 1.44.0-rc.1

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 (162) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -5
  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/config/constants.d.ts +1 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +2 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/debug/index.d.ts.map +1 -1
  12. package/lib/api/impl/debug/index.js +69 -12
  13. package/lib/api/impl/debug/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  15. package/lib/api/impl/lodestar/index.js +28 -0
  16. package/lib/api/impl/lodestar/index.js.map +1 -1
  17. package/lib/api/impl/validator/index.d.ts.map +1 -1
  18. package/lib/api/impl/validator/index.js +41 -16
  19. package/lib/api/impl/validator/index.js.map +1 -1
  20. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  21. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js +0 -4
  23. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +1 -1
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/chain.d.ts.map +1 -1
  28. package/lib/chain/chain.js +8 -1
  29. package/lib/chain/chain.js.map +1 -1
  30. package/lib/chain/emitter.d.ts +2 -1
  31. package/lib/chain/emitter.d.ts.map +1 -1
  32. package/lib/chain/emitter.js.map +1 -1
  33. package/lib/chain/errors/blockError.d.ts +0 -7
  34. package/lib/chain/errors/blockError.d.ts.map +1 -1
  35. package/lib/chain/errors/blockError.js +0 -3
  36. package/lib/chain/errors/blockError.js.map +1 -1
  37. package/lib/chain/errors/payloadAttestation.d.ts +6 -0
  38. package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
  39. package/lib/chain/errors/payloadAttestation.js +1 -0
  40. package/lib/chain/errors/payloadAttestation.js.map +1 -1
  41. package/lib/chain/forkChoice/index.d.ts +4 -4
  42. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  43. package/lib/chain/forkChoice/index.js +10 -7
  44. package/lib/chain/forkChoice/index.js.map +1 -1
  45. package/lib/chain/options.d.ts.map +1 -1
  46. package/lib/chain/options.js +1 -0
  47. package/lib/chain/options.js.map +1 -1
  48. package/lib/chain/prepareNextSlot.js +1 -1
  49. package/lib/chain/prepareNextSlot.js.map +1 -1
  50. package/lib/chain/produceBlock/produceBlockBody.js +3 -3
  51. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  52. package/lib/chain/regen/interface.d.ts +1 -1
  53. package/lib/chain/regen/interface.d.ts.map +1 -1
  54. package/lib/chain/regen/interface.js +1 -0
  55. package/lib/chain/regen/interface.js.map +1 -1
  56. package/lib/chain/regen/queued.d.ts +0 -1
  57. package/lib/chain/regen/queued.d.ts.map +1 -1
  58. package/lib/chain/regen/queued.js +0 -4
  59. package/lib/chain/regen/queued.js.map +1 -1
  60. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
  61. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  62. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
  63. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  64. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
  65. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  66. package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
  67. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  68. package/lib/chain/stateCache/types.d.ts +0 -2
  69. package/lib/chain/stateCache/types.d.ts.map +1 -1
  70. package/lib/chain/stateCache/types.js.map +1 -1
  71. package/lib/chain/validation/block.d.ts +5 -1
  72. package/lib/chain/validation/block.d.ts.map +1 -1
  73. package/lib/chain/validation/block.js +4 -14
  74. package/lib/chain/validation/block.js.map +1 -1
  75. package/lib/chain/validation/executionPayloadBid.js +22 -5
  76. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  77. package/lib/chain/validation/executionPayloadEnvelope.js +0 -2
  78. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  79. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  80. package/lib/chain/validation/payloadAttestationMessage.js +24 -4
  81. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  82. package/lib/metrics/metrics/lodestar.d.ts +4 -0
  83. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  84. package/lib/metrics/metrics/lodestar.js +10 -0
  85. package/lib/metrics/metrics/lodestar.js.map +1 -1
  86. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  87. package/lib/network/processor/gossipHandlers.js +10 -3
  88. package/lib/network/processor/gossipHandlers.js.map +1 -1
  89. package/lib/network/processor/index.d.ts +2 -2
  90. package/lib/network/processor/index.d.ts.map +1 -1
  91. package/lib/network/processor/index.js +25 -23
  92. package/lib/network/processor/index.js.map +1 -1
  93. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  94. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
  95. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  96. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  97. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +13 -3
  98. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  99. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
  100. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  101. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
  102. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  103. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
  104. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  105. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts +2 -1
  106. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  107. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +15 -1
  108. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  109. package/lib/network/reqresp/handlers/index.js +4 -4
  110. package/lib/network/reqresp/handlers/index.js.map +1 -1
  111. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  112. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +22 -3
  113. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  114. package/lib/sync/types.d.ts +9 -1
  115. package/lib/sync/types.d.ts.map +1 -1
  116. package/lib/sync/types.js +9 -2
  117. package/lib/sync/types.js.map +1 -1
  118. package/lib/sync/unknownBlock.d.ts.map +1 -1
  119. package/lib/sync/unknownBlock.js +64 -30
  120. package/lib/sync/unknownBlock.js.map +1 -1
  121. package/lib/util/dataColumns.d.ts.map +1 -1
  122. package/lib/util/dataColumns.js +16 -11
  123. package/lib/util/dataColumns.js.map +1 -1
  124. package/package.json +15 -17
  125. package/src/api/impl/beacon/blocks/index.ts +13 -5
  126. package/src/api/impl/beacon/pool/index.ts +1 -0
  127. package/src/api/impl/config/constants.ts +2 -0
  128. package/src/api/impl/debug/index.ts +73 -12
  129. package/src/api/impl/lodestar/index.ts +30 -0
  130. package/src/api/impl/validator/index.ts +52 -22
  131. package/src/chain/archiveStore/archiveStore.ts +0 -5
  132. package/src/chain/blocks/importBlock.ts +1 -0
  133. package/src/chain/chain.ts +10 -1
  134. package/src/chain/emitter.ts +3 -2
  135. package/src/chain/errors/blockError.ts +0 -4
  136. package/src/chain/errors/payloadAttestation.ts +2 -0
  137. package/src/chain/forkChoice/index.ts +13 -0
  138. package/src/chain/options.ts +1 -0
  139. package/src/chain/prepareNextSlot.ts +1 -1
  140. package/src/chain/produceBlock/produceBlockBody.ts +3 -3
  141. package/src/chain/regen/interface.ts +1 -1
  142. package/src/chain/regen/queued.ts +0 -5
  143. package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
  144. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  145. package/src/chain/stateCache/types.ts +0 -2
  146. package/src/chain/validation/block.ts +12 -16
  147. package/src/chain/validation/executionPayloadBid.ts +23 -5
  148. package/src/chain/validation/executionPayloadEnvelope.ts +0 -2
  149. package/src/chain/validation/payloadAttestationMessage.ts +26 -4
  150. package/src/metrics/metrics/lodestar.ts +11 -0
  151. package/src/network/processor/gossipHandlers.ts +10 -2
  152. package/src/network/processor/index.ts +27 -27
  153. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
  154. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -3
  155. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
  156. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
  157. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +20 -1
  158. package/src/network/reqresp/handlers/index.ts +4 -4
  159. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +21 -3
  160. package/src/sync/types.ts +11 -2
  161. package/src/sync/unknownBlock.ts +70 -31
  162. package/src/util/dataColumns.ts +17 -12
@@ -280,6 +280,36 @@ export function getLodestarApi({
280
280
  };
281
281
  },
282
282
 
283
+ async getFastConfirmationInfo() {
284
+ const confirmedRoot = chain.forkChoice.getConfirmedRoot();
285
+ const confirmedBlock = chain.forkChoice.getConfirmedBlock();
286
+ const justifiedCheckpoint = chain.forkChoice.getJustifiedCheckpoint();
287
+ const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
288
+ const headRoot = chain.forkChoice.getHeadRoot();
289
+ const head = chain.forkChoice.getHead();
290
+
291
+ return {
292
+ data: {
293
+ confirmed: {
294
+ rootHex: confirmedRoot,
295
+ slot: confirmedBlock?.slot ?? null,
296
+ },
297
+ head: {
298
+ rootHex: headRoot,
299
+ slot: head.slot,
300
+ },
301
+ justifiedCheckpoint: {
302
+ rootHex: justifiedCheckpoint.rootHex,
303
+ epoch: justifiedCheckpoint.epoch,
304
+ },
305
+ finalizedCheckpoint: {
306
+ rootHex: finalizedCheckpoint.rootHex,
307
+ epoch: finalizedCheckpoint.epoch,
308
+ },
309
+ },
310
+ };
311
+ },
312
+
283
313
  async getAttesterSlashingsFromBlocks({signedBlocks}) {
284
314
  const attestations = new Map<Epoch, Attestation[]>();
285
315
 
@@ -927,11 +927,9 @@ export function getValidatorApi(
927
927
  // TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
928
928
  // the user control bid source preferences and value comparison. Also add external builder api
929
929
  // support when it is implemented.
930
- const builderBid = chain.executionPayloadBidPool.getBestBid(
931
- slot,
932
- parentBlock.executionPayloadBlockHash,
933
- parentBlockRootHex
934
- );
930
+ const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
931
+ const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
932
+ const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
935
933
 
936
934
  const logCtx = {
937
935
  slot,
@@ -1112,23 +1110,34 @@ export function getValidatorApi(
1112
1110
  notWhileSyncing();
1113
1111
  await waitForSlot(slot);
1114
1112
 
1115
- const block = chain.forkChoice.getCanonicalBlockClosestLteSlot(slot);
1113
+ const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
1116
1114
  if (!block) {
1117
- throw new ApiError(404, `No canonical block found at or before slot=${slot}`);
1115
+ // No block is seen at slot. Return 404 so vc can skip casting payload attestation.
1116
+ throw new ApiError(404, `No canonical block found at slot=${slot}`);
1118
1117
  }
1119
1118
 
1120
- const blockIsForSlot = block.slot === slot;
1121
1119
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
1122
1120
  // Spec: set payload_present only if the envelope was seen before get_payload_due_ms()
1123
1121
  // into the slot. Use the envelope's own arrival time (getPayloadEnvelopeSource), not
1124
1122
  // the input's creation time.
1125
1123
  const payloadDueSec = config.getPayloadDueMs() / 1000;
1126
- const payloadPresent =
1127
- blockIsForSlot &&
1128
- payloadInput !== undefined &&
1129
- payloadInput.hasPayloadEnvelope() &&
1130
- chain.clock.secFromSlot(slot, payloadInput.getPayloadEnvelopeSource().seenTimestampSec) < payloadDueSec;
1131
- const blobDataAvailable = blockIsForSlot && (payloadInput?.hasAllData() ?? false);
1124
+ const payloadSeenSec =
1125
+ payloadInput?.hasPayloadEnvelope() === true
1126
+ ? chain.clock.secFromSlot(slot, payloadInput.getPayloadEnvelopeSource().seenTimestampSec)
1127
+ : null;
1128
+ const payloadPresent = payloadSeenSec !== null && payloadSeenSec < payloadDueSec;
1129
+ const blobDataAvailable = payloadInput?.hasAllData() === true;
1130
+
1131
+ logger.debug("Produced payload attestation data", {
1132
+ slot,
1133
+ blockRoot: block.blockRoot,
1134
+ blockSlot: block.slot,
1135
+ payloadPresent,
1136
+ blobDataAvailable,
1137
+ hasPayloadInput: payloadInput !== undefined,
1138
+ payloadSeenSec,
1139
+ payloadDueSec,
1140
+ });
1132
1141
 
1133
1142
  return {
1134
1143
  data: {
@@ -1198,7 +1207,10 @@ export function getValidatorApi(
1198
1207
  const isPostFulu = isForkPostFulu(config.getForkName(startSlot));
1199
1208
  const maxFutureEpoch = isPostFulu && nearNextEpoch && opts?.v2 ? nextEpoch + 1 : nextEpoch;
1200
1209
  if (currentEpoch >= 0 && epoch > maxFutureEpoch) {
1201
- 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
+ );
1202
1214
  }
1203
1215
 
1204
1216
  const head = chain.forkChoice.getHead();
@@ -1282,17 +1294,35 @@ export function getValidatorApi(
1282
1294
  duties.push({slot: startSlot + i, validatorIndex: indexes[i], pubkey: pubkeys[i]});
1283
1295
  }
1284
1296
 
1285
- // Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
1286
- // It should be set to the latest block applied to `self` or the genesis block root.
1287
- const dependentRoot =
1288
- // In v2 the dependent root is different after fulu due to deterministic proposer lookahead
1289
- proposerShufflingDecisionRoot(opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0, state, epoch) ||
1290
- (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});
1291
1321
 
1292
1322
  return {
1293
1323
  data: duties,
1294
1324
  meta: {
1295
- dependentRoot: toRootHex(dependentRoot),
1325
+ dependentRoot: dependentRootHex,
1296
1326
  executionOptimistic: isOptimisticBlock(head),
1297
1327
  },
1298
1328
  };
@@ -30,7 +30,6 @@ export enum ArchiveStoreTask {
30
30
  PruneHistory = "prune_history",
31
31
  OnFinalizedCheckpoint = "on_finalized_checkpoint",
32
32
  MaybeArchiveState = "maybe_archive_state",
33
- RegenPruneOnFinalized = "regen_prune_on_finalized",
34
33
  ForkchoicePrune = "forkchoice_prune",
35
34
  UpdateBackfillRange = "update_backfill_range",
36
35
  }
@@ -229,10 +228,6 @@ export class ArchiveStore {
229
228
  await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics);
230
229
  timer?.({source: ArchiveStoreTask.MaybeArchiveState});
231
230
 
232
- timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
233
- this.chain.regen.pruneOnFinalized(finalizedEpoch);
234
- timer?.({source: ArchiveStoreTask.RegenPruneOnFinalized});
235
-
236
231
  // tasks rely on extended fork choice
237
232
  timer = this.metrics?.processFinalizedCheckpoint.durationByTask.startTimer();
238
233
  const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex);
@@ -263,6 +263,7 @@ export async function importBlock(
263
263
  if (ptcIndices.length > 0) {
264
264
  this.forkChoice.notifyPtcMessages(
265
265
  toRootHex(payloadAttestation.data.beaconBlockRoot),
266
+ payloadAttestation.data.slot,
266
267
  ptcIndices,
267
268
  payloadAttestation.data.payloadPresent,
268
269
  payloadAttestation.data.blobDataAvailable
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Type} from "@chainsafe/ssz";
4
4
  import {BeaconConfig} from "@lodestar/config";
5
- import {CheckpointWithHex, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
5
+ import {CheckpointWithHex, ForkChoiceStateGetter, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
6
6
  import {LoggerNode} from "@lodestar/logger/node";
7
7
  import {
8
8
  EFFECTIVE_BALANCE_INCREMENT,
@@ -382,6 +382,14 @@ export class BeaconChain implements IBeaconChain {
382
382
  blockStateCache.setHeadState(anchorState);
383
383
  checkpointStateCache.add(checkpoint, anchorState);
384
384
 
385
+ const forkChoiceStateGetter: ForkChoiceStateGetter = ({stateRoot, checkpoint}) => {
386
+ if (stateRoot) return blockStateCache.get(stateRoot);
387
+
388
+ if (checkpoint) return checkpointStateCache.get({epoch: checkpoint.epoch, rootHex: checkpoint.rootHex});
389
+
390
+ return null;
391
+ };
392
+
385
393
  const forkChoice = initializeForkChoice(
386
394
  config,
387
395
  emitter,
@@ -390,6 +398,7 @@ export class BeaconChain implements IBeaconChain {
390
398
  isAnchorStateFinalized,
391
399
  opts,
392
400
  this.justifiedBalancesGetter.bind(this),
401
+ forkChoiceStateGetter,
393
402
  metrics,
394
403
  logger
395
404
  );
@@ -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}
@@ -5,6 +5,7 @@ export enum PayloadAttestationErrorCode {
5
5
  NOT_CURRENT_SLOT = "PAYLOAD_ATTESTATION_ERROR_NOT_CURRENT_SLOT",
6
6
  PAYLOAD_ATTESTATION_ALREADY_KNOWN = "PAYLOAD_ATTESTATION_ERROR_PAYLOAD_ATTESTATION_ALREADY_KNOWN",
7
7
  UNKNOWN_BLOCK_ROOT = "PAYLOAD_ATTESTATION_ERROR_UNKNOWN_BLOCK_ROOT",
8
+ INVALID_BLOCK_SLOT = "PAYLOAD_ATTESTATION_ERROR_INVALID_BLOCK_SLOT",
8
9
  INVALID_BLOCK = "PAYLOAD_ATTESTATION_ERROR_INVALID_BLOCK",
9
10
  INVALID_ATTESTER = "PAYLOAD_ATTESTATION_ERROR_INVALID_ATTESTER",
10
11
  INVALID_SIGNATURE = "PAYLOAD_ATTESTATION_ERROR_INVALID_SIGNATURE",
@@ -18,6 +19,7 @@ export type PayloadAttestationErrorType =
18
19
  blockRoot: RootHex;
19
20
  }
20
21
  | {code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT; blockRoot: RootHex}
22
+ | {code: PayloadAttestationErrorCode.INVALID_BLOCK_SLOT; blockRoot: RootHex; blockSlot: Slot; slot: Slot}
21
23
  | {code: PayloadAttestationErrorCode.INVALID_BLOCK; blockRoot: RootHex}
22
24
  | {code: PayloadAttestationErrorCode.INVALID_ATTESTER; attesterIndex: ValidatorIndex}
23
25
  | {code: PayloadAttestationErrorCode.INVALID_SIGNATURE};
@@ -1,7 +1,9 @@
1
+ import {routes} from "@lodestar/api";
1
2
  import {ChainForkConfig} from "@lodestar/config";
2
3
  import {
3
4
  ExecutionStatus,
4
5
  ForkChoice,
6
+ ForkChoiceStateGetter,
5
7
  ForkChoiceStore,
6
8
  JustifiedBalancesGetter,
7
9
  PayloadStatus,
@@ -45,6 +47,7 @@ export function initializeForkChoice(
45
47
  isFinalizedState: boolean,
46
48
  opts: ForkChoiceOpts,
47
49
  justifiedBalancesGetter: JustifiedBalancesGetter,
50
+ stateGetter: ForkChoiceStateGetter,
48
51
  metrics: Metrics | null,
49
52
  logger?: Logger
50
53
  ): ForkChoice {
@@ -56,6 +59,7 @@ export function initializeForkChoice(
56
59
  state,
57
60
  opts,
58
61
  justifiedBalancesGetter,
62
+ stateGetter,
59
63
  metrics,
60
64
  logger
61
65
  )
@@ -66,6 +70,7 @@ export function initializeForkChoice(
66
70
  state,
67
71
  opts,
68
72
  justifiedBalancesGetter,
73
+ stateGetter,
69
74
  metrics,
70
75
  logger
71
76
  );
@@ -81,6 +86,7 @@ export function initializeForkChoiceFromFinalizedState(
81
86
  state: IBeaconStateView,
82
87
  opts: ForkChoiceOpts,
83
88
  justifiedBalancesGetter: JustifiedBalancesGetter,
89
+ stateGetter: ForkChoiceStateGetter,
84
90
  metrics: Metrics | null,
85
91
  logger?: Logger
86
92
  ): ForkChoice {
@@ -112,9 +118,12 @@ export function initializeForkChoiceFromFinalizedState(
112
118
  finalizedCheckpoint,
113
119
  justifiedBalances,
114
120
  justifiedBalancesGetter,
121
+ stateGetter,
115
122
  {
116
123
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
117
124
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
125
+ onFastConfirmation: ({block, slot, currentSlot}) =>
126
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
118
127
  }
119
128
  ),
120
129
 
@@ -172,6 +181,7 @@ export function initializeForkChoiceFromUnfinalizedState(
172
181
  unfinalizedState: IBeaconStateView,
173
182
  opts: ForkChoiceOpts,
174
183
  justifiedBalancesGetter: JustifiedBalancesGetter,
184
+ stateGetter: ForkChoiceStateGetter,
175
185
  metrics: Metrics | null,
176
186
  logger?: Logger
177
187
  ): ForkChoice {
@@ -203,9 +213,12 @@ export function initializeForkChoiceFromUnfinalizedState(
203
213
  finalizedCheckpoint,
204
214
  justifiedBalances,
205
215
  justifiedBalancesGetter,
216
+ stateGetter,
206
217
  {
207
218
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
208
219
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
220
+ onFastConfirmation: ({block, slot, currentSlot}) =>
221
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
209
222
  }
210
223
  );
211
224
 
@@ -105,6 +105,7 @@ export const defaultChainOptions: IChainOptions = {
105
105
  proposerBoost: true,
106
106
  proposerBoostReorg: true,
107
107
  computeUnrealized: true,
108
+ fastConfirmation: false,
108
109
  suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
109
110
  serveHistoricalState: false,
110
111
  assertCorrectProgressiveBalances: false,
@@ -170,7 +170,7 @@ export class PrepareNextSlotScheduler {
170
170
  let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
171
171
  if (isStatePostGloas(updatedPrepareState)) {
172
172
  // Spec: should_build_on_full(store, head) — see produceBlockBody.ts for context.
173
- if (this.chain.forkChoice.shouldBuildOnFull(updatedHead)) {
173
+ if (this.chain.forkChoice.shouldBuildOnFull(updatedHead, prepareSlot)) {
174
174
  parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
175
175
  // Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
176
176
  if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
@@ -276,9 +276,9 @@ export async function produceBlockBody<T extends BlockType>(
276
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
277
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
278
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);
279
+ // (set by chain.getProposerHead(slot)). Returns false when the PTC majority signalled
280
+ // the blob data is not available or the payload was not timely, forcing a build on EMPTY (reorg).
281
+ const isBuildingOnFull = this.forkChoice.shouldBuildOnFull(parentBlock, blockSlot);
282
282
  if (isBuildingOnFull) {
283
283
  parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
284
284
  parentExecutionRequests = await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot);
@@ -22,6 +22,7 @@ export enum RegenCaller {
22
22
  validateGossipAttestation = "validateGossipAttestation",
23
23
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
24
24
  validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
25
+ validateGossipPayloadAttestationMessage = "validateGossipPayloadAttestationMessage",
25
26
  validateGossipProposerPreferences = "validateGossipProposerPreferences",
26
27
  onForkChoiceFinalized = "onForkChoiceFinalized",
27
28
  restApi = "restApi",
@@ -46,7 +47,6 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
46
47
  getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
47
48
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
48
49
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
49
- pruneOnFinalized(finalizedEpoch: Epoch): void;
50
50
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
51
51
  addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
52
52
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void;
@@ -143,11 +143,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
143
143
  this.blockStateCache.prune(headStateRoot);
144
144
  }
145
145
 
146
- pruneOnFinalized(finalizedEpoch: number): void {
147
- this.checkpointStateCache.pruneFinalized(finalizedEpoch);
148
- this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
149
- }
150
-
151
146
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void {
152
147
  this.blockStateCache.add(postState);
153
148
  this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
@@ -167,12 +167,6 @@ export class FIFOBlockStateCache implements BlockStateCache {
167
167
  }
168
168
  }
169
169
 
170
- /**
171
- * No need for this implementation
172
- * This is only to conform to the old api
173
- */
174
- deleteAllBeforeEpoch(): void {}
175
-
176
170
  /**
177
171
  * ONLY FOR DEBUGGING PURPOSES. For lodestar debug API.
178
172
  */
@@ -414,11 +414,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
414
414
 
415
415
  /**
416
416
  * Prune all checkpoint states before the provided finalized epoch.
417
+ * Driven sequentially from processState() so it never interleaves with persist.
417
418
  */
418
- pruneFinalized(finalizedEpoch: Epoch): void {
419
+ private async pruneFinalized(finalizedEpoch: Epoch): Promise<void> {
419
420
  for (const epoch of this.epochIndex.keys()) {
420
421
  if (epoch < finalizedEpoch) {
421
- this.deleteAllEpochItems(epoch).catch((e) =>
422
+ await this.deleteAllEpochItems(epoch).catch((e) =>
422
423
  this.logger.debug("Error delete all epoch items", {epoch, finalizedEpoch}, e as Error)
423
424
  );
424
425
  }
@@ -476,6 +477,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
476
477
  * As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
477
478
  */
478
479
  async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
480
+ // prune finalized in the same flow so a finalized cp state is pruned, never persisted
481
+ await this.pruneFinalized(state.finalizedCheckpoint.epoch);
482
+
479
483
  let persistCount = 0;
480
484
  // it's important to sort the epochs in ascending order, in case of big reorg we always want to keep the most recent checkpoint states
481
485
  const sortedEpochs = Array.from(this.epochIndex.keys()).sort((a, b) => a - b);
@@ -30,7 +30,6 @@ export interface BlockStateCache {
30
30
  clear(): void;
31
31
  size: number;
32
32
  prune(headStateRootHex: RootHex): void;
33
- deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
34
33
  dumpSummary(): routes.lodestar.StateCacheItem[];
35
34
  /** Expose beacon states stored in cache. Use with caution */
36
35
  getStates(): IterableIterator<IBeaconStateView>;
@@ -67,7 +66,6 @@ export interface CheckpointStateCache {
67
66
  getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
68
67
  updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
69
68
  prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
70
- pruneFinalized(finalizedEpoch: Epoch): void;
71
69
  processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;
72
70
  clear(): void;
73
71
  dumpSummary(): routes.lodestar.StateCacheItem[];
@@ -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
  }
@@ -35,10 +35,6 @@ async function validateExecutionPayloadBid(
35
35
  const bid = signedExecutionPayloadBid.message;
36
36
  const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
37
37
  const parentBlockHashHex = toRootHex(bid.parentBlockHash);
38
- const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipExecutionPayloadBid);
39
- if (!isStatePostGloas(state)) {
40
- throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
41
- }
42
38
 
43
39
  // [IGNORE] `bid.slot` is the current slot or the next slot.
44
40
  const currentSlot = chain.clock.currentSlot;
@@ -111,9 +107,31 @@ async function validateExecutionPayloadBid(
111
107
  });
112
108
  }
113
109
 
110
+ // Use the bid's parent branch state for builder checks
111
+ const state = await chain.regen
112
+ .getBlockSlotState(parentBlock, bid.slot, {dontTransferCache: true}, RegenCaller.validateGossipExecutionPayloadBid)
113
+ .catch(() => {
114
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
115
+ code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
116
+ parentBlockRoot: parentBlockRootHex,
117
+ });
118
+ });
119
+
120
+ if (!isStatePostGloas(state)) {
121
+ throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
122
+ }
123
+
114
124
  // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
115
125
  // `is_active_builder(state, bid.builder_index)` returns `True`.
116
- const builder = state.getBuilder(bid.builderIndex);
126
+ let builder: gloas.Builder;
127
+ try {
128
+ builder = state.getBuilder(bid.builderIndex);
129
+ } catch {
130
+ throw new ExecutionPayloadBidError(GossipAction.REJECT, {
131
+ code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
132
+ builderIndex: bid.builderIndex,
133
+ });
134
+ }
117
135
  if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
118
136
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
119
137
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
@@ -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, {
@@ -8,6 +8,7 @@ import {RootHex, gloas, ssz} from "@lodestar/types";
8
8
  import {toRootHex} from "@lodestar/utils";
9
9
  import {GossipAction, PayloadAttestationError, PayloadAttestationErrorCode} from "../errors/index.js";
10
10
  import {IBeaconChain} from "../index.js";
11
+ import {RegenCaller} from "../regen/index.js";
11
12
 
12
13
  export type PayloadAttestationValidationResult = {
13
14
  attDataRootHex: RootHex;
@@ -61,22 +62,43 @@ async function validatePayloadAttestationMessage(
61
62
  // [IGNORE] The message's block `data.beacon_block_root` has been seen (via
62
63
  // gossip or non-gossip sources) (a client MAY queue attestation for processing
63
64
  // once the block is retrieved. Note a client might want to request payload after).
64
- if (!chain.forkChoice.hasBlock(data.beaconBlockRoot)) {
65
+ const block = chain.forkChoice.getBlockDefaultStatus(data.beaconBlockRoot);
66
+ if (!block) {
65
67
  throw new PayloadAttestationError(GossipAction.IGNORE, {
66
68
  code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
67
69
  blockRoot: toRootHex(data.beaconBlockRoot),
68
70
  });
69
71
  }
70
72
 
71
- const state = chain.getHeadState();
72
- if (!isStatePostGloas(state)) {
73
- throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
73
+ // [IGNORE] The block referenced by `data.beacon_block_root` is at slot `data.slot`,
74
+ // i.e. the block has `block.slot == data.slot`.
75
+ if (block.slot !== data.slot) {
76
+ throw new PayloadAttestationError(GossipAction.IGNORE, {
77
+ code: PayloadAttestationErrorCode.INVALID_BLOCK_SLOT,
78
+ blockRoot: toRootHex(data.beaconBlockRoot),
79
+ blockSlot: block.slot,
80
+ slot: data.slot,
81
+ });
74
82
  }
75
83
 
76
84
  // [REJECT] The message's block `data.beacon_block_root` passes validation.
77
85
  // TODO GLOAS: implement this. Technically if we cannot get proto block from fork choice,
78
86
  // it is possible that the block didn't pass the validation
79
87
 
88
+ // Use the referenced block's branch state for the PTC committee check
89
+ const state = await chain.regen
90
+ .getBlockSlotState(block, data.slot, {dontTransferCache: true}, RegenCaller.validateGossipPayloadAttestationMessage)
91
+ .catch(() => {
92
+ throw new PayloadAttestationError(GossipAction.IGNORE, {
93
+ code: PayloadAttestationErrorCode.UNKNOWN_BLOCK_ROOT,
94
+ blockRoot: toRootHex(data.beaconBlockRoot),
95
+ });
96
+ });
97
+
98
+ if (!isStatePostGloas(state)) {
99
+ throw new Error(`Expected gloas+ state for payload attestation validation, got fork=${state.forkName}`);
100
+ }
101
+
80
102
  // [REJECT] The message's validator index is within the payload committee in
81
103
  // `get_ptc(state, data.slot)`. The `state` is the head state corresponding to
82
104
  // processing the block up to the current slot as determined by the fork choice.