Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/workflows/exercise.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
name: Chain Exercise Suite

# Runs `quantus exercise` against a freshly built fast-governance dev node.
# Nightly + manual for now: building the node from the chain repo dominates
# the runtime, and QPoW block times make a full run take tens of minutes.
on:
schedule:
- cron: "0 2 * * *" # nightly at 02:00 UTC
workflow_dispatch:
inputs:
chain_ref:
description: "Git ref of Quantus-Network/chain to build the node from"
required: false
default: "main"
fuzz_iterations:
description: "Number of seeded fuzz iterations"
required: false
default: "25"

permissions:
contents: read

env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: always
CARGO_NET_RETRY: 10
CARGO_NET_TIMEOUT: 60

jobs:
exercise:
name: 🏋️ Exercise Suite (fast-governance dev node)
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Checkout quantus-cli
uses: actions/checkout@v5

- name: Checkout chain
uses: actions/checkout@v5
with:
repository: Quantus-Network/chain
ref: ${{ github.event.inputs.chain_ref || 'main' }}
path: chain

- uses: ./.github/actions/ubuntu

- name: Cache cargo registry & CLI target
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-exercise-cli-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}
restore-keys: |
${{ runner.os }}-cargo-exercise-cli-

- name: Cache chain target
uses: actions/cache@v5
with:
path: chain/target
key: ${{ runner.os }}-cargo-exercise-node-${{ hashFiles('chain/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-exercise-node-

- name: Build quantus-node (fast-governance)
working-directory: chain
run: cargo build --release -p quantus-node --features quantus-runtime/fast-governance

- name: Build quantus-cli
run: SKIP_CIRCUIT_BUILD=1 cargo build --release --locked

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exercise CI missing circuits checkout

High Severity

The new Chain Exercise workflow checks out only quantus-cli and chain, then runs cargo build --release --locked. The same commit adds [patch.crates-io] entries pointing at ../qp-zk-circuits/..., which is not present on the runner, so dependency resolution fails before the suite can run.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c6fadf8. Configure here.


- name: Start dev node
run: |
nohup chain/target/release/quantus-node --dev \
--base-path /tmp/quantus-exercise-dev \
> /tmp/quantus-node.log 2>&1 &
echo "NODE_PID=$!" >> "$GITHUB_ENV"
# Wait for RPC to answer (mining warm-up can take a while).
for i in $(seq 1 60); do
if curl -sf -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_health","params":[]}' \
http://127.0.0.1:9944 > /dev/null; then
echo "RPC up after ${i}s"
exit 0
fi
sleep 1
done
echo "node RPC never came up" >&2
tail -50 /tmp/quantus-node.log >&2
exit 1

- name: Run exercise suite
run: |
./target/release/quantus exercise \
--json \
--fuzz-iterations "${{ github.event.inputs.fuzz_iterations || '25' }}" \
| tee exercise-report.txt

- name: Upload report and node log
if: always()
uses: actions/upload-artifact@v4
with:
name: exercise-report
path: |
exercise-report.txt
/tmp/quantus-node.log
if-no-files-found: ignore

- name: Stop dev node
if: always()
run: kill "$NODE_PID" 2>/dev/null || true
14 changes: 0 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,13 @@ opt-level = 3

[profile.release.build-override]
opt-level = 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI version not bumped

Medium Severity

The crate version stays at 1.5.0 while this change adds public-batch circuit parameters (DEFAULT_NUM_PRIVATE_BATCH_PROOFS), new required bin artifacts, and [patch.crates-io] overrides for the wormhole circuit crates. Without a CLI version bump, is_ready()’s .quantus-cli-version marker can still match for installs that already had 1.5.0 but stale generated bins from an earlier build with the same version string.

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by learned rule: Bump CLI version when proof system parameters or circuit crate versions change

Reviewed by Cursor Bugbot for commit c6fadf8. Configure here.

# TEMPORARY: local circuits repo until the private/public batch rename ships as 3.1.0.
[patch.crates-io]
qp-wormhole-aggregator = { path = "../qp-zk-circuits/wormhole/aggregator" }
qp-wormhole-circuit = { path = "../qp-zk-circuits/wormhole/circuit" }
qp-wormhole-circuit-builder = { path = "../qp-zk-circuits/wormhole/circuit-builder" }
qp-wormhole-inputs = { path = "../qp-zk-circuits/wormhole/inputs" }
qp-wormhole-prover = { path = "../qp-zk-circuits/wormhole/prover" }
qp-wormhole-verifier = { path = "../qp-zk-circuits/wormhole/verifier" }
qp-zk-circuits-common = { path = "../qp-zk-circuits/common" }
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ quantus developer build-circuits \

**What it does (3 steps):**
1. Clears stale artifacts from the CLI's `generated-bins/` directory.
2. Calls the `qp-wormhole-circuit-builder` library directly to regenerate binary files in `generated-bins/` (`verifier.bin`, `common.bin`, `aggregated_verifier.bin`, `aggregated_common.bin`, `config.json`, plus prover binaries unless `--skip-prover` is set).
3. Copies chain-relevant binaries (`aggregated_common.bin`, `aggregated_verifier.bin`, `config.json`) to `chain/pallets/wormhole/` and touches the pallet source.
2. Calls the `qp-wormhole-circuit-builder` library directly to regenerate binary files in `generated-bins/` (`verifier.bin`, `common.bin`, `private_batch_verifier.bin`, `private_batch_common.bin`, `config.json`, plus prover binaries unless `--skip-prover` is set).
3. Copies chain-relevant binaries (`private_batch_common.bin`, `private_batch_verifier.bin`, `config.json`) to `chain/pallets/wormhole/` and touches the pallet source.

After running, rebuild the chain (`cargo build --release` in the chain directory) so `include_bytes!()` picks up the new binaries.

Expand Down
20 changes: 14 additions & 6 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ fn main() {
.map(|v| v.parse().expect("QP_NUM_LEAF_PROOFS must be a valid usize"))
.unwrap_or(DEFAULT_NUM_LEAF_PROOFS);

let num_private_batch_proofs: usize = env::var("QP_NUM_PRIVATE_BATCH_PROOFS")
.map(|v| v.parse().expect("QP_NUM_PRIVATE_BATCH_PROOFS must be a valid usize"))
.unwrap_or(DEFAULT_NUM_PRIVATE_BATCH_PROOFS);

// Re-run when QP_NUM_LEAF_PROOFS env var changes. Note: emitting any `rerun-if-*`
// directive opts out of Cargo's default "re-run when any package file changes"
// behavior. However, the important cases still work:
Expand All @@ -66,10 +70,11 @@ fn main() {
// For installed binaries, runtime detection in bins.rs `is_ready()` handles leaf
// count mismatches by regenerating on first use.
println!("cargo:rerun-if-env-changed=QP_NUM_LEAF_PROOFS");
println!("cargo:rerun-if-env-changed=QP_NUM_PRIVATE_BATCH_PROOFS");

println!(
"cargo:warning=[quantus-cli] Generating ZK circuit binaries (num_leaf_proofs={})...",
num_leaf_proofs
"cargo:warning=[quantus-cli] Generating ZK circuit binaries (num_leaf_proofs={}, num_private_batch_proofs={})...",
num_leaf_proofs, num_private_batch_proofs
);

let start = Instant::now();
Expand All @@ -81,7 +86,7 @@ fn main() {
&build_output_dir,
true,
num_leaf_proofs,
None,
Some(num_private_batch_proofs),
)
.expect("Failed to generate circuit binaries");

Expand All @@ -100,9 +105,12 @@ fn main() {
print_bin_hash(&build_output_dir, "verifier.bin");
print_bin_hash(&build_output_dir, "prover.bin");
print_bin_hash(&build_output_dir, "dummy_proof.bin");
print_bin_hash(&build_output_dir, "aggregated_common.bin");
print_bin_hash(&build_output_dir, "aggregated_verifier.bin");
print_bin_hash(&build_output_dir, "aggregated_prover.bin");
print_bin_hash(&build_output_dir, "private_batch_common.bin");
print_bin_hash(&build_output_dir, "private_batch_verifier.bin");
print_bin_hash(&build_output_dir, "private_batch_prover.bin");
print_bin_hash(&build_output_dir, "public_batch_common.bin");
print_bin_hash(&build_output_dir, "public_batch_verifier.bin");
print_bin_hash(&build_output_dir, "public_batch_prover.bin");

// Copy bins to project root for runtime access, but only during local source
// builds — never during `cargo publish` verification (manifest_dir is inside
Expand Down
10 changes: 5 additions & 5 deletions examples/wormhole_sdk_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! 6. compute_merkle_positions -> (siblings, positions)
//! 7. wormhole_lib::generate_proof -> leaf proof bytes
//! 8. aggregate_proofs([leaf]) -> agg.hex
//! 9. verify_aggregated_and_get_events -> minted NativeTransferred
//! 9. verify_private_batch_and_get_events -> minted NativeTransferred
//! ```
//!
//! All helpers are the same ones consumed by `stress-test`. The example is
Expand Down Expand Up @@ -54,7 +54,7 @@ use quantus_cli::{
cli::{address_format::bytes_to_quantus_ss58, common::ExecutionMode, send::parse_amount},
compute_merkle_positions, compute_wormhole_address, decode_full_leaf_data,
error::{QuantusError, Result},
get_zk_merkle_proof, parse_transfer_events, transfer, verify_aggregated_and_get_events,
get_zk_merkle_proof, parse_transfer_events, transfer, verify_private_batch_and_get_events,
wallet::WalletManager,
wormhole_lib::{
self, ProofGenerationInput, NATIVE_ASSET_ID, SCALE_DOWN_FACTOR, VOLUME_FEE_BPS,
Expand Down Expand Up @@ -307,18 +307,18 @@ async fn main() -> Result<()> {

// 9. verify + submit -------------------------------------------------------
println!();
println!("[8/9] verify_aggregated_and_get_events (off-chain verify + on-chain submit)");
println!("[8/9] verify_private_batch_and_get_events (off-chain verify + on-chain submit)");
let verify_start = std::time::Instant::now();
let (mint_block, mint_tx, transfers) =
verify_aggregated_and_get_events(agg_path.to_str().unwrap(), &client).await?;
verify_private_batch_and_get_events(agg_path.to_str().unwrap(), &client).await?;
println!(" verified+included in {:.2}s", verify_start.elapsed().as_secs_f64());
println!(" mint block : {:?}", mint_block);
println!(" mint tx : {:?}", mint_tx);

println!();
println!("[9/9] minted NativeTransferred events:");
if transfers.is_empty() {
println!(" (none — verify_aggregated_proof did not emit any events?)");
println!(" (none — verify_private_batch did not emit any events?)");
}
for (i, ev) in transfers.iter().enumerate() {
let to_ss58 = bytes_to_quantus_ss58(&ev.to.0);
Expand Down
14 changes: 7 additions & 7 deletions examples/wormhole_sdk_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
//! submitting anything: `at_best_block`, scans recent blocks for a real `NativeTransferred`
//! event, runs `parse_transfer_events`, fetches the ZK Merkle proof via `get_zk_merkle_proof`,
//! computes positions with `compute_merkle_positions` and decodes the leaf bytes with
//! `decode_full_leaf_data`. The submission path (`submit_unsigned_verify_aggregated_proof`,
//! `verify_aggregated_and_get_events`) is shown only as pseudocode — it requires a funded
//! `decode_full_leaf_data`. The submission path (`submit_unsigned_verify_private_batch`,
//! `verify_private_batch_and_get_events`) is shown only as pseudocode — it requires a funded
//! deposit + ZK proof generation, which [`wormhole_sdk_e2e.rs`](./wormhole_sdk_e2e.rs)
//! demonstrates end-to-end. If the node is unreachable the example exits cleanly with hints; CI
//! without a node still builds and runs it green.
Expand All @@ -34,8 +34,8 @@
//! aggregate_proofs(leaf_files, "agg.hex") → aggregated proof file
//! │
//! ▼
//! verify_aggregated_and_get_events("agg.hex", &client)
//! │ (locally verifies + submits unsigned `verify_aggregated_proof`
//! verify_private_batch_and_get_events("agg.hex", &client)
//! │ (locally verifies + submits unsigned `verify_private_batch`
//! │ + waits for inclusion + collects NativeTransferred events)
//! ▼
//! Vec<NativeTransferred> with the minted amounts at the exit accounts
Expand Down Expand Up @@ -219,7 +219,7 @@ async fn online_demo() -> Result<()> {
println!(
" This is normal on a fresh dev chain. Run examples/wormhole_sdk_e2e.rs first"
);
println!(" (it submits a deposit + verify_aggregated_proof) to populate the chain.");
println!(" (it submits a deposit + verify_private_batch) to populate the chain.");
},
}

Expand Down Expand Up @@ -332,13 +332,13 @@ fn print_online_recipe() {
println!(" let bytes = std::fs::read(\"agg.hex\")?;");
println!(" let bytes = hex::decode(bytes.trim_ascii())?;");
println!(" let (included_at, block_hash, tx_hash) =");
println!(" submit_unsigned_verify_aggregated_proof(&client, bytes).await?;");
println!(" submit_unsigned_verify_private_batch(&client, bytes).await?;");
println!(" println!(\"included @ {{}} block={{:?}} tx={{:?}}\",");
println!(" included_at, block_hash, tx_hash);");
println!();
println!(" // Or, with local verify + event collection:");
println!(" let (block_hash, tx_hash, transfers) =");
println!(" verify_aggregated_and_get_events(\"agg.hex\", &client).await?;");
println!(" verify_private_batch_and_get_events(\"agg.hex\", &client).await?;");
println!(" for ev in transfers {{");
println!(" println!(\" -> {{}} planck to {{}}\", ev.amount, ev.to.to_ss58check());");
println!(" }}");
Expand Down
Loading
Loading