python3 - <<'PY'
import subprocess
import urllib.parse
title = "collect-rewards on runtime spec 131: stale nullifier data causes partial withdrawal and NullifierAlreadyUsed"
body = """## Summary
quantus wormhole collect-rewards partially completed, then failed. A later retry generated proofs for transfers that had already been spent and stopped with NullifierAlreadyUsed during pre-validation.
There appear to be two related problems:
- Subsquid nullifier data can lag behind the on-chain
UsedNullifiers storage, causing already-spent transfers to remain in the collection candidate list.
- Quantus CLI v1.5.0 appears incompatible with the connected runtime version.
Environment
- OS: Ubuntu 24.04 LTS
- Quantus CLI:
1.5.0
- Node runtime:
specVersion: 131
transactionVersion: 2
- Local node URL:
ws://127.0.0.1:9944
- Subsquid endpoint:
https://sub2.quantus.com/v1/graphql
CLI v1.5.0 appears to support runtime spec 127 / transaction version 2, while the connected node reports runtime spec 131 / transaction version 2.
Initial dry run
First collection attempt
The collection successfully completed 86 of 116 batches:
Batch 84/116: 2.52 DEV withdrawn
Batch 85/116: 2.52 DEV withdrawn
Batch 86/116: 2.16 DEV withdrawn
It then failed with:
Error: Proof verification failed - no ProofVerified event
Failed after 1645.83s
Log summary:
Successful batches: 86
Last successful batch: 86/116
Total withdrawn: 216.36 DEV
Subsequent dry runs
The reported available-transfer count changed as follows:
Mining was stopped before the later checks.
The decreasing count suggests that spent nullifiers were gradually being indexed. However, the final candidate list still included at least one already-spent transfer.
Retry attempt
The retry generated all 293 proofs:
[291/293] Proof generated
[292/293] Proof generated
[293/293] Proof generated
Step 6: Submitting 42 batch(es)...
It then failed before the first batch was submitted:
Pre-validation failed: NullifierAlreadyUsed
Nullifier 0 has already been spent
No Batch 1/42 success line appeared, so the retry withdrew no additional funds.
Observed behavior
The CLI appears to:
- Query transfers from Subsquid.
- Query spent nullifiers from Subsquid.
- Accept a successful Subsquid response even when its nullifier data is stale.
- Generate proofs for transfers that are already spent on-chain.
- Detect the spent nullifier only during final on-chain pre-validation.
The earlier Proof verification failed - no ProofVerified event message also hides the actual runtime dispatch error. The corresponding System.ExtrinsicFailed reason is not displayed.
Expected behavior
Before proof generation, the CLI should reliably remove all spent transfers using the on-chain UsedNullifiers storage.
The CLI should also display the actual runtime dispatch error and reject collect-rewards when its metadata is incompatible with the connected runtime.
Suggested fixes
- Check all selected nullifiers against on-chain
UsedNullifiers before generating proofs.
- Do not use on-chain checking only when the Subsquid request itself fails.
- Add an indexer freshness or completeness check.
- Decode and display
System.ExtrinsicFailed.
- Run a runtime compatibility check before proof generation.
- Publish a CLI build compatible with runtime spec 131.
- Skip spent transfers and continue with valid batches instead of aborting the whole collection.
Safety impact
No double withdrawal occurred because on-chain pre-validation rejected the already-used nullifier.
However:
- Remaining rewards cannot currently be collected safely.
- Hundreds of unnecessary ZK proofs were generated.
- Significant CPU time was wasted.
- The original batch failure reason remains hidden.
- The dry-run transfer count cannot be trusted as the true unspent count.
"""
url = "https://github.com/Quantus-Network/quantus-cli/issues/new?" + urllib.parse.urlencode({
"title": title,
"body": body
})
subprocess.run(["xdg-open", url], check=False)
print()
print("GitHubのIssue作成画面を開きました。")
print("内容を確認して「Submit new issue」を押してください。")
PY
python3 - <<'PY'
import subprocess
import urllib.parse
title = "collect-rewards on runtime spec 131: stale nullifier data causes partial withdrawal and NullifierAlreadyUsed"
body = """## Summary
quantus wormhole collect-rewardspartially completed, then failed. A later retry generated proofs for transfers that had already been spent and stopped withNullifierAlreadyUsedduring pre-validation.There appear to be two related problems:
UsedNullifiersstorage, causing already-spent transfers to remain in the collection candidate list.Environment
1.5.0specVersion: 131transactionVersion: 2ws://127.0.0.1:9944https://sub2.quantus.com/v1/graphqlCLI v1.5.0 appears to support runtime spec 127 / transaction version 2, while the connected node reports runtime spec 131 / transaction version 2.
Initial dry run
First collection attempt
The collection successfully completed 86 of 116 batches:
It then failed with:
Log summary:
Subsequent dry runs
The reported available-transfer count changed as follows:
Mining was stopped before the later checks.
The decreasing count suggests that spent nullifiers were gradually being indexed. However, the final candidate list still included at least one already-spent transfer.
Retry attempt
The retry generated all 293 proofs:
It then failed before the first batch was submitted:
No
Batch 1/42success line appeared, so the retry withdrew no additional funds.Observed behavior
The CLI appears to:
The earlier
Proof verification failed - no ProofVerified eventmessage also hides the actual runtime dispatch error. The correspondingSystem.ExtrinsicFailedreason is not displayed.Expected behavior
Before proof generation, the CLI should reliably remove all spent transfers using the on-chain
UsedNullifiersstorage.The CLI should also display the actual runtime dispatch error and reject
collect-rewardswhen its metadata is incompatible with the connected runtime.Suggested fixes
UsedNullifiersbefore generating proofs.System.ExtrinsicFailed.Safety impact
No double withdrawal occurred because on-chain pre-validation rejected the already-used nullifier.
However:
"""
url = "https://github.com/Quantus-Network/quantus-cli/issues/new?" + urllib.parse.urlencode({
"title": title,
"body": body
})
subprocess.run(["xdg-open", url], check=False)
print()
print("GitHubのIssue作成画面を開きました。")
print("内容を確認して「Submit new issue」を押してください。")
PY