@tokamak-private-dapps/private-state-cli 0.1.3 → 0.1.4

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-04-28
4
+
5
+ - Paced chunked log recovery queries at five requests per second to avoid RPC throughput bursts.
6
+ - Combined channel manager recovery log scans and filtered wallet note recovery scans to reduce RPC usage.
7
+
3
8
  ## 0.1.3 - 2026-04-28
4
9
 
5
10
  - Installed the Groth16 runtime during `private-state-cli --install` and reported Groth16 readiness from `--doctor`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamak-private-dapps/private-state-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Command-line client for the Tokamak private-state DApp.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "Tokamak Network",
@@ -152,6 +152,9 @@ const JUBJUB_D = jubjub.CURVE.d;
152
152
  const BLS12_381_SCALAR_FIELD_MODULUS =
153
153
  hexToBigInt(addHexPrefix("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"));
154
154
  const DEFAULT_LOG_CHUNK_SIZE = 2000;
155
+ const DEFAULT_LOG_REQUESTS_PER_SECOND = 5;
156
+ const LOG_REQUEST_INTERVAL_MS = Math.ceil(1000 / DEFAULT_LOG_REQUESTS_PER_SECOND);
157
+ let lastLogRequestStartedAtMs = 0;
155
158
 
156
159
  async function prepareDeploymentArtifacts(chainId) {
157
160
  const normalizedChainId = Number(chainId);
@@ -2030,6 +2033,7 @@ async function recoverDeliveredNotesFromEventLogs({
2030
2033
  const nullifierUsedSlot = ethers.toBigInt(findStorageSlot(storageLayoutManifest, "PrivateStateController", "nullifierUsed"));
2031
2034
  const observedLogs = await fetchLogsChunked(provider, {
2032
2035
  address: context.workspace.channelManager,
2036
+ topics: [NOTE_VALUE_ENCRYPTED_TOPIC],
2033
2037
  fromBlock: scanStartBlock,
2034
2038
  toBlock: latestBlock,
2035
2039
  });
@@ -3854,30 +3858,39 @@ async function reconstructChannelSnapshot({
3854
3858
  provider,
3855
3859
  );
3856
3860
  const latestBlock = await provider.getBlockNumber();
3857
- const rootEvents = await queryContractEventsChunked({
3858
- contract: channelManager,
3859
- eventName: "CurrentRootVectorObserved",
3861
+ const currentRootVectorObservedTopic =
3862
+ normalizeBytes32Hex(channelManager.interface.getEvent("CurrentRootVectorObserved").topicHash);
3863
+ const channelManagerLogs = await fetchLogsChunked(provider, {
3864
+ address: channelInfo.manager,
3865
+ topics: [[
3866
+ currentRootVectorObservedTopic,
3867
+ CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC,
3868
+ VAULT_STORAGE_WRITE_OBSERVED_TOPIC,
3869
+ ]],
3860
3870
  fromBlock: genesisBlockNumber,
3861
3871
  toBlock: latestBlock,
3862
3872
  });
3873
+ const channelManagerEvents = channelManagerLogs.map((log) => {
3874
+ const topic0 = log.topics[0] ? normalizeBytes32Hex(log.topics[0]) : null;
3875
+ if (topic0 !== null && ethers.toBigInt(topic0) === ethers.toBigInt(currentRootVectorObservedTopic)) {
3876
+ const parsed = channelManager.interface.parseLog(log);
3877
+ return {
3878
+ ...log,
3879
+ args: parsed.args,
3880
+ fragment: parsed.fragment,
3881
+ };
3882
+ }
3883
+ return log;
3884
+ });
3863
3885
  const vaultStorageWriteEvents = await queryContractEventsChunked({
3864
3886
  contract: bridgeTokenVault,
3865
3887
  eventName: "StorageWriteObserved",
3866
3888
  fromBlock: genesisBlockNumber,
3867
3889
  toBlock: latestBlock,
3868
3890
  });
3869
- const observedStorageLogs = await fetchLogsChunked(provider, {
3870
- address: channelInfo.manager,
3871
- topics: [[
3872
- CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC,
3873
- VAULT_STORAGE_WRITE_OBSERVED_TOPIC,
3874
- ]],
3875
- fromBlock: genesisBlockNumber,
3876
- toBlock: latestBlock,
3877
- });
3878
3891
 
3879
3892
  const groupedEvents = new Map();
3880
- for (const event of [...rootEvents, ...vaultStorageWriteEvents, ...observedStorageLogs]) {
3893
+ for (const event of [...channelManagerEvents, ...vaultStorageWriteEvents]) {
3881
3894
  const key = event.transactionHash;
3882
3895
  const group = groupedEvents.get(key) ?? [];
3883
3896
  group.push(event);
@@ -4000,6 +4013,7 @@ async function fetchLogsChunked(provider, {
4000
4013
  while (cursor <= resolvedToBlock) {
4001
4014
  const chunkToBlock = Math.min(resolvedToBlock, cursor + chunkSize - 1);
4002
4015
  try {
4016
+ await throttleLogRequest();
4003
4017
  const logs = await provider.getLogs({
4004
4018
  address,
4005
4019
  topics,
@@ -4009,6 +4023,12 @@ async function fetchLogsChunked(provider, {
4009
4023
  aggregatedLogs.push(...logs);
4010
4024
  cursor = chunkToBlock + 1;
4011
4025
  } catch (error) {
4026
+ if (isRateLimitError(error)) {
4027
+ throw new Error(
4028
+ `RPC log query rate limit exceeded. Log chunk requests are paced at ${DEFAULT_LOG_REQUESTS_PER_SECOND} requests per second.`,
4029
+ { cause: error },
4030
+ );
4031
+ }
4012
4032
  const suggestedChunkSize = deriveRecommendedLogChunkSize(error, chunkSize);
4013
4033
  if (suggestedChunkSize >= chunkSize) {
4014
4034
  throw error;
@@ -4020,6 +4040,31 @@ async function fetchLogsChunked(provider, {
4020
4040
  return aggregatedLogs;
4021
4041
  }
4022
4042
 
4043
+ async function throttleLogRequest() {
4044
+ const elapsedMs = Date.now() - lastLogRequestStartedAtMs;
4045
+ if (elapsedMs < LOG_REQUEST_INTERVAL_MS) {
4046
+ await sleep(LOG_REQUEST_INTERVAL_MS - elapsedMs);
4047
+ }
4048
+ lastLogRequestStartedAtMs = Date.now();
4049
+ }
4050
+
4051
+ function sleep(ms) {
4052
+ return new Promise((resolve) => setTimeout(resolve, ms));
4053
+ }
4054
+
4055
+ function isRateLimitError(error) {
4056
+ const serializedError = [
4057
+ error?.code,
4058
+ error?.status,
4059
+ error?.message,
4060
+ error?.shortMessage,
4061
+ error?.info?.responseStatus,
4062
+ error?.info?.responseBody,
4063
+ ].filter((value) => value !== undefined && value !== null).join("\n");
4064
+
4065
+ return /\b429\b|too many requests|rate limit|compute units/i.test(serializedError);
4066
+ }
4067
+
4023
4068
  function deriveRecommendedLogChunkSize(error, currentChunkSize) {
4024
4069
  const serializedError = [
4025
4070
  error?.message,