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

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.5 - 2026-04-28
4
+
5
+ - Switched channel balance proof generation to invoke `tokamak-groth16 --prove` instead of importing Groth16 proof internals directly.
6
+ - Read proof artifacts from the fixed Groth16 runtime workspace manifest.
7
+
8
+ ## 0.1.4 - 2026-04-28
9
+
10
+ - Paced chunked log recovery queries at five requests per second to avoid RPC throughput bursts.
11
+ - Combined channel manager recovery log scans and filtered wallet note recovery scans to reduce RPC usage.
12
+
3
13
  ## 0.1.3 - 2026-04-28
4
14
 
5
15
  - 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.5",
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",
@@ -43,7 +43,7 @@
43
43
  "@ethereumjs/util": "^10.1.1",
44
44
  "@noble/curves": "^1.2.0",
45
45
  "@tokamak-private-dapps/common-library": "^0.1.0",
46
- "@tokamak-private-dapps/groth16": "^0.1.0",
46
+ "@tokamak-private-dapps/groth16": "^0.1.1",
47
47
  "@tokamak-zk-evm/cli": "^2.0.8",
48
48
  "ethers": "^6.14.1",
49
49
  "tokamak-l2js": "^0.1.3"
@@ -67,7 +67,6 @@ import {
67
67
  PUBLIC_GROTH16_MPC_DRIVE_FOLDER_ID,
68
68
  downloadLatestPublicGroth16MpcArtifacts,
69
69
  } from "@tokamak-private-dapps/groth16/public-drive-crs";
70
- import { main as generateUpdateTreeProof } from "@tokamak-private-dapps/groth16/prover/updateTree/generateProof";
71
70
  import {
72
71
  CHANNEL_BOUND_L2_DERIVATION_MODE,
73
72
  deriveChannelIdFromName,
@@ -152,6 +151,9 @@ const JUBJUB_D = jubjub.CURVE.d;
152
151
  const BLS12_381_SCALAR_FIELD_MODULUS =
153
152
  hexToBigInt(addHexPrefix("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"));
154
153
  const DEFAULT_LOG_CHUNK_SIZE = 2000;
154
+ const DEFAULT_LOG_REQUESTS_PER_SECOND = 5;
155
+ const LOG_REQUEST_INTERVAL_MS = Math.ceil(1000 / DEFAULT_LOG_REQUESTS_PER_SECOND);
156
+ let lastLogRequestStartedAtMs = 0;
155
157
 
156
158
  async function prepareDeploymentArtifacts(chainId) {
157
159
  const normalizedChainId = Number(chainId);
@@ -2030,6 +2032,7 @@ async function recoverDeliveredNotesFromEventLogs({
2030
2032
  const nullifierUsedSlot = ethers.toBigInt(findStorageSlot(storageLayoutManifest, "PrivateStateController", "nullifierUsed"));
2031
2033
  const observedLogs = await fetchLogsChunked(provider, {
2032
2034
  address: context.workspace.channelManager,
2035
+ topics: [NOTE_VALUE_ENCRYPTED_TOPIC],
2033
2036
  fromBlock: scanStartBlock,
2034
2037
  toBlock: latestBlock,
2035
2038
  });
@@ -3453,10 +3456,7 @@ async function buildGrothTransition({ operationDir, workspace, stateManager, vau
3453
3456
 
3454
3457
  const inputPath = path.join(operationDir, "input.json");
3455
3458
  writeJson(inputPath, input);
3456
- const proofManifest = await generateUpdateTreeProof([
3457
- "--input",
3458
- inputPath,
3459
- ]);
3459
+ const proofManifest = runGroth16UpdateTreeProof(inputPath);
3460
3460
 
3461
3461
  const proofJson = readJson(proofManifest.proofPath);
3462
3462
  const publicSignals = readJson(proofManifest.publicPath);
@@ -3503,6 +3503,21 @@ function runCaptured(command, args, { cwd = defaultCommandCwd, env = process.env
3503
3503
  };
3504
3504
  }
3505
3505
 
3506
+ function runGroth16UpdateTreeProof(inputPath) {
3507
+ const packageRoot = resolveGroth16PackageRoot();
3508
+ const entryPath = resolveGroth16CliEntryPath(packageRoot);
3509
+ run(process.execPath, [entryPath, "--prove", inputPath], { cwd: packageRoot });
3510
+ const manifestPath = groth16ProofManifestPath();
3511
+ const manifest = readJson(manifestPath);
3512
+ expect(typeof manifest.proofPath === "string" && manifest.proofPath.length > 0, "Groth16 proof manifest is missing proofPath.");
3513
+ expect(typeof manifest.publicPath === "string" && manifest.publicPath.length > 0, "Groth16 proof manifest is missing publicPath.");
3514
+ return manifest;
3515
+ }
3516
+
3517
+ function groth16ProofManifestPath() {
3518
+ return path.join(os.homedir(), "tokamak-private-channels", "groth16", "proof", "proof-manifest.json");
3519
+ }
3520
+
3506
3521
  function runTokamakProofPipeline({ operationDir, bundlePath }) {
3507
3522
  runTokamakCliStage({
3508
3523
  operationDir,
@@ -3854,30 +3869,39 @@ async function reconstructChannelSnapshot({
3854
3869
  provider,
3855
3870
  );
3856
3871
  const latestBlock = await provider.getBlockNumber();
3857
- const rootEvents = await queryContractEventsChunked({
3858
- contract: channelManager,
3859
- eventName: "CurrentRootVectorObserved",
3872
+ const currentRootVectorObservedTopic =
3873
+ normalizeBytes32Hex(channelManager.interface.getEvent("CurrentRootVectorObserved").topicHash);
3874
+ const channelManagerLogs = await fetchLogsChunked(provider, {
3875
+ address: channelInfo.manager,
3876
+ topics: [[
3877
+ currentRootVectorObservedTopic,
3878
+ CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC,
3879
+ VAULT_STORAGE_WRITE_OBSERVED_TOPIC,
3880
+ ]],
3860
3881
  fromBlock: genesisBlockNumber,
3861
3882
  toBlock: latestBlock,
3862
3883
  });
3884
+ const channelManagerEvents = channelManagerLogs.map((log) => {
3885
+ const topic0 = log.topics[0] ? normalizeBytes32Hex(log.topics[0]) : null;
3886
+ if (topic0 !== null && ethers.toBigInt(topic0) === ethers.toBigInt(currentRootVectorObservedTopic)) {
3887
+ const parsed = channelManager.interface.parseLog(log);
3888
+ return {
3889
+ ...log,
3890
+ args: parsed.args,
3891
+ fragment: parsed.fragment,
3892
+ };
3893
+ }
3894
+ return log;
3895
+ });
3863
3896
  const vaultStorageWriteEvents = await queryContractEventsChunked({
3864
3897
  contract: bridgeTokenVault,
3865
3898
  eventName: "StorageWriteObserved",
3866
3899
  fromBlock: genesisBlockNumber,
3867
3900
  toBlock: latestBlock,
3868
3901
  });
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
3902
 
3879
3903
  const groupedEvents = new Map();
3880
- for (const event of [...rootEvents, ...vaultStorageWriteEvents, ...observedStorageLogs]) {
3904
+ for (const event of [...channelManagerEvents, ...vaultStorageWriteEvents]) {
3881
3905
  const key = event.transactionHash;
3882
3906
  const group = groupedEvents.get(key) ?? [];
3883
3907
  group.push(event);
@@ -4000,6 +4024,7 @@ async function fetchLogsChunked(provider, {
4000
4024
  while (cursor <= resolvedToBlock) {
4001
4025
  const chunkToBlock = Math.min(resolvedToBlock, cursor + chunkSize - 1);
4002
4026
  try {
4027
+ await throttleLogRequest();
4003
4028
  const logs = await provider.getLogs({
4004
4029
  address,
4005
4030
  topics,
@@ -4009,6 +4034,12 @@ async function fetchLogsChunked(provider, {
4009
4034
  aggregatedLogs.push(...logs);
4010
4035
  cursor = chunkToBlock + 1;
4011
4036
  } catch (error) {
4037
+ if (isRateLimitError(error)) {
4038
+ throw new Error(
4039
+ `RPC log query rate limit exceeded. Log chunk requests are paced at ${DEFAULT_LOG_REQUESTS_PER_SECOND} requests per second.`,
4040
+ { cause: error },
4041
+ );
4042
+ }
4012
4043
  const suggestedChunkSize = deriveRecommendedLogChunkSize(error, chunkSize);
4013
4044
  if (suggestedChunkSize >= chunkSize) {
4014
4045
  throw error;
@@ -4020,6 +4051,31 @@ async function fetchLogsChunked(provider, {
4020
4051
  return aggregatedLogs;
4021
4052
  }
4022
4053
 
4054
+ async function throttleLogRequest() {
4055
+ const elapsedMs = Date.now() - lastLogRequestStartedAtMs;
4056
+ if (elapsedMs < LOG_REQUEST_INTERVAL_MS) {
4057
+ await sleep(LOG_REQUEST_INTERVAL_MS - elapsedMs);
4058
+ }
4059
+ lastLogRequestStartedAtMs = Date.now();
4060
+ }
4061
+
4062
+ function sleep(ms) {
4063
+ return new Promise((resolve) => setTimeout(resolve, ms));
4064
+ }
4065
+
4066
+ function isRateLimitError(error) {
4067
+ const serializedError = [
4068
+ error?.code,
4069
+ error?.status,
4070
+ error?.message,
4071
+ error?.shortMessage,
4072
+ error?.info?.responseStatus,
4073
+ error?.info?.responseBody,
4074
+ ].filter((value) => value !== undefined && value !== null).join("\n");
4075
+
4076
+ return /\b429\b|too many requests|rate limit|compute units/i.test(serializedError);
4077
+ }
4078
+
4023
4079
  function deriveRecommendedLogChunkSize(error, currentChunkSize) {
4024
4080
  const serializedError = [
4025
4081
  error?.message,