@tokamak-private-dapps/private-state-cli 1.2.0 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1 - 2026-05-11
4
+
5
+ - Changed pre-command automatic recovery from an RPC log request time estimate to a fixed
6
+ 7,200-block recovery delta budget.
7
+
3
8
  ## 1.2.0 - 2026-05-08
4
9
 
5
10
  - Added optional channel workspace mirror recovery. `channel recover-workspace` now accepts
@@ -20,8 +25,6 @@
20
25
  when a genesis rebuild is required.
21
26
  - Restored received-note event-log refresh for `wallet get-notes`, `wallet transfer-notes`, and
22
27
  `wallet redeem-notes`, limited to the saved wallet note recovery index.
23
- - Limited pre-command automatic recovery to a 10 second RPC log scan budget based on the CLI's
24
- paced log query rate.
25
28
  - Reworked workspace mirror recovery around leader-signed checkpoint manifests and
26
29
  delta bundles. When a local recovery index exists, the CLI prechecks the mirror checkpoint and
27
30
  downloads only the matching delta bundle instead of a full workspace bundle.
package/README.md CHANGED
@@ -118,9 +118,9 @@ saved index instead of silently replaying from genesis.
118
118
  Wallet getter commands that need channel state, including `wallet get-meta`, `wallet get-channel-fund`, and
119
119
  `wallet get-notes`, refresh stale local workspaces through saved recovery indexes before reading state. `wallet get-notes`
120
120
  also refreshes received-note logs through the saved wallet note recovery index. Automatic refresh never replays from
121
- channel genesis and only runs when the estimated RPC log scan fits within the 10 second pre-command budget. If a saved
122
- index is missing, unusable, or too far behind, the command stops and asks the user to run the appropriate recovery
123
- command with `--from-genesis` explicitly when needed.
121
+ channel genesis and only runs when the recovery delta fits within the 7,200-block pre-command budget. If a saved index
122
+ is missing, unusable, or too far behind, the command stops and asks the user to run the appropriate recovery command
123
+ with `--from-genesis` explicitly when needed.
124
124
 
125
125
  Channel leaders can optionally register a workspace mirror server so users can bootstrap recovery
126
126
  from a signed checkpoint and download only the local-to-checkpoint delta when a local recovery index
@@ -341,7 +341,7 @@ Suggested interaction flow:
341
341
  `wallet mint-notes`.
342
342
  7. For a private transfer, select available note IDs from `wallet get-notes`, find the recipient L2 address from
343
343
  `wallet get-meta`, then build `wallet transfer-notes`.
344
- 8. After transfer, guide the recipient to run `wallet get-notes`; it refreshes received notes from the saved recovery index when the delta fits the 10 second pre-command budget. If the index is missing or too far behind, explain `wallet recover-workspace --from-genesis`.
344
+ 8. After transfer, guide the recipient to run `wallet get-notes`; it refreshes received notes from the saved recovery index when the delta fits the 7,200-block pre-command budget. If the index is missing or too far behind, explain `wallet recover-workspace --from-genesis`.
345
345
 
346
346
  Example onboarding explanation for `channel join`:
347
347
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamak-private-dapps/private-state-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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",
@@ -207,8 +207,7 @@ const ZERO_TOPIC = normalizeBytes32Hex(ethers.ZeroHash);
207
207
  const DEFAULT_LOG_CHUNK_SIZE = 2000;
208
208
  const DEFAULT_LOG_REQUESTS_PER_SECOND = 5;
209
209
  const LOG_REQUEST_INTERVAL_MS = Math.ceil(1000 / DEFAULT_LOG_REQUESTS_PER_SECOND);
210
- const AUTO_RECOVERY_TIME_BUDGET_SECONDS = 10;
211
- const AUTO_RECOVERY_LOG_REQUEST_BUDGET = DEFAULT_LOG_REQUESTS_PER_SECOND * AUTO_RECOVERY_TIME_BUDGET_SECONDS;
210
+ const AUTO_RECOVERY_BLOCK_BUDGET = 7200;
212
211
  let lastLogRequestStartedAtMs = 0;
213
212
 
214
213
  function printImmutableChannelPolicyWarning({
@@ -4456,7 +4455,7 @@ async function handleRedeemNotes({ args, provider }) {
4456
4455
  context: preparedContextResult.context,
4457
4456
  provider,
4458
4457
  progressAction: "wallet redeem-notes",
4459
- preConsumedLogRequests: preparedContextResult.autoRecoveryLogRequests,
4458
+ preConsumedBlockDelta: preparedContextResult.autoRecoveryBlockDelta,
4460
4459
  });
4461
4460
  const inputNotes = loadWalletUnusedInputNotes(wallet, noteIds);
4462
4461
  const templatePayload = buildRedeemNotesTemplatePayload({
@@ -4519,7 +4518,7 @@ async function handleWalletGetNotes({ args, provider }) {
4519
4518
  context,
4520
4519
  provider,
4521
4520
  progressAction: "wallet get-notes",
4522
- preConsumedLogRequests: contextResult.autoRecoveryLogRequests,
4521
+ preConsumedBlockDelta: contextResult.autoRecoveryBlockDelta,
4523
4522
  });
4524
4523
 
4525
4524
  const unusedTrackedNotes = wallet.wallet.notes.unusedOrder
@@ -4580,7 +4579,7 @@ async function handleTransferNotes({ args, provider }) {
4580
4579
  provider,
4581
4580
  signer,
4582
4581
  progressAction: "wallet transfer-notes",
4583
- preConsumedLogRequests: preparedContextResult.autoRecoveryLogRequests,
4582
+ preConsumedBlockDelta: preparedContextResult.autoRecoveryBlockDelta,
4584
4583
  });
4585
4584
  const canonicalAssetDecimals = Number(wallet.wallet.canonicalAssetDecimals);
4586
4585
  const noteIds = parseNoteIdVector(requireArg(args.noteIds, "--note-ids"));
@@ -5142,7 +5141,7 @@ async function ensureWalletNoteReceiveStateCurrent({
5142
5141
  provider,
5143
5142
  signer = null,
5144
5143
  progressAction = null,
5145
- preConsumedLogRequests = 0,
5144
+ preConsumedBlockDelta = 0,
5146
5145
  }) {
5147
5146
  const latestBlock = await provider.getBlockNumber();
5148
5147
  let nextBlock;
@@ -5167,17 +5166,16 @@ async function ensureWalletNoteReceiveStateCurrent({
5167
5166
  nextBlock,
5168
5167
  recoveredWalletWorkspace: false,
5169
5168
  recoveredDeliveryState: null,
5170
- autoRecoveryLogRequests: 0,
5169
+ autoRecoveryBlockDelta: 0,
5171
5170
  };
5172
5171
  }
5173
- const remainingLogRequestBudget = AUTO_RECOVERY_LOG_REQUEST_BUDGET - Math.max(0, Number(preConsumedLogRequests));
5174
- const autoRecoveryLogRequests = assertAutoRecoveryLogScanBudget({
5172
+ const remainingBlockBudget = AUTO_RECOVERY_BLOCK_BUDGET - Math.max(0, Number(preConsumedBlockDelta));
5173
+ const autoRecoveryBlockDelta = assertAutoRecoveryBlockBudget({
5175
5174
  label: `wallet note workspace ${walletContext.walletName}`,
5176
5175
  fromBlock: nextBlock,
5177
5176
  toBlock: latestBlock,
5178
- logScanCount: 1,
5179
5177
  recoveryCommand: `wallet recover-workspace --channel-name ${context.workspace.channelName} --network ${context.workspace.network} --account <ACCOUNT>`,
5180
- logRequestBudget: remainingLogRequestBudget,
5178
+ blockBudget: remainingBlockBudget,
5181
5179
  });
5182
5180
 
5183
5181
  const resolvedSigner = signer ?? restoreWalletParticipant(walletContext, provider).signer;
@@ -5205,7 +5203,7 @@ async function ensureWalletNoteReceiveStateCurrent({
5205
5203
  ...freshness,
5206
5204
  recoveredWalletWorkspace: true,
5207
5205
  recoveredDeliveryState,
5208
- autoRecoveryLogRequests,
5206
+ autoRecoveryBlockDelta,
5209
5207
  };
5210
5208
  } catch (postRecoveryError) {
5211
5209
  throw new Error([
@@ -5721,7 +5719,7 @@ async function loadFreshWalletChannelContext({
5721
5719
  network: resolveCliNetwork(contextResult.context.workspace.network),
5722
5720
  usingWorkspaceCache: !contextResult.recoveredWorkspace,
5723
5721
  recoveredWorkspace: contextResult.recoveredWorkspace,
5724
- autoRecoveryLogRequests: contextResult.autoRecoveryLogRequests,
5722
+ autoRecoveryBlockDelta: contextResult.autoRecoveryBlockDelta,
5725
5723
  };
5726
5724
  }
5727
5725
 
@@ -5753,7 +5751,7 @@ async function loadFreshChannelWorkspaceContextResult({
5753
5751
  return {
5754
5752
  context,
5755
5753
  recoveredWorkspace: false,
5756
- autoRecoveryLogRequests: 0,
5754
+ autoRecoveryBlockDelta: 0,
5757
5755
  };
5758
5756
  } catch (error) {
5759
5757
  const recovery = await recoverChannelWorkspaceFromIndexOnly({
@@ -5766,7 +5764,7 @@ async function loadFreshChannelWorkspaceContextResult({
5766
5764
  return {
5767
5765
  context: recovery.context,
5768
5766
  recoveredWorkspace: true,
5769
- autoRecoveryLogRequests: recovery.autoRecoveryLogRequests,
5767
+ autoRecoveryBlockDelta: recovery.autoRecoveryBlockDelta,
5770
5768
  };
5771
5769
  }
5772
5770
  }
@@ -5790,7 +5788,7 @@ async function recoverChannelWorkspaceFromIndexOnly({
5790
5788
  if (readiness.alreadyCurrent) {
5791
5789
  return {
5792
5790
  context: await loadWorkspaceContext(channelName, networkName, provider),
5793
- autoRecoveryLogRequests: 0,
5791
+ autoRecoveryBlockDelta: 0,
5794
5792
  };
5795
5793
  }
5796
5794
  try {
@@ -5830,7 +5828,7 @@ async function recoverChannelWorkspaceFromIndexOnly({
5830
5828
  }
5831
5829
  return {
5832
5830
  context,
5833
- autoRecoveryLogRequests: readiness.autoRecoveryLogRequests,
5831
+ autoRecoveryBlockDelta: readiness.autoRecoveryBlockDelta,
5834
5832
  };
5835
5833
  }
5836
5834
 
@@ -5886,14 +5884,13 @@ async function requireChannelWorkspaceRecoveryIndexForAutoRefresh({
5886
5884
  if (Number(recoveryIndex.nextBlock) > Number(latestBlock)) {
5887
5885
  fail(`Channel workspace recovery index has already scanned through block ${recoveryIndex.nextBlock - 1}, but the local snapshot is not current.`);
5888
5886
  }
5889
- const autoRecoveryLogRequests = assertAutoRecoveryLogScanBudget({
5887
+ const autoRecoveryBlockDelta = assertAutoRecoveryBlockBudget({
5890
5888
  label: `channel workspace ${channelName} on ${networkName}`,
5891
5889
  fromBlock: recoveryIndex.nextBlock,
5892
5890
  toBlock: latestBlock,
5893
- logScanCount: 2,
5894
5891
  recoveryCommand: `channel recover-workspace --channel-name ${channelName} --network ${networkName}`,
5895
5892
  });
5896
- return { alreadyCurrent: false, autoRecoveryLogRequests };
5893
+ return { alreadyCurrent: false, autoRecoveryBlockDelta };
5897
5894
  }
5898
5895
 
5899
5896
  async function refreshPersistedWorkspaceAfterLocalTransaction({
@@ -7416,12 +7413,7 @@ async function fetchLogsChunked(provider, {
7416
7413
  return aggregatedLogs;
7417
7414
  }
7418
7415
 
7419
- function estimateLogScanRequestCount({
7420
- fromBlock,
7421
- toBlock,
7422
- logScanCount = 1,
7423
- chunkSize = DEFAULT_LOG_CHUNK_SIZE,
7424
- }) {
7416
+ function recoveryBlockDelta({ fromBlock, toBlock }) {
7425
7417
  const normalizedFromBlock = Number(fromBlock);
7426
7418
  const normalizedToBlock = Number(toBlock);
7427
7419
  if (!Number.isInteger(normalizedFromBlock) || !Number.isInteger(normalizedToBlock)) {
@@ -7430,38 +7422,27 @@ function estimateLogScanRequestCount({
7430
7422
  if (normalizedFromBlock > normalizedToBlock) {
7431
7423
  return 0;
7432
7424
  }
7433
- const totalBlocks = normalizedToBlock - normalizedFromBlock + 1;
7434
- return Math.ceil(totalBlocks / Math.max(1, Number(chunkSize))) * Math.max(1, Number(logScanCount));
7425
+ return normalizedToBlock - normalizedFromBlock + 1;
7435
7426
  }
7436
7427
 
7437
- function assertAutoRecoveryLogScanBudget({
7428
+ function assertAutoRecoveryBlockBudget({
7438
7429
  label,
7439
7430
  fromBlock,
7440
7431
  toBlock,
7441
- logScanCount,
7442
7432
  recoveryCommand,
7443
- logRequestBudget = AUTO_RECOVERY_LOG_REQUEST_BUDGET,
7433
+ blockBudget = AUTO_RECOVERY_BLOCK_BUDGET,
7444
7434
  }) {
7445
- const estimatedRequests = estimateLogScanRequestCount({
7446
- fromBlock,
7447
- toBlock,
7448
- logScanCount,
7449
- });
7450
- const normalizedBudget = Math.max(0, Number(logRequestBudget));
7451
- if (estimatedRequests <= normalizedBudget) {
7452
- return estimatedRequests;
7435
+ const blockDelta = recoveryBlockDelta({ fromBlock, toBlock });
7436
+ const normalizedBudget = Math.max(0, Number(blockBudget));
7437
+ if (blockDelta <= normalizedBudget) {
7438
+ return blockDelta;
7453
7439
  }
7454
7440
  const normalizedFromBlock = Number(fromBlock);
7455
7441
  const normalizedToBlock = Number(toBlock);
7456
- const totalBlocks = normalizedFromBlock <= normalizedToBlock
7457
- ? normalizedToBlock - normalizedFromBlock + 1
7458
- : 0;
7459
- const estimatedSeconds = estimatedRequests / DEFAULT_LOG_REQUESTS_PER_SECOND;
7460
7442
  throw new Error([
7461
- `Automatic recovery for ${label} would exceed the ${AUTO_RECOVERY_TIME_BUDGET_SECONDS}s pre-command budget.`,
7462
- `Recovery delta is ${totalBlocks} blocks from ${normalizedFromBlock} to ${normalizedToBlock}.`,
7463
- `Estimated log requests: ${estimatedRequests}; remaining budget: ${normalizedBudget} of ${AUTO_RECOVERY_LOG_REQUEST_BUDGET} at ${DEFAULT_LOG_REQUESTS_PER_SECOND}/s.`,
7464
- `Estimated minimum scan time: ${estimatedSeconds.toFixed(1)}s.`,
7443
+ `Automatic recovery for ${label} would exceed the ${AUTO_RECOVERY_BLOCK_BUDGET}-block pre-command budget.`,
7444
+ `Recovery delta is ${blockDelta} blocks from ${normalizedFromBlock} to ${normalizedToBlock}.`,
7445
+ `Remaining automatic recovery budget is ${normalizedBudget} blocks.`,
7465
7446
  `Run ${recoveryCommand} first; add --from-genesis only if the saved recovery index is unusable.`,
7466
7447
  ].join(" "));
7467
7448
  }