@lodestar/beacon-node 1.35.0-dev.21ba2777a9 → 1.35.0-dev.248b252a94

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 (107) hide show
  1. package/lib/api/impl/beacon/blocks/index.js +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  3. package/lib/api/impl/lodestar/index.js +1 -1
  4. package/lib/api/impl/lodestar/index.js.map +1 -1
  5. package/lib/api/impl/validator/index.d.ts.map +1 -1
  6. package/lib/api/impl/validator/index.js +20 -25
  7. package/lib/api/impl/validator/index.js.map +1 -1
  8. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  9. package/lib/chain/blocks/blockInput/blockInput.js +1 -1
  10. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  11. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  12. package/lib/chain/blocks/importBlock.js +7 -6
  13. package/lib/chain/blocks/importBlock.js.map +1 -1
  14. package/lib/chain/blocks/verifyBlocksDataAvailability.d.ts.map +1 -1
  15. package/lib/chain/blocks/verifyBlocksDataAvailability.js +8 -1
  16. package/lib/chain/blocks/verifyBlocksDataAvailability.js.map +1 -1
  17. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  18. package/lib/chain/blocks/writeBlockInputToDb.js +1 -7
  19. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  20. package/lib/chain/chain.d.ts.map +1 -1
  21. package/lib/chain/chain.js +3 -4
  22. package/lib/chain/chain.js.map +1 -1
  23. package/lib/chain/opPools/attestationPool.d.ts +2 -3
  24. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  25. package/lib/chain/opPools/attestationPool.js +3 -5
  26. package/lib/chain/opPools/attestationPool.js.map +1 -1
  27. package/lib/chain/opPools/syncCommitteeMessagePool.d.ts +3 -2
  28. package/lib/chain/opPools/syncCommitteeMessagePool.d.ts.map +1 -1
  29. package/lib/chain/opPools/syncCommitteeMessagePool.js +6 -5
  30. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  31. package/lib/chain/opPools/types.d.ts +1 -1
  32. package/lib/chain/opPools/types.d.ts.map +1 -1
  33. package/lib/chain/opPools/types.js +1 -1
  34. package/lib/chain/opPools/types.js.map +1 -1
  35. package/lib/chain/prepareNextSlot.d.ts +3 -3
  36. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  37. package/lib/chain/prepareNextSlot.js +8 -8
  38. package/lib/chain/prepareNextSlot.js.map +1 -1
  39. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  40. package/lib/chain/stateCache/persistentCheckpointsCache.js +9 -8
  41. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  42. package/lib/chain/validation/lightClientFinalityUpdate.d.ts.map +1 -1
  43. package/lib/chain/validation/lightClientFinalityUpdate.js +4 -3
  44. package/lib/chain/validation/lightClientFinalityUpdate.js.map +1 -1
  45. package/lib/chain/validation/lightClientOptimisticUpdate.d.ts +6 -4
  46. package/lib/chain/validation/lightClientOptimisticUpdate.d.ts.map +1 -1
  47. package/lib/chain/validation/lightClientOptimisticUpdate.js +11 -11
  48. package/lib/chain/validation/lightClientOptimisticUpdate.js.map +1 -1
  49. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  50. package/lib/chain/validatorMonitor.js +21 -15
  51. package/lib/chain/validatorMonitor.js.map +1 -1
  52. package/lib/metrics/metrics/lodestar.d.ts +1 -1
  53. package/lib/metrics/metrics/lodestar.js +3 -3
  54. package/lib/metrics/metrics/lodestar.js.map +1 -1
  55. package/lib/network/gossip/gossipsub.js +1 -1
  56. package/lib/network/gossip/gossipsub.js.map +1 -1
  57. package/lib/network/gossip/scoringParameters.js +4 -4
  58. package/lib/network/gossip/scoringParameters.js.map +1 -1
  59. package/lib/network/network.d.ts +1 -1
  60. package/lib/network/network.d.ts.map +1 -1
  61. package/lib/network/network.js +9 -9
  62. package/lib/network/network.js.map +1 -1
  63. package/lib/network/peers/peerManager.d.ts.map +1 -1
  64. package/lib/network/peers/peerManager.js +2 -1
  65. package/lib/network/peers/peerManager.js.map +1 -1
  66. package/lib/network/processor/gossipHandlers.js +1 -1
  67. package/lib/network/processor/gossipHandlers.js.map +1 -1
  68. package/lib/network/subnets/attnetsService.d.ts.map +1 -1
  69. package/lib/network/subnets/attnetsService.js +1 -1
  70. package/lib/network/subnets/attnetsService.js.map +1 -1
  71. package/lib/node/notifier.js +1 -1
  72. package/lib/node/notifier.js.map +1 -1
  73. package/lib/sync/unknownBlock.d.ts +0 -1
  74. package/lib/sync/unknownBlock.d.ts.map +1 -1
  75. package/lib/sync/unknownBlock.js +7 -6
  76. package/lib/sync/unknownBlock.js.map +1 -1
  77. package/lib/util/clock.d.ts +8 -3
  78. package/lib/util/clock.d.ts.map +1 -1
  79. package/lib/util/clock.js +8 -5
  80. package/lib/util/clock.js.map +1 -1
  81. package/package.json +14 -14
  82. package/src/api/impl/beacon/blocks/index.ts +1 -1
  83. package/src/api/impl/lodestar/index.ts +1 -1
  84. package/src/api/impl/validator/index.ts +21 -26
  85. package/src/chain/blocks/blockInput/blockInput.ts +2 -1
  86. package/src/chain/blocks/importBlock.ts +8 -12
  87. package/src/chain/blocks/verifyBlocksDataAvailability.ts +10 -2
  88. package/src/chain/blocks/writeBlockInputToDb.ts +1 -9
  89. package/src/chain/chain.ts +3 -14
  90. package/src/chain/opPools/attestationPool.ts +2 -3
  91. package/src/chain/opPools/syncCommitteeMessagePool.ts +5 -3
  92. package/src/chain/opPools/types.ts +1 -1
  93. package/src/chain/prepareNextSlot.ts +8 -8
  94. package/src/chain/stateCache/persistentCheckpointsCache.ts +10 -8
  95. package/src/chain/validation/lightClientFinalityUpdate.ts +4 -3
  96. package/src/chain/validation/lightClientOptimisticUpdate.ts +12 -11
  97. package/src/chain/validatorMonitor.ts +28 -17
  98. package/src/metrics/metrics/lodestar.ts +3 -3
  99. package/src/network/gossip/gossipsub.ts +1 -1
  100. package/src/network/gossip/scoringParameters.ts +4 -4
  101. package/src/network/network.ts +9 -9
  102. package/src/network/peers/peerManager.ts +2 -1
  103. package/src/network/processor/gossipHandlers.ts +1 -1
  104. package/src/network/subnets/attnetsService.ts +3 -6
  105. package/src/node/notifier.ts +1 -1
  106. package/src/sync/unknownBlock.ts +7 -6
  107. package/src/util/clock.ts +14 -6
@@ -22,6 +22,7 @@ import {
22
22
  calculateCommitteeAssignments,
23
23
  computeEpochAtSlot,
24
24
  computeStartSlotAtEpoch,
25
+ computeTimeAtSlot,
25
26
  createCachedBeaconState,
26
27
  getBlockRootAtSlot,
27
28
  getCurrentSlot,
@@ -66,7 +67,7 @@ import {
66
67
  SyncCommitteeErrorCode,
67
68
  } from "../../../chain/errors/index.js";
68
69
  import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js";
69
- import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js";
70
+ import {PREPARE_NEXT_SLOT_BPS} from "../../../chain/prepareNextSlot.js";
70
71
  import {BlockType, ProduceFullDeneb} from "../../../chain/produceBlock/index.js";
71
72
  import {RegenCaller} from "../../../chain/regen/index.js";
72
73
  import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
@@ -82,7 +83,7 @@ import {getDefaultGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
82
83
  import {getLodestarClientVersion} from "../../../util/metadata.js";
83
84
  import {ApiOptions} from "../../options.js";
84
85
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
85
- import {ApiError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
86
+ import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
86
87
  import {ApiModules} from "../types.js";
87
88
  import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
88
89
 
@@ -108,6 +109,8 @@ export const SYNC_TOLERANCE_EPOCHS = 1;
108
109
  * Empirically the builder block resolves in ~1 second, and execution block resolves in <500 ms.
109
110
  * A cutoff of 2 seconds gives enough time and if there are unexpected delays it ensures we publish
110
111
  * in time as proposals post 4 seconds into the slot will likely be orphaned due to proposer boost reorg.
112
+ *
113
+ * TODO GLOAS: re-evaluate cutoff timing
111
114
  */
112
115
  const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
113
116
  /** Overall timeout for execution and block production apis */
@@ -178,7 +181,7 @@ export function getValidatorApi(
178
181
  * This value is the same to MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC.
179
182
  * For very fast networks, reduce clock disparity to half a slot.
180
183
  */
181
- const MAX_API_CLOCK_DISPARITY_SEC = Math.min(0.5, config.SECONDS_PER_SLOT / 2);
184
+ const MAX_API_CLOCK_DISPARITY_SEC = Math.min(0.5, config.SLOT_DURATION_MS / 2000);
182
185
  const MAX_API_CLOCK_DISPARITY_MS = MAX_API_CLOCK_DISPARITY_SEC * 1000;
183
186
 
184
187
  /** Compute and cache the genesis block root */
@@ -211,7 +214,7 @@ export function getValidatorApi(
211
214
  return;
212
215
  }
213
216
 
214
- const slotStartSec = chain.genesisTime + slot * config.SECONDS_PER_SLOT;
217
+ const slotStartSec = computeTimeAtSlot(config, slot, chain.genesisTime);
215
218
  const msToSlot = slotStartSec * 1000 - Date.now();
216
219
 
217
220
  if (msToSlot > MAX_API_CLOCK_DISPARITY_MS) {
@@ -242,7 +245,7 @@ export function getValidatorApi(
242
245
  */
243
246
  function msToNextEpoch(): number {
244
247
  const nextEpoch = chain.clock.currentEpoch + 1;
245
- const secPerEpoch = SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT;
248
+ const secPerEpoch = (SLOTS_PER_EPOCH * config.SLOT_DURATION_MS) / 1000;
246
249
  const nextEpochStartSec = chain.genesisTime + nextEpoch * secPerEpoch;
247
250
  return nextEpochStartSec * 1000 - Date.now();
248
251
  }
@@ -666,7 +669,7 @@ export function getValidatorApi(
666
669
  : Promise.reject(new Error("Engine disabled"));
667
670
 
668
671
  // Calculate cutoff time based on start of the slot
669
- const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - Math.round(chain.clock.secFromSlot(slot) * 1000));
672
+ const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
670
673
 
671
674
  logger.verbose("Block production race (builder vs execution) starting", {
672
675
  ...loggerContext,
@@ -1016,8 +1019,8 @@ export function getValidatorApi(
1016
1019
  const head = chain.forkChoice.getHead();
1017
1020
  let state: CachedBeaconStateAllForks | undefined = undefined;
1018
1021
  const startSlot = computeStartSlotAtEpoch(epoch);
1019
- const slotMs = config.SECONDS_PER_SLOT * 1000;
1020
- const prepareNextSlotLookAheadMs = slotMs / SCHEDULER_LOOKAHEAD_FACTOR;
1022
+ const prepareNextSlotLookAheadMs =
1023
+ config.SLOT_DURATION_MS - config.getSlotComponentDurationMs(PREPARE_NEXT_SLOT_BPS);
1021
1024
  const toNextEpochMs = msToNextEpoch();
1022
1025
  // validators may request next epoch's duties when it's close to next epoch
1023
1026
  // this is to avoid missed block proposal due to 0 epoch look ahead
@@ -1290,7 +1293,7 @@ export function getValidatorApi(
1290
1293
  notWhileSyncing();
1291
1294
 
1292
1295
  const seenTimestampSec = Date.now() / 1000;
1293
- const errors: Error[] = [];
1296
+ const failures: FailureList = [];
1294
1297
  const fork = chain.config.getForkName(chain.clock.currentSlot);
1295
1298
 
1296
1299
  await Promise.all(
@@ -1326,8 +1329,8 @@ export function getValidatorApi(
1326
1329
  return; // Ok to submit the same aggregate twice
1327
1330
  }
1328
1331
 
1329
- errors.push(e as Error);
1330
- logger.error(`Error on publishAggregateAndProofs [${i}]`, logCtx, e as Error);
1332
+ failures.push({index: i, message: (e as Error).message});
1333
+ logger.verbose(`Error on publishAggregateAndProofs [${i}]`, logCtx, e as Error);
1331
1334
  if (e instanceof AttestationError && e.action === GossipAction.REJECT) {
1332
1335
  chain.persistInvalidSszValue(ssz.phase0.SignedAggregateAndProof, signedAggregateAndProof, "api_reject");
1333
1336
  }
@@ -1335,12 +1338,8 @@ export function getValidatorApi(
1335
1338
  })
1336
1339
  );
1337
1340
 
1338
- if (errors.length > 1) {
1339
- throw Error("Multiple errors on publishAggregateAndProofs\n" + errors.map((e) => e.message).join("\n"));
1340
- }
1341
-
1342
- if (errors.length === 1) {
1343
- throw errors[0];
1341
+ if (failures.length > 0) {
1342
+ throw new IndexedError("Error processing aggregate and proofs", failures);
1344
1343
  }
1345
1344
  },
1346
1345
 
@@ -1354,7 +1353,7 @@ export function getValidatorApi(
1354
1353
  async publishContributionAndProofs({contributionAndProofs}) {
1355
1354
  notWhileSyncing();
1356
1355
 
1357
- const errors: Error[] = [];
1356
+ const failures: FailureList = [];
1358
1357
 
1359
1358
  await Promise.all(
1360
1359
  contributionAndProofs.map(async (contributionAndProof, i) => {
@@ -1386,8 +1385,8 @@ export function getValidatorApi(
1386
1385
  return; // Ok to submit the same aggregate twice
1387
1386
  }
1388
1387
 
1389
- errors.push(e as Error);
1390
- logger.error(`Error on publishContributionAndProofs [${i}]`, logCtx, e as Error);
1388
+ failures.push({index: i, message: (e as Error).message});
1389
+ logger.verbose(`Error on publishContributionAndProofs [${i}]`, logCtx, e as Error);
1391
1390
  if (e instanceof SyncCommitteeError && e.action === GossipAction.REJECT) {
1392
1391
  chain.persistInvalidSszValue(ssz.altair.SignedContributionAndProof, contributionAndProof, "api_reject");
1393
1392
  }
@@ -1395,12 +1394,8 @@ export function getValidatorApi(
1395
1394
  })
1396
1395
  );
1397
1396
 
1398
- if (errors.length > 1) {
1399
- throw Error("Multiple errors on publishContributionAndProofs\n" + errors.map((e) => e.message).join("\n"));
1400
- }
1401
-
1402
- if (errors.length === 1) {
1403
- throw errors[0];
1397
+ if (failures.length > 0) {
1398
+ throw new IndexedError("Error processing contribution and proofs", failures);
1404
1399
  }
1405
1400
  },
1406
1401
 
@@ -652,7 +652,8 @@ export class BlockInputColumns extends AbstractBlockInput<ForkColumnsDA, fulu.Da
652
652
  static createFromColumn(
653
653
  props: AddColumn & CreateBlockInputMeta & {sampledColumns: ColumnIndex[]; custodyColumns: ColumnIndex[]}
654
654
  ): BlockInputColumns {
655
- const hasAllData = props.sampledColumns.length === 0;
655
+ const hasAllData =
656
+ props.daOutOfRange || props.columnSidecar.kzgCommitments.length === 0 || props.sampledColumns.length === 0;
656
657
  const state: BlockInputColumnsState = {
657
658
  hasBlock: false,
658
659
  hasAllData,
@@ -7,20 +7,14 @@ import {
7
7
  ForkChoiceErrorCode,
8
8
  NotReorgedReason,
9
9
  } from "@lodestar/fork-choice";
10
- import {
11
- ForkPostAltair,
12
- ForkPostElectra,
13
- ForkSeq,
14
- INTERVALS_PER_SLOT,
15
- MAX_SEED_LOOKAHEAD,
16
- SLOTS_PER_EPOCH,
17
- } from "@lodestar/params";
10
+ import {ForkPostAltair, ForkPostElectra, ForkSeq, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
18
11
  import {
19
12
  CachedBeaconStateAltair,
20
13
  EpochCache,
21
14
  RootCache,
22
15
  computeEpochAtSlot,
23
16
  computeStartSlotAtEpoch,
17
+ computeTimeAtSlot,
24
18
  isExecutionStateType,
25
19
  isStartSlotOfEpoch,
26
20
  isStateValidatorsNodesPopulated,
@@ -85,7 +79,8 @@ export async function importBlock(
85
79
  const currentEpoch = computeEpochAtSlot(currentSlot);
86
80
  const blockEpoch = computeEpochAtSlot(blockSlot);
87
81
  const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch;
88
- const blockDelaySec = (fullyVerifiedBlock.seenTimestampSec - postState.genesisTime) % this.config.SECONDS_PER_SLOT;
82
+ const blockDelaySec =
83
+ fullyVerifiedBlock.seenTimestampSec - computeTimeAtSlot(this.config, blockSlot, postState.genesisTime);
89
84
  const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000);
90
85
  const fork = this.config.getForkSeq(blockSlot);
91
86
 
@@ -265,10 +260,11 @@ export async function importBlock(
265
260
  this.metrics.headSlot.set(newHead.slot);
266
261
  // Only track "recent" blocks. Otherwise sync can distort this metrics heavily.
267
262
  // We want to track recent blocks coming from gossip, unknown block sync, and API.
268
- if (delaySec < SLOTS_PER_EPOCH * this.config.SECONDS_PER_SLOT) {
263
+ if (delaySec < (SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000) {
269
264
  this.metrics.importBlock.elapsedTimeTillBecomeHead.observe(delaySec);
270
- if (delaySec > this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT) {
271
- this.metrics.importBlock.setHeadAfterFirstInterval.inc();
265
+ const cutOffSec = this.config.getAttestationDueMs(this.config.getForkName(blockSlot)) / 1000;
266
+ if (delaySec > cutOffSec) {
267
+ this.metrics.importBlock.setHeadAfterCutoff.inc();
272
268
  }
273
269
  }
274
270
  }
@@ -1,5 +1,5 @@
1
1
  import {DataAvailabilityStatus} from "@lodestar/state-transition";
2
- import {DAType, IBlockInput} from "./blockInput/index.js";
2
+ import {DAData, DAType, IBlockInput} from "./blockInput/index.js";
3
3
 
4
4
  // we can now wait for full 12 seconds because unavailable block sync will try pulling
5
5
  // the blobs from the network anyway after 500ms of seeing the block
@@ -18,7 +18,15 @@ export async function verifyBlocksDataAvailability(
18
18
  dataAvailabilityStatuses: DataAvailabilityStatus[];
19
19
  availableTime: number;
20
20
  }> {
21
- await Promise.all(blocks.map((blockInput) => blockInput.waitForAllData(BLOB_AVAILABILITY_TIMEOUT, signal)));
21
+ const promises: Promise<DAData>[] = [];
22
+ for (const blockInput of blocks) {
23
+ // block verification is triggered on a verified gossip block so we only need to wait for all data
24
+ if (!blockInput.hasAllData()) {
25
+ promises.push(blockInput.waitForAllData(BLOB_AVAILABILITY_TIMEOUT, signal));
26
+ }
27
+ }
28
+ await Promise.all(promises);
29
+
22
30
  const availableTime = Math.max(0, Math.max(...blocks.map((blockInput) => blockInput.getTimeComplete())));
23
31
  const dataAvailabilityStatuses: DataAvailabilityStatus[] = blocks.map((blockInput) => {
24
32
  if (blockInput.type === DAType.PreData) {
@@ -102,15 +102,7 @@ export async function removeEagerlyPersistedBlockInputs(this: BeaconChain, block
102
102
  if (!this.forkChoice.hasBlockHex(blockRootHex)) {
103
103
  blockToRemove.push(block);
104
104
 
105
- if (isBlockInputColumns(blockInput)) {
106
- const {custodyColumns} = this.custodyConfig;
107
- const dataColumnsLen = custodyColumns.length;
108
- const dataColumnSidecars = blockInput.getCustodyColumns();
109
- if (dataColumnSidecars.length !== dataColumnsLen) {
110
- throw Error(
111
- `Invalid dataColumnSidecars=${dataColumnSidecars.length} for custody expected custodyColumnsLen=${dataColumnsLen}`
112
- );
113
- }
105
+ if (isBlockInputColumns(blockInput) && blockInput.getCustodyColumns().length > 0) {
114
106
  dataColumnsToRemove.push(blockRoot);
115
107
  } else if (isBlockInputBlobs(blockInput)) {
116
108
  const blobSidecars = blockInput.getBlobs();
@@ -254,20 +254,9 @@ export class BeaconChain implements IBeaconChain {
254
254
  if (!clock) clock = new Clock({config, genesisTime: this.genesisTime, signal});
255
255
 
256
256
  this.blacklistedBlocks = new Map((opts.blacklistedBlocks ?? []).map((hex) => [hex, null]));
257
- const preAggregateCutOffTime = (2 / 3) * this.config.SECONDS_PER_SLOT;
258
- this.attestationPool = new AttestationPool(
259
- config,
260
- clock,
261
- preAggregateCutOffTime,
262
- this.opts?.preaggregateSlotDistance,
263
- metrics
264
- );
257
+ this.attestationPool = new AttestationPool(config, clock, this.opts?.preaggregateSlotDistance, metrics);
265
258
  this.aggregatedAttestationPool = new AggregatedAttestationPool(this.config, metrics);
266
- this.syncCommitteeMessagePool = new SyncCommitteeMessagePool(
267
- clock,
268
- preAggregateCutOffTime,
269
- this.opts?.preaggregateSlotDistance
270
- );
259
+ this.syncCommitteeMessagePool = new SyncCommitteeMessagePool(config, clock, this.opts?.preaggregateSlotDistance);
271
260
  this.syncContributionAndProofPool = new SyncContributionAndProofPool(clock, metrics, logger);
272
261
 
273
262
  this.seenAggregatedAttestations = new SeenAggregatedAttestations(metrics);
@@ -1150,7 +1139,7 @@ export class BeaconChain implements IBeaconChain {
1150
1139
  const metrics = this.metrics;
1151
1140
  if (metrics && (slot + 1) % SLOTS_PER_EPOCH === 0) {
1152
1141
  // On the last slot of the epoch
1153
- sleep((1000 * this.config.SECONDS_PER_SLOT) / 2)
1142
+ sleep(this.config.SLOT_DURATION_MS / 2)
1154
1143
  .then(() => this.validatorMonitor?.onceEveryEndOfEpoch(this.getHeadState()))
1155
1144
  .catch((e) => {
1156
1145
  if (!isErrorAborted(e)) this.logger.error("Error on validator monitor onceEveryEndOfEpoch", {slot}, e);
@@ -74,7 +74,6 @@ export class AttestationPool {
74
74
  constructor(
75
75
  private readonly config: ChainForkConfig,
76
76
  private readonly clock: IClock,
77
- private readonly cutOffSecFromSlot: number,
78
77
  private readonly preaggregateSlotDistance = 0,
79
78
  private readonly metrics: Metrics | null = null
80
79
  ) {}
@@ -98,7 +97,7 @@ export class AttestationPool {
98
97
  * `SignedAggregateAndProof`.
99
98
  *
100
99
  * If the attestation is too old (low slot) to be included in the pool it is simply dropped
101
- * and no error is returned. Also if it's at clock slot but come to the pool later than 2/3
100
+ * and no error is returned. Also if it's at clock slot but come to the pool later than AGGREGATE_DUE_BPS
102
101
  * of slot time, it's dropped too since it's not helpful for the validator anymore
103
102
  *
104
103
  * Expects the attestation to be fully validated:
@@ -126,7 +125,7 @@ export class AttestationPool {
126
125
 
127
126
  // Reject gossip attestations in the current slot but come to this pool very late
128
127
  // for api attestations, we allow them to be added to the pool
129
- if (!priority && this.clock.secFromSlot(slot) > this.cutOffSecFromSlot) {
128
+ if (!priority && this.clock.msFromSlot(slot) > this.config.getAggregateDueMs(fork)) {
130
129
  return InsertOutcome.Late;
131
130
  }
132
131
 
@@ -1,5 +1,6 @@
1
1
  import {Signature, aggregateSignatures} from "@chainsafe/blst";
2
2
  import {BitArray} from "@chainsafe/ssz";
3
+ import {ChainForkConfig} from "@lodestar/config";
3
4
  import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
4
5
  import {Root, Slot, SubcommitteeIndex, SubnetID, altair} from "@lodestar/types";
5
6
  import {MapDef, toRootHex} from "@lodestar/utils";
@@ -44,8 +45,8 @@ export class SyncCommitteeMessagePool {
44
45
  private lowestPermissibleSlot = 0;
45
46
 
46
47
  constructor(
48
+ private readonly config: ChainForkConfig,
47
49
  private readonly clock: IClock,
48
- private readonly cutOffSecFromSlot: number,
49
50
  private readonly preaggregateSlotDistance = 0
50
51
  ) {}
51
52
 
@@ -68,6 +69,7 @@ export class SyncCommitteeMessagePool {
68
69
  priority?: boolean
69
70
  ): InsertOutcome {
70
71
  const {slot, beaconBlockRoot} = signature;
72
+ const fork = this.config.getForkName(slot);
71
73
  const rootHex = toRootHex(beaconBlockRoot);
72
74
  const lowestPermissibleSlot = this.lowestPermissibleSlot;
73
75
 
@@ -76,8 +78,8 @@ export class SyncCommitteeMessagePool {
76
78
  return InsertOutcome.Old;
77
79
  }
78
80
 
79
- // validator gets SyncCommitteeContribution at 2/3 of slot, it's no use to preaggregate later than that time
80
- if (!priority && this.clock.secFromSlot(slot) > this.cutOffSecFromSlot) {
81
+ // validator gets SyncCommitteeContribution at CONTRIBUTION_DUE_BPS of slot, it's no use to preaggregate later than that time
82
+ if (!priority && this.clock.msFromSlot(slot) > this.config.getSyncContributionDueMs(fork)) {
81
83
  return InsertOutcome.Late;
82
84
  }
83
85
 
@@ -13,7 +13,7 @@ export enum InsertOutcome {
13
13
  Old = "Old",
14
14
  /** The pool has reached its limit. No changes were made. */
15
15
  ReachLimit = "ReachLimit",
16
- /** Messages don't bring any value, for example attestations come to the pool at > 2/3 of slot. No changes were made */
16
+ /** Messages don't bring any value, for example attestations come to the pool at > AGGREGATE_DUE_BPS of slot. No changes were made */
17
17
  Late = "Late",
18
18
  /** The data is know, and the new participants have been added to the aggregated signature */
19
19
  Aggregated = "Aggregated",
@@ -22,17 +22,18 @@ import {IBeaconChain} from "./interface.js";
22
22
  import {getPayloadAttributesForSSE, prepareExecutionPayload} from "./produceBlock/produceBlockBody.js";
23
23
  import {RegenCaller} from "./regen/index.js";
24
24
 
25
- /* With 12s slot times, this scheduler will run 4s before the start of each slot (`12 / 3 = 4`). */
26
- export const SCHEDULER_LOOKAHEAD_FACTOR = 3;
25
+ // TODO GLOAS: re-evaluate this timing
26
+ /* With 12s slot times, this scheduler will run 4s before the start of each slot (`12 - 0.6667 * 12 = 4`). */
27
+ export const PREPARE_NEXT_SLOT_BPS = 6667;
27
28
 
28
29
  /* We don't want to do more epoch transition than this */
29
30
  const PREPARE_EPOCH_LIMIT = 1;
30
31
 
31
32
  /**
32
33
  * At Bellatrix, if we are responsible for proposing in next slot, we want to prepare payload
33
- * 4s (1/3 slot) before the start of next slot
34
+ * 4s before the start of next slot at PREPARE_NEXT_SLOT_BPS of the current slot.
34
35
  *
35
- * For all forks, when clock is 1/3 slot before an epoch, we want to prepare for the next epoch
36
+ * For all forks, when clock reaches PREPARE_NEXT_SLOT_BPS of slot before an epoch, we want to prepare for the next epoch
36
37
  * transition from our head so that:
37
38
  * + validators vote for block head on time through attestation
38
39
  * + validators propose blocks on time
@@ -74,10 +75,9 @@ export class PrepareNextSlotScheduler {
74
75
  }
75
76
 
76
77
  try {
77
- // At 1/3 slot time before the next slot, we either prepare payload or precompute
78
- // epoch transition
79
- const slotMs = this.config.SECONDS_PER_SLOT * 1000;
80
- await sleep(slotMs - slotMs / SCHEDULER_LOOKAHEAD_FACTOR, this.signal);
78
+ // At PREPARE_NEXT_SLOT_BPS (~67%) of the current slot we prepare payload for the next slot
79
+ // or precompute epoch transition
80
+ await sleep(this.config.getSlotComponentDurationMs(PREPARE_NEXT_SLOT_BPS), this.signal);
81
81
 
82
82
  // calling updateHead() here before we produce a block to reduce reorg possibility
83
83
  const {slot: headSlot, blockRoot: headRoot} = this.chain.recomputeForkChoiceHead(
@@ -1,5 +1,4 @@
1
1
  import {routes} from "@lodestar/api";
2
- import {INTERVALS_PER_SLOT} from "@lodestar/params";
3
2
  import {
4
3
  CachedBeaconStateAllForks,
5
4
  computeStartSlotAtEpoch,
@@ -59,6 +58,9 @@ type LoadedStateBytesData = {persistedKey: DatastoreKey; stateBytes: Uint8Array}
59
58
  */
60
59
  export const DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY = 3;
61
60
 
61
+ // TODO GLOAS: re-evaluate this timing
62
+ const PROCESS_CHECKPOINT_STATES_BPS = 6667;
63
+
62
64
  /**
63
65
  * An implementation of CheckpointStateCache that keep up to n epoch checkpoint states in memory and persist the rest to disk
64
66
  * - If it's more than `maxEpochsInMemory` epochs old, it will persist n last epochs to disk based on the view of the block
@@ -464,14 +466,14 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
464
466
  }
465
467
 
466
468
  const blockSlot = state.slot;
467
- const twoThirdsSlot = (2 * state.config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT;
469
+ const processCPStatesTimeMs = state.config.getSlotComponentDurationMs(PROCESS_CHECKPOINT_STATES_BPS);
468
470
  // we always have clock in production, fallback value is only for test
469
- const secFromSlot = this.clock?.secFromSlot(blockSlot) ?? twoThirdsSlot;
470
- const secToTwoThirdsSlot = twoThirdsSlot - secFromSlot;
471
- if (secToTwoThirdsSlot > 0) {
472
- // 2/3 of slot is the most free time of every slot, take that chance to persist checkpoint states
473
- // normally it should only persist checkpoint states at 2/3 of slot 0 of epoch
474
- await sleep(secToTwoThirdsSlot * 1000, this.signal);
471
+ const msFromSlot = this.clock?.msFromSlot(blockSlot) ?? processCPStatesTimeMs;
472
+ const msToProcessCPStates = processCPStatesTimeMs - msFromSlot;
473
+ if (msToProcessCPStates > 0) {
474
+ // At ~67% of slot is the most free time of every slot, take that chance to persist checkpoint states
475
+ // normally it should only persist checkpoint states at ~67% of slot 0 of epoch
476
+ await sleep(msToProcessCPStates, this.signal);
475
477
  }
476
478
  // at syncing time, it's critical to persist checkpoint states as soon as possible to avoid OOM during unfinality time
477
479
  // if node is synced this is not a hot time because block comes late, we'll likely miss attestation already, or the block is orphaned
@@ -25,9 +25,10 @@ export function validateLightClientFinalityUpdate(
25
25
  }
26
26
 
27
27
  // [IGNORE] The finality_update is received after the block at signature_slot was given enough time to propagate
28
- // through the network -- i.e. validate that one-third of finality_update.signature_slot has transpired
29
- // (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
30
- if (updateReceivedTooEarly(config, chain.genesisTime, gossipedFinalityUpdate)) {
28
+ // through the network -- i.e. validate that `get_sync_message_due_ms(epoch)`
29
+ // milliseconds (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) has
30
+ // transpired since the start of `signature_slot`.
31
+ if (updateReceivedTooEarly(config, chain.clock, gossipedFinalityUpdate)) {
31
32
  throw new LightClientError(GossipAction.IGNORE, {
32
33
  code: LightClientErrorCode.FINALITY_UPDATE_RECEIVED_TOO_EARLY,
33
34
  });
@@ -1,8 +1,8 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {computeTimeAtSlot} from "@lodestar/state-transition";
3
2
  import {LightClientOptimisticUpdate} from "@lodestar/types";
4
3
  import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js";
5
4
  import {assertLightClientServer} from "../../node/utils/lightclient.js";
5
+ import {IClock} from "../../util/clock.js";
6
6
  import {GossipAction} from "../errors/index.js";
7
7
  import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js";
8
8
  import {IBeaconChain} from "../interface.js";
@@ -26,9 +26,10 @@ export function validateLightClientOptimisticUpdate(
26
26
  }
27
27
 
28
28
  // [IGNORE] The optimistic_update is received after the block at signature_slot was given enough time to propagate
29
- // through the network -- i.e. validate that one-third of optimistic_update.signature_slot has transpired
30
- // (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
31
- if (updateReceivedTooEarly(config, chain.genesisTime, gossipedOptimisticUpdate)) {
29
+ // through the network -- i.e. validate that `get_sync_message_due_ms(epoch)`
30
+ // milliseconds (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) has
31
+ // transpired since the start of `signature_slot`.
32
+ if (updateReceivedTooEarly(config, chain.clock, gossipedOptimisticUpdate)) {
32
33
  throw new LightClientError(GossipAction.IGNORE, {
33
34
  code: LightClientErrorCode.OPTIMISTIC_UPDATE_RECEIVED_TOO_EARLY,
34
35
  });
@@ -48,20 +49,20 @@ export function validateLightClientOptimisticUpdate(
48
49
  /**
49
50
  * Returns true, if the spec condition below triggers an IGNORE.
50
51
  *
51
- * Sig +1/3 time
52
+ * Sig + SYNC_MESSAGE_DUE_BPS time
52
53
  * -----|-----
53
54
  * xxx|------- (x is not okay)
54
55
  *
55
56
  * [IGNORE] The *update is received after the block at signature_slot was given enough time to propagate
56
- * through the network -- i.e. validate that one-third of *update.signature_slot has transpired
57
- * (SECONDS_PER_SLOT / INTERVALS_PER_SLOT seconds after the start of the slot, with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
57
+ * through the network -- i.e. validate that `get_sync_message_due_ms(epoch)`
58
+ * milliseconds (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) has
59
+ * transpired since the start of `signature_slot`.
58
60
  */
59
61
  export function updateReceivedTooEarly(
60
62
  config: ChainForkConfig,
61
- genesisTime: number,
63
+ clock: IClock,
62
64
  update: Pick<LightClientOptimisticUpdate, "signatureSlot">
63
65
  ): boolean {
64
- const signatureSlot13TimestampMs = computeTimeAtSlot(config, update.signatureSlot + 1 / 3, genesisTime) * 1000;
65
- const earliestAllowedTimestampMs = signatureSlot13TimestampMs - MAXIMUM_GOSSIP_CLOCK_DISPARITY;
66
- return Date.now() < earliestAllowedTimestampMs;
66
+ const fork = config.getForkName(update.signatureSlot);
67
+ return clock.msFromSlot(update.signatureSlot) < config.getSyncMessageDueMs(fork) - MAXIMUM_GOSSIP_CLOCK_DISPARITY;
67
68
  }
@@ -1,11 +1,12 @@
1
- import {ChainConfig, ChainForkConfig} from "@lodestar/config";
2
- import {ForkSeq, INTERVALS_PER_SLOT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
1
+ import {ChainForkConfig} from "@lodestar/config";
2
+ import {ForkSeq, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
3
3
  import {
4
4
  CachedBeaconStateAllForks,
5
5
  CachedBeaconStateAltair,
6
6
  ParticipationFlags,
7
7
  computeEpochAtSlot,
8
8
  computeStartSlotAtEpoch,
9
+ computeTimeAtSlot,
9
10
  getBlockRootAtSlot,
10
11
  getCurrentSlot,
11
12
  parseAttesterFlags,
@@ -34,8 +35,9 @@ const MAX_CACHED_EPOCHS = 4;
34
35
 
35
36
  const MAX_CACHED_DISTINCT_TARGETS = 4;
36
37
 
37
- const INTERVALS_LATE_ATTESTATION_SUBMISSION = 1.5;
38
- const INTERVALS_LATE_BLOCK_SUBMISSION = 0.75;
38
+ // TODO GLOAS: re-evaluate these timings
39
+ const LATE_ATTESTATION_SUBMISSION_BPS = 5000;
40
+ const LATE_BLOCK_SUBMISSION_BPS = 2500;
39
41
 
40
42
  const RETAIN_REGISTERED_VALIDATORS_MS = 1 * 3600 * 1000; // 1 hour
41
43
 
@@ -450,8 +452,11 @@ export function createValidatorMonitor(
450
452
 
451
453
  onPoolSubmitUnaggregatedAttestation(seenTimestampSec, indexedAttestation, subnet, sentPeers) {
452
454
  const data = indexedAttestation.data;
453
- // Returns the duration between when the attestation `data` could be produced (1/3rd through the slot) and `seenTimestamp`.
454
- const delaySec = seenTimestampSec - (genesisTime + (data.slot + 1 / 3) * config.SECONDS_PER_SLOT);
455
+ const fork = config.getForkName(data.slot);
456
+ // Returns the duration between when the attestation `data` could be produced (ATTESTATION_DUE_BPS through the slot) and `seenTimestamp`.
457
+ const delaySec =
458
+ seenTimestampSec -
459
+ (computeTimeAtSlot(config, data.slot, genesisTime) + config.getAttestationDueMs(fork) / 1000);
455
460
  for (const index of indexedAttestation.attestingIndices) {
456
461
  const validator = validators.get(index);
457
462
  if (validator) {
@@ -483,8 +488,11 @@ export function createValidatorMonitor(
483
488
  const src = OpSource.gossip;
484
489
  const data = indexedAttestation.data;
485
490
  const epoch = computeEpochAtSlot(data.slot);
486
- // Returns the duration between when the attestation `data` could be produced (1/3rd through the slot) and `seenTimestamp`.
487
- const delaySec = seenTimestampSec - (genesisTime + (data.slot + 1 / 3) * config.SECONDS_PER_SLOT);
491
+ const fork = config.getForkName(data.slot);
492
+ // Returns the duration between when the attestation `data` could be produced (ATTESTATION_DUE_BPS through the slot) and `seenTimestamp`.
493
+ const delaySec =
494
+ seenTimestampSec -
495
+ (computeTimeAtSlot(config, data.slot, genesisTime) + config.getAttestationDueMs(fork) / 1000);
488
496
 
489
497
  for (const index of indexedAttestation.attestingIndices) {
490
498
  const validator = validators.get(index);
@@ -500,8 +508,10 @@ export function createValidatorMonitor(
500
508
 
501
509
  onPoolSubmitAggregatedAttestation(seenTimestampSec, indexedAttestation, sentPeers) {
502
510
  const data = indexedAttestation.data;
503
- // Returns the duration between when a `AggregateAndproof` with `data` could be produced (2/3rd through the slot) and `seenTimestamp`.
504
- const delaySec = seenTimestampSec - (genesisTime + (data.slot + 2 / 3) * config.SECONDS_PER_SLOT);
511
+ const fork = config.getForkName(data.slot);
512
+ // Returns the duration between when a `AggregateAndproof` with `data` could be produced (AGGREGATE_DUE_BPS through the slot) and `seenTimestamp`.
513
+ const delaySec =
514
+ seenTimestampSec - (computeTimeAtSlot(config, data.slot, genesisTime) + config.getAggregateDueMs(fork) / 1000);
505
515
 
506
516
  for (const index of indexedAttestation.attestingIndices) {
507
517
  const validator = validators.get(index);
@@ -527,8 +537,10 @@ export function createValidatorMonitor(
527
537
  const src = OpSource.gossip;
528
538
  const data = indexedAttestation.data;
529
539
  const epoch = computeEpochAtSlot(data.slot);
530
- // Returns the duration between when a `AggregateAndProof` with `data` could be produced (2/3rd through the slot) and `seenTimestamp`.
531
- const delaySec = seenTimestampSec - (genesisTime + (data.slot + 2 / 3) * config.SECONDS_PER_SLOT);
540
+ const fork = config.getForkName(data.slot);
541
+ // Returns the duration between when a `AggregateAndproof` with `data` could be produced (AGGREGATE_DUE_BPS through the slot) and `seenTimestamp`.
542
+ const delaySec =
543
+ seenTimestampSec - (computeTimeAtSlot(config, data.slot, genesisTime) + config.getAggregateDueMs(fork) / 1000);
532
544
 
533
545
  const aggregatorIndex = signedAggregateAndProof.message.aggregatorIndex;
534
546
  const validatorAggregator = validators.get(aggregatorIndex);
@@ -822,7 +834,7 @@ export function createValidatorMonitor(
822
834
  * - Was the attestation seen in a block?
823
835
  */
824
836
  function renderAttestationSummary(
825
- config: ChainConfig,
837
+ config: ChainForkConfig,
826
838
  rootCache: RootHexCache,
827
839
  summary: AttestationSummary | undefined,
828
840
  flags: ParticipationFlags
@@ -925,8 +937,7 @@ function renderAttestationSummary(
925
937
  }
926
938
 
927
939
  const submittedLate =
928
- summary.poolSubmitDelayMinSec >
929
- (INTERVALS_LATE_ATTESTATION_SUBMISSION * config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT;
940
+ summary.poolSubmitDelayMinSec > config.getSlotComponentDurationMs(LATE_ATTESTATION_SUBMISSION_BPS) / 1000;
930
941
 
931
942
  const aggregateInclusion = summary.aggregateInclusionDelaysSec.length > 0;
932
943
 
@@ -1030,7 +1041,7 @@ function isMissedSlot(rootCache: RootHexCache, slot: Slot): boolean {
1030
1041
  }
1031
1042
 
1032
1043
  function renderBlockProposalSummary(
1033
- config: ChainConfig,
1044
+ config: ChainForkConfig,
1034
1045
  rootCache: RootHexCache,
1035
1046
  summary: EpochSummary | undefined,
1036
1047
  proposalSlot: Slot
@@ -1053,7 +1064,7 @@ function renderBlockProposalSummary(
1053
1064
 
1054
1065
  if (
1055
1066
  proposal.poolSubmitDelaySec !== null &&
1056
- proposal.poolSubmitDelaySec > (INTERVALS_LATE_BLOCK_SUBMISSION * config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT
1067
+ proposal.poolSubmitDelaySec > config.getSlotComponentDurationMs(LATE_BLOCK_SUBMISSION_BPS) / 1000
1057
1068
  ) {
1058
1069
  out += "_late";
1059
1070
  }
@@ -862,9 +862,9 @@ export function createLodestarMetrics(
862
862
  help: "Time elapsed between block slot time and the time block becomes head",
863
863
  buckets: [0.5, 1, 2, 4, 6, 12],
864
864
  }),
865
- setHeadAfterFirstInterval: register.gauge({
866
- name: "lodestar_import_block_set_head_after_first_interval_total",
867
- help: "Total times an imported block is set as head after the first slot interval",
865
+ setHeadAfterCutoff: register.gauge({
866
+ name: "lodestar_import_block_set_head_after_cutoff_total",
867
+ help: "Total times an imported block is set as head after ATTESTATION_DUE_BPS of the slot",
868
868
  }),
869
869
  bySource: register.gauge<{source: BlockInputSource}>({
870
870
  name: "lodestar_import_block_by_source_total",
@@ -105,7 +105,7 @@ export class Eth2Gossipsub extends GossipSub {
105
105
  mcacheLength: 6,
106
106
  mcacheGossip: 3,
107
107
  // this should be in ms
108
- seenTTL: config.SECONDS_PER_SLOT * SLOTS_PER_EPOCH * 2 * 1000,
108
+ seenTTL: config.SLOT_DURATION_MS * SLOTS_PER_EPOCH * 2,
109
109
  scoreParams,
110
110
  scoreThresholds: gossipScoreThresholds,
111
111
  // For a single stream, await processing each RPC before processing the next