@lodestar/beacon-node 1.36.0-dev.6604fa5719 → 1.36.0-dev.6f46a8bd20

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 (127) hide show
  1. package/lib/api/impl/config/constants.d.ts +4 -2
  2. package/lib/api/impl/config/constants.d.ts.map +1 -1
  3. package/lib/api/impl/config/constants.js +6 -3
  4. package/lib/api/impl/config/constants.js.map +1 -1
  5. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  6. package/lib/api/impl/lodestar/index.js +6 -9
  7. package/lib/api/impl/lodestar/index.js.map +1 -1
  8. package/lib/api/impl/validator/index.d.ts.map +1 -1
  9. package/lib/api/impl/validator/index.js +2 -2
  10. package/lib/api/impl/validator/index.js.map +1 -1
  11. package/lib/chain/archiveStore/archiveStore.d.ts +9 -0
  12. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.js +24 -0
  14. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  15. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +7 -0
  16. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  17. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +31 -5
  18. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  19. package/lib/chain/beaconProposerCache.d.ts +3 -0
  20. package/lib/chain/beaconProposerCache.d.ts.map +1 -1
  21. package/lib/chain/beaconProposerCache.js +4 -6
  22. package/lib/chain/beaconProposerCache.js.map +1 -1
  23. package/lib/chain/chain.js +1 -1
  24. package/lib/chain/chain.js.map +1 -1
  25. package/lib/chain/errors/dataColumnSidecarError.d.ts +7 -0
  26. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  27. package/lib/chain/errors/dataColumnSidecarError.js +1 -0
  28. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  29. package/lib/chain/errors/voluntaryExitError.d.ts +16 -2
  30. package/lib/chain/errors/voluntaryExitError.d.ts.map +1 -1
  31. package/lib/chain/errors/voluntaryExitError.js +22 -1
  32. package/lib/chain/errors/voluntaryExitError.js.map +1 -1
  33. package/lib/chain/opPools/syncContributionAndProofPool.d.ts +3 -1
  34. package/lib/chain/opPools/syncContributionAndProofPool.d.ts.map +1 -1
  35. package/lib/chain/opPools/syncContributionAndProofPool.js +4 -3
  36. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  37. package/lib/chain/validation/attestation.d.ts.map +1 -1
  38. package/lib/chain/validation/attestation.js +6 -7
  39. package/lib/chain/validation/attestation.js.map +1 -1
  40. package/lib/chain/validation/block.d.ts.map +1 -1
  41. package/lib/chain/validation/block.js +1 -2
  42. package/lib/chain/validation/block.js.map +1 -1
  43. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  44. package/lib/chain/validation/dataColumnSidecar.js +14 -3
  45. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  46. package/lib/chain/validation/lightClientOptimisticUpdate.d.ts.map +1 -1
  47. package/lib/chain/validation/lightClientOptimisticUpdate.js +1 -2
  48. package/lib/chain/validation/lightClientOptimisticUpdate.js.map +1 -1
  49. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  50. package/lib/chain/validation/voluntaryExit.js +5 -4
  51. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  52. package/lib/constants/constants.d.ts +0 -5
  53. package/lib/constants/constants.d.ts.map +1 -1
  54. package/lib/constants/constants.js +0 -5
  55. package/lib/constants/constants.js.map +1 -1
  56. package/lib/constants/network.d.ts +0 -14
  57. package/lib/constants/network.d.ts.map +1 -1
  58. package/lib/constants/network.js +0 -15
  59. package/lib/constants/network.js.map +1 -1
  60. package/lib/metrics/metrics/lodestar.d.ts +10 -0
  61. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  62. package/lib/metrics/metrics/lodestar.js +15 -1
  63. package/lib/metrics/metrics/lodestar.js.map +1 -1
  64. package/lib/network/core/networkCoreWorker.js +2 -7
  65. package/lib/network/core/networkCoreWorker.js.map +1 -1
  66. package/lib/network/discv5/utils.d.ts.map +1 -1
  67. package/lib/network/discv5/utils.js +1 -2
  68. package/lib/network/discv5/utils.js.map +1 -1
  69. package/lib/network/discv5/worker.js +2 -7
  70. package/lib/network/discv5/worker.js.map +1 -1
  71. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  72. package/lib/network/gossip/gossipsub.js +1 -7
  73. package/lib/network/gossip/gossipsub.js.map +1 -1
  74. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  75. package/lib/network/processor/gossipHandlers.js +14 -8
  76. package/lib/network/processor/gossipHandlers.js.map +1 -1
  77. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +5 -5
  78. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  79. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js +4 -4
  80. package/lib/network/reqresp/handlers/blobSidecarsByRoot.js.map +1 -1
  81. package/lib/network/subnets/attnetsService.d.ts +1 -1
  82. package/lib/network/subnets/attnetsService.js +6 -6
  83. package/lib/network/subnets/attnetsService.js.map +1 -1
  84. package/lib/network/subnets/util.d.ts +3 -2
  85. package/lib/network/subnets/util.d.ts.map +1 -1
  86. package/lib/network/subnets/util.js +6 -6
  87. package/lib/network/subnets/util.js.map +1 -1
  88. package/lib/sync/utils/remoteSyncType.js +2 -2
  89. package/lib/sync/utils/remoteSyncType.js.map +1 -1
  90. package/lib/util/clock.d.ts.map +1 -1
  91. package/lib/util/clock.js +3 -4
  92. package/lib/util/clock.js.map +1 -1
  93. package/lib/util/profile.d.ts +6 -4
  94. package/lib/util/profile.d.ts.map +1 -1
  95. package/lib/util/profile.js +40 -3
  96. package/lib/util/profile.js.map +1 -1
  97. package/package.json +26 -24
  98. package/src/api/impl/config/constants.ts +10 -4
  99. package/src/api/impl/lodestar/index.ts +6 -9
  100. package/src/api/impl/validator/index.ts +5 -2
  101. package/src/chain/archiveStore/archiveStore.ts +27 -0
  102. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +32 -5
  103. package/src/chain/beaconProposerCache.ts +4 -8
  104. package/src/chain/chain.ts +1 -1
  105. package/src/chain/errors/dataColumnSidecarError.ts +8 -0
  106. package/src/chain/errors/voluntaryExitError.ts +30 -2
  107. package/src/chain/opPools/syncContributionAndProofPool.ts +3 -2
  108. package/src/chain/validation/attestation.ts +6 -7
  109. package/src/chain/validation/block.ts +1 -2
  110. package/src/chain/validation/dataColumnSidecar.ts +21 -4
  111. package/src/chain/validation/lightClientOptimisticUpdate.ts +3 -2
  112. package/src/chain/validation/voluntaryExit.ts +14 -4
  113. package/src/constants/constants.ts +0 -6
  114. package/src/constants/network.ts +0 -19
  115. package/src/metrics/metrics/lodestar.ts +18 -1
  116. package/src/network/core/networkCoreWorker.ts +2 -7
  117. package/src/network/discv5/utils.ts +1 -2
  118. package/src/network/discv5/worker.ts +2 -7
  119. package/src/network/gossip/gossipsub.ts +1 -10
  120. package/src/network/processor/gossipHandlers.ts +16 -7
  121. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +6 -6
  122. package/src/network/reqresp/handlers/blobSidecarsByRoot.ts +5 -5
  123. package/src/network/subnets/attnetsService.ts +6 -6
  124. package/src/network/subnets/util.ts +12 -12
  125. package/src/sync/utils/remoteSyncType.ts +2 -2
  126. package/src/util/clock.ts +3 -4
  127. package/src/util/profile.ts +45 -3
@@ -1,6 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {LightClientOptimisticUpdate} from "@lodestar/types";
3
- import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js";
4
3
  import {assertLightClientServer} from "../../node/utils/lightclient.js";
5
4
  import {IClock} from "../../util/clock.js";
6
5
  import {GossipAction} from "../errors/index.js";
@@ -64,5 +63,7 @@ export function updateReceivedTooEarly(
64
63
  update: Pick<LightClientOptimisticUpdate, "signatureSlot">
65
64
  ): boolean {
66
65
  const fork = config.getForkName(update.signatureSlot);
67
- return clock.msFromSlot(update.signatureSlot) < config.getSyncMessageDueMs(fork) - MAXIMUM_GOSSIP_CLOCK_DISPARITY;
66
+ return (
67
+ clock.msFromSlot(update.signatureSlot) < config.getSyncMessageDueMs(fork) - config.MAXIMUM_GOSSIP_CLOCK_DISPARITY
68
+ );
68
69
  }
@@ -1,6 +1,15 @@
1
- import {getVoluntaryExitSignatureSet, isValidVoluntaryExit} from "@lodestar/state-transition";
1
+ import {
2
+ VoluntaryExitValidity,
3
+ getVoluntaryExitSignatureSet,
4
+ getVoluntaryExitValidity,
5
+ } from "@lodestar/state-transition";
2
6
  import {phase0} from "@lodestar/types";
3
- import {GossipAction, VoluntaryExitError, VoluntaryExitErrorCode} from "../errors/index.js";
7
+ import {
8
+ GossipAction,
9
+ VoluntaryExitError,
10
+ VoluntaryExitErrorCode,
11
+ voluntaryExitValidityToErrorCode,
12
+ } from "../errors/index.js";
4
13
  import {IBeaconChain} from "../index.js";
5
14
  import {RegenCaller} from "../regen/index.js";
6
15
 
@@ -43,9 +52,10 @@ async function validateVoluntaryExit(
43
52
 
44
53
  // [REJECT] All of the conditions within process_voluntary_exit pass validation.
45
54
  // verifySignature = false, verified in batch below
46
- if (!isValidVoluntaryExit(chain.config.getForkSeq(state.slot), state, voluntaryExit, false)) {
55
+ const validity = getVoluntaryExitValidity(chain.config.getForkSeq(state.slot), state, voluntaryExit, false);
56
+ if (validity !== VoluntaryExitValidity.valid) {
47
57
  throw new VoluntaryExitError(GossipAction.REJECT, {
48
- code: VoluntaryExitErrorCode.INVALID,
58
+ code: voluntaryExitValidityToErrorCode(validity),
49
59
  });
50
60
  }
51
61
 
@@ -7,9 +7,3 @@ export const ZERO_HASH = Buffer.alloc(32, 0);
7
7
  export const ZERO_HASH_HEX = "0x" + "00".repeat(32);
8
8
  export const EMPTY_SIGNATURE = Buffer.alloc(96, 0);
9
9
  export const GRAFFITI_SIZE = 32;
10
-
11
- /**
12
- * The maximum milliseconds of clock disparity assumed between honest nodes.
13
- */
14
- export const MAXIMUM_GOSSIP_CLOCK_DISPARITY = 500;
15
- export const MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC = MAXIMUM_GOSSIP_CLOCK_DISPARITY / 1000;
@@ -1,22 +1,3 @@
1
- /**
2
- * For more info on some of these constants:
3
- * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#configuration
4
- */
5
-
6
- // Gossip constants
7
-
8
- /**
9
- * The maximum number of slots during which an attestation can be propagated.
10
- */
11
- export const ATTESTATION_PROPAGATION_SLOT_RANGE = 32;
12
-
13
- /** The maximum allowed size of uncompressed gossip messages. */
14
- export const GOSSIP_MAX_SIZE = 2 ** 20;
15
- export const GOSSIP_MAX_SIZE_BELLATRIX = 10 * GOSSIP_MAX_SIZE;
16
- /** The maximum allowed size of uncompressed req/resp chunked responses. */
17
- export const MAX_CHUNK_SIZE = 2 ** 20;
18
- export const MAX_CHUNK_SIZE_BELLATRIX = 10 * MAX_CHUNK_SIZE;
19
-
20
1
  export enum GoodByeReasonCode {
21
2
  INBOUND_DISCONNECT = -1,
22
3
  CLIENT_SHUTDOWN = 1,
@@ -1,5 +1,7 @@
1
1
  /** biome-ignore-all lint/suspicious/noTemplateCurlyInString: The metric templates requires to have `${}` in a normal string */
2
2
  import {NotReorgedReason} from "@lodestar/fork-choice";
3
+ import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
4
+ import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
3
5
  import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
4
6
  import {JobQueueItemType} from "../../chain/bls/index.js";
5
7
  import {AttestationErrorCode, BlockErrorCode} from "../../chain/errors/index.js";
@@ -1420,6 +1422,21 @@ export function createLodestarMetrics(
1420
1422
  },
1421
1423
  },
1422
1424
 
1425
+ processFinalizedCheckpoint: {
1426
+ durationByTask: register.histogram<{source: ArchiveStoreTask}>({
1427
+ name: "lodestar_process_finalized_checkpoint_seconds",
1428
+ help: "Histogram of time to process finalized checkpoint",
1429
+ buckets: [0.1, 0.5, 1, 2, 4, 8],
1430
+ labelNames: ["source"],
1431
+ }),
1432
+ frequencyStateArchive: register.histogram<{step: FrequencyStateArchiveStep}>({
1433
+ name: "lodestar_process_finalized_checkpoint_frequency_state_archive_seconds",
1434
+ help: "Histogram of FrequencyStateArchive duration by step",
1435
+ buckets: [0.1, 0.5, 1, 2, 4, 8],
1436
+ labelNames: ["step"],
1437
+ }),
1438
+ },
1439
+
1423
1440
  regenFnCallTotal: register.gauge<{entrypoint: RegenFnName; caller: RegenCaller}>({
1424
1441
  name: "lodestar_regen_fn_call_total",
1425
1442
  help: "Total number of calls for regen functions",
@@ -1861,7 +1878,7 @@ export function createLodestarMetrics(
1861
1878
  fetchKeys: register.histogram({
1862
1879
  name: "lodestar_prune_history_fetch_keys_time_seconds",
1863
1880
  help: "Time to fetch keys in seconds",
1864
- buckets: [0.001, 0.01, 0.1, 1],
1881
+ buckets: [0.001, 0.01, 0.1, 0.3, 0.5, 1],
1865
1882
  }),
1866
1883
 
1867
1884
  pruneKeys: register.histogram({
@@ -1,5 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import worker from "node:worker_threads";
4
2
  import {privateKeyFromProtobuf} from "@libp2p/crypto/keys";
5
3
  import {peerIdFromPrivateKey} from "@libp2p/peer-id";
@@ -11,7 +9,7 @@ import {RegistryMetricCreator, collectNodeJSMetrics} from "../../metrics/index.j
11
9
  import {AsyncIterableBridgeCaller, AsyncIterableBridgeHandler} from "../../util/asyncIterableToEvents.js";
12
10
  import {Clock} from "../../util/clock.js";
13
11
  import {peerIdToString} from "../../util/peerId.js";
14
- import {profileNodeJS, writeHeapSnapshot} from "../../util/profile.js";
12
+ import {ProfileThread, profileThread, writeHeapSnapshot} from "../../util/profile.js";
15
13
  import {wireEventsOnWorkerThread} from "../../util/workerEvents.js";
16
14
  import {NetworkEventBus, NetworkEventData, networkEventDirection} from "../events.js";
17
15
  import {
@@ -157,10 +155,7 @@ const libp2pWorkerApi: NetworkWorkerApi = {
157
155
  dumpDiscv5KadValues: () => core.dumpDiscv5KadValues(),
158
156
  dumpMeshPeers: () => core.dumpMeshPeers(),
159
157
  writeProfile: async (durationMs: number, dirpath: string) => {
160
- const profile = await profileNodeJS(durationMs);
161
- const filePath = path.join(dirpath, `network_thread_${new Date().toISOString()}.cpuprofile`);
162
- fs.writeFileSync(filePath, profile);
163
- return filePath;
158
+ return profileThread(ProfileThread.NETWORK, durationMs, dirpath);
164
159
  },
165
160
  writeDiscv5Profile: async (durationMs: number, dirpath: string) => {
166
161
  return core.writeDiscv5Profile(durationMs, dirpath);
@@ -1,6 +1,5 @@
1
1
  import {ENR} from "@chainsafe/enr";
2
2
  import {BeaconConfig} from "@lodestar/config";
3
- import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/constants.js";
4
3
  import {IClock} from "../../util/clock.js";
5
4
  import {ENRKey} from "../metadata.js";
6
5
 
@@ -35,7 +34,7 @@ export function enrRelevance(enr: ENR, config: BeaconConfig, clock: IClock): ENR
35
34
  }
36
35
 
37
36
  // Check if fork digest's fork matches ours
38
- const currentSlot = clock.slotWithFutureTolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY);
37
+ const currentSlot = clock.slotWithFutureTolerance(config.MAXIMUM_GOSSIP_CLOCK_DISPARITY / 1000);
39
38
  const localForkInfo = config.getForkInfo(currentSlot);
40
39
  // We only connect if the ENR's fork matches our current fork.
41
40
  // We also allow it to be the previous fork due to delay and infrequent update of DHT.
@@ -1,5 +1,3 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import worker from "node:worker_threads";
4
2
  import {privateKeyFromProtobuf} from "@libp2p/crypto/keys";
5
3
  import {peerIdFromPrivateKey} from "@libp2p/peer-id";
@@ -14,7 +12,7 @@ import {Gauge} from "@lodestar/utils";
14
12
  import {RegistryMetricCreator} from "../../metrics/index.js";
15
13
  import {collectNodeJSMetrics} from "../../metrics/nodeJsMetrics.js";
16
14
  import {Clock} from "../../util/clock.js";
17
- import {profileNodeJS, writeHeapSnapshot} from "../../util/profile.js";
15
+ import {ProfileThread, profileThread, writeHeapSnapshot} from "../../util/profile.js";
18
16
  import {Discv5WorkerApi, Discv5WorkerData} from "./types.js";
19
17
  import {ENRRelevance, enrRelevance} from "./utils.js";
20
18
 
@@ -108,10 +106,7 @@ const module: Discv5WorkerApi = {
108
106
  return (await metricsRegistry?.metrics()) ?? "";
109
107
  },
110
108
  writeProfile: async (durationMs: number, dirpath: string) => {
111
- const profile = await profileNodeJS(durationMs);
112
- const filePath = path.join(dirpath, `discv5_thread_${new Date().toISOString()}.cpuprofile`);
113
- fs.writeFileSync(filePath, profile);
114
- return filePath;
109
+ return profileThread(ProfileThread.DISC5, durationMs, dirpath);
115
110
  },
116
111
  writeHeapSnapshot: async (prefix: string, dirpath: string) => {
117
112
  return writeHeapSnapshot(prefix, dirpath);
@@ -6,7 +6,6 @@ import {BeaconConfig, ForkBoundary} from "@lodestar/config";
6
6
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
7
7
  import {SubnetID} from "@lodestar/types";
8
8
  import {Logger, Map2d, Map2dArr} from "@lodestar/utils";
9
- import {GOSSIP_MAX_SIZE, GOSSIP_MAX_SIZE_BELLATRIX} from "../../constants/network.js";
10
9
  import {RegistryMetricCreator} from "../../metrics/index.js";
11
10
  import {callInNextEventLoop} from "../../util/eventLoop.js";
12
11
  import {NetworkEvent, NetworkEventBus, NetworkEventData} from "../events.js";
@@ -117,15 +116,7 @@ export class Eth2Gossipsub extends GossipSub {
117
116
  fastMsgIdFn: fastMsgIdFn,
118
117
  msgIdFn: msgIdFn.bind(msgIdFn, gossipTopicCache),
119
118
  msgIdToStrFn: msgIdToStrFn,
120
- // Use the bellatrix max size if the merge is configured. pre-merge using this size
121
- // could only be an issue on outgoing payloads, its highly unlikely we will send out
122
- // a chunk bigger than GOSSIP_MAX_SIZE pre merge even on mainnet network.
123
- //
124
- // TODO: figure out a way to dynamically transition to the size
125
- dataTransform: new DataTransformSnappy(
126
- gossipTopicCache,
127
- Number.isFinite(config.BELLATRIX_FORK_EPOCH) ? GOSSIP_MAX_SIZE_BELLATRIX : GOSSIP_MAX_SIZE
128
- ),
119
+ dataTransform: new DataTransformSnappy(gossipTopicCache, config.MAX_PAYLOAD_SIZE),
129
120
  metricsRegister: metricsRegister as MetricsRegister | null,
130
121
  metricsTopicStrToLabel: metricsRegister
131
122
  ? getMetricsTopicStrToLabel(networkConfig, {disableLightClientServer: opts.disableLightClientServer ?? false})
@@ -39,6 +39,7 @@ import {
39
39
  BlockError,
40
40
  BlockErrorCode,
41
41
  BlockGossipError,
42
+ DataColumnSidecarErrorCode,
42
43
  DataColumnSidecarGossipError,
43
44
  GossipAction,
44
45
  GossipActionError,
@@ -304,7 +305,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
304
305
  ...blockInput.getLogMeta(),
305
306
  index: dataColumnSidecar.index,
306
307
  });
307
- return blockInput;
308
+ throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
309
+ code: DataColumnSidecarErrorCode.ALREADY_KNOWN,
310
+ columnIdx: dataColumnSidecar.index,
311
+ slot,
312
+ });
308
313
  }
309
314
  }
310
315
 
@@ -556,6 +561,16 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
556
561
  metrics?.dataColumns.elapsedTimeTillReceived.observe({receivedOrder: receivedColumns}, delaySec);
557
562
  break;
558
563
  }
564
+
565
+ if (!blockInput.hasAllData()) {
566
+ // immediately attempt fetch of data columns from execution engine
567
+ chain.getBlobsTracker.triggerGetBlobs(blockInput);
568
+ // if we've received at least half of the columns, trigger reconstruction of the rest
569
+ if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
570
+ chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
571
+ }
572
+ }
573
+
559
574
  if (!blockInput.hasBlockAndAllData()) {
560
575
  const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
561
576
  chain.logger.debug("Received gossip data column, waiting for full data availability", {
@@ -578,12 +593,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
578
593
  source: BlockInputSource.gossip,
579
594
  });
580
595
  });
581
- // immediately attempt fetch of data columns from execution engine
582
- chain.getBlobsTracker.triggerGetBlobs(blockInput);
583
- // if we've received at least half of the columns, trigger reconstruction of the rest
584
- if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
585
- chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
586
- }
587
596
  }
588
597
  },
589
598
 
@@ -1,5 +1,5 @@
1
1
  import {ChainConfig} from "@lodestar/config";
2
- import {BLOBSIDECAR_FIXED_SIZE, GENESIS_SLOT} from "@lodestar/params";
2
+ import {BLOB_SIDECAR_FIXED_SIZE, GENESIS_SLOT} from "@lodestar/params";
3
3
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
4
4
  import {computeEpochAtSlot} from "@lodestar/state-transition";
5
5
  import {Slot, deneb} from "@lodestar/types";
@@ -71,17 +71,17 @@ export function* iterateBlobBytesFromWrapper(
71
71
  blockSlot: Slot
72
72
  ): Iterable<ResponseOutgoing> {
73
73
  const allBlobSideCarsBytes = blobSideCarsBytesWrapped.slice(BLOB_SIDECARS_IN_WRAPPER_INDEX);
74
- const blobsLen = allBlobSideCarsBytes.length / BLOBSIDECAR_FIXED_SIZE;
74
+ const blobsLen = allBlobSideCarsBytes.length / BLOB_SIDECAR_FIXED_SIZE;
75
75
 
76
76
  for (let index = 0; index < blobsLen; index++) {
77
77
  const blobSideCarBytes = allBlobSideCarsBytes.slice(
78
- index * BLOBSIDECAR_FIXED_SIZE,
79
- (index + 1) * BLOBSIDECAR_FIXED_SIZE
78
+ index * BLOB_SIDECAR_FIXED_SIZE,
79
+ (index + 1) * BLOB_SIDECAR_FIXED_SIZE
80
80
  );
81
- if (blobSideCarBytes.length !== BLOBSIDECAR_FIXED_SIZE) {
81
+ if (blobSideCarBytes.length !== BLOB_SIDECAR_FIXED_SIZE) {
82
82
  throw new ResponseError(
83
83
  RespStatus.SERVER_ERROR,
84
- `Invalid blobSidecar index=${index} bytes length=${blobSideCarBytes.length} expected=${BLOBSIDECAR_FIXED_SIZE} for slot ${blockSlot} blobsLen=${blobsLen}`
84
+ `Invalid blobSidecar index=${index} bytes length=${blobSideCarBytes.length} expected=${BLOB_SIDECAR_FIXED_SIZE} for slot ${blockSlot} blobsLen=${blobsLen}`
85
85
  );
86
86
  }
87
87
  yield {
@@ -1,4 +1,4 @@
1
- import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params";
1
+ import {BLOB_SIDECAR_FIXED_SIZE} from "@lodestar/params";
2
2
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
3
3
  import {computeEpochAtSlot} from "@lodestar/state-transition";
4
4
  import {RootHex} from "@lodestar/types";
@@ -45,12 +45,12 @@ export async function* onBlobSidecarsByRoot(
45
45
  }
46
46
 
47
47
  const blobSidecarBytes = lastFetchedSideCars.bytes.slice(
48
- index * BLOBSIDECAR_FIXED_SIZE,
49
- (index + 1) * BLOBSIDECAR_FIXED_SIZE
48
+ index * BLOB_SIDECAR_FIXED_SIZE,
49
+ (index + 1) * BLOB_SIDECAR_FIXED_SIZE
50
50
  );
51
- if (blobSidecarBytes.length !== BLOBSIDECAR_FIXED_SIZE) {
51
+ if (blobSidecarBytes.length !== BLOB_SIDECAR_FIXED_SIZE) {
52
52
  throw Error(
53
- `Inconsistent state, blobSidecar blockRoot=${blockRootHex} index=${index} blobSidecarBytes=${blobSidecarBytes.length} expected=${BLOBSIDECAR_FIXED_SIZE}`
53
+ `Inconsistent state, blobSidecar blockRoot=${blockRootHex} index=${index} blobSidecarBytes=${blobSidecarBytes.length} expected=${BLOB_SIDECAR_FIXED_SIZE}`
54
54
  );
55
55
  }
56
56
 
@@ -1,5 +1,5 @@
1
1
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
2
- import {ATTESTATION_SUBNET_COUNT, EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION, SLOTS_PER_EPOCH} from "@lodestar/params";
2
+ import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH} from "@lodestar/params";
3
3
  import {computeEpochAtSlot} from "@lodestar/state-transition";
4
4
  import {Epoch, Slot, SubnetID, ssz} from "@lodestar/types";
5
5
  import {Logger, MapDef} from "@lodestar/utils";
@@ -92,8 +92,8 @@ export class AttnetsService implements IAttnetsService {
92
92
  const shortLivedSubnets = this.committeeSubnets.getActiveTtl(this.clock.currentSlot);
93
93
 
94
94
  const longLivedSubscriptionsToSlot =
95
- (Math.floor(this.clock.currentEpoch / EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION) + 1) *
96
- EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION *
95
+ (Math.floor(this.clock.currentEpoch / this.config.EPOCHS_PER_SUBNET_SUBSCRIPTION) + 1) *
96
+ this.config.EPOCHS_PER_SUBNET_SUBSCRIPTION *
97
97
  SLOTS_PER_EPOCH;
98
98
  const longLivedSubnets = Array.from(this.longLivedSubscriptions).map((subnet) => ({
99
99
  subnet,
@@ -234,11 +234,11 @@ export class AttnetsService implements IAttnetsService {
234
234
 
235
235
  /**
236
236
  * Run per epoch, clean-up operations that are not urgent
237
- * Subscribe to new random subnets every EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION epochs
237
+ * Subscribe to new random subnets every EPOCHS_PER_SUBNET_SUBSCRIPTION epochs
238
238
  */
239
239
  private onEpoch = (epoch: Epoch): void => {
240
240
  try {
241
- if (epoch % EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION === 0) {
241
+ if (epoch % this.config.EPOCHS_PER_SUBNET_SUBSCRIPTION === 0) {
242
242
  this.recomputeLongLivedSubnets();
243
243
  }
244
244
  } catch (e) {
@@ -253,7 +253,7 @@ export class AttnetsService implements IAttnetsService {
253
253
  }
254
254
 
255
255
  const oldSubnets = this.longLivedSubscriptions;
256
- const newSubnets = computeSubscribedSubnet(this.nodeId, this.clock.currentEpoch);
256
+ const newSubnets = computeSubscribedSubnet(this.config, this.nodeId, this.clock.currentEpoch);
257
257
  this.logger.verbose("Recomputing long-lived subscriptions", {
258
258
  epoch: this.clock.currentEpoch,
259
259
  oldSubnets: Array.from(oldSubnets).join(","),
@@ -1,11 +1,6 @@
1
1
  import {digest} from "@chainsafe/as-sha256";
2
- import {
3
- ATTESTATION_SUBNET_COUNT,
4
- ATTESTATION_SUBNET_PREFIX_BITS,
5
- EPOCHS_PER_SUBNET_SUBSCRIPTION,
6
- NODE_ID_BITS,
7
- SUBNETS_PER_NODE,
8
- } from "@lodestar/params";
2
+ import {ChainConfig} from "@lodestar/config";
3
+ import {ATTESTATION_SUBNET_COUNT, ATTESTATION_SUBNET_PREFIX_BITS, NODE_ID_BITS} from "@lodestar/params";
9
4
  import {computeShuffledIndex} from "@lodestar/state-transition";
10
5
  import {Epoch, ssz} from "@lodestar/types";
11
6
  import {NodeId} from "./interface.js";
@@ -13,10 +8,10 @@ import {NodeId} from "./interface.js";
13
8
  /**
14
9
  * Spec https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/phase0/p2p-interface.md
15
10
  */
16
- export function computeSubscribedSubnet(nodeId: NodeId, epoch: Epoch): number[] {
11
+ export function computeSubscribedSubnet(config: ChainConfig, nodeId: NodeId, epoch: Epoch): number[] {
17
12
  const subnets: number[] = [];
18
- for (let index = 0; index < SUBNETS_PER_NODE; index++) {
19
- subnets.push(computeSubscribedSubnetByIndex(nodeId, epoch, index));
13
+ for (let index = 0; index < config.SUBNETS_PER_NODE; index++) {
14
+ subnets.push(computeSubscribedSubnetByIndex(config, nodeId, epoch, index));
20
15
  }
21
16
  return subnets;
22
17
  }
@@ -24,11 +19,16 @@ export function computeSubscribedSubnet(nodeId: NodeId, epoch: Epoch): number[]
24
19
  /**
25
20
  * Spec https://github.com/ethereum/consensus-specs/blob/v1.4.0-alpha.3/specs/phase0/p2p-interface.md
26
21
  */
27
- export function computeSubscribedSubnetByIndex(nodeId: NodeId, epoch: Epoch, index: number): number {
22
+ export function computeSubscribedSubnetByIndex(
23
+ config: ChainConfig,
24
+ nodeId: NodeId,
25
+ epoch: Epoch,
26
+ index: number
27
+ ): number {
28
28
  const nodeIdPrefix = getNodeIdPrefix(nodeId);
29
29
  const nodeOffset = getNodeOffset(nodeId);
30
30
  const permutationSeed = digest(
31
- ssz.UintNum64.serialize(Math.floor((epoch + nodeOffset) / EPOCHS_PER_SUBNET_SUBSCRIPTION))
31
+ ssz.UintNum64.serialize(Math.floor((epoch + nodeOffset) / config.EPOCHS_PER_SUBNET_SUBSCRIPTION))
32
32
  );
33
33
  const permutatedPrefix = computeShuffledIndex(nodeIdPrefix, 1 << ATTESTATION_SUBNET_PREFIX_BITS, permutationSeed);
34
34
  return (permutatedPrefix + index) % ATTESTATION_SUBNET_COUNT;
@@ -134,8 +134,8 @@ export function getRangeSyncTarget(
134
134
  return {
135
135
  syncType: RangeSyncType.Head,
136
136
  // The new peer has the same finalized (earlier filters should prevent a peer with an
137
- // earlier finalized chain from reaching here).
138
- startEpoch: Math.min(computeEpochAtSlot(local.headSlot), remote.finalizedEpoch),
137
+ // earlier finalized chain from reaching here) and local head will always be >= local finalized.
138
+ startEpoch: computeEpochAtSlot(local.headSlot),
139
139
  target: {
140
140
  slot: remote.headSlot,
141
141
  root: remote.headRoot,
package/src/util/clock.ts CHANGED
@@ -4,7 +4,6 @@ import {ChainForkConfig} from "@lodestar/config";
4
4
  import {computeEpochAtSlot, computeTimeAtSlot, getCurrentSlot} from "@lodestar/state-transition";
5
5
  import type {Epoch, Slot} from "@lodestar/types";
6
6
  import {ErrorAborted} from "@lodestar/utils";
7
- import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../constants/constants.js";
8
7
 
9
8
  export enum ClockEvent {
10
9
  /**
@@ -101,7 +100,7 @@ export class Clock extends EventEmitter implements IClock {
101
100
  get currentSlotWithGossipDisparity(): Slot {
102
101
  const currentSlot = this.currentSlot;
103
102
  const nextSlotTime = computeTimeAtSlot(this.config, currentSlot + 1, this.genesisTime) * 1000;
104
- return nextSlotTime - Date.now() < MAXIMUM_GOSSIP_CLOCK_DISPARITY ? currentSlot + 1 : currentSlot;
103
+ return nextSlotTime - Date.now() < this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY ? currentSlot + 1 : currentSlot;
105
104
  }
106
105
 
107
106
  get currentEpoch(): Epoch {
@@ -130,12 +129,12 @@ export class Clock extends EventEmitter implements IClock {
130
129
  }
131
130
  const nextSlotTime = computeTimeAtSlot(this.config, currentSlot + 1, this.genesisTime) * 1000;
132
131
  // we're too close to next slot, accept next slot
133
- if (nextSlotTime - Date.now() < MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
132
+ if (nextSlotTime - Date.now() < this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
134
133
  return slot === currentSlot + 1;
135
134
  }
136
135
  const currentSlotTime = computeTimeAtSlot(this.config, currentSlot, this.genesisTime) * 1000;
137
136
  // we've just passed the current slot, accept previous slot
138
- if (Date.now() - currentSlotTime < MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
137
+ if (Date.now() - currentSlotTime < this.config.MAXIMUM_GOSSIP_CLOCK_DISPARITY) {
139
138
  return slot === currentSlot - 1;
140
139
  }
141
140
  return false;
@@ -1,13 +1,32 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  import {sleep} from "@lodestar/utils";
2
4
 
5
+ export enum ProfileThread {
6
+ MAIN = "main",
7
+ NETWORK = "network",
8
+ DISC5 = "discv5",
9
+ }
10
+
11
+ /**
12
+ * The time to take a Bun profile.
13
+ * If we increase this time it'll potentiall cause the app to crash.
14
+ * If we decrease this time, profile recorded will be fragmented and hard to analyze.
15
+ */
16
+ const BUN_PROFILE_MS = 3 * 1000;
17
+
18
+ export async function profileThread(thread: ProfileThread, durationMs: number, dirpath: string): Promise<string> {
19
+ return globalThis.Bun ? profileBun(thread, durationMs) : profileNodeJS(thread, durationMs, dirpath);
20
+ }
21
+
3
22
  /**
4
- * Take 10m profile of the current thread without promise tracking.
23
+ * Take `durationMs` profile of the current thread and return the persisted file path.
5
24
  */
6
- export async function profileNodeJS(durationMs: number): Promise<string> {
25
+ async function profileNodeJS(thread: ProfileThread, durationMs: number, dirpath: string): Promise<string> {
7
26
  const inspector = await import("node:inspector");
8
27
 
9
28
  // due to some typing issues, not able to use promisify here
10
- return new Promise<string>((resolve, reject) => {
29
+ const profile = await new Promise<string>((resolve, reject) => {
11
30
  // Start the inspector and connect to it
12
31
  const session = new inspector.Session();
13
32
  session.connect();
@@ -29,6 +48,29 @@ export async function profileNodeJS(durationMs: number): Promise<string> {
29
48
  });
30
49
  });
31
50
  });
51
+
52
+ const filePath = path.join(dirpath, `${thread}_thread_${new Date().toISOString()}.cpuprofile`);
53
+ fs.writeFileSync(filePath, profile);
54
+ return filePath;
55
+ }
56
+
57
+ /**
58
+ * Unlike NodeJS, Bun console.profile() api flush data to the inspector,
59
+ * so this api returns ms taken of this profile instead of file path.
60
+ */
61
+ async function profileBun(thread: ProfileThread, durationMs: number): Promise<string> {
62
+ const start = Date.now();
63
+ let now = Date.now();
64
+ while (now - start < durationMs) {
65
+ // biome-ignore lint/suspicious/noConsole: need to use console api to profile in Bun
66
+ console.profile(String(now));
67
+ await sleep(BUN_PROFILE_MS);
68
+ // biome-ignore lint/suspicious/noConsole: need to use console api to profile in Bun
69
+ console.profileEnd(String(now));
70
+ now = Date.now();
71
+ }
72
+
73
+ return `Successfully take Bun ${thread} thread profile in ${now - start}ms. Check your inspector to see the profile.`;
32
74
  }
33
75
 
34
76
  /**