@tokamak-private-dapps/private-state-cli 0.1.2 → 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 +12 -0
- package/README.md +71 -4
- package/cli-assistant.html +3 -1
- package/package.json +1 -1
- package/private-state-bridge-cli.mjs +339 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
8
|
+
## 0.1.3 - 2026-04-28
|
|
9
|
+
|
|
10
|
+
- Installed the Groth16 runtime during `private-state-cli --install` and reported Groth16 readiness from `--doctor`.
|
|
11
|
+
- Added live NVIDIA and Docker GPU probes to `--doctor`, with a hard failure when live Docker GPU readiness does not match the recorded Tokamak CLI metadata.
|
|
12
|
+
- Renamed `get-my-address` to `get-my-wallet-meta`, added `get-my-l1-address`, and added `list-local-wallets`.
|
|
13
|
+
- Documented the private-state CLI helper commands, common flow examples, and LLM agent guidance.
|
|
14
|
+
|
|
3
15
|
## 0.1.2 - 2026-04-28
|
|
4
16
|
|
|
5
17
|
- Added `private-state-cli --doctor` to report CLI and install-time dependency versions through `tokamak-l2js`.
|
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ Command-line client for the Tokamak private-state DApp.
|
|
|
8
8
|
npm install -g @tokamak-private-dapps/private-state-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Install the local Tokamak zk-EVM runtime workspace and public private-state deployment
|
|
11
|
+
Install the local Tokamak zk-EVM runtime workspace, Groth16 runtime workspace, and public private-state deployment
|
|
12
|
+
artifacts:
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
15
|
private-state-cli --install
|
|
@@ -35,7 +36,7 @@ private-state-cli --doctor
|
|
|
35
36
|
|
|
36
37
|
## Commands
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
A common private-state flow is:
|
|
39
40
|
|
|
40
41
|
1. `create-channel`
|
|
41
42
|
2. `deposit-bridge`
|
|
@@ -52,7 +53,21 @@ Use `private-state-cli --help` for the full command list and required options.
|
|
|
52
53
|
|
|
53
54
|
`private-state-cli --doctor` reports the CLI package version, dependency versions recorded by the last
|
|
54
55
|
`private-state-cli --install`, current dependency versions through `tokamak-l2js`, and Tokamak zk-EVM runtime
|
|
55
|
-
install mode, Docker mode,
|
|
56
|
+
install mode, Docker mode, CUDA runtime metadata, live `nvidia-smi` and Docker GPU probe results, and Groth16
|
|
57
|
+
runtime health. The doctor check fails when the Tokamak Docker `useGpus` metadata does not match the live GPU probes.
|
|
58
|
+
|
|
59
|
+
Local helper commands:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
private-state-cli list-local-wallets --network sepolia --channel-name cuda
|
|
63
|
+
private-state-cli get-my-wallet-meta --wallet <WALLET_NAME> --password <PASSWORD> --network sepolia
|
|
64
|
+
private-state-cli get-my-l1-address --private-key <HEX>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`list-local-wallets` reads only the local workspace and prints saved wallet names that can be reused with `--wallet`.
|
|
68
|
+
`get-my-wallet-meta` opens an encrypted local wallet and reports the stored L1/L2 identity metadata plus the current
|
|
69
|
+
on-chain channel registration match state. `get-my-l1-address` is a simple offline helper that derives the L1 address
|
|
70
|
+
for a private key.
|
|
56
71
|
|
|
57
72
|
## Workspace
|
|
58
73
|
|
|
@@ -64,6 +79,57 @@ The CLI stores user workspaces under:
|
|
|
64
79
|
|
|
65
80
|
Wallet data is encrypted with the password supplied to `join-channel` or `recover-wallet`.
|
|
66
81
|
|
|
82
|
+
## LLM Agent Guidance
|
|
83
|
+
|
|
84
|
+
You may act as an interactive guide for users who do not understand this CLI or the private-state DApp. Assume the
|
|
85
|
+
user only knows that the DApp is a way to send funds privately. Translate the user's intent into safe, step-by-step CLI
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
Primary goal: help the user privately send funds by guiding them through the required private-state CLI commands,
|
|
89
|
+
explaining each step only as much as needed to proceed safely.
|
|
90
|
+
|
|
91
|
+
Operating rules:
|
|
92
|
+
|
|
93
|
+
- Do not ask the user to reveal raw private keys in chat. Use environment variable placeholders such as `$ADDR6`,
|
|
94
|
+
`$CREATOR`, or `$PRIVATE_STATE_TEST_PK`.
|
|
95
|
+
- Prefer testnet examples unless the user explicitly asks for mainnet.
|
|
96
|
+
- Before any proof-backed or bridge-facing workflow, ask the user to run `private-state-cli --doctor` and inspect
|
|
97
|
+
whether the runtime, Docker mode, CUDA/GPU probes, Groth16 runtime, and deployment artifacts are healthy.
|
|
98
|
+
- Use `private-state-cli list-local-wallets` to discover local wallet names instead of asking the user to inspect
|
|
99
|
+
filesystem paths manually.
|
|
100
|
+
- Use `private-state-cli get-my-l1-address --private-key "$KEY_ENV"` to derive the L1 address for a private-key
|
|
101
|
+
environment variable when wallet ownership needs to be identified.
|
|
102
|
+
- Use `private-state-cli get-my-wallet-meta --wallet <WALLET> --password <PASSWORD> --network <NETWORK>` to inspect
|
|
103
|
+
local wallet metadata and on-chain channel registration state.
|
|
104
|
+
- Use `private-state-cli get-my-bridge-fund` and `private-state-cli get-my-channel-fund` to check balances before
|
|
105
|
+
telling the user to move funds.
|
|
106
|
+
- Explain that wallet names are local CLI identifiers, while private transfers use notes owned by L2 addresses
|
|
107
|
+
registered in the channel.
|
|
108
|
+
- Do not present one fixed command sequence as universally correct. Some flows start from an existing channel or wallet,
|
|
109
|
+
while others require creating or joining a channel first.
|
|
110
|
+
- When the user asks for a transfer, first determine whether the sender has minted notes available. If not, guide them
|
|
111
|
+
through funding the bridge, joining or recovering the channel wallet, depositing into the channel, and minting notes.
|
|
112
|
+
- When generating commands, use placeholders for secrets and explicit values for public fields. Show one command at a
|
|
113
|
+
time unless the user asks for a batch.
|
|
114
|
+
|
|
115
|
+
Suggested interaction flow:
|
|
116
|
+
|
|
117
|
+
1. Identify the target network, usually `sepolia` for testing.
|
|
118
|
+
2. Identify whether a channel already exists.
|
|
119
|
+
3. Identify the sender and recipient wallets or private-key environment variables.
|
|
120
|
+
4. Run `--doctor`.
|
|
121
|
+
5. Run `list-local-wallets` and relevant metadata or balance checks.
|
|
122
|
+
6. If needed, guide the user through `create-channel`, `deposit-bridge`, `join-channel`, `deposit-channel`, and
|
|
123
|
+
`mint-notes`.
|
|
124
|
+
7. For a private transfer, select available note IDs from `get-my-notes`, find the recipient L2 address from
|
|
125
|
+
`get-my-wallet-meta`, then build `transfer-notes`.
|
|
126
|
+
8. After transfer, guide the recipient to run `get-my-notes` to recover received notes from event logs.
|
|
127
|
+
|
|
128
|
+
Example style: if the user says, "ADDR6 sends 10 tokens privately to ADDR8", do not assume the required note exists.
|
|
129
|
+
First ask or check which channel and network to use, whether ADDR6 and ADDR8 are already joined, what the local wallet
|
|
130
|
+
names are, and whether ADDR6 has an unused note worth exactly 10 or notes that sum to 10. Then provide the next concrete
|
|
131
|
+
command.
|
|
132
|
+
|
|
67
133
|
## Artifacts
|
|
68
134
|
|
|
69
135
|
Proof-backed commands require installed bridge, DApp, and Groth16 artifacts. Run `private-state-cli --install` before
|
|
@@ -81,7 +147,8 @@ Release order matters for npm publication. `@tokamak-private-dapps/common-librar
|
|
|
81
147
|
### What does this package install?
|
|
82
148
|
|
|
83
149
|
It installs the `private-state-cli` terminal command and the local files needed by that command.
|
|
84
|
-
It does not install bridge contracts, app contracts, or local deployment outputs.
|
|
150
|
+
It does not install bridge contracts, app contracts, or local deployment outputs. The `private-state-cli --install`
|
|
151
|
+
command provisions the local Tokamak zk-EVM and Groth16 runtime workspaces used by proof-backed commands.
|
|
85
152
|
|
|
86
153
|
### When should I run `private-state-cli --install`?
|
|
87
154
|
|
package/cli-assistant.html
CHANGED
|
@@ -528,7 +528,9 @@
|
|
|
528
528
|
{ id: "get-my-bridge-fund", description: "Read the current bridge-vault balance.", fields: ["network", "privateKey", "alchemyApiKey"] },
|
|
529
529
|
{ id: "join-channel", description: "Bind the caller to a channel-specific L2 identity.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
|
|
530
530
|
{ id: "recover-wallet", description: "Rebuild the recoverable portion of a wallet.", fields: ["channelName", "password", "network", "privateKey", "alchemyApiKey"] },
|
|
531
|
-
{ id: "get-my-
|
|
531
|
+
{ id: "get-my-wallet-meta", description: "Check whether a saved wallet matches on-chain registration.", fields: ["wallet", "password", "network"] },
|
|
532
|
+
{ id: "get-my-l1-address", description: "Derive the L1 address for a private key.", fields: ["privateKey"] },
|
|
533
|
+
{ id: "list-local-wallets", description: "List saved local wallet names that can be reused with --wallet.", fields: ["network", "channelName"] },
|
|
532
534
|
{ id: "deposit-channel", description: "Move bridged funds into the channel L2 accounting balance.", fields: ["wallet", "password", "network", "amount"] },
|
|
533
535
|
{ id: "withdraw-channel", description: "Move channel L2 balance back into the shared bridge vault.", fields: ["wallet", "password", "network", "amount"] },
|
|
534
536
|
{ id: "get-my-channel-fund", description: "Read the current channel L2 accounting balance.", fields: ["wallet", "password", "network"] },
|
package/package.json
CHANGED
|
@@ -90,6 +90,8 @@ const tokamakCliInvocation = buildTokamakCliInvocation();
|
|
|
90
90
|
const tokamakCliCommand = tokamakCliInvocation.command;
|
|
91
91
|
const tokamakCliBaseArgs = tokamakCliInvocation.args;
|
|
92
92
|
const flatDeploymentArtifactPathsByChainId = new Map();
|
|
93
|
+
const DOCKER_CUDA_PROBE_IMAGE = "nvidia/cuda:12.2.0-base-ubuntu22.04";
|
|
94
|
+
const DOCTOR_GPU_PROBE_TIMEOUT_MS = 120000;
|
|
93
95
|
|
|
94
96
|
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
95
97
|
const erc20MetadataAbi = [
|
|
@@ -150,6 +152,9 @@ const JUBJUB_D = jubjub.CURVE.d;
|
|
|
150
152
|
const BLS12_381_SCALAR_FIELD_MODULUS =
|
|
151
153
|
hexToBigInt(addHexPrefix("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"));
|
|
152
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;
|
|
153
158
|
|
|
154
159
|
async function prepareDeploymentArtifacts(chainId) {
|
|
155
160
|
const normalizedChainId = Number(chainId);
|
|
@@ -231,6 +236,18 @@ async function main() {
|
|
|
231
236
|
return;
|
|
232
237
|
}
|
|
233
238
|
|
|
239
|
+
if (args.command === "get-my-l1-address") {
|
|
240
|
+
assertGetMyL1AddressArgs(args);
|
|
241
|
+
handleGetMyL1Address({ args });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (args.command === "list-local-wallets") {
|
|
246
|
+
assertListLocalWalletsArgs(args);
|
|
247
|
+
handleListLocalWallets({ args });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
234
251
|
const walletCommandHandlers = {
|
|
235
252
|
"mint-notes": {
|
|
236
253
|
assert: assertMintNotesArgs,
|
|
@@ -256,9 +273,9 @@ async function main() {
|
|
|
256
273
|
assert: (parsedArgs) => assertWalletChannelMoveArgs(parsedArgs, "withdraw-channel"),
|
|
257
274
|
run: ({ provider }) => handleGrothVaultMove({ args, provider, direction: "withdraw" }),
|
|
258
275
|
},
|
|
259
|
-
"get-my-
|
|
260
|
-
assert:
|
|
261
|
-
run: ({ provider }) =>
|
|
276
|
+
"get-my-wallet-meta": {
|
|
277
|
+
assert: assertGetMyWalletMetaArgs,
|
|
278
|
+
run: ({ provider }) => handleGetMyWalletMeta({ args, provider }),
|
|
262
279
|
},
|
|
263
280
|
"get-my-channel-fund": {
|
|
264
281
|
assert: assertGetMyChannelFundArgs,
|
|
@@ -942,6 +959,9 @@ async function handleInstallZkEvm({ args }) {
|
|
|
942
959
|
}
|
|
943
960
|
run(tokamakCliCommand, installArgs);
|
|
944
961
|
const tokamakRuntimeRoot = resolveTokamakCliRuntimeRoot();
|
|
962
|
+
const groth16Runtime = installGroth16RuntimeForPrivateState({
|
|
963
|
+
docker: Boolean(args.docker),
|
|
964
|
+
});
|
|
945
965
|
const localDeploymentBaseRoot = args.includeLocalArtifacts ? process.cwd() : null;
|
|
946
966
|
const deploymentArtifacts = await installPrivateStateCliArtifacts({
|
|
947
967
|
dappName: PRIVATE_STATE_DAPP_LABEL,
|
|
@@ -952,11 +972,13 @@ async function handleInstallZkEvm({ args }) {
|
|
|
952
972
|
includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
|
|
953
973
|
localDeploymentBaseRoot,
|
|
954
974
|
deploymentArtifacts,
|
|
975
|
+
groth16Runtime,
|
|
955
976
|
});
|
|
956
977
|
printJson({
|
|
957
978
|
action: "install",
|
|
958
979
|
tokamakCli: tokamakCliBaseArgs[0],
|
|
959
980
|
runtimeRoot: tokamakRuntimeRoot,
|
|
981
|
+
groth16Runtime,
|
|
960
982
|
docker: Boolean(args.docker),
|
|
961
983
|
includeLocalArtifacts: Boolean(args.includeLocalArtifacts),
|
|
962
984
|
localDeploymentBaseRoot,
|
|
@@ -997,7 +1019,38 @@ async function handleDoctor() {
|
|
|
997
1019
|
}
|
|
998
1020
|
}
|
|
999
1021
|
|
|
1000
|
-
|
|
1022
|
+
function handleGetMyL1Address({ args }) {
|
|
1023
|
+
const privateKey = normalizePrivateKey(requireArg(args.privateKey, "--private-key"));
|
|
1024
|
+
const signer = new Wallet(privateKey);
|
|
1025
|
+
printJson({
|
|
1026
|
+
action: "get-my-l1-address",
|
|
1027
|
+
l1Address: signer.address,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function handleListLocalWallets({ args }) {
|
|
1032
|
+
const networkFilter = args.network ? requireNetworkName(args) : null;
|
|
1033
|
+
if (networkFilter) {
|
|
1034
|
+
resolveCliNetwork(networkFilter);
|
|
1035
|
+
}
|
|
1036
|
+
const channelFilter = args.channelName ? slugifyPathComponent(requireArg(args.channelName, "--channel-name")) : null;
|
|
1037
|
+
const wallets = listLocalWallets({
|
|
1038
|
+
networkFilter,
|
|
1039
|
+
channelFilter,
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
printJson({
|
|
1043
|
+
action: "list-local-wallets",
|
|
1044
|
+
workspaceRoot,
|
|
1045
|
+
filters: {
|
|
1046
|
+
network: networkFilter,
|
|
1047
|
+
channelName: args.channelName ?? null,
|
|
1048
|
+
},
|
|
1049
|
+
wallets,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
async function handleGetMyWalletMeta({ args, provider }) {
|
|
1001
1054
|
const { wallet, walletMetadata } = loadUnlockedWalletWithMetadata(args);
|
|
1002
1055
|
const { signer, l2Identity } = restoreWalletParticipant(wallet, provider);
|
|
1003
1056
|
const context = await loadChannelContext({
|
|
@@ -1015,7 +1068,7 @@ async function handleGetMyAddress({ args, provider }) {
|
|
|
1015
1068
|
=== ethers.toBigInt(normalizeBytes32Hex(expectedStorageKey));
|
|
1016
1069
|
|
|
1017
1070
|
printJson({
|
|
1018
|
-
action: "get-my-
|
|
1071
|
+
action: "get-my-wallet-meta",
|
|
1019
1072
|
wallet: wallet.walletName,
|
|
1020
1073
|
network: walletMetadata.network,
|
|
1021
1074
|
channelName: walletMetadata.channelName,
|
|
@@ -1980,6 +2033,7 @@ async function recoverDeliveredNotesFromEventLogs({
|
|
|
1980
2033
|
const nullifierUsedSlot = ethers.toBigInt(findStorageSlot(storageLayoutManifest, "PrivateStateController", "nullifierUsed"));
|
|
1981
2034
|
const observedLogs = await fetchLogsChunked(provider, {
|
|
1982
2035
|
address: context.workspace.channelManager,
|
|
2036
|
+
topics: [NOTE_VALUE_ENCRYPTED_TOPIC],
|
|
1983
2037
|
fromBlock: scanStartBlock,
|
|
1984
2038
|
toBlock: latestBlock,
|
|
1985
2039
|
});
|
|
@@ -3804,30 +3858,39 @@ async function reconstructChannelSnapshot({
|
|
|
3804
3858
|
provider,
|
|
3805
3859
|
);
|
|
3806
3860
|
const latestBlock = await provider.getBlockNumber();
|
|
3807
|
-
const
|
|
3808
|
-
|
|
3809
|
-
|
|
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
|
+
]],
|
|
3810
3870
|
fromBlock: genesisBlockNumber,
|
|
3811
3871
|
toBlock: latestBlock,
|
|
3812
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
|
+
});
|
|
3813
3885
|
const vaultStorageWriteEvents = await queryContractEventsChunked({
|
|
3814
3886
|
contract: bridgeTokenVault,
|
|
3815
3887
|
eventName: "StorageWriteObserved",
|
|
3816
3888
|
fromBlock: genesisBlockNumber,
|
|
3817
3889
|
toBlock: latestBlock,
|
|
3818
3890
|
});
|
|
3819
|
-
const observedStorageLogs = await fetchLogsChunked(provider, {
|
|
3820
|
-
address: channelInfo.manager,
|
|
3821
|
-
topics: [[
|
|
3822
|
-
CONTROLLER_STORAGE_KEY_OBSERVED_TOPIC,
|
|
3823
|
-
VAULT_STORAGE_WRITE_OBSERVED_TOPIC,
|
|
3824
|
-
]],
|
|
3825
|
-
fromBlock: genesisBlockNumber,
|
|
3826
|
-
toBlock: latestBlock,
|
|
3827
|
-
});
|
|
3828
3891
|
|
|
3829
3892
|
const groupedEvents = new Map();
|
|
3830
|
-
for (const event of [...
|
|
3893
|
+
for (const event of [...channelManagerEvents, ...vaultStorageWriteEvents]) {
|
|
3831
3894
|
const key = event.transactionHash;
|
|
3832
3895
|
const group = groupedEvents.get(key) ?? [];
|
|
3833
3896
|
group.push(event);
|
|
@@ -3950,6 +4013,7 @@ async function fetchLogsChunked(provider, {
|
|
|
3950
4013
|
while (cursor <= resolvedToBlock) {
|
|
3951
4014
|
const chunkToBlock = Math.min(resolvedToBlock, cursor + chunkSize - 1);
|
|
3952
4015
|
try {
|
|
4016
|
+
await throttleLogRequest();
|
|
3953
4017
|
const logs = await provider.getLogs({
|
|
3954
4018
|
address,
|
|
3955
4019
|
topics,
|
|
@@ -3959,6 +4023,12 @@ async function fetchLogsChunked(provider, {
|
|
|
3959
4023
|
aggregatedLogs.push(...logs);
|
|
3960
4024
|
cursor = chunkToBlock + 1;
|
|
3961
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
|
+
}
|
|
3962
4032
|
const suggestedChunkSize = deriveRecommendedLogChunkSize(error, chunkSize);
|
|
3963
4033
|
if (suggestedChunkSize >= chunkSize) {
|
|
3964
4034
|
throw error;
|
|
@@ -3970,6 +4040,31 @@ async function fetchLogsChunked(provider, {
|
|
|
3970
4040
|
return aggregatedLogs;
|
|
3971
4041
|
}
|
|
3972
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
|
+
|
|
3973
4068
|
function deriveRecommendedLogChunkSize(error, currentChunkSize) {
|
|
3974
4069
|
const serializedError = [
|
|
3975
4070
|
error?.message,
|
|
@@ -4377,6 +4472,48 @@ function resolveWalletPathCandidates(walletName) {
|
|
|
4377
4472
|
return candidates;
|
|
4378
4473
|
}
|
|
4379
4474
|
|
|
4475
|
+
function listLocalWallets({ networkFilter = null, channelFilter = null } = {}) {
|
|
4476
|
+
if (!fs.existsSync(workspaceRoot)) {
|
|
4477
|
+
return [];
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
const wallets = [];
|
|
4481
|
+
for (const networkEntry of fs.readdirSync(workspaceRoot, { withFileTypes: true })) {
|
|
4482
|
+
if (!networkEntry.isDirectory() || (networkFilter && networkEntry.name !== slugifyPathComponent(networkFilter))) {
|
|
4483
|
+
continue;
|
|
4484
|
+
}
|
|
4485
|
+
const networkDir = path.join(workspaceRoot, networkEntry.name);
|
|
4486
|
+
for (const channelEntry of fs.readdirSync(networkDir, { withFileTypes: true })) {
|
|
4487
|
+
if (!channelEntry.isDirectory() || (channelFilter && channelEntry.name !== channelFilter)) {
|
|
4488
|
+
continue;
|
|
4489
|
+
}
|
|
4490
|
+
const walletsDir = path.join(networkDir, channelEntry.name, "wallets");
|
|
4491
|
+
if (!fs.existsSync(walletsDir)) {
|
|
4492
|
+
continue;
|
|
4493
|
+
}
|
|
4494
|
+
for (const walletEntry of fs.readdirSync(walletsDir, { withFileTypes: true })) {
|
|
4495
|
+
if (!walletEntry.isDirectory()) {
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4498
|
+
const walletDir = path.join(walletsDir, walletEntry.name);
|
|
4499
|
+
wallets.push({
|
|
4500
|
+
wallet: walletEntry.name,
|
|
4501
|
+
network: networkEntry.name,
|
|
4502
|
+
channelName: channelEntry.name,
|
|
4503
|
+
walletDir,
|
|
4504
|
+
metadataPath: walletMetadataPath(walletDir),
|
|
4505
|
+
hasMetadata: fs.existsSync(walletMetadataPath(walletDir)),
|
|
4506
|
+
hasEncryptedWallet: walletConfigExists(walletDir),
|
|
4507
|
+
});
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
return wallets.sort((left, right) =>
|
|
4512
|
+
[left.network, left.channelName, left.wallet].join("\0")
|
|
4513
|
+
.localeCompare([right.network, right.channelName, right.wallet].join("\0")),
|
|
4514
|
+
);
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4380
4517
|
function channelDataPath(workspaceDir) {
|
|
4381
4518
|
return workspaceChannelDir(workspaceDir);
|
|
4382
4519
|
}
|
|
@@ -4566,8 +4703,33 @@ function assertJoinChannelArgs(args) {
|
|
|
4566
4703
|
assertExplicitSignerPasswordCommandArgs(args, "join-channel");
|
|
4567
4704
|
}
|
|
4568
4705
|
|
|
4569
|
-
function
|
|
4570
|
-
assertWalletPasswordArgs(args, "get-my-
|
|
4706
|
+
function assertGetMyWalletMetaArgs(args) {
|
|
4707
|
+
assertWalletPasswordArgs(args, "get-my-wallet-meta", [], "--wallet, --password, and --network");
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
function assertGetMyL1AddressArgs(args) {
|
|
4711
|
+
requireArg(args.privateKey, "--private-key");
|
|
4712
|
+
assertAllowedCommandKeys(
|
|
4713
|
+
args,
|
|
4714
|
+
"get-my-l1-address",
|
|
4715
|
+
new Set(["command", "positional", "privateKey"]),
|
|
4716
|
+
"--private-key",
|
|
4717
|
+
);
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
function assertListLocalWalletsArgs(args) {
|
|
4721
|
+
if (args.network !== undefined) {
|
|
4722
|
+
requireNetworkName(args);
|
|
4723
|
+
}
|
|
4724
|
+
if (args.channelName !== undefined) {
|
|
4725
|
+
requireArg(args.channelName, "--channel-name");
|
|
4726
|
+
}
|
|
4727
|
+
assertAllowedCommandKeys(
|
|
4728
|
+
args,
|
|
4729
|
+
"list-local-wallets",
|
|
4730
|
+
new Set(["command", "positional", "network", "channelName"]),
|
|
4731
|
+
"optional --network and --channel-name",
|
|
4732
|
+
);
|
|
4571
4733
|
}
|
|
4572
4734
|
|
|
4573
4735
|
function assertWithdrawBridgeArgs(args) {
|
|
@@ -4629,8 +4791,8 @@ function printHelp() {
|
|
|
4629
4791
|
console.log(`
|
|
4630
4792
|
Commands:
|
|
4631
4793
|
--install [--docker] [--include-local-artifacts]
|
|
4632
|
-
Install the Tokamak zk-EVM CLI runtime
|
|
4633
|
-
Use --docker on Linux to forward
|
|
4794
|
+
Install the Tokamak zk-EVM CLI runtime, Groth16 runtime, and private-state deployment artifacts
|
|
4795
|
+
Use --docker on Linux to forward Docker mode to the Tokamak zk-EVM and Groth16 runtimes
|
|
4634
4796
|
Use --include-local-artifacts to also install local deployment/ artifacts from the current working directory
|
|
4635
4797
|
|
|
4636
4798
|
uninstall-zk-evm
|
|
@@ -4660,9 +4822,15 @@ Commands:
|
|
|
4660
4822
|
join-channel --channel-name <NAME> --password <PASSWORD> --network <NAME> --private-key <HEX> --alchemy-api-key <KEY>
|
|
4661
4823
|
Pay the channel join fee and bind a wallet to a channel-specific L2 identity
|
|
4662
4824
|
|
|
4663
|
-
get-my-
|
|
4825
|
+
get-my-wallet-meta --wallet <NAME> --password <PASSWORD> --network <NAME>
|
|
4664
4826
|
Check whether a wallet matches the on-chain channel registration
|
|
4665
4827
|
|
|
4828
|
+
get-my-l1-address --private-key <HEX>
|
|
4829
|
+
Derive the L1 address for a private key
|
|
4830
|
+
|
|
4831
|
+
list-local-wallets [--network <NAME>] [--channel-name <NAME>]
|
|
4832
|
+
List saved local wallet names that can be reused with --wallet
|
|
4833
|
+
|
|
4666
4834
|
deposit-channel --wallet <NAME> --password <PASSWORD> --network <NAME> --amount <TOKENS>
|
|
4667
4835
|
Move bridged funds into the channel L2 accounting balance
|
|
4668
4836
|
|
|
@@ -4927,6 +5095,7 @@ function writePrivateStateCliInstallManifest({
|
|
|
4927
5095
|
includeLocalArtifacts,
|
|
4928
5096
|
localDeploymentBaseRoot,
|
|
4929
5097
|
deploymentArtifacts,
|
|
5098
|
+
groth16Runtime,
|
|
4930
5099
|
}) {
|
|
4931
5100
|
const manifestPath = privateStateCliInstallManifestPath(deploymentArtifacts.cacheBaseRoot);
|
|
4932
5101
|
const manifest = {
|
|
@@ -4941,6 +5110,7 @@ function writePrivateStateCliInstallManifest({
|
|
|
4941
5110
|
includeLocalArtifacts,
|
|
4942
5111
|
localDeploymentBaseRoot,
|
|
4943
5112
|
artifactCacheRoot: deploymentArtifacts.cacheBaseRoot,
|
|
5113
|
+
groth16Runtime,
|
|
4944
5114
|
installedDeploymentArtifacts: deploymentArtifacts.installed.map((entry) => ({
|
|
4945
5115
|
chainId: entry.chainId,
|
|
4946
5116
|
source: entry.source,
|
|
@@ -4966,6 +5136,8 @@ function buildDoctorReport() {
|
|
|
4966
5136
|
const installManifest = readJsonIfExists(installManifestPath);
|
|
4967
5137
|
const dependencyReports = collectDependencyPackageReports(installManifest);
|
|
4968
5138
|
const tokamakCli = inspectTokamakCliRuntime();
|
|
5139
|
+
const groth16Runtime = inspectGroth16Runtime();
|
|
5140
|
+
const gpuDockerReadiness = inspectGpuDockerReadiness(tokamakCli);
|
|
4969
5141
|
const checks = [
|
|
4970
5142
|
{
|
|
4971
5143
|
name: "dependency package versions",
|
|
@@ -4993,6 +5165,28 @@ function buildDoctorReport() {
|
|
|
4993
5165
|
})),
|
|
4994
5166
|
},
|
|
4995
5167
|
},
|
|
5168
|
+
{
|
|
5169
|
+
name: "tokamak docker gpu readiness",
|
|
5170
|
+
ok: gpuDockerReadiness.ok,
|
|
5171
|
+
details: {
|
|
5172
|
+
expectedUseGpus: gpuDockerReadiness.expectedUseGpus,
|
|
5173
|
+
liveUseGpus: gpuDockerReadiness.liveUseGpus,
|
|
5174
|
+
mismatch: gpuDockerReadiness.mismatch,
|
|
5175
|
+
mismatchError: gpuDockerReadiness.mismatchError,
|
|
5176
|
+
hostNvidiaSmi: summarizeProbeResult(gpuDockerReadiness.hostNvidiaSmi),
|
|
5177
|
+
dockerNvidiaSmi: summarizeProbeResult(gpuDockerReadiness.dockerNvidiaSmi),
|
|
5178
|
+
},
|
|
5179
|
+
},
|
|
5180
|
+
{
|
|
5181
|
+
name: "groth16 runtime",
|
|
5182
|
+
ok: groth16Runtime.installed,
|
|
5183
|
+
details: {
|
|
5184
|
+
packageRoot: groth16Runtime.packageRoot,
|
|
5185
|
+
workspaceRoot: groth16Runtime.workspaceRoot,
|
|
5186
|
+
doctorStatus: groth16Runtime.doctor.status,
|
|
5187
|
+
checks: groth16Runtime.checks,
|
|
5188
|
+
},
|
|
5189
|
+
},
|
|
4996
5190
|
];
|
|
4997
5191
|
|
|
4998
5192
|
return {
|
|
@@ -5012,10 +5206,25 @@ function buildDoctorReport() {
|
|
|
5012
5206
|
},
|
|
5013
5207
|
dependencies: dependencyReports,
|
|
5014
5208
|
tokamakCli,
|
|
5209
|
+
groth16Runtime,
|
|
5210
|
+
gpuDockerReadiness,
|
|
5015
5211
|
checks,
|
|
5016
5212
|
};
|
|
5017
5213
|
}
|
|
5018
5214
|
|
|
5215
|
+
function installGroth16RuntimeForPrivateState({ docker }) {
|
|
5216
|
+
const packageRoot = resolveGroth16PackageRoot();
|
|
5217
|
+
const entryPath = resolveGroth16CliEntryPath(packageRoot);
|
|
5218
|
+
const args = [entryPath, "--install"];
|
|
5219
|
+
if (docker) {
|
|
5220
|
+
args.push("--docker");
|
|
5221
|
+
}
|
|
5222
|
+
run(process.execPath, args, { cwd: packageRoot });
|
|
5223
|
+
const runtime = inspectGroth16Runtime();
|
|
5224
|
+
expect(runtime.installed, "Groth16 runtime install completed, but tokamak-groth16 --doctor still reports an unhealthy runtime.");
|
|
5225
|
+
return runtime;
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5019
5228
|
function collectDependencyPackageReports(installManifest = null) {
|
|
5020
5229
|
const installVersions = new Map(
|
|
5021
5230
|
Array.isArray(installManifest?.dependencies)
|
|
@@ -5087,6 +5296,35 @@ function findPackageJsonForName(startDir, expectedName) {
|
|
|
5087
5296
|
throw new Error(`Cannot locate package.json for ${expectedName} above ${startDir}.`);
|
|
5088
5297
|
}
|
|
5089
5298
|
|
|
5299
|
+
function resolveGroth16PackageRoot() {
|
|
5300
|
+
const publicDriveCrsPath = require.resolve("@tokamak-private-dapps/groth16/public-drive-crs");
|
|
5301
|
+
return path.dirname(findPackageJsonForName(path.dirname(publicDriveCrsPath), "@tokamak-private-dapps/groth16"));
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5304
|
+
function resolveGroth16CliEntryPath(packageRoot = resolveGroth16PackageRoot()) {
|
|
5305
|
+
return path.join(packageRoot, "cli", "tokamak-groth16-cli.mjs");
|
|
5306
|
+
}
|
|
5307
|
+
|
|
5308
|
+
function inspectGroth16Runtime() {
|
|
5309
|
+
const packageRoot = resolveGroth16PackageRoot();
|
|
5310
|
+
const entryPath = resolveGroth16CliEntryPath(packageRoot);
|
|
5311
|
+
const doctor = runCaptured(process.execPath, [entryPath, "--doctor", "--verbose"], { cwd: packageRoot });
|
|
5312
|
+
const stdout = stripAnsi(doctor.stdout).trim();
|
|
5313
|
+
const stderr = stripAnsi(doctor.stderr).trim();
|
|
5314
|
+
const report = parseJsonReport(stdout);
|
|
5315
|
+
return {
|
|
5316
|
+
installed: doctor.status === 0 && report?.ok === true,
|
|
5317
|
+
packageRoot,
|
|
5318
|
+
workspaceRoot: report?.workspaceRoot ?? null,
|
|
5319
|
+
checks: report?.checks ?? [],
|
|
5320
|
+
doctor: {
|
|
5321
|
+
status: doctor.status,
|
|
5322
|
+
stdout,
|
|
5323
|
+
stderr,
|
|
5324
|
+
},
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5327
|
+
|
|
5090
5328
|
function inspectTokamakCliRuntime() {
|
|
5091
5329
|
const doctor = runCaptured(tokamakCliCommand, [...tokamakCliBaseArgs, "--doctor"], {
|
|
5092
5330
|
cwd: resolveTokamakCliPackageRoot(),
|
|
@@ -5118,6 +5356,84 @@ function inspectTokamakCliRuntime() {
|
|
|
5118
5356
|
};
|
|
5119
5357
|
}
|
|
5120
5358
|
|
|
5359
|
+
function inspectGpuDockerReadiness(tokamakCli) {
|
|
5360
|
+
const hostNvidiaSmi = runProbe("nvidia-smi", ["--query-gpu=name,driver_version", "--format=csv,noheader"]);
|
|
5361
|
+
const dockerNvidiaSmi = runProbe("docker", [
|
|
5362
|
+
"run",
|
|
5363
|
+
"--rm",
|
|
5364
|
+
"--gpus",
|
|
5365
|
+
"all",
|
|
5366
|
+
DOCKER_CUDA_PROBE_IMAGE,
|
|
5367
|
+
"nvidia-smi",
|
|
5368
|
+
]);
|
|
5369
|
+
const expectedUseGpus = Boolean(tokamakCli.cudaCompatible);
|
|
5370
|
+
const liveUseGpus = hostNvidiaSmi.ok && dockerNvidiaSmi.ok;
|
|
5371
|
+
const mismatch = expectedUseGpus !== liveUseGpus;
|
|
5372
|
+
return {
|
|
5373
|
+
ok: !mismatch,
|
|
5374
|
+
expectedUseGpus,
|
|
5375
|
+
liveUseGpus,
|
|
5376
|
+
mismatch,
|
|
5377
|
+
mismatchError: mismatch
|
|
5378
|
+
? [
|
|
5379
|
+
"Tokamak CLI Docker GPU metadata does not match live NVIDIA/Docker GPU probes.",
|
|
5380
|
+
`metadata useGpus=${expectedUseGpus}; live useGpus=${liveUseGpus}.`,
|
|
5381
|
+
].join(" ")
|
|
5382
|
+
: null,
|
|
5383
|
+
probeImage: DOCKER_CUDA_PROBE_IMAGE,
|
|
5384
|
+
hostNvidiaSmi,
|
|
5385
|
+
dockerNvidiaSmi,
|
|
5386
|
+
};
|
|
5387
|
+
}
|
|
5388
|
+
|
|
5389
|
+
function runProbe(command, args) {
|
|
5390
|
+
const result = spawnSync(command, args, {
|
|
5391
|
+
encoding: "utf8",
|
|
5392
|
+
timeout: DOCTOR_GPU_PROBE_TIMEOUT_MS,
|
|
5393
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5394
|
+
});
|
|
5395
|
+
return {
|
|
5396
|
+
command,
|
|
5397
|
+
args,
|
|
5398
|
+
ok: !result.error && result.status === 0,
|
|
5399
|
+
status: result.status,
|
|
5400
|
+
signal: result.signal,
|
|
5401
|
+
error: result.error ? result.error.message : null,
|
|
5402
|
+
stdout: stripAnsi(result.stdout ?? "").trim(),
|
|
5403
|
+
stderr: stripAnsi(result.stderr ?? "").trim(),
|
|
5404
|
+
timedOut: result.error?.code === "ETIMEDOUT",
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5408
|
+
function summarizeProbeResult(result) {
|
|
5409
|
+
return {
|
|
5410
|
+
command: [result.command, ...result.args].join(" "),
|
|
5411
|
+
ok: result.ok,
|
|
5412
|
+
status: result.status,
|
|
5413
|
+
signal: result.signal,
|
|
5414
|
+
error: result.error,
|
|
5415
|
+
timedOut: result.timedOut,
|
|
5416
|
+
stdout: truncateText(result.stdout, 2000),
|
|
5417
|
+
stderr: truncateText(result.stderr, 2000),
|
|
5418
|
+
};
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
function truncateText(value, maxLength) {
|
|
5422
|
+
const text = String(value ?? "");
|
|
5423
|
+
if (text.length <= maxLength) {
|
|
5424
|
+
return text;
|
|
5425
|
+
}
|
|
5426
|
+
return `${text.slice(0, maxLength)}...`;
|
|
5427
|
+
}
|
|
5428
|
+
|
|
5429
|
+
function parseJsonReport(value) {
|
|
5430
|
+
try {
|
|
5431
|
+
return JSON.parse(value);
|
|
5432
|
+
} catch {
|
|
5433
|
+
return null;
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5121
5437
|
function resolveTokamakCliCacheRoot() {
|
|
5122
5438
|
return path.resolve(process.env.TOKAMAK_ZKEVM_CLI_CACHE_DIR ?? path.join(os.homedir(), ".tokamak-zk-evm"));
|
|
5123
5439
|
}
|