Build, Test, and Contribute
1. Why this chapter exists
You can read every other chapter without ever running a command, and you will get nothing useful out of it. Code understanding without an executable loop is theatre. This chapter exists so that by the end of it, you can:
- compile the workspace,
- run a focused test against a single function,
- run the whole CI matrix locally,
- and reproduce the four checks that CI gates a PR on (test, fmt, clippy, docs).
It also identifies the files that contributors actually touch, so you know where to look for examples of what good change sets look like.
2. Definitions
Definition (MSRV). Minimum Supported Rust Version. Set in
rust-toolchain.toml
to 1.60.0. The MSRV is binding: a PR that requires a newer
compiler is rejected.
Definition (Hot file). A source file modified more than twice in
the last twelve months. These are the files where most contributions
land; the list is in discovery.md.
Definition (Feature flag set). The exact set of --features
flags CI uses. Defined in
.github/actions/prepare/action.yml.
3. The code
3.1 The toolchain
[toolchain]
channel = "1.60.0"
components = ["clippy", "rustfmt"]
Install rustup, then cd into the repo. The toolchain file will
pin your compiler to 1.60.0 automatically. Two facts to keep in
mind:
clippyandrustfmtare listed as required components; rustup will install them on first use.- Many crates in the wider ecosystem have moved past MSRV 1.60. The
workspace pins indirect-dev dependencies (
dashmap,image,tempfile) to versions that still build on 1.60, as documented inhalo2_proofs/Cargo.toml. Do not bump them without coordinating an MSRV update.
3.2 The feature flags CI uses
The composite action that all CI jobs share is:
name: 'Prepare Halo 2'
description: 'Prepares feature flags'
inputs:
beta-features:
description: 'Include beta features'
required: false
default: false
nightly-features:
description: 'Include nightly features'
required: false
default: false
test-dependencies:
description: 'Include test dependencies'
required: false
default: true
outputs:
feature-flags:
description: 'Feature flags'
value: ${{ steps.prepare-flags.outputs.flags }}
runs:
using: 'composite'
steps:
- id: beta
shell: bash
run: echo "feature=beta" >> $GITHUB_OUTPUT
if: inputs.beta-features == 'true'
- id: nightly
shell: bash
run: echo "feature=nightly" >> $GITHUB_OUTPUT
if: inputs.nightly-features == 'true'
- id: test
shell: bash
run: echo "feature=test-dependencies" >> $GITHUB_OUTPUT
if: inputs.test-dependencies == 'true'
- id: prepare-flags
shell: bash
run: >
echo "flags=--no-default-features --features '
batch
dev-graph
gadget-traces
${{ steps.beta.outputs.feature }}
${{ steps.nightly.outputs.feature }}
${{ steps.test.outputs.feature }}
'" >> $GITHUB_OUTPUT
The flag string is:
--no-default-features --features 'batch dev-graph gadget-traces [beta] [nightly] [test-dependencies]'
To reproduce a CI test run locally:
cargo test --release --workspace \
--no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies'
For the beta and nightly variants, append --features beta or
--features nightly.
3.3 Test layout
- Unit tests are inline in the files they test; search for
#[cfg(test)] mod testsat the bottom of any.rsfile. - Integration tests live in
halo2_proofs/tests/. The main one isplonk_api.rswith a checked-in proof fixtureplonk_api_proof.bin. Any change that alters the byte layout of a proof must regenerate this fixture or fail CI. - Examples (
halo2_proofs/examples/) are also compiled by CI under thebitrotjob. - Benchmarks live in
halo2_proofs/benches/withharness = falsedeclared inCargo.toml; run withcargo bench.
3.4 Local commands you will use daily
# Build the workspace in debug mode.
cargo build --workspace
# Run all tests with the CI feature set.
cargo test --release --workspace \
--no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies'
# Run one test by name (substring match).
cargo test --release -p halo2_proofs plonk_api -- --nocapture
# Format check (CI-equivalent).
cargo fmt --all -- --check
# Format in place.
cargo fmt --all
# Clippy (MSRV) with all targets and warnings denied.
cargo clippy --no-default-features \
--features 'batch dev-graph gadget-traces test-dependencies' \
--all-targets -- -D warnings
# Run the simple-example.
cargo run --release --example simple-example -p halo2_proofs
# Build the rendered API docs.
cargo doc --workspace --all-features --document-private-items --open
3.5 The CI graph
The PR checks are split across several workflows:
name: CI checks
on:
pull_request:
push:
branches: main
permissions: {}
jobs:
test:
name: Test on ${{ matrix.os }}${{ matrix.name_suffix }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
stage: [stable, beta, nightly]
os: [ubuntu-latest, windows-latest, macOS-latest]
include:
- stage: beta
name_suffix: " with beta features"
extra_flags: --features beta
- stage: nightly
name_suffix: " with nightly features"
extra_flags: --features nightly
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
- name: Run tests
run: >
cargo test
--verbose
--release
--workspace
${{ steps.prepare.outputs.feature-flags }}
${{ matrix.extra_flags }}
- name: Verify working directory is clean
run: git diff --exit-code
test-32-bit:
name: Test on i686-unknown-linux-gnu${{ matrix.name_suffix }}
runs-on: ubuntu-latest
strategy:
matrix:
stage: [stable, beta, nightly]
include:
- stage: beta
name_suffix: " with beta features"
- stage: nightly
name_suffix: " with nightly features"
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
beta-features: ${{ matrix.stage == 'beta' }}
nightly-features: ${{ matrix.stage == 'nightly' }}
- name: Install cross-platform support dependencies
run: sudo apt install gcc-multilib
- run: rustup target add i686-unknown-linux-gnu
- name: Run tests
run: >
cargo test
--verbose
--release
--workspace
--target i686-unknown-linux-gnu
${{ steps.prepare.outputs.feature-flags }}
build:
name: Build target ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
matrix:
target:
- wasm32-unknown-unknown
- wasm32-wasi
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
nightly-features: true
test-dependencies: false
- name: Add target
run: rustup target add ${{ matrix.target }}
- name: cargo build
run: >
cargo build
${{ steps.prepare.outputs.feature-flags }}
--target ${{ matrix.target }}
bitrot:
name: Bitrot check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
# Check bitrot with stable (as we don't need benchmarks or the test-dev-graph
# feature flag to work with MSRV).
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- run: rustup override set "${TOOLCHAIN}"
shell: sh
env:
TOOLCHAIN: ${{steps.toolchain.outputs.name}}
- run: sudo apt-get -y install libfontconfig1-dev
# Build benchmarks and all-features to prevent bitrot
- name: Build benchmarks
run: cargo build --benches --examples --all-features
book:
name: Book tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
# Build with stable so the .rmeta files match what mdBook expects.
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- run: rustup override set "${TOOLCHAIN}"
shell: sh
env:
TOOLCHAIN: ${{steps.toolchain.outputs.name}}
- name: cargo build
run: cargo build
- name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
with:
mdbook-version: '0.5.1'
- name: Test halo2 book
run: mdbook test -L target/debug/deps book/
codecov:
name: Code coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- id: prepare
uses: ./.github/actions/prepare
with:
nightly-features: true
# Extend the timeout to 3600 to ensure the code coverage test pass
- name: Generate coverage report
run: >
cargo tarpaulin
--engine llvm
${{ steps.prepare.outputs.feature-flags }}
--timeout 3600
--out xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3.1.4
doc-links:
name: Intra-doc links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
- run: sudo apt-get -y install libfontconfig1-dev
- run: cargo fetch
# Ensure intra-documentation links all resolve correctly
# Requires #![deny(intra_doc_link_resolution_failure)] in crates.
- name: Check intra-doc links
run: cargo doc --all --all-features --document-private-items
fmt:
name: Rustfmt
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: ./.github/actions/prepare
- run: cargo fmt --all -- --check
The notable jobs:
test: matrix over{stable, beta, nightly}x{ubuntu, windows, macOS}, runningcargo test --release --workspace.test-32-bit: i686 cross-compile and test. Catches usize-as-32-bit bugs.build: cross-compiles towasm32-unknown-unknownandwasm32-wasi. WASM-incompatible imports break here.bitrot: builds benchmarks and examples with--all-featuresusing the stable toolchain.book: runsmdbook testagainst thebook/directory; the book includes runnable Rust examples that must compile.codecov: runscargo tarpaulinwith nightly features and uploads coverage.doc-links: runscargo doc --all --all-features --document-private-itemswith#![deny(rustdoc::broken_intra_doc_links)].fmt:cargo fmt --all -- --check.lints-stable.ymlandlints-beta.yml: clippy at MSRV / beta with-D warnings.
3.6 Hot files (where contributions land)
The most-changed files in the last twelve months. Each is a likely landing point for newcomer PRs:
halo2_proofs/src/poly/multiopen.rs: the multiopen wrapper; modified by the 0.3.1 soundness fix.halo2_gadgets/src/utilities/lookup_range_check.rs: a heavily reused range-check helper.halo2_proofs/src/poly/multiopen/prover.rs: the multiopen prover.halo2_gadgets/src/sinsemilla/merkle.rs: the Sinsemilla-Merkle path verifier.halo2_gadgets/src/sinsemilla/chip/hash_to_point.rs: the inner loop of the Sinsemilla chip.halo2_proofs/src/arithmetic.rs:best_multiexpand FFT helpers.
3.7 Good first issues
The repo does not use a good-first-issue label, but two
categories of open issues are within reach of a newcomer:
A-bookissues (example: #800, #810): they only require editingbook/src/....- Sanity-check or documentation improvements (e.g. #778, #794): small, well-scoped edits.
A complete list is at zcash/halo2/issues.
3.8 The PR checklist
Before pushing a PR, run locally:
cargo fmt --alland verify withcargo fmt --all -- --check.cargo clippy --no-default-features --features 'batch dev-graph gadget-traces test-dependencies' --all-targets -- -D warnings.- The CI test command above.
cargo doc --workspace --all-features --document-private-itemsto verify no broken intra-doc links.- If you touched any file that affects proof bytes, regenerate
halo2_proofs/tests/plonk_api_proof.bin. - Update the relevant
CHANGELOG.mdin eitherhalo2_proofs/orhalo2_gadgets/. The entry goes under## [Unreleased].
The repo does not require signed commits, but it does require
Apache-2.0 / MIT dual-licensing of contributions (the standard
sentence is in
README.md).
4. Failure modes
- Forgetting
--release. Runningcargo testwithout--releasetakes 10 to 100x longer because polynomial FFTs dominate runtime. The CI matrix always uses--release; you should too. - Forgetting the feature flags. Running
cargo testwith default features omitsdev-graph(and so omits a number of tests). Always use the CI flag string above. - Bumping an indirect-dev dependency past MSRV. The pins in
halo2_proofs/Cargo.tomlexist precisely to keep MSRV at 1.60. If a tool nags you to update one, check its new MSRV first. - Editing
plonk_api_proof.binby hand. This file is binary and is regenerated by the test suite when you set the right env var; never edit it manually.
5. Spec pointers
- The
GitHub Actions documentation on composite actions
explains the pattern used in
.github/actions/prepare/. - The Rust toolchain file format is documented in the rustup book.
6. Exercises
- Run
cargo test --release -p halo2_proofs plonk_apifrom a clean clone. Time how long it takes. Report which test names were executed. - Find the line in
halo2_proofs/Cargo.tomlthat pinsdashmapto< 5.5.0. Note the inline comment that explains why; this is the canonical example of an MSRV-driven indirect-dep pin. - Pick one open
A-bookissue from zcash/halo2/issues. In two sentences each, identify (a) which book file would need to change, (b) which chapter of this course covers the same material. - Open
halo2_proofs/CHANGELOG.mdand find the0.3.1entry. Note what the soundness bug was; you will see this again in chapter 10 (multiopen).
Answers in the code
- Exercise 1: the matching test names are inside
halo2_proofs/tests/plonk_api.rs. - Exercise 2: the
dashmap = ">=5, <5.5.0"line is in the[dev-dependencies]section ofhalo2_proofs/Cargo.tomlwith comment# dashmap 5.5.0 has MSRV 1.64. - Exercise 4: the
0.3.1security section ofhalo2_proofs/CHANGELOG.md.