@tokamak-private-dapps/private-state-cli 2.2.0 → 2.3.0
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 +12 -0
- package/README.md +6 -0
- package/commands/system.mjs +6 -0
- package/lib/private-state-cli-command-registry.mjs +22 -3
- package/lib/private-state-runtime-management.mjs +21 -26
- package/lib/runtime.mjs +305 -72
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.3.0 - 2026-05-20
|
|
6
|
+
|
|
7
|
+
- Added `help observer` to print the deployed public observer URL for the private-state monitoring
|
|
8
|
+
surface.
|
|
9
|
+
- Documented the deployed public observer in the monitoring audit packet and observability matrix.
|
|
10
|
+
|
|
11
|
+
## 2.2.1 - 2026-05-18
|
|
12
|
+
|
|
13
|
+
- Added `channel recover-workspace --source rpc --output-raw` to append raw JSON-RPC request and response history
|
|
14
|
+
to method-specific JSON files under the channel workspace `rpcCallHistory/` directory, with `eth_getLogs` split by event.
|
|
15
|
+
Indexed recovery appends to existing history; `--from-genesis` overwrites it with one full genesis-to-latest scan.
|
|
16
|
+
|
|
5
17
|
## 2.2.0 - 2026-05-18
|
|
6
18
|
|
|
7
19
|
- Added `install --read-only` for channel-state read commands and commands that do not depend on channel state. This
|
package/README.md
CHANGED
|
@@ -192,6 +192,12 @@ run is interrupted, the next non-`--from-genesis` RPC recovery resumes from the
|
|
|
192
192
|
can also start from that local checkpoint: it uses a matching delta bundle when one is available, otherwise a newer
|
|
193
193
|
verified full mirror checkpoint replaces the local checkpoint before RPC catch-up.
|
|
194
194
|
|
|
195
|
+
Use `channel recover-workspace --source rpc --output-raw` when you need to preserve the raw JSON-RPC request and
|
|
196
|
+
response history for inspection. The CLI appends calls to method-specific JSON files, and splits `eth_getLogs` into
|
|
197
|
+
event-specific files such as `eth_getLogs.CurrentRootVectorObserved.json`, under
|
|
198
|
+
`~/tokamak-private-channels/workspace/<network>/<channel>/channel/rpcCallHistory/`. Indexed recovery appends to the
|
|
199
|
+
existing history, while `--from-genesis` overwrites it with one full genesis-to-latest scan.
|
|
200
|
+
|
|
195
201
|
`channel create` is the exception: after the channel is created on-chain, the CLI initializes that new local workspace
|
|
196
202
|
by replaying from the channel's genesis block because no prior recovery index can exist for a new channel.
|
|
197
203
|
|
package/commands/system.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertDoctorArgs,
|
|
3
3
|
assertGuideArgs,
|
|
4
|
+
assertObserverArgs,
|
|
4
5
|
assertInstallZkEvmArgs,
|
|
5
6
|
assertSetRpcArgs,
|
|
6
7
|
assertTransactionFeesArgs,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
assertUpdateArgs,
|
|
9
10
|
handleDoctor,
|
|
10
11
|
handleGuide,
|
|
12
|
+
handleObserver,
|
|
11
13
|
handleInstallZkEvm,
|
|
12
14
|
handleSetRpc,
|
|
13
15
|
handleTransactionFees,
|
|
@@ -41,6 +43,10 @@ export const systemCommands = Object.freeze({
|
|
|
41
43
|
assertGuideArgs(args);
|
|
42
44
|
await handleGuide({ args });
|
|
43
45
|
},
|
|
46
|
+
"help-observer": async (args) => {
|
|
47
|
+
assertObserverArgs(args);
|
|
48
|
+
handleObserver();
|
|
49
|
+
},
|
|
44
50
|
"help-transaction-fees": async (args) => {
|
|
45
51
|
assertTransactionFeesArgs(args);
|
|
46
52
|
const { network, provider, rpcUrl } = loadExplicitCommandRuntime(args);
|
|
@@ -202,6 +202,13 @@ export const PRIVATE_STATE_CLI_FIELD_CATALOG = Object.freeze({
|
|
|
202
202
|
option: "--from-genesis",
|
|
203
203
|
optional: true,
|
|
204
204
|
},
|
|
205
|
+
outputRaw: {
|
|
206
|
+
label: "Output Raw RPC History",
|
|
207
|
+
type: "checkbox",
|
|
208
|
+
hint: "With channel recover-workspace --source rpc, preserve raw JSON-RPC request and response history under the channel workspace.",
|
|
209
|
+
option: "--output-raw",
|
|
210
|
+
optional: true,
|
|
211
|
+
},
|
|
205
212
|
source: {
|
|
206
213
|
label: "Recovery Source",
|
|
207
214
|
type: "select",
|
|
@@ -322,6 +329,17 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
322
329
|
usage: "optional --network, --channel-name, --account, and --wallet",
|
|
323
330
|
help: ["Does not accept --rpc-url and never writes RPC configuration"],
|
|
324
331
|
},
|
|
332
|
+
{
|
|
333
|
+
id: "help-observer",
|
|
334
|
+
display: "help observer",
|
|
335
|
+
description: "Show the deployed public observer URL.",
|
|
336
|
+
fields: [],
|
|
337
|
+
usage: "no options",
|
|
338
|
+
help: [
|
|
339
|
+
"Prints the deployed observer URL so terminals can present it as a clickable link",
|
|
340
|
+
"The observer is a public monitoring surface; it is not a wallet, key manager, or disclosure authority",
|
|
341
|
+
],
|
|
342
|
+
},
|
|
325
343
|
{
|
|
326
344
|
id: "help-transaction-fees",
|
|
327
345
|
display: "help transaction-fees",
|
|
@@ -383,8 +401,8 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
383
401
|
display: "channel recover-workspace",
|
|
384
402
|
description: "Rebuild the local channel workspace from bridge state.",
|
|
385
403
|
installMode: "read-only",
|
|
386
|
-
fields: ["channelName", "network", "source", "fromGenesis"],
|
|
387
|
-
usage: "--channel-name, --network, optional --source, optional --from-genesis",
|
|
404
|
+
fields: ["channelName", "network", "source", "fromGenesis", "outputRaw"],
|
|
405
|
+
usage: "--channel-name, --network, optional --source, optional --from-genesis, optional --output-raw",
|
|
388
406
|
help: [
|
|
389
407
|
"By default, --source rpc resumes RPC log scanning from the workspace recovery index when available",
|
|
390
408
|
"--source mirror validates the channel leader's registered checkpoint manifest, downloads only the needed checkpoint or delta bundle, and then replays RPC logs to latest",
|
|
@@ -392,6 +410,7 @@ export const PRIVATE_STATE_CLI_COMMANDS = Object.freeze([
|
|
|
392
410
|
"Mirror recovery uses a matching delta bundle when available; otherwise a newer verified full checkpoint replaces the local checkpoint before RPC catch-up",
|
|
393
411
|
"Fails instead of falling back to genesis when no usable recovery index exists",
|
|
394
412
|
"Use --source rpc --from-genesis to ignore the recovery index and replay logs from channel genesis",
|
|
413
|
+
"--output-raw with --source rpc appends raw JSON-RPC request and response history to method-specific JSON files under the channel workspace rpcCallHistory directory; eth_getLogs is split by event",
|
|
395
414
|
"--from-genesis moves the existing local channel workspace to workspace-rebuild-backups before writing the current-format workspace; local secrets are preserved",
|
|
396
415
|
"Prints RPC log scan progress while rebuilding the workspace",
|
|
397
416
|
],
|
|
@@ -731,7 +750,7 @@ export function privateStateCliCommandSynopsis(command) {
|
|
|
731
750
|
return null;
|
|
732
751
|
}
|
|
733
752
|
const valueLabel = field.valueLabel ?? field.placeholderLabel ?? `<${field.label?.toUpperCase().replace(/\s+/g, "_") ?? "VALUE"}>`;
|
|
734
|
-
const option = field.type === "checkbox"
|
|
753
|
+
const option = field.type === "checkbox"
|
|
735
754
|
? field.option
|
|
736
755
|
: `${field.option} ${valueLabel}`;
|
|
737
756
|
return field.optional || optionalFields.has(fieldKey) ? `[${option}]` : option;
|
|
@@ -108,10 +108,6 @@ function runCaptured(command, args, { cwd = defaultCommandCwd, env = process.env
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function requireSemverVersion(value, label) {
|
|
112
|
-
return requireExactSemverVersion(value, label);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
111
|
function readTokamakCliPackageReport(packageRoot = null) {
|
|
116
112
|
try {
|
|
117
113
|
const resolvedPackageRoot = packageRoot ?? resolveActiveTokamakCliPackageRoot();
|
|
@@ -234,19 +230,17 @@ function buildDoctorHumanRows(report) {
|
|
|
234
230
|
check: `${report.installManifest.mode} deployment artifacts`,
|
|
235
231
|
status: doctorStatus(report.checks.find((check) => check.name === `${report.installManifest.mode} deployment artifacts`)?.ok),
|
|
236
232
|
detail: [
|
|
237
|
-
`readOnlyChains=${report.deploymentArtifacts.
|
|
238
|
-
|
|
239
|
-
.map((entry) => entry.chainId)
|
|
240
|
-
.join(",") || "none"}`,
|
|
241
|
-
`fullChains=${report.deploymentArtifacts.chains
|
|
242
|
-
.filter((entry) => entry.modes[PRIVATE_STATE_INSTALL_MODES.FULL].ok)
|
|
243
|
-
.map((entry) => entry.chainId)
|
|
244
|
-
.join(",") || "none"}`,
|
|
233
|
+
`readOnlyChains=${formatInstalledArtifactChains(report.deploymentArtifacts, PRIVATE_STATE_INSTALL_MODES.READ_ONLY)}`,
|
|
234
|
+
`fullChains=${formatInstalledArtifactChains(report.deploymentArtifacts, PRIVATE_STATE_INSTALL_MODES.FULL)}`,
|
|
245
235
|
].join(" "),
|
|
246
236
|
},
|
|
247
237
|
];
|
|
248
238
|
}
|
|
249
239
|
|
|
240
|
+
function formatInstalledArtifactChains(artifactReadiness, mode) {
|
|
241
|
+
return installedArtifactChainIds(artifactReadiness, mode).join(",") || "none";
|
|
242
|
+
}
|
|
243
|
+
|
|
250
244
|
function formatDoctorTable(rows) {
|
|
251
245
|
const headers = ["Check", "Status", "Detail"];
|
|
252
246
|
const checkWidth = Math.max(headers[0].length, ...rows.map((row) => row.check.length));
|
|
@@ -660,19 +654,20 @@ function buildCommandAvailability({ artifactReadiness, installMode, tokamakCli,
|
|
|
660
654
|
requiredInstallMode,
|
|
661
655
|
available,
|
|
662
656
|
chains: requiredInstallMode === PRIVATE_STATE_INSTALL_MODES.FULL
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
: requiredInstallMode === PRIVATE_STATE_INSTALL_MODES.READ_ONLY
|
|
667
|
-
? artifactReadiness.chains
|
|
668
|
-
.filter((entry) => entry.modes[PRIVATE_STATE_INSTALL_MODES.READ_ONLY].ok)
|
|
669
|
-
.map((entry) => entry.chainId)
|
|
670
|
-
: [],
|
|
657
|
+
|| requiredInstallMode === PRIVATE_STATE_INSTALL_MODES.READ_ONLY
|
|
658
|
+
? installedArtifactChainIds(artifactReadiness, requiredInstallMode)
|
|
659
|
+
: [],
|
|
671
660
|
reasons,
|
|
672
661
|
};
|
|
673
662
|
});
|
|
674
663
|
}
|
|
675
664
|
|
|
665
|
+
function installedArtifactChainIds(artifactReadiness, mode) {
|
|
666
|
+
return artifactReadiness.chains
|
|
667
|
+
.filter((entry) => entry.modes[mode]?.ok)
|
|
668
|
+
.map((entry) => entry.chainId);
|
|
669
|
+
}
|
|
670
|
+
|
|
676
671
|
function buildSelectedRuntimeVersionCheck({ installManifest, installMode, tokamakCli, groth16Runtime }) {
|
|
677
672
|
if (installMode === PRIVATE_STATE_INSTALL_MODES.READ_ONLY) {
|
|
678
673
|
return {
|
|
@@ -752,16 +747,16 @@ async function resolveRequestedGroth16PackageVersion(requestedVersion) {
|
|
|
752
747
|
}
|
|
753
748
|
|
|
754
749
|
const bundledPackageJson = readJson(path.join(resolveGroth16PackageRoot(), "package.json"));
|
|
755
|
-
return
|
|
750
|
+
return requireExactSemverVersion(bundledPackageJson.version, `${GROTH16_PACKAGE_NAME} bundled package version`);
|
|
756
751
|
}
|
|
757
752
|
|
|
758
753
|
async function resolveRequestedNpmPackageVersion({ packageName, requestedVersion, optionName }) {
|
|
759
754
|
const metadata = await fetchNpmPackageMetadata(packageName);
|
|
760
755
|
if (requestedVersion === undefined || requestedVersion === null) {
|
|
761
|
-
return
|
|
756
|
+
return requireExactSemverVersion(metadata?.["dist-tags"]?.latest, `${packageName} npm latest version`);
|
|
762
757
|
}
|
|
763
758
|
|
|
764
|
-
const normalizedVersion =
|
|
759
|
+
const normalizedVersion = requireExactSemverVersion(requestedVersion, optionName);
|
|
765
760
|
if (!metadata.versions?.[normalizedVersion]) {
|
|
766
761
|
throw new Error(`npm package ${packageName} does not contain version ${normalizedVersion}.`);
|
|
767
762
|
}
|
|
@@ -832,7 +827,7 @@ async function installGroth16RuntimeForPrivateState({ version, docker }) {
|
|
|
832
827
|
}
|
|
833
828
|
|
|
834
829
|
function resolveGroth16RuntimePackageInstall(version) {
|
|
835
|
-
const normalizedVersion =
|
|
830
|
+
const normalizedVersion = requireExactSemverVersion(version, `${GROTH16_PACKAGE_NAME} version`);
|
|
836
831
|
const bundledPackageRoot = resolveGroth16PackageRoot();
|
|
837
832
|
const bundledPackageJson = readJson(path.join(bundledPackageRoot, "package.json"));
|
|
838
833
|
if (bundledPackageJson.name === GROTH16_PACKAGE_NAME && bundledPackageJson.version === normalizedVersion) {
|
|
@@ -930,7 +925,7 @@ function parseDriveFileIdFromDownloadUrl(value) {
|
|
|
930
925
|
|
|
931
926
|
function installManagedNpmPackage({ packageName, version, cacheBaseRoot = resolveArtifactCacheBaseRoot() }) {
|
|
932
927
|
const normalizedPackageName = requireNonEmptyString(packageName, "packageName");
|
|
933
|
-
const normalizedVersion =
|
|
928
|
+
const normalizedVersion = requireExactSemverVersion(version, `${normalizedPackageName} version`);
|
|
934
929
|
const installPrefix = managedNpmPackageInstallPrefix({
|
|
935
930
|
packageName: normalizedPackageName,
|
|
936
931
|
version: normalizedVersion,
|
|
@@ -965,7 +960,7 @@ function managedNpmPackageInstallPrefix({ packageName, version, cacheBaseRoot =
|
|
|
965
960
|
const safePackageName = requireNonEmptyString(packageName, "packageName")
|
|
966
961
|
.replace(/^@/, "")
|
|
967
962
|
.replace(/[^A-Za-z0-9._-]+/g, "__");
|
|
968
|
-
return path.join(privateStateCliRuntimeRoot(cacheBaseRoot), "npm", safePackageName,
|
|
963
|
+
return path.join(privateStateCliRuntimeRoot(cacheBaseRoot), "npm", safePackageName, requireExactSemverVersion(version, "version"));
|
|
969
964
|
}
|
|
970
965
|
|
|
971
966
|
async function downloadGroth16CrsArtifactsForPrivateState({
|
package/lib/runtime.mjs
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
deriveChannelTokenVaultLeafIndex,
|
|
53
53
|
deriveLiquidBalanceStorageKey,
|
|
54
54
|
fetchContractCodes,
|
|
55
|
+
getBlockInfoAt,
|
|
55
56
|
normalizeBytesHex,
|
|
56
57
|
normalizeBytes32Hex,
|
|
57
58
|
serializeBigInts,
|
|
@@ -136,6 +137,7 @@ const PRIVATE_STATE_UNINSTALL_CONFIRMATION =
|
|
|
136
137
|
const ACTION_IMPACT_CONFIRMATION =
|
|
137
138
|
"I understand the public and private impact of this action";
|
|
138
139
|
const PRIVATE_STATE_CLI_PACKAGE_NAME = privateStateCliPackageJson.name;
|
|
140
|
+
const PRIVATE_STATE_OBSERVER_URL = "https://project-scw1r.vercel.app";
|
|
139
141
|
const GROTH16_PACKAGE_NAME = "@tokamak-private-dapps/groth16";
|
|
140
142
|
const TOKAMAK_ZKEVM_CLI_PACKAGE_NAME = "@tokamak-zk-evm/cli";
|
|
141
143
|
const WALLET_BACKUP_EXPORT_FORMAT = "tokamak-private-state-wallet-backup-export";
|
|
@@ -554,25 +556,25 @@ function requireFlatDeploymentArtifactPathsForChainId(chainId) {
|
|
|
554
556
|
}
|
|
555
557
|
|
|
556
558
|
function requireInstalledDeploymentArtifacts(artifactPaths, chainId, mode) {
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!fs.existsSync(entry.path)) {
|
|
561
|
-
throw new Error(`Missing ${entry.label}: ${entry.path}.`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} catch (error) {
|
|
565
|
-
throw cliError(
|
|
566
|
-
CLI_ERROR_CODES.MISSING_DEPLOYMENT_ARTIFACTS,
|
|
567
|
-
[
|
|
568
|
-
`Missing ${mode} installed deployment artifacts for chain ${chainId} under ${artifactPaths.rootDir}.`,
|
|
569
|
-
mode === PRIVATE_STATE_INSTALL_MODES.FULL
|
|
570
|
-
? "Run install before running private-state CLI commands that write channel state."
|
|
571
|
-
: "Run install --read-only before running private-state CLI commands that read channel state.",
|
|
572
|
-
`Original error: ${error.message}`,
|
|
573
|
-
].join(" "),
|
|
574
|
-
);
|
|
559
|
+
const missingFiles = missingInstalledDeploymentArtifactFiles(artifactPaths, mode);
|
|
560
|
+
if (missingFiles.length === 0) {
|
|
561
|
+
return;
|
|
575
562
|
}
|
|
563
|
+
throw cliError(
|
|
564
|
+
CLI_ERROR_CODES.MISSING_DEPLOYMENT_ARTIFACTS,
|
|
565
|
+
[
|
|
566
|
+
`Missing ${mode} installed deployment artifacts for chain ${chainId} under ${artifactPaths.rootDir}.`,
|
|
567
|
+
mode === PRIVATE_STATE_INSTALL_MODES.FULL
|
|
568
|
+
? "Run install before running private-state CLI commands that write channel state."
|
|
569
|
+
: "Run install --read-only before running private-state CLI commands that read channel state.",
|
|
570
|
+
`Original error: ${missingFiles.map((entry) => `Missing ${entry.label}: ${entry.path}.`).join(" ")}`,
|
|
571
|
+
].join(" "),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function missingInstalledDeploymentArtifactFiles(artifactPaths, mode) {
|
|
576
|
+
return privateStateCliArtifactRequiredFiles(artifactPaths, mode)
|
|
577
|
+
.filter((entry) => !fs.existsSync(entry.path));
|
|
576
578
|
}
|
|
577
579
|
|
|
578
580
|
async function handleChannelCreate({ args, network, provider }) {
|
|
@@ -721,12 +723,14 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
721
723
|
const workspaceName = channelName;
|
|
722
724
|
const bridgeResources = loadBridgeResources({ chainId: network.chainId });
|
|
723
725
|
const recoverySource = resolveWorkspaceRecoverySource(args);
|
|
726
|
+
const outputRawRpcCallHistory = args.outputRaw === true;
|
|
724
727
|
|
|
725
728
|
const {
|
|
726
729
|
workspaceDir,
|
|
727
730
|
workspace,
|
|
728
731
|
currentSnapshot,
|
|
729
732
|
cleanRebuildBackup,
|
|
733
|
+
rpcCallHistory,
|
|
730
734
|
} = await syncChannelWorkspace({
|
|
731
735
|
workspaceName,
|
|
732
736
|
channelName,
|
|
@@ -738,6 +742,7 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
738
742
|
useWorkspaceRecoveryIndex: true,
|
|
739
743
|
fromGenesis: args.fromGenesis === true,
|
|
740
744
|
recoverySource,
|
|
745
|
+
outputRawRpcCallHistory,
|
|
741
746
|
progressAction: "channel recover-workspace",
|
|
742
747
|
});
|
|
743
748
|
|
|
@@ -757,6 +762,7 @@ async function handleWorkspaceInit({ args, network, provider }) {
|
|
|
757
762
|
recoveryLastScannedBlock: workspace.recoveryLastScannedBlock,
|
|
758
763
|
recoveryRootVectorHash: workspace.recoveryRootVectorHash,
|
|
759
764
|
recoveryScanRange: workspace.recoveryScanRange,
|
|
765
|
+
rpcCallHistory,
|
|
760
766
|
workspaceMirror: workspace.workspaceMirror ?? null,
|
|
761
767
|
});
|
|
762
768
|
}
|
|
@@ -1968,6 +1974,7 @@ async function syncChannelWorkspace({
|
|
|
1968
1974
|
useWorkspaceRecoveryIndex = false,
|
|
1969
1975
|
fromGenesis = false,
|
|
1970
1976
|
recoverySource = "rpc",
|
|
1977
|
+
outputRawRpcCallHistory = false,
|
|
1971
1978
|
minimumToBlock = null,
|
|
1972
1979
|
progressAction = null,
|
|
1973
1980
|
}) {
|
|
@@ -1993,8 +2000,15 @@ async function syncChannelWorkspace({
|
|
|
1993
2000
|
});
|
|
1994
2001
|
}
|
|
1995
2002
|
|
|
2003
|
+
const rpcCallHistoryRecorder = outputRawRpcCallHistory
|
|
2004
|
+
? createRpcCallHistoryRecorder({ workspaceDir })
|
|
2005
|
+
: null;
|
|
2006
|
+
const activeProvider = rpcCallHistoryRecorder
|
|
2007
|
+
? attachRpcCallHistoryRecorderToProvider(provider, rpcCallHistoryRecorder)
|
|
2008
|
+
: provider;
|
|
2009
|
+
|
|
1996
2010
|
const { bridgeDeployment, bridgeAbiManifest } = bridgeResources;
|
|
1997
|
-
const bridgeCore = new Contract(bridgeDeployment.bridgeCore, bridgeAbiManifest.contracts.bridgeCore.abi,
|
|
2011
|
+
const bridgeCore = new Contract(bridgeDeployment.bridgeCore, bridgeAbiManifest.contracts.bridgeCore.abi, activeProvider);
|
|
1998
2012
|
const channelId = deriveChannelIdFromName(channelName);
|
|
1999
2013
|
const channelInfo = await bridgeCore.getChannel(channelId);
|
|
2000
2014
|
if (!channelInfo.exists) {
|
|
@@ -2004,13 +2018,13 @@ async function syncChannelWorkspace({
|
|
|
2004
2018
|
const channelManager = new Contract(
|
|
2005
2019
|
channelInfo.manager,
|
|
2006
2020
|
bridgeAbiManifest.contracts.channelManager.abi,
|
|
2007
|
-
|
|
2021
|
+
activeProvider,
|
|
2008
2022
|
);
|
|
2009
2023
|
const canonicalAsset = getAddress(channelInfo.asset);
|
|
2010
|
-
const canonicalAssetDecimals = await fetchTokenDecimals(
|
|
2024
|
+
const canonicalAssetDecimals = await fetchTokenDecimals(activeProvider, canonicalAsset);
|
|
2011
2025
|
const currentRootVectorHash = normalizeBytes32Hex(await channelManager.currentRootVectorHash());
|
|
2012
2026
|
const genesisBlockNumber = Number(await channelManager.genesisBlockNumber());
|
|
2013
|
-
const observedLatestBlock = await
|
|
2027
|
+
const observedLatestBlock = await activeProvider.getBlockNumber();
|
|
2014
2028
|
const latestBlock = minimumToBlock === null
|
|
2015
2029
|
? observedLatestBlock
|
|
2016
2030
|
: Math.max(observedLatestBlock, Number(minimumToBlock));
|
|
@@ -2038,8 +2052,8 @@ async function syncChannelWorkspace({
|
|
|
2038
2052
|
`Managed storage vector does not include L2 accounting vault ${l2AccountingVaultAddress}.`,
|
|
2039
2053
|
);
|
|
2040
2054
|
|
|
2041
|
-
const contractCodes = await fetchContractCodes(
|
|
2042
|
-
const blockInfo = await
|
|
2055
|
+
const contractCodes = await fetchContractCodes(activeProvider, managedStorageAddresses);
|
|
2056
|
+
const blockInfo = await getBlockInfoAt(activeProvider, genesisBlockNumber);
|
|
2043
2057
|
const derivedAPubBlockHash = normalizeBytes32Hex(hashTokamakPublicInputs(encodeTokamakBlockInfo(blockInfo)));
|
|
2044
2058
|
expect(
|
|
2045
2059
|
ethers.toBigInt(derivedAPubBlockHash) === ethers.toBigInt(normalizeBytes32Hex(channelInfo.aPubBlockHash)),
|
|
@@ -2154,6 +2168,10 @@ async function syncChannelWorkspace({
|
|
|
2154
2168
|
});
|
|
2155
2169
|
}
|
|
2156
2170
|
: null;
|
|
2171
|
+
rpcCallHistoryRecorder?.setScanRange({
|
|
2172
|
+
fromBlock: selectedRecoveryIndex?.nextBlock ?? genesisBlockNumber,
|
|
2173
|
+
toBlock: latestBlock,
|
|
2174
|
+
});
|
|
2157
2175
|
const reconstruction = localSnapshotReusable
|
|
2158
2176
|
? {
|
|
2159
2177
|
currentSnapshot: existingArtifacts.stateSnapshot,
|
|
@@ -2173,7 +2191,7 @@ async function syncChannelWorkspace({
|
|
|
2173
2191
|
},
|
|
2174
2192
|
}
|
|
2175
2193
|
: await reconstructChannelSnapshot({
|
|
2176
|
-
provider,
|
|
2194
|
+
provider: activeProvider,
|
|
2177
2195
|
bridgeAbiManifest,
|
|
2178
2196
|
channelInfo,
|
|
2179
2197
|
channelManager,
|
|
@@ -2190,7 +2208,9 @@ async function syncChannelWorkspace({
|
|
|
2190
2208
|
toBlock: latestBlock,
|
|
2191
2209
|
progressAction,
|
|
2192
2210
|
onCheckpoint: persistWorkspaceCheckpoint,
|
|
2211
|
+
rpcCallHistoryRecorder,
|
|
2193
2212
|
});
|
|
2213
|
+
rpcCallHistoryRecorder?.setScanRange(reconstruction.scanRange);
|
|
2194
2214
|
const currentSnapshot = reconstruction.currentSnapshot;
|
|
2195
2215
|
const workspace = buildWorkspaceForSnapshot({
|
|
2196
2216
|
currentSnapshot,
|
|
@@ -2215,6 +2235,7 @@ async function syncChannelWorkspace({
|
|
|
2215
2235
|
blockInfo,
|
|
2216
2236
|
contractCodes,
|
|
2217
2237
|
cleanRebuildBackup,
|
|
2238
|
+
rpcCallHistory: rpcCallHistoryRecorder?.finish() ?? null,
|
|
2218
2239
|
};
|
|
2219
2240
|
}
|
|
2220
2241
|
|
|
@@ -2656,6 +2677,198 @@ function persistChannelWorkspaceFiles({
|
|
|
2656
2677
|
writeJsonIfChanged(channelWorkspaceConfigPath(workspaceDir), workspace);
|
|
2657
2678
|
}
|
|
2658
2679
|
|
|
2680
|
+
function channelWorkspaceRpcCallHistoryPath(workspaceDir) {
|
|
2681
|
+
return path.join(channelDataPath(workspaceDir), "rpcCallHistory");
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
function createRpcCallHistoryRecorder({ workspaceDir }) {
|
|
2685
|
+
const historyDir = channelWorkspaceRpcCallHistoryPath(workspaceDir);
|
|
2686
|
+
const entriesByFile = new Map();
|
|
2687
|
+
let scanRange = null;
|
|
2688
|
+
let callCount = 0;
|
|
2689
|
+
ensureDir(historyDir);
|
|
2690
|
+
|
|
2691
|
+
const pushEntry = ({ method, eventName = null, entry }) => {
|
|
2692
|
+
const file = rpcCallHistoryFileName(method, eventName);
|
|
2693
|
+
const entries = entriesByFile.get(file) ?? [];
|
|
2694
|
+
entries.push({
|
|
2695
|
+
method,
|
|
2696
|
+
...(eventName ? { event: eventName } : {}),
|
|
2697
|
+
...entry,
|
|
2698
|
+
});
|
|
2699
|
+
entriesByFile.set(file, entries);
|
|
2700
|
+
};
|
|
2701
|
+
|
|
2702
|
+
return {
|
|
2703
|
+
historyDir,
|
|
2704
|
+
setScanRange(nextScanRange) {
|
|
2705
|
+
scanRange = {
|
|
2706
|
+
fromBlock: Number(nextScanRange.fromBlock),
|
|
2707
|
+
toBlock: Number(nextScanRange.toBlock),
|
|
2708
|
+
...(nextScanRange.mode ? { mode: nextScanRange.mode } : {}),
|
|
2709
|
+
};
|
|
2710
|
+
},
|
|
2711
|
+
recordRpcCall({ method, params, response, error = null }) {
|
|
2712
|
+
if (method === "eth_getLogs") {
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
callCount += 1;
|
|
2716
|
+
pushEntry({
|
|
2717
|
+
method,
|
|
2718
|
+
entry: {
|
|
2719
|
+
recordedAt: new Date().toISOString(),
|
|
2720
|
+
request: buildRawJsonRpcRequest(method, params),
|
|
2721
|
+
...(error ? { error } : { response }),
|
|
2722
|
+
},
|
|
2723
|
+
});
|
|
2724
|
+
},
|
|
2725
|
+
recordEthGetLogs({ request, logs, groupedValues, chunkFromBlock, chunkToBlock }) {
|
|
2726
|
+
callCount += 1;
|
|
2727
|
+
const eventBuckets = groupRawEthGetLogsByRecoveryEvent({ logs, groupedValues });
|
|
2728
|
+
for (const [eventName, response] of eventBuckets.entries()) {
|
|
2729
|
+
pushEntry({
|
|
2730
|
+
method: "eth_getLogs",
|
|
2731
|
+
eventName,
|
|
2732
|
+
entry: {
|
|
2733
|
+
recordedAt: new Date().toISOString(),
|
|
2734
|
+
chunkRange: { fromBlock: Number(chunkFromBlock), toBlock: Number(chunkToBlock) },
|
|
2735
|
+
request: buildRawEthGetLogsRequest(request),
|
|
2736
|
+
response,
|
|
2737
|
+
},
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
},
|
|
2741
|
+
finish() {
|
|
2742
|
+
const files = [...entriesByFile.entries()].map(([file, entries]) =>
|
|
2743
|
+
appendRpcCallHistoryEntries({
|
|
2744
|
+
historyDir,
|
|
2745
|
+
file,
|
|
2746
|
+
entries: entries.map((entry) => ({ scanRange, ...entry })),
|
|
2747
|
+
}));
|
|
2748
|
+
return {
|
|
2749
|
+
historyDir,
|
|
2750
|
+
scanRange,
|
|
2751
|
+
callCount,
|
|
2752
|
+
files: files.sort((left, right) => left.file.localeCompare(right.file)),
|
|
2753
|
+
};
|
|
2754
|
+
},
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
function attachRpcCallHistoryRecorderToProvider(provider, recorder) {
|
|
2759
|
+
const send = provider.send.bind(provider);
|
|
2760
|
+
provider.send = async (method, params) => {
|
|
2761
|
+
try {
|
|
2762
|
+
const response = await send(method, params);
|
|
2763
|
+
recorder.recordRpcCall({ method, params, response });
|
|
2764
|
+
return response;
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
recorder.recordRpcCall({ method, params, error: normalizeRpcCallHistoryError(error) });
|
|
2767
|
+
throw error;
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
return provider;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function normalizeRpcCallHistoryError(error) {
|
|
2774
|
+
return {
|
|
2775
|
+
name: error?.name ?? "Error",
|
|
2776
|
+
code: error?.code ?? null,
|
|
2777
|
+
message: error?.message ?? String(error),
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
function appendRpcCallHistoryEntries({ historyDir, file, entries }) {
|
|
2782
|
+
const filePath = path.join(historyDir, file);
|
|
2783
|
+
const { method, event: eventName = null } = entries[0];
|
|
2784
|
+
const current = readJsonIfExists(filePath) ?? {
|
|
2785
|
+
method,
|
|
2786
|
+
...(eventName ? { event: eventName } : {}),
|
|
2787
|
+
entries: [],
|
|
2788
|
+
};
|
|
2789
|
+
expect(current.method === method, `RPC call history file method mismatch: ${filePath}.`);
|
|
2790
|
+
expect(Array.isArray(current.entries), `RPC call history file entries must be an array: ${filePath}.`);
|
|
2791
|
+
if (eventName) {
|
|
2792
|
+
expect(current.event === eventName, `RPC call history file event mismatch: ${filePath}.`);
|
|
2793
|
+
}
|
|
2794
|
+
current.updatedAt = new Date().toISOString();
|
|
2795
|
+
current.entries.push(...entries);
|
|
2796
|
+
writeJson(filePath, current);
|
|
2797
|
+
return {
|
|
2798
|
+
method,
|
|
2799
|
+
...(eventName ? { event: eventName } : {}),
|
|
2800
|
+
file,
|
|
2801
|
+
path: filePath,
|
|
2802
|
+
entriesAdded: entries.length,
|
|
2803
|
+
totalEntries: current.entries.length,
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
function buildRawJsonRpcRequest(method, params = []) {
|
|
2808
|
+
return {
|
|
2809
|
+
jsonrpc: "2.0",
|
|
2810
|
+
method,
|
|
2811
|
+
params: params ?? [],
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
function rpcCallHistoryFileName(method, eventName = null) {
|
|
2816
|
+
const suffix = eventName ? `.${safeRpcCallHistoryFileToken(eventName)}` : "";
|
|
2817
|
+
return `${safeRpcCallHistoryFileToken(method)}${suffix}.json`;
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
function safeRpcCallHistoryFileToken(value) {
|
|
2821
|
+
return String(value).replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
function groupRawEthGetLogsByRecoveryEvent({ logs, groupedValues }) {
|
|
2825
|
+
if (logs.length === 0) {
|
|
2826
|
+
return new Map([["noLogs", []]]);
|
|
2827
|
+
}
|
|
2828
|
+
const eventNamesByLog = new Map();
|
|
2829
|
+
for (const group of groupedValues) {
|
|
2830
|
+
for (const event of group) {
|
|
2831
|
+
eventNamesByLog.set(recoveryLogHistoryKey(event), channelRecoveryEventName(event));
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
const buckets = new Map();
|
|
2835
|
+
for (const log of logs) {
|
|
2836
|
+
const eventName = eventNamesByLog.get(recoveryLogHistoryKey(log)) ?? "unknown";
|
|
2837
|
+
const bucket = buckets.get(eventName) ?? [];
|
|
2838
|
+
bucket.push(log);
|
|
2839
|
+
buckets.set(eventName, bucket);
|
|
2840
|
+
}
|
|
2841
|
+
return buckets;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
function recoveryLogHistoryKey(log) {
|
|
2845
|
+
return `${normalizeBytes32Hex(log.transactionHash)}:${Number(log.index ?? log.logIndex)}`;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
function channelRecoveryEventName(event) {
|
|
2849
|
+
if (event.fragment?.name) {
|
|
2850
|
+
return event.fragment.name;
|
|
2851
|
+
}
|
|
2852
|
+
const topic0 = event.topics[0] ? normalizeBytes32Hex(event.topics[0]) : null;
|
|
2853
|
+
if (topic0 === normalizeBytes32Hex(CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC)) {
|
|
2854
|
+
return "StorageKeyObserved";
|
|
2855
|
+
}
|
|
2856
|
+
if (topic0 === normalizeBytes32Hex(VAULT_STORAGE_WRITE_OBSERVED_TOPIC)) {
|
|
2857
|
+
return "LiquidBalanceStorageWriteObserved";
|
|
2858
|
+
}
|
|
2859
|
+
return "unknown";
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
function buildRawEthGetLogsRequest(request) {
|
|
2863
|
+
const filter = {
|
|
2864
|
+
address: request.address,
|
|
2865
|
+
topics: request.topics,
|
|
2866
|
+
fromBlock: ethers.toQuantity(request.fromBlock),
|
|
2867
|
+
toBlock: ethers.toQuantity(request.toBlock),
|
|
2868
|
+
};
|
|
2869
|
+
return buildRawJsonRpcRequest("eth_getLogs", [filter]);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2659
2872
|
function nextAvailablePath(basePath) {
|
|
2660
2873
|
if (!fs.existsSync(basePath)) {
|
|
2661
2874
|
return basePath;
|
|
@@ -2866,6 +3079,18 @@ async function handleDoctor({ args }) {
|
|
|
2866
3079
|
}
|
|
2867
3080
|
}
|
|
2868
3081
|
|
|
3082
|
+
function handleObserver() {
|
|
3083
|
+
printJson({
|
|
3084
|
+
action: "observer",
|
|
3085
|
+
url: PRIVATE_STATE_OBSERVER_URL,
|
|
3086
|
+
scope: "Public monitoring observer for Tokamak Private App Channels and the private-state DApp.",
|
|
3087
|
+
notes: [
|
|
3088
|
+
"The observer helps users and reviewers inspect public monitoring surfaces.",
|
|
3089
|
+
"The observer does not receive wallet secrets, spending keys, viewing keys, or private note plaintext.",
|
|
3090
|
+
],
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
|
|
2869
3094
|
function handleInvestigator() {
|
|
2870
3095
|
const htmlPath = resolveInvestigatorIndexPath();
|
|
2871
3096
|
const fileUrl = pathToFileURL(htmlPath).href;
|
|
@@ -3623,12 +3848,10 @@ function inspectGuideNetworkRuntime(networkName) {
|
|
|
3623
3848
|
|
|
3624
3849
|
function inspectGuideDeploymentArtifacts(chainId) {
|
|
3625
3850
|
const paths = privateStateCliArtifactPaths(resolveArtifactCacheBaseRoot(), chainId);
|
|
3626
|
-
const readOnlyMissingFiles =
|
|
3627
|
-
.map((entry) => entry.path)
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
.map((entry) => entry.path)
|
|
3631
|
-
.filter((filePath) => !fs.existsSync(filePath));
|
|
3851
|
+
const readOnlyMissingFiles = missingInstalledDeploymentArtifactFiles(paths, PRIVATE_STATE_INSTALL_MODES.READ_ONLY)
|
|
3852
|
+
.map((entry) => entry.path);
|
|
3853
|
+
const fullMissingFiles = missingInstalledDeploymentArtifactFiles(paths, PRIVATE_STATE_INSTALL_MODES.FULL)
|
|
3854
|
+
.map((entry) => entry.path);
|
|
3632
3855
|
return {
|
|
3633
3856
|
installed: readOnlyMissingFiles.length === 0,
|
|
3634
3857
|
readOnlyInstalled: readOnlyMissingFiles.length === 0,
|
|
@@ -8437,39 +8660,6 @@ function appendSplitWord(target, startIndex, value) {
|
|
|
8437
8660
|
target[startIndex + 1] = normalized >> 128n;
|
|
8438
8661
|
}
|
|
8439
8662
|
|
|
8440
|
-
async function fetchChannelBlockInfo(provider, blockNumber) {
|
|
8441
|
-
const blockTag = ethers.toQuantity(blockNumber);
|
|
8442
|
-
const block = await provider.send("eth_getBlockByNumber", [blockTag, false]);
|
|
8443
|
-
if (!block) {
|
|
8444
|
-
throw new Error(`Unable to fetch channel genesis block ${blockNumber}.`);
|
|
8445
|
-
}
|
|
8446
|
-
|
|
8447
|
-
const prevBlockHashes = [];
|
|
8448
|
-
for (let offset = 1; offset <= TOKAMAK_PREVIOUS_BLOCK_HASH_COUNT; offset += 1) {
|
|
8449
|
-
if (blockNumber <= offset) {
|
|
8450
|
-
prevBlockHashes.push("0x0");
|
|
8451
|
-
continue;
|
|
8452
|
-
}
|
|
8453
|
-
const previousBlock = await provider.send("eth_getBlockByNumber", [ethers.toQuantity(blockNumber - offset), false]);
|
|
8454
|
-
if (!previousBlock) {
|
|
8455
|
-
throw new Error(`Unable to fetch previous block hash for block ${blockNumber - offset}.`);
|
|
8456
|
-
}
|
|
8457
|
-
prevBlockHashes.push(previousBlock.hash);
|
|
8458
|
-
}
|
|
8459
|
-
|
|
8460
|
-
return {
|
|
8461
|
-
coinBase: block.miner,
|
|
8462
|
-
timeStamp: block.timestamp,
|
|
8463
|
-
blockNumber: block.number,
|
|
8464
|
-
prevRanDao: block.prevRandao ?? block.mixHash ?? block.difficulty ?? "0x0",
|
|
8465
|
-
gasLimit: block.gasLimit,
|
|
8466
|
-
chainId: await provider.send("eth_chainId", []),
|
|
8467
|
-
selfBalance: "0x0",
|
|
8468
|
-
baseFee: block.baseFeePerGas ?? "0x0",
|
|
8469
|
-
prevBlockHashes,
|
|
8470
|
-
};
|
|
8471
|
-
}
|
|
8472
|
-
|
|
8473
8663
|
async function fetchChannelRecoveryLogs({
|
|
8474
8664
|
provider,
|
|
8475
8665
|
bridgeAbiManifest,
|
|
@@ -8510,6 +8700,7 @@ async function fetchChannelRecoveryEventGroupsChunked({
|
|
|
8510
8700
|
fromBlock,
|
|
8511
8701
|
toBlock,
|
|
8512
8702
|
progressAction = null,
|
|
8703
|
+
rpcCallHistoryRecorder = null,
|
|
8513
8704
|
onChunk,
|
|
8514
8705
|
}) {
|
|
8515
8706
|
const recoveryFilter = buildChannelRecoveryLogFilter({
|
|
@@ -8527,7 +8718,7 @@ async function fetchChannelRecoveryEventGroupsChunked({
|
|
|
8527
8718
|
onProgress: progressAction
|
|
8528
8719
|
? createRpcLogScanProgress({ action: progressAction, label: "channel-recovery chunks" })
|
|
8529
8720
|
: null,
|
|
8530
|
-
onChunk: async ({ logs, chunkFromBlock, chunkToBlock }) => {
|
|
8721
|
+
onChunk: async ({ request, logs, chunkFromBlock, chunkToBlock }) => {
|
|
8531
8722
|
const groupedValues = normalizeWorkspaceMirrorDeltaEventGroups({
|
|
8532
8723
|
logs,
|
|
8533
8724
|
channelInfo,
|
|
@@ -8535,6 +8726,13 @@ async function fetchChannelRecoveryEventGroupsChunked({
|
|
|
8535
8726
|
fromBlock: chunkFromBlock,
|
|
8536
8727
|
toBlock: chunkToBlock,
|
|
8537
8728
|
});
|
|
8729
|
+
rpcCallHistoryRecorder?.recordEthGetLogs({
|
|
8730
|
+
request,
|
|
8731
|
+
logs,
|
|
8732
|
+
groupedValues,
|
|
8733
|
+
chunkFromBlock,
|
|
8734
|
+
chunkToBlock,
|
|
8735
|
+
});
|
|
8538
8736
|
await onChunk?.({
|
|
8539
8737
|
groupedValues,
|
|
8540
8738
|
chunkFromBlock,
|
|
@@ -8591,6 +8789,7 @@ async function reconstructChannelSnapshot({
|
|
|
8591
8789
|
toBlock = null,
|
|
8592
8790
|
progressAction = null,
|
|
8593
8791
|
onCheckpoint = null,
|
|
8792
|
+
rpcCallHistoryRecorder = null,
|
|
8594
8793
|
}) {
|
|
8595
8794
|
let startingSnapshot = baseSnapshot;
|
|
8596
8795
|
if (!startingSnapshot) {
|
|
@@ -8627,6 +8826,7 @@ async function reconstructChannelSnapshot({
|
|
|
8627
8826
|
fromBlock: scanFromBlock,
|
|
8628
8827
|
toBlock: latestBlock,
|
|
8629
8828
|
progressAction,
|
|
8829
|
+
rpcCallHistoryRecorder,
|
|
8630
8830
|
onChunk: async ({ groupedValues, chunkFromBlock, chunkToBlock }) => {
|
|
8631
8831
|
currentSnapshot = await applyChannelRecoveryEventGroupsToStateManager({
|
|
8632
8832
|
stateManager,
|
|
@@ -8979,13 +9179,14 @@ async function fetchLogsChunked(provider, {
|
|
|
8979
9179
|
while (cursor <= resolvedToBlock) {
|
|
8980
9180
|
const chunkToBlock = Math.min(resolvedToBlock, cursor + chunkSize - 1);
|
|
8981
9181
|
let logs;
|
|
9182
|
+
const request = {
|
|
9183
|
+
address,
|
|
9184
|
+
topics,
|
|
9185
|
+
fromBlock: cursor,
|
|
9186
|
+
toBlock: chunkToBlock,
|
|
9187
|
+
};
|
|
8982
9188
|
try {
|
|
8983
|
-
logs = await fetchLogsRateLimited(provider,
|
|
8984
|
-
address,
|
|
8985
|
-
topics,
|
|
8986
|
-
fromBlock: cursor,
|
|
8987
|
-
toBlock: chunkToBlock,
|
|
8988
|
-
});
|
|
9189
|
+
logs = await fetchLogsRateLimited(provider, request);
|
|
8989
9190
|
} catch (error) {
|
|
8990
9191
|
throw buildRpcLogQueryConfigError({
|
|
8991
9192
|
error,
|
|
@@ -9019,6 +9220,7 @@ async function fetchLogsChunked(provider, {
|
|
|
9019
9220
|
totalBlocks,
|
|
9020
9221
|
logsFound,
|
|
9021
9222
|
chunkLogs: logs.length,
|
|
9223
|
+
request,
|
|
9022
9224
|
logs,
|
|
9023
9225
|
});
|
|
9024
9226
|
cursor = chunkToBlock + 1;
|
|
@@ -10066,6 +10268,10 @@ function assertGuideArgs(args) {
|
|
|
10066
10268
|
assertAllowedCommandSchema(args, "help-guide");
|
|
10067
10269
|
}
|
|
10068
10270
|
|
|
10271
|
+
function assertObserverArgs(args) {
|
|
10272
|
+
assertAllowedCommandSchema(args, "help-observer");
|
|
10273
|
+
}
|
|
10274
|
+
|
|
10069
10275
|
function assertTransactionFeesArgs(args) {
|
|
10070
10276
|
assertAllowedCommandSchema(args, "help-transaction-fees");
|
|
10071
10277
|
}
|
|
@@ -10154,10 +10360,16 @@ function assertCreateChannelArgs(args) {
|
|
|
10154
10360
|
|
|
10155
10361
|
function assertRecoverWorkspaceArgs(args) {
|
|
10156
10362
|
assertAllowedCommandSchema(args, "channel-recover-workspace");
|
|
10157
|
-
resolveWorkspaceRecoverySource(args);
|
|
10363
|
+
const source = resolveWorkspaceRecoverySource(args);
|
|
10158
10364
|
if (args.fromGenesis !== undefined && args.fromGenesis !== true) {
|
|
10159
10365
|
throw new Error("channel recover-workspace option --from-genesis does not accept a value.");
|
|
10160
10366
|
}
|
|
10367
|
+
if (args.outputRaw !== undefined && args.outputRaw !== true) {
|
|
10368
|
+
throw new Error("channel recover-workspace option --output-raw does not accept a value.");
|
|
10369
|
+
}
|
|
10370
|
+
if (args.outputRaw === true && source !== "rpc") {
|
|
10371
|
+
throw new Error("channel recover-workspace option --output-raw requires --source rpc.");
|
|
10372
|
+
}
|
|
10161
10373
|
}
|
|
10162
10374
|
|
|
10163
10375
|
function assertGetChannelArgs(args) {
|
|
@@ -11011,6 +11223,7 @@ function loadWalletCommandRuntime(args, { prepareArtifacts = false } = {}) {
|
|
|
11011
11223
|
const HUMAN_RESULT_RENDERERS = Object.freeze({
|
|
11012
11224
|
guide: printGuideHumanResult,
|
|
11013
11225
|
investigator: printInvestigatorHumanResult,
|
|
11226
|
+
observer: printObserverHumanResult,
|
|
11014
11227
|
"transaction-fees": printTransactionFeesHumanResult,
|
|
11015
11228
|
update: printUpdateHumanResult,
|
|
11016
11229
|
});
|
|
@@ -11093,6 +11306,24 @@ function printInvestigatorHumanResult(result) {
|
|
|
11093
11306
|
console.log(lines.join("\n"));
|
|
11094
11307
|
}
|
|
11095
11308
|
|
|
11309
|
+
function printObserverHumanResult(result) {
|
|
11310
|
+
const lines = [
|
|
11311
|
+
"Private-State Public Observer",
|
|
11312
|
+
`URL: ${formatHumanValue(result.url)}`,
|
|
11313
|
+
];
|
|
11314
|
+
if (result.scope) {
|
|
11315
|
+
lines.push(`Scope: ${formatHumanValue(result.scope)}`);
|
|
11316
|
+
}
|
|
11317
|
+
if (Array.isArray(result.notes) && result.notes.length > 0) {
|
|
11318
|
+
lines.push(
|
|
11319
|
+
"",
|
|
11320
|
+
"Notes",
|
|
11321
|
+
...result.notes.map((note) => `- ${note}`),
|
|
11322
|
+
);
|
|
11323
|
+
}
|
|
11324
|
+
console.log(lines.join("\n"));
|
|
11325
|
+
}
|
|
11326
|
+
|
|
11096
11327
|
function printTransactionFeesHumanResult(report) {
|
|
11097
11328
|
const lines = [
|
|
11098
11329
|
"Transaction Fees",
|
|
@@ -11505,6 +11736,7 @@ export {
|
|
|
11505
11736
|
assertUpdateArgs,
|
|
11506
11737
|
assertDoctorArgs,
|
|
11507
11738
|
assertGuideArgs,
|
|
11739
|
+
assertObserverArgs,
|
|
11508
11740
|
assertTransactionFeesArgs,
|
|
11509
11741
|
assertInvestigatorArgs,
|
|
11510
11742
|
assertAccountGetL1AddressArgs,
|
|
@@ -11538,6 +11770,7 @@ export {
|
|
|
11538
11770
|
handleUpdate,
|
|
11539
11771
|
handleDoctor,
|
|
11540
11772
|
handleGuide,
|
|
11773
|
+
handleObserver,
|
|
11541
11774
|
handleTransactionFees,
|
|
11542
11775
|
handleInvestigator,
|
|
11543
11776
|
handleAccountGetL1Address,
|
package/package.json
CHANGED