Skip to content

collect-rewards on runtime spec 131: stale nullifier data causes partial withdrawal and NullifierAlreadyUsed #118

Description

@ogaworld369

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:

  1. Subsquid nullifier data can lag behind the on-chain UsedNullifiers storage, causing already-spent transfers to remain in the collection candidate list.
  2. 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

Transfers available: 806

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:

806 -> 830 -> 405 -> 293

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:

  1. Query transfers from Subsquid.
  2. Query spent nullifiers from Subsquid.
  3. Accept a successful Subsquid response even when its nullifier data is stale.
  4. Generate proofs for transfers that are already spent on-chain.
  5. 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

  1. Check all selected nullifiers against on-chain UsedNullifiers before generating proofs.
  2. Do not use on-chain checking only when the Subsquid request itself fails.
  3. Add an indexer freshness or completeness check.
  4. Decode and display System.ExtrinsicFailed.
  5. Run a runtime compatibility check before proof generation.
  6. Publish a CLI build compatible with runtime spec 131.
  7. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions