From db477a42d0c0846ac491ce68b052167764c2cf05 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 May 2026 19:41:34 +0800 Subject: [PATCH 01/18] feat: apply changes from PR1 except corpus --- .github/actions/checkout-lez/action.yml | 18 + .github/workflows/fuzz-afl.yml | 387 ++++++++++++ .github/workflows/fuzz.yml | 164 ++++- .github/workflows/lint.yml | 75 +++ Cargo.toml | 1 + Justfile | 586 +++++++++++++++++- current_vs_alternative_approach.md | 10 +- docs/fuzzing.md | 329 +++++++++- fuzz/Cargo.lock | 28 + fuzz/Cargo.toml | 8 +- fuzz/fuzz_targets/_template.rs | 28 +- .../fuzz_apply_state_diff_split_path.rs | 26 +- fuzz/fuzz_targets/fuzz_block_verification.rs | 5 +- fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs | 5 +- .../fuzz_multi_block_state_sequence.rs | 26 +- .../fuzz_program_deployment_lifecycle.rs | 5 +- fuzz/fuzz_targets/fuzz_replay_prevention.rs | 32 +- .../fuzz_sequencer_vs_replayer.rs | 5 +- .../fuzz_signature_verification.rs | 5 +- .../fuzz_state_diff_computation.rs | 5 +- fuzz/fuzz_targets/fuzz_state_serialization.rs | 5 +- fuzz/fuzz_targets/fuzz_state_transition.rs | 32 +- .../fuzz_stateless_verification.rs | 5 +- .../fuzz_targets/fuzz_transaction_decoding.rs | 5 +- .../fuzz_validate_execute_consistency.rs | 24 +- .../fuzz_witness_set_verification.rs | 5 +- fuzz_props/Cargo.toml | 4 + fuzz_props/src/arbitrary_types.rs | 31 +- fuzz_props/src/generators.rs | 54 +- fuzz_props/src/invariants.rs | 210 +------ fuzz_props/src/lib.rs | 108 +++- fuzz_props/src/tests.rs | 3 + fuzz_props/src/tests/invariants.rs | 119 ++++ fuzz_props/src/tests/replay_proptest.rs | 33 + fuzz_props/src/tests/seed_gen.rs | 25 + scripts/add_fuzz_target.py | 29 +- 36 files changed, 1935 insertions(+), 505 deletions(-) create mode 100644 .github/actions/checkout-lez/action.yml create mode 100644 .github/workflows/fuzz-afl.yml create mode 100644 .github/workflows/lint.yml create mode 100644 fuzz_props/src/tests.rs create mode 100644 fuzz_props/src/tests/invariants.rs create mode 100644 fuzz_props/src/tests/replay_proptest.rs create mode 100644 fuzz_props/src/tests/seed_gen.rs diff --git a/.github/actions/checkout-lez/action.yml b/.github/actions/checkout-lez/action.yml new file mode 100644 index 0000000..435920a --- /dev/null +++ b/.github/actions/checkout-lez/action.yml @@ -0,0 +1,18 @@ +name: Checkout logos-execution-zone +description: > + Checks out logos-blockchain/logos-execution-zone into a sub-directory and + symlinks it to the expected sibling path (../logos-execution-zone) so that + Cargo path dependencies resolve correctly. + +runs: + using: composite + steps: + - name: Checkout logos-execution-zone alongside lez-fuzzing + uses: actions/checkout@v4 + with: + repository: logos-blockchain/logos-execution-zone + path: logos-execution-zone + + - name: Symlink logos-execution-zone to sibling directory + run: ln -s "$GITHUB_WORKSPACE/logos-execution-zone" "$GITHUB_WORKSPACE/../logos-execution-zone" + shell: bash diff --git a/.github/workflows/fuzz-afl.yml b/.github/workflows/fuzz-afl.yml new file mode 100644 index 0000000..542af4b --- /dev/null +++ b/.github/workflows/fuzz-afl.yml @@ -0,0 +1,387 @@ +name: AFL++ Fuzzing + +on: + schedule: + - cron: "0 2 * * *" + workflow_dispatch: + push: + branches: [main] + +env: + RISC0_DEV_MODE: "1" + CARGO_TERM_COLOR: always + +jobs: + # ──────────────────────────────────────────────────────────────────────────── + # afl-smoke — 60-second per targets + # ──────────────────────────────────────────────────────────────────────────── + afl-smoke: + name: "AFL++ smoke — ${{ matrix.target }}" + runs-on: ubuntu-latest + + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + target: + - fuzz_apply_state_diff_split_path + - fuzz_block_verification + - fuzz_encoding_roundtrip + - fuzz_multi_block_state_sequence + - fuzz_program_deployment_lifecycle + - fuzz_replay_prevention + - fuzz_sequencer_vs_replayer + - fuzz_signature_verification + - fuzz_state_diff_computation + - fuzz_state_serialization + - fuzz_state_transition + - fuzz_stateless_verification + - fuzz_transaction_decoding + - fuzz_validate_execute_consistency + - fuzz_witness_set_verification + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + - name: Install logos-blockchain-circuits + uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install AFL++ v4.40c from source + run: | + sudo apt-get update -q + sudo apt-get install -y \ + build-essential python3-dev automake cmake \ + flex bison libglib2.0-dev libpixman-1-dev \ + python3-setuptools ninja-build + git clone --depth 1 --branch v4.40c \ + https://github.com/AFLplusplus/AFLplusplus /tmp/aflplusplus + cd /tmp/aflplusplus + make distrib + sudo make install + afl-fuzz --version + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-afl + run: cargo install cargo-afl --locked + + - name: Build fuzz target + run: | + cargo afl build \ + --manifest-path fuzz/Cargo.toml \ + --no-default-features \ + --features fuzzer-afl \ + --release \ + --bin ${{ matrix.target }} + + - name: Prepare seed corpus + run: | + TARGET="${{ matrix.target }}" + SEEDS="afl-seeds/${TARGET}" + mkdir -p "$SEEDS" + # Merge checked-in libFuzzer corpus and accumulated AFL corpus + for src in corpus/libfuzz/${TARGET} corpus/afl/${TARGET}; do + [ -d "$src" ] || continue + for f in "$src"/*; do + [ -f "$f" ] || continue + cp -n "$f" "$SEEDS/" 2>/dev/null || true + done + done + # Guarantee at least one seed so afl-fuzz does not abort + if [ -z "$(ls -A "$SEEDS")" ]; then + echo -n "seed" > "$SEEDS/default_seed" + fi + echo "Seed inputs: $(ls "$SEEDS" | wc -l)" + + - name: Run AFL++ for 60 seconds + env: + AFL_SKIP_CPUFREQ: "1" + AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: "1" + run: | + TARGET="${{ matrix.target }}" + mkdir -p afl-output/${TARGET} + # Disable errexit so that timeout's exit code 124 (expected signal) does not + # cause bash -e to abort the script before the guard below can run. + set +e + timeout 60 \ + afl-fuzz \ + -i afl-seeds/${TARGET} \ + -o afl-output/${TARGET} \ + -- fuzz/target/release/${TARGET} + rc=$? + set -e + # 124 = SIGALRM from timeout (expected); 0 = clean exit; anything else is a real failure + [ $rc -eq 0 ] || [ $rc -eq 124 ] || exit $rc + + - name: Calculate and show edge bitmap coverage + if: always() + run: | + TARGET="${{ matrix.target }}" + MAP_SIZE=65536 + + # ── Method 1: bitmap_cvg from fuzzer_stats (written live by afl-fuzz) ── + STATS="afl-output/${TARGET}/default/fuzzer_stats" + if [ -f "$STATS" ]; then + cvg=$(grep '^bitmap_cvg' "$STATS" | awk '{print $3}') + filled_stat=$(grep '^edges_found' "$STATS" | awk '{print $3}' || echo "n/a") + else + cvg="n/a" + filled_stat="n/a" + fi + + # ── Method 2: afl-showmap union over checked-in corpus ── + CORPUS="corpus/afl/${TARGET}" + BINARY="fuzz/target/release/${TARGET}" + showmap_filled="n/a" + showmap_pct="n/a" + if [ -d "$CORPUS" ] && [ -f "$BINARY" ]; then + afl-showmap -C \ + -i "$CORPUS" \ + -o "afl-edges-${TARGET}.txt" \ + -- "$BINARY" 2>/dev/null || true + if [ -f "afl-edges-${TARGET}.txt" ]; then + showmap_filled=$(wc -l < "afl-edges-${TARGET}.txt" | tr -d ' ') + showmap_pct=$(echo "scale=2; ${showmap_filled} * 100 / ${MAP_SIZE}" | bc) + fi + fi + + # ── ASCII bitmap visualisation (64×64 grid, one cell = 1024 slots) ── + # Each of the 4096 cells represents 16 consecutive bitmap slots. + # Cell is '■' if ANY of its 16 slots is non-zero, '·' otherwise. + EDGE_FILE="afl-edges-${TARGET}.txt" + CELLS=64 # 64 cells wide × 64 tall = 4096 cells × 16 slots = 65536 + SLOTS_PER_CELL=16 + if [ -f "$EDGE_FILE" ]; then + python3 - "$EDGE_FILE" "$CELLS" "$SLOTS_PER_CELL" <<'PYEOF' + import sys, math + + edge_file = sys.argv[1] + cells = int(sys.argv[2]) # cells per row + spc = int(sys.argv[3]) # slots per cell + MAP_SIZE = 65536 + total_cells = cells * cells # 4096 + + hit = set() + with open(edge_file) as f: + for line in f: + line = line.strip() + if ':' in line: + slot = int(line.split(':')[0]) + hit.add(slot) + + print(f"\nEdge bitmap visualisation — {cells}×{cells} grid " + f"(each cell = {spc} slots, ■=any hit, ·=none)") + print("+" + "─" * (cells * 2 - 1) + "+") + for row in range(cells): + row_str = "" + for col in range(cells): + cell_idx = row * cells + col + slot_start = cell_idx * spc + slot_end = slot_start + spc + filled = any(s in hit for s in range(slot_start, slot_end)) + row_str += ("■" if filled else "·") + " " + print("|" + row_str.rstrip() + "|") + print("+" + "─" * (cells * 2 - 1) + "+") + + filled_cells = sum( + 1 for c in range(total_cells) + if any((c * spc + s) in hit for s in range(spc)) + ) + print(f"Cells filled: {filled_cells}/{total_cells} " + f"({filled_cells*100/total_cells:.1f}%)\n") + PYEOF + fi + + # ── GitHub Step Summary ── + { + echo "## Edge Bitmap Coverage — \`${TARGET}\`" + echo "" + echo "| Method | Filled slots | Bitmap filled % |" + echo "|---|---|---|" + echo "| \`fuzzer_stats\` (afl-fuzz live) | ${filled_stat} | **${cvg}** |" + echo "| \`afl-showmap\` (corpus union) | ${showmap_filled} | **${showmap_pct}%** |" + echo "" + echo "> MAP_SIZE = ${MAP_SIZE} slots (2¹⁶). " + echo "> A slot is filled when any corpus input exercises that program edge." + } >> "$GITHUB_STEP_SUMMARY" + + - name: Package AFL findings into tarball + if: always() + run: | + TARGET="${{ matrix.target }}" + OUTPUT="afl-output/${TARGET}" + # AFL++ queue/crash/hang filenames contain colons, which are forbidden by + # actions/upload-artifact on NTFS-based runners. Bundle everything into a + # single tarball so the colon-bearing filenames never appear as individual + # artifact entries. + if [ -d "$OUTPUT" ]; then + tar -czf "afl-findings-${TARGET}.tar.gz" \ + -C "$(dirname "$OUTPUT")" "$(basename "$OUTPUT")" + else + tar -czf "afl-findings-${TARGET}.tar.gz" -T /dev/null + fi + + - name: Upload AFL findings artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: afl-findings-${{ matrix.target }} + path: afl-findings-${{ matrix.target }}.tar.gz + if-no-files-found: ignore + + # ──────────────────────────────────────────────────────────────────────────── + # afl-coverage — LLVM coverage report for all 15 targets + # ──────────────────────────────────────────────────────────────────────────── + afl-coverage: + name: "AFL++ coverage — ${{ matrix.target }}" + runs-on: ubuntu-latest + needs: afl-smoke + + strategy: + fail-fast: false + matrix: + target: + - fuzz_apply_state_diff_split_path + - fuzz_block_verification + - fuzz_encoding_roundtrip + - fuzz_multi_block_state_sequence + - fuzz_program_deployment_lifecycle + - fuzz_replay_prevention + - fuzz_sequencer_vs_replayer + - fuzz_signature_verification + - fuzz_state_diff_computation + - fuzz_state_serialization + - fuzz_state_transition + - fuzz_stateless_verification + - fuzz_transaction_decoding + - fuzz_validate_execute_consistency + - fuzz_witness_set_verification + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + - name: Install logos-blockchain-circuits + uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Rust nightly + llvm-tools-preview + uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools-preview + + - name: Download smoke findings for ${{ matrix.target }} + uses: actions/download-artifact@v4 + with: + name: afl-findings-${{ matrix.target }} + path: . + continue-on-error: true # no crashes/hangs/queue is fine + + - name: Extract AFL findings tarball + run: | + TARGET="${{ matrix.target }}" + TARBALL="afl-findings-${TARGET}.tar.gz" + if [ -f "$TARBALL" ]; then + tar -xzf "$TARBALL" + fi + + - name: Build with LLVM instrumented coverage + env: + RUSTFLAGS: "-C instrument-coverage" + RISC0_DEV_MODE: "1" + run: | + # Build with the libfuzzer harness: libFuzzer accepts corpus files as + # positional arguments, runs each through the fuzz closure once, then + # exits — LLVM coverage counters (-C instrument-coverage) are flushed + # to the .profraw file on exit regardless of the fuzzer runtime used. + cargo build \ + --manifest-path fuzz/Cargo.toml \ + --no-default-features \ + --features fuzzer-libfuzzer \ + --release \ + --bin ${{ matrix.target }} + + - name: Run corpus + queue entries through instrumented binary + run: | + TARGET="${{ matrix.target }}" + BINARY="fuzz/target/release/${TARGET}" + PROFRAW_DIR="coverage/afl/${TARGET}/profraw" + mkdir -p "$PROFRAW_DIR" + idx=0 + + # AFL corpus (checked-in, accumulated from prior runs) + for f in corpus/afl/${TARGET}/*; do + [ -f "$f" ] || continue + LLVM_PROFILE_FILE="${PROFRAW_DIR}/${idx}.profraw" "$BINARY" "$f" 2>/dev/null || true + idx=$((idx + 1)) + done + + # AFL++ queue entries from today's smoke run (downloaded artifact) + for instance_dir in afl-output/${TARGET}/*/; do + QUEUE="${instance_dir}queue" + [ -d "$QUEUE" ] || continue + for f in "$QUEUE"/id:*; do + [ -f "$f" ] || continue + LLVM_PROFILE_FILE="${PROFRAW_DIR}/${idx}.profraw" "$BINARY" "$f" 2>/dev/null || true + idx=$((idx + 1)) + done + done + echo "Ran ${idx} inputs through ${TARGET}" + + - name: Merge raw profiles + run: | + TARGET="${{ matrix.target }}" + PROFRAW_DIR="coverage/afl/${TARGET}/profraw" + PROFDATA="coverage/afl/${TARGET}/merged.profdata" + SYSROOT="$(rustc --print sysroot)" + HOST_TRIPLE="$(rustc -vV | awk '/^host:/{print $2}')" + LLVM_PROFDATA="${SYSROOT}/lib/rustlib/${HOST_TRIPLE}/bin/llvm-profdata" + shopt -s nullglob + files=("${PROFRAW_DIR}"/*.profraw) + if [ ${#files[@]} -eq 0 ]; then + echo "No .profraw files found — skipping merge." + exit 0 + fi + "$LLVM_PROFDATA" merge -sparse "${files[@]}" -o "$PROFDATA" + + - name: Generate HTML coverage report + run: | + TARGET="${{ matrix.target }}" + BINARY="fuzz/target/release/${TARGET}" + PROFDATA="coverage/afl/${TARGET}/merged.profdata" + HTML_DIR="coverage/afl/${TARGET}/html" + SYSROOT="$(rustc --print sysroot)" + HOST_TRIPLE="$(rustc -vV | awk '/^host:/{print $2}')" + LLVM_COV="${SYSROOT}/lib/rustlib/${HOST_TRIPLE}/bin/llvm-cov" + if [ ! -f "$PROFDATA" ]; then + echo "No profdata — skipping HTML report." + exit 0 + fi + mkdir -p "$HTML_DIR" + "$LLVM_COV" show \ + "$BINARY" \ + --instr-profile="$PROFDATA" \ + --format=html \ + --output-dir="$HTML_DIR" \ + --ignore-filename-regex='\.cargo|rustc' + echo "Coverage report: ${HTML_DIR}/index.html" + + - name: Upload coverage report artifact + uses: actions/upload-artifact@v4 + with: + name: afl-coverage-${{ matrix.target }} + path: coverage/afl/${{ matrix.target }}/html/ + if-no-files-found: ignore diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 87a6666..8253824 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -1,12 +1,11 @@ name: Fuzzing on: - push: - branches: [main, develop] - pull_request: schedule: - # Nightly full run - cron: "0 2 * * *" + workflow_dispatch: + push: + branches: [main] env: RISC0_DEV_MODE: "1" @@ -39,14 +38,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Checkout logos-execution-zone alongside lez-fuzzing - uses: actions/checkout@v4 - with: - repository: logos-blockchain/logos-execution-zone - path: logos-execution-zone - - - name: Symlink logos-execution-zone to sibling directory - run: ln -s "$GITHUB_WORKSPACE/logos-execution-zone" "$GITHUB_WORKSPACE/../logos-execution-zone" + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez - name: Install Rust nightly (required by cargo-fuzz) uses: dtolnay/rust-toolchain@nightly @@ -75,9 +68,126 @@ jobs: - name: Run smoke fuzz (60 s) run: | + mkdir -p corpus/libfuzz/${{ matrix.target }} cargo fuzz run ${{ matrix.target }} \ + corpus/libfuzz/${{ matrix.target }} \ -- -max_total_time=60 -jobs=2 -workers=2 + - name: Calculate and show edge bitmap coverage + if: always() + run: | + TARGET="${{ matrix.target }}" + CORPUS="corpus/libfuzz/${TARGET}" + mkdir -p "$CORPUS" + + # ── Build and replay the corpus with LLVM coverage instrumentation ── + # Capture output so we can parse the libFuzzer edge-bitmap lines. + # cargo fuzz coverage builds into fuzz/target//coverage/ + # and writes the merged profdata to fuzz/coverage//coverage.profdata + COVERAGE_LOG=$(cargo fuzz coverage "$TARGET" "$CORPUS" 2>&1 || true) + echo "$COVERAGE_LOG" + + # ── Extract libFuzzer edge-bitmap metrics from the merge log ── + # Total edges: "INFO: Loaded 1 modules (N inline 8-bit counters)" + # Covered edges: "MERGE-OUTER: ... N new coverage edges" + edge_total=$(echo "$COVERAGE_LOG" \ + | grep -oP '(?<=Loaded 1 modules\s{1,10}\()\d+(?= inline 8-bit counters)' \ + | tail -1) + edge_covered=$(echo "$COVERAGE_LOG" \ + | grep -oP '\d+(?= new coverage edges)' \ + | tail -1) + + if [ -n "$edge_total" ] && [ -n "$edge_covered" ] && [ "$edge_total" -gt 0 ]; then + edge_pct=$(python3 -c "print(f'{100*${edge_covered}/${edge_total}:.2f}')") + else + edge_pct="n/a" + fi + [ -z "$edge_total" ] && edge_total="n/a" + [ -z "$edge_covered" ] && edge_covered="n/a" + + echo "Edge bitmap: ${edge_covered}/${edge_total} (${edge_pct}%)" + + # ── Locate llvm-cov from the installed nightly toolchain ── + SYSROOT="$(rustc --print sysroot)" + HOST_TRIPLE="$(rustc -vV | awk '/^host:/{print $2}')" + LLVM_COV="${SYSROOT}/lib/rustlib/${HOST_TRIPLE}/bin/llvm-cov" + + # Use deterministic paths — cargo-fuzz always places artefacts here: + # binary → fuzz/target//coverage/ + # profdata → fuzz/coverage//coverage.profdata + PROFDATA="fuzz/coverage/${TARGET}/coverage.profdata" + BINARY="fuzz/target/${HOST_TRIPLE}/coverage/${TARGET}" + + echo "llvm-cov : ${LLVM_COV}" + echo "profdata : ${PROFDATA} (exists: $([ -f "$PROFDATA" ] && echo yes || echo no))" + echo "binary : ${BINARY} (exists: $([ -f "$BINARY" ] && echo yes || echo no))" + + branches_covered="n/a" + branches_total="n/a" + branch_pct="n/a" + + if [ -f "$PROFDATA" ] && [ -f "$BINARY" ]; then + JSON=$("$LLVM_COV" export "$BINARY" \ + --instr-profile="$PROFDATA" \ + --summary-only \ + --ignore-filename-regex='\.cargo|rustc' 2>/dev/null || echo "{}") + + echo "llvm-cov JSON (first 400 chars): $(echo "$JSON" | head -c 400)" + + branches_covered=$(echo "$JSON" | python3 -c " + import sys, json + data = json.load(sys.stdin) + try: + br = data['data'][0]['totals']['branches'] + print(br['covered']) + except Exception: + print('n/a') + ") + branches_total=$(echo "$JSON" | python3 -c " + import sys, json + data = json.load(sys.stdin) + try: + br = data['data'][0]['totals']['branches'] + print(br['count']) + except Exception: + print('n/a') + ") + branch_pct=$(echo "$JSON" | python3 -c " + import sys, json + data = json.load(sys.stdin) + try: + br = data['data'][0]['totals']['branches'] + print(f\"{br['percent']:.2f}\") + except Exception: + print('n/a') + ") + else + echo "WARNING: profdata or binary not found — skipping llvm-cov." + fi + + echo "Branch coverage: ${branches_covered}/${branches_total} (${branch_pct}%)" + + # ── GitHub Step Summary ── + { + echo "## Edge Bitmap Coverage — \`${TARGET}\`" + echo "" + echo "### libFuzzer edge bitmap (inline 8-bit counters)" + echo "" + echo "| Metric | Value |" + echo "|---|---|" + echo "| Total edges | ${edge_total} |" + echo "| Covered edges | ${edge_covered} |" + echo "| Edge coverage | **${edge_pct}%** |" + echo "" + echo "### LLVM source-based branch coverage" + echo "" + echo "| Covered branches | Total branches | Coverage % |" + echo "|---|---|---|" + echo "| ${branches_covered} | ${branches_total} | **${branch_pct}%** |" + echo "" + echo "> Edge bitmap from libFuzzer merge; branch coverage from \`llvm-cov\` over \`${CORPUS}\`." + } >> "$GITHUB_STEP_SUMMARY" + - name: Upload crash artifacts if: failure() uses: actions/upload-artifact@v4 @@ -110,13 +220,8 @@ jobs: - fuzz_sequencer_vs_replayer steps: - uses: actions/checkout@v4 - - name: Checkout logos-execution-zone alongside lez-fuzzing - uses: actions/checkout@v4 - with: - repository: logos-blockchain/logos-execution-zone - path: logos-execution-zone - - name: Symlink logos-execution-zone to sibling directory - run: ln -s "$GITHUB_WORKSPACE/logos-execution-zone" "$GITHUB_WORKSPACE/../logos-execution-zone" + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez - uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools-preview @@ -127,8 +232,9 @@ jobs: - run: cargo install cargo-fuzz - name: Reproduce corpus run: | + mkdir -p corpus/libfuzz/${{ matrix.target }} cargo fuzz run ${{ matrix.target }} \ - fuzz/corpus/${{ matrix.target }} -- -runs=0 + corpus/libfuzz/${{ matrix.target }} -- -runs=0 # ── proptest property tests ────────────────────────────────────────────────── proptest: @@ -136,13 +242,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Checkout logos-execution-zone alongside lez-fuzzing - uses: actions/checkout@v4 - with: - repository: logos-blockchain/logos-execution-zone - path: logos-execution-zone - - name: Symlink logos-execution-zone to sibling directory - run: ln -s "$GITHUB_WORKSPACE/logos-execution-zone" "$GITHUB_WORKSPACE/../logos-execution-zone" + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez - uses: dtolnay/rust-toolchain@stable - name: Install logos-blockchain-circuits uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits @@ -157,13 +258,8 @@ jobs: if: github.event_name == 'schedule' steps: - uses: actions/checkout@v4 - - name: Checkout logos-execution-zone alongside lez-fuzzing - uses: actions/checkout@v4 - with: - repository: logos-blockchain/logos-execution-zone - path: logos-execution-zone - - name: Symlink logos-execution-zone to sibling directory - run: ln -s "$GITHUB_WORKSPACE/logos-execution-zone" "$GITHUB_WORKSPACE/../logos-execution-zone" + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez - uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools-preview diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a486d6c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,75 @@ +name: Lint + +on: + push: + branches: + - main + paths-ignore: + - "**.md" + - "!.github/workflows/*.yml" + + pull_request: + paths-ignore: + - "**.md" + - "!.github/workflows/*.yml" + +env: + RISC0_DEV_MODE: "1" + CARGO_TERM_COLOR: always + +permissions: + contents: read + pull-requests: read + +jobs: + # ── rustfmt ────────────────────────────────────────────────────────────────── + fmt-rs: + name: Rust formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + - name: Install nightly toolchain for rustfmt + run: rustup install nightly --profile minimal --component rustfmt + + - name: Check Rust files are formatted + run: cargo +nightly fmt --check + + # ── clippy ─────────────────────────────────────────────────────────────────── + lint: + name: Clippy + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + - name: Install logos-blockchain-circuits + uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install stable toolchain with clippy + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Restore Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: lint-rust-cache + save-if: ${{ github.ref == 'refs/heads/main' }} + + - name: Lint workspace + env: + RISC0_DEV_MODE: "1" + run: cargo clippy --workspace --all-targets --all-features -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index 16ef6e1..af287fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ clippy.absolute-paths = "allow" clippy.min-ident-chars = "allow" clippy.indexing-slicing = "allow" clippy.little-endian-bytes = "allow" +clippy.self-named-module-files = "allow" [workspace.lints.rust] unsafe_code = "deny" diff --git a/Justfile b/Justfile index 2dc0ac9..7bfb9be 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,28 @@ # ── Fuzzing ─────────────────────────────────────────────────────────────────── export RISC0_DEV_MODE := "1" +# ── Directory layout ────────────────────────────────────────────────────────── +# +# corpus/ +# libfuzz// — inputs generated/discovered by cargo-fuzz (libFuzzer) +# afl// — inputs generated by AFL++ (synced from afl-output queue) +# +# coverage/ +# libfuzz// — per-target libFuzzer coverage report + profdata +# libfuzz/summary/ — merged libFuzzer summary (all targets) +# afl// — per-target AFL++ corpus coverage report + profdata +# afl/summary/ — merged AFL++ corpus summary (all targets) +# +# afl-output// — AFL++'s raw working directory (queue, crashes, hangs) +# The queue is synced to corpus/afl// via +# `just afl-corpus-sync`; crashes/hangs are kept here. +# +# Note: cargo-fuzz (coverage, run, cmin) always writes its profdata to the +# fixed path fuzz/coverage//coverage.profdata regardless of the +# corpus argument. The coverage recipes copy that file into the organised +# coverage/ tree immediately after it is produced, so AFL passes can never +# overwrite a libFuzzer profdata that is still needed. + # List all registered fuzz targets (reads fuzz/Cargo.toml via cargo-fuzz) list-targets: cargo fuzz list @@ -13,7 +35,8 @@ fuzz TIME="30": set -euo pipefail for target in $(cargo fuzz list 2>/dev/null); do echo "=== fuzzing $target for {{TIME}}s ===" - cargo fuzz run "$target" -- -max_total_time={{TIME}} + mkdir -p "corpus/libfuzz/$target" + cargo fuzz run "$target" "corpus/libfuzz/$target" -- -max_total_time={{TIME}} done # Re-run the saved corpus for every target (regression mode, no new mutations) @@ -22,8 +45,8 @@ fuzz-regression: set -euo pipefail for target in $(cargo fuzz list 2>/dev/null); do echo "=== regression $target ===" - mkdir -p "fuzz/corpus/$target" - cargo fuzz run "$target" "fuzz/corpus/$target" -- -runs=0 + mkdir -p "corpus/libfuzz/$target" + cargo fuzz run "$target" "corpus/libfuzz/$target" -- -runs=0 done # Minimise a crash artifact @@ -47,20 +70,22 @@ corpus-cmin: set -euo pipefail for target in $(cargo fuzz list 2>/dev/null); do echo "=== cmin $target ===" - cargo fuzz cmin "$target" + mkdir -p "corpus/libfuzz/$target" + cargo fuzz cmin "$target" "corpus/libfuzz/$target" done # Minimise the corpus for a single target # Usage: just corpus-cmin-target fuzz_state_transition corpus-cmin-target TARGET: - cargo fuzz cmin {{TARGET}} + mkdir -p corpus/libfuzz/{{TARGET}} + cargo fuzz cmin {{TARGET}} corpus/libfuzz/{{TARGET}} # ── Adding a new target ─────────────────────────────────────────────────────── # Scaffold a new fuzz target — fully automated, no manual edits required. # # Steps performed automatically: -# 1. Creates fuzz/corpus// +# 1. Creates corpus/libfuzz// # 2. Copies fuzz/fuzz_targets/_template.rs → fuzz/fuzz_targets/.rs # 3. Appends the [[bin]] entry to fuzz/Cargo.toml # 4. Inserts into every strategy matrix in .github/workflows/fuzz.yml @@ -73,7 +98,7 @@ new-target NAME: TARGET="fuzz_{{NAME}}" TEMPLATE="fuzz/fuzz_targets/_template.rs" RS_FILE="fuzz/fuzz_targets/${TARGET}.rs" - CORPUS_DIR="fuzz/corpus/${TARGET}" + CORPUS_DIR="corpus/libfuzz/${TARGET}" # ── 1. Create corpus directory ──────────────────────────────────────────── mkdir -p "$CORPUS_DIR" @@ -90,8 +115,533 @@ new-target NAME: # ── 3 & 4. Update Cargo.toml and fuzz.yml automatically ────────────────── python3 scripts/add_fuzz_target.py "$TARGET" echo "" - echo "Done! Verify the build with:" + echo "Done! Verify the libFuzzer build with:" echo " RISC0_DEV_MODE=1 cargo fuzz build ${TARGET}" + echo "" + echo "Verify the AFL++ build with:" + echo " cd fuzz && cargo afl build --no-default-features --features fuzzer-afl --release --bin ${TARGET}" + +# ── AFL++ fuzzing ────────────────────────────────────────────────────────────── +# Prerequisites (install once): +# macOS: brew install afl-fuzz && cargo install cargo-afl +# Linux: Build AFL++ from source (recommended — Debian/Ubuntu apt packages are +# several major versions behind; see https://github.com/AFLplusplus/AFLplusplus): +# git clone https://github.com/AFLplusplus/AFLplusplus +# cd AFLplusplus && make distrib && sudo make install +# Then: cargo install cargo-afl + +# Build ALL fuzz targets for AFL++ (output: fuzz/target/release/) +afl-build: + cd fuzz && cargo afl build --no-default-features --features fuzzer-afl --release + +# Build a SINGLE fuzz target for AFL++ +# Usage: just afl-build-target fuzz_state_transition +afl-build-target TARGET: + cd fuzz && cargo afl build --no-default-features --features fuzzer-afl --release --bin {{TARGET}} + +# Disable the macOS crash reporter daemon so AFL++ can detect crashes reliably. +# This is a macOS-only requirement; on Linux this is a no-op. +# The `fuzz-afl` recipe calls this automatically; run it manually if you want +# to keep the reporter disabled across multiple just invocations. +# +# Re-enable with: just afl-macos-teardown +afl-macos-setup: + #!/bin/bash + if [ "$(uname)" != "Darwin" ]; then echo "Not macOS — nothing to do."; exit 0; fi + SL=/System/Library; PL=com.apple.ReportCrash + echo "Disabling macOS crash reporter (required by AFL++)…" + launchctl unload -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl unload -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + echo "Done. Re-enable with: just afl-macos-teardown" + +# Re-enable the macOS crash reporter after an AFL++ session. +afl-macos-teardown: + #!/bin/bash + if [ "$(uname)" != "Darwin" ]; then echo "Not macOS — nothing to do."; exit 0; fi + SL=/System/Library; PL=com.apple.ReportCrash + echo "Re-enabling macOS crash reporter…" + launchctl load -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl load -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + echo "Done." + +# Run AFL++ on one target or ALL targets when no target is supplied. +# Builds binaries as needed; syncs the queue to corpus/afl// when done. +# +# AFL++ is seeded from corpus/libfuzz// (the libFuzzer corpus). +# After the run, new inputs discovered by AFL++ are synced to corpus/afl// +# via `just afl-corpus-sync`. +# +# On macOS the crash reporter is disabled automatically for the duration of the +# run and re-enabled when the script exits (via a shell trap). +# +# Requires afl-fuzz and cargo-afl to be installed locally: +# macOS: brew install afl-fuzz && cargo install cargo-afl +# Linux: Build AFL++ from source (apt packages are several major versions +# behind): see https://github.com/AFLplusplus/AFLplusplus +# +# Usage: just fuzz-afl # all targets, 30 s each +# just fuzz-afl "" 60 # all targets, 60 s each +# just fuzz-afl fuzz_state_transition # single target, 30 s +# just fuzz-afl fuzz_state_transition 300 # single target, 300 s +fuzz-afl TARGET="" TIME="30": + #!/bin/bash + set -euo pipefail + TARGET="{{TARGET}}" + TIME="{{TIME}}" + + # ── Collect targets to run ──────────────────────────────────────────────── + if [ -z "$TARGET" ]; then + TARGETS=($(cargo fuzz list 2>/dev/null)) + else + TARGETS=("$TARGET") + fi + + # ── Require local AFL++ installation ───────────────────────────────────── + if ! command -v afl-fuzz &>/dev/null; then + echo "ERROR: afl-fuzz not found in PATH." + echo "" + echo "Install AFL++ before running this recipe:" + echo "" + echo " macOS : brew install afl-fuzz" + echo "" + echo " Linux : Build from source (apt packages are several major versions behind):" + echo " git clone https://github.com/AFLplusplus/AFLplusplus" + echo " cd AFLplusplus && make distrib && sudo make install" + echo "" + echo "Also install the cargo-afl build wrapper:" + echo " cargo install cargo-afl" + echo "" + exit 1 + fi + if ! command -v cargo-afl &>/dev/null && ! cargo afl --version &>/dev/null 2>&1; then + echo "ERROR: cargo-afl not found." + echo " cargo install cargo-afl" + exit 1 + fi + + # ── macOS: disable crash reporter for the duration of this run ─────────── + if [ "$(uname)" = "Darwin" ]; then + SL=/System/Library; PL=com.apple.ReportCrash + echo "macOS: disabling crash reporter (AFL++ requirement)…" + launchctl unload -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl unload -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + # Re-enable on any exit — normal, error, or Ctrl-C + trap ' + echo "Re-enabling macOS crash reporter…" + SL=/System/Library; PL=com.apple.ReportCrash + launchctl load -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl load -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + ' EXIT + fi + + # ── Run targets ─────────────────────────────────────────────────────────── + _run_one() { + local t="$1" + local BINARY="fuzz/target/release/$t" + local CORPUS="corpus/libfuzz/$t" # seed from libFuzzer corpus + local OUTPUT="afl-output/$t" + mkdir -p "$CORPUS" "$OUTPUT" + if [ ! -f "$BINARY" ]; then + echo "Binary not found — building $t first…" + just afl-build-target "$t" + fi + timeout "$TIME" afl-fuzz -i "$CORPUS" -o "$OUTPUT" -- "$BINARY" || true + } + for t in "${TARGETS[@]}"; do + echo "=== afl++ $t for ${TIME}s ===" + _run_one "$t" + done + just afl-corpus-sync + + # ── Crash / hang summary ────────────────────────────────────────────────── + echo "" + echo "=== AFL++ crash / hang summary ===" + total_crashes=0 + total_hangs=0 + for target_dir in afl-output/*/; do + [ -d "$target_dir" ] || continue + for instance_dir in "$target_dir"*/; do + [ -d "$instance_dir" ] || continue + crashes_dir="${instance_dir}crashes" + hangs_dir="${instance_dir}hangs" + n_crashes=0 + n_hangs=0 + if [ -d "$crashes_dir" ]; then + n_crashes=$(find "$crashes_dir" -maxdepth 1 -type f | wc -l | tr -d ' ') + fi + if [ -d "$hangs_dir" ]; then + n_hangs=$(find "$hangs_dir" -maxdepth 1 -type f | wc -l | tr -d ' ') + fi + if [ "$n_crashes" -gt 0 ] || [ "$n_hangs" -gt 0 ]; then + echo " !! $(basename "$target_dir")/$(basename "$instance_dir") crashes=$n_crashes hangs=$n_hangs" + for f in "$crashes_dir"/id:*; do + [ -f "$f" ] && echo " $f" + done + for f in "$hangs_dir"/id:*; do + [ -f "$f" ] && echo " $f" + done + fi + total_crashes=$((total_crashes + n_crashes)) + total_hangs=$((total_hangs + n_hangs)) + done + done + echo "" + if [ "$total_crashes" -eq 0 ] && [ "$total_hangs" -eq 0 ]; then + echo " ✓ No crashes or hangs found across all targets." + else + echo " TOTAL crashes=$total_crashes hangs=$total_hangs" + echo "" + echo " Minimise a crash : just afl-tmin " + echo " Format for a report: just afl-fmt " + fi + +# Run AFL++ with N parallel instances (1 main + N-1 secondary) for TIME seconds. +# Requires that afl-fuzz is on PATH; all instances share afl-output/{{TARGET}}/. +# On macOS the crash reporter is disabled automatically for the duration of the +# run and re-enabled when the script exits. +# +# Usage: just fuzz-afl-parallel fuzz_state_transition +# just fuzz-afl-parallel fuzz_state_transition 8 600 +fuzz-afl-parallel TARGET WORKERS="4" TIME="300": + #!/bin/bash + set -euo pipefail + BINARY="fuzz/target/release/{{TARGET}}" + CORPUS="corpus/libfuzz/{{TARGET}}" # seed from libFuzzer corpus + OUTPUT="afl-output/{{TARGET}}" + mkdir -p "$CORPUS" "$OUTPUT" + if [ ! -f "$BINARY" ]; then + echo "Binary not found — building first…" + just afl-build-target {{TARGET}} + fi + # ── macOS: disable crash reporter for the duration of this run ─────────── + if [ "$(uname)" = "Darwin" ]; then + SL=/System/Library; PL=com.apple.ReportCrash + echo "macOS: disabling crash reporter (AFL++ requirement)…" + launchctl unload -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl unload -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + trap ' + echo "Re-enabling macOS crash reporter…" + SL=/System/Library; PL=com.apple.ReportCrash + launchctl load -w "${SL}/LaunchAgents/${PL}.plist" 2>/dev/null || true + sudo launchctl load -w "${SL}/LaunchDaemons/${PL}.Root.plist" 2>/dev/null || true + ' EXIT + fi + # Main instance + afl-fuzz -M main -i "$CORPUS" -o "$OUTPUT" -- "$BINARY" & + # Secondary instances + for i in $(seq 1 $(( {{WORKERS}} - 1 ))); do + afl-fuzz -S "secondary${i}" -i "$CORPUS" -o "$OUTPUT" -- "$BINARY" & + done + sleep {{TIME}} + kill $(jobs -p) 2>/dev/null || true + wait 2>/dev/null || true + just afl-corpus-sync + +# Copy all queue entries from every AFL++ output directory into the matching +# AFL corpus directory (corpus/afl//). Run after any AFL++ session +# to make new interesting inputs available for coverage measurement and future runs. +afl-corpus-sync: + #!/bin/bash + set -euo pipefail + if [ ! -d afl-output ]; then + echo "afl-output/ does not exist — nothing to sync." + exit 0 + fi + for target_dir in afl-output/*/; do + TARGET=$(basename "$target_dir") + DEST="corpus/afl/${TARGET}" + mkdir -p "$DEST" + count=0 + for instance_dir in "$target_dir"*/; do + QUEUE="${instance_dir}queue" + [ -d "$QUEUE" ] || continue + for f in "$QUEUE"/id:*; do + [ -f "$f" ] || continue + HASH=$(sha1sum "$f" | cut -d' ' -f1) + DEST_FILE="${DEST}/${HASH}" + if [ ! -f "$DEST_FILE" ]; then + cp "$f" "$DEST_FILE" + count=$((count + 1)) + fi + done + done + echo "Synced $count new input(s) → $DEST" + done + +# Show AFL++ campaign statistics for a target +# Usage: just afl-status fuzz_state_transition +afl-status TARGET: + afl-whatsup afl-output/{{TARGET}} + +# Minimise a crash or hang artifact to the smallest reproducing input. +# Usage: just afl-tmin fuzz_state_transition afl-output/fuzz_state_transition/crashes/id:000000,... +afl-tmin TARGET ARTIFACT: + afl-tmin -i {{ARTIFACT}} -o {{ARTIFACT}}.min -- fuzz/target/release/{{TARGET}} + +# Pretty-print an AFL++ artifact as a Rust byte-string literal (for copy-paste +# into a unit test or issue report). +# Usage: just afl-fmt afl-output/fuzz_state_transition/crashes/id:000000,... +afl-fmt ARTIFACT: + python3 -c "import sys; data=open(sys.argv[1],'rb').read(); print('b\"' + ''.join(f'\\\\x{b:02x}' for b in data) + '\"')" {{ARTIFACT}} + +# ── Coverage ────────────────────────────────────────────────────────────────── +# +# cargo-fuzz always writes its profdata to the fixed path: +# fuzz/coverage//coverage.profdata +# Each coverage recipe immediately copies that file into the organised tree +# (coverage/libfuzz/ or coverage/afl/) so that a subsequent run of the other +# engine cannot overwrite the data we need for the summary reports. + +# Generate a libFuzzer-only coverage report for a single target. +# Runs `cargo fuzz coverage` against corpus/libfuzz//, then copies the +# profdata into coverage/libfuzz// and renders an HTML report there. +# Output: coverage/libfuzz/{{TARGET}}/html/index.html +# Usage: just coverage-libfuzz fuzz_state_transition +coverage-libfuzz TARGET: + #!/bin/bash + set -euo pipefail + + # ── Resolve LLVM tools from the active Rust toolchain ───────────────────── + _SYSROOT=$(rustc --print sysroot) + _HOST=$(rustc -vV | sed -n 's/^host: //p') + _LLVM_BIN="${_SYSROOT}/lib/rustlib/${_HOST}/bin" + LLVM_COV="${_LLVM_BIN}/llvm-cov" + command -v "$LLVM_COV" &>/dev/null || LLVM_COV=$(command -v llvm-cov 2>/dev/null || true) + if [ -z "$LLVM_COV" ] || [ ! -x "$LLVM_COV" ]; then + echo "ERROR: llvm-cov not found in Rust sysroot (${_LLVM_BIN}) or PATH." + echo " Run: rustup component add llvm-tools-preview" + exit 1 + fi + + CORPUS="corpus/libfuzz/{{TARGET}}" + mkdir -p "$CORPUS" + + echo "=== cargo fuzz coverage {{TARGET}} (libFuzzer corpus) ===" + cargo fuzz coverage {{TARGET}} "$CORPUS" + + # ── Copy profdata to the organised tree ─────────────────────────────────── + # cargo-fuzz always writes here; we copy immediately so a later AFL pass + # cannot clobber this file before the summary reads it. + CARGO_PROFDATA="fuzz/coverage/{{TARGET}}/coverage.profdata" + LF_COV_DIR="coverage/libfuzz/{{TARGET}}" + mkdir -p "$LF_COV_DIR" + if [ ! -f "$CARGO_PROFDATA" ]; then + echo "WARNING: profdata not produced — skipping HTML generation." + exit 0 + fi + cp "$CARGO_PROFDATA" "${LF_COV_DIR}/coverage.profdata" + PROFDATA="${LF_COV_DIR}/coverage.profdata" + + # ── Render HTML ─────────────────────────────────────────────────────────── + BINARY="target/${_HOST}/coverage/${_HOST}/release/{{TARGET}}" + HTML_DIR="${LF_COV_DIR}/html" + if [ -f "$BINARY" ]; then + mkdir -p "$HTML_DIR" + "$LLVM_COV" show \ + "$BINARY" \ + --instr-profile="$PROFDATA" \ + --format=html \ + --output-dir="$HTML_DIR" \ + --ignore-filename-regex='\.cargo|rustc' + echo "libFuzzer HTML coverage report: ${HTML_DIR}/index.html" + else + echo "WARNING: binary not found — skipping HTML generation." + echo " Binary: $BINARY" + fi + +# Measure code coverage exercised by the AFL++ corpus for a single target. +# +# Strategy: replay corpus/afl// through the libFuzzer coverage binary +# (built by `cargo fuzz coverage`). Run `just afl-corpus-sync` first to +# populate corpus/afl// from the AFL++ queue. +# +# Output: coverage/afl/{{TARGET}}/html/index.html +# Usage: just coverage-afl fuzz_state_transition +coverage-afl TARGET: + #!/bin/bash + set -euo pipefail + + # ── Resolve LLVM tools from the active Rust toolchain ───────────────────── + _SYSROOT=$(rustc --print sysroot) + _HOST=$(rustc -vV | sed -n 's/^host: //p') + _LLVM_BIN="${_SYSROOT}/lib/rustlib/${_HOST}/bin" + LLVM_COV="${_LLVM_BIN}/llvm-cov" + command -v "$LLVM_COV" &>/dev/null || LLVM_COV=$(command -v llvm-cov 2>/dev/null || true) + if [ -z "$LLVM_COV" ] || [ ! -x "$LLVM_COV" ]; then + echo "ERROR: llvm-cov not found in Rust sysroot (${_LLVM_BIN}) or PATH." + echo " Run: rustup component add llvm-tools-preview" + exit 1 + fi + + AFL_CORPUS="corpus/afl/{{TARGET}}" + if [ ! -d "$AFL_CORPUS" ] || [ -z "$(ls -A "$AFL_CORPUS" 2>/dev/null)" ]; then + echo "No AFL++ corpus for {{TARGET}} at $AFL_CORPUS." + echo " Run 'just afl-corpus-sync' after an AFL++ session to populate it." + exit 0 + fi + + echo "=== AFL++ corpus coverage for {{TARGET}} ===" + + # ── Replay AFL++ corpus through the libFuzzer coverage binary ───────────── + # cargo fuzz coverage always writes: + # profdata → fuzz/coverage/{{TARGET}}/coverage.profdata + # binary → target//coverage//release/{{TARGET}} + cargo fuzz coverage {{TARGET}} "$AFL_CORPUS" + + # ── Copy profdata to the organised tree ─────────────────────────────────── + CARGO_PROFDATA="fuzz/coverage/{{TARGET}}/coverage.profdata" + AFL_COV_DIR="coverage/afl/{{TARGET}}" + mkdir -p "$AFL_COV_DIR" + if [ ! -f "$CARGO_PROFDATA" ]; then + echo "WARNING: profdata not produced — skipping HTML generation." + exit 0 + fi + cp "$CARGO_PROFDATA" "${AFL_COV_DIR}/coverage.profdata" + + # ── Render HTML ─────────────────────────────────────────────────────────── + BINARY="target/${_HOST}/coverage/${_HOST}/release/{{TARGET}}" + HTML_DIR="${AFL_COV_DIR}/html" + mkdir -p "$HTML_DIR" + if [ -f "$BINARY" ]; then + "$LLVM_COV" show \ + "$BINARY" \ + --instr-profile="${AFL_COV_DIR}/coverage.profdata" \ + --format=html \ + --output-dir="$HTML_DIR" \ + --ignore-filename-regex='\.cargo|rustc' + echo "AFL++ corpus HTML coverage report: ${HTML_DIR}/index.html" + else + echo "WARNING: binary not found: $BINARY" + fi + +# Generate a combined coverage report for a single target (libFuzzer + AFL++). +# Delegates to coverage-libfuzz then coverage-afl. +# Usage: just coverage fuzz_state_transition +coverage TARGET: + just coverage-libfuzz {{TARGET}} || true + just coverage-afl {{TARGET}} + +# Generate coverage for ALL registered fuzz targets. +# ENGINE selects which fuzzer engine to measure: +# "all" — libFuzzer + AFL++ (default) +# "libfuzz" — libFuzzer only (cargo fuzz coverage against corpus/libfuzz/) +# "afl" — AFL++ only (cargo fuzz coverage against corpus/afl/) +# +# After the per-target loop, a merged summary HTML report is written: +# libfuzz → coverage/libfuzz/summary/html/index.html +# afl → coverage/afl/summary/html/index.html +# +# Usage: just coverage-all # both engines +# just coverage-all libfuzz # libFuzzer only +# just coverage-all afl # AFL++ only +coverage-all ENGINE="all": + #!/bin/bash + set -euo pipefail + + # ── Resolve LLVM tools from the active Rust toolchain ───────────────────── + _SYSROOT=$(rustc --print sysroot) + _HOST=$(rustc -vV | sed -n 's/^host: //p') + _LLVM_BIN="${_SYSROOT}/lib/rustlib/${_HOST}/bin" + LLVM_COV="${_LLVM_BIN}/llvm-cov" + LLVM_PROFDATA="${_LLVM_BIN}/llvm-profdata" + command -v "$LLVM_COV" &>/dev/null || LLVM_COV=$(command -v llvm-cov 2>/dev/null || true) + command -v "$LLVM_PROFDATA" &>/dev/null || LLVM_PROFDATA=$(command -v llvm-profdata 2>/dev/null || true) + + TARGETS=($(cargo fuzz list 2>/dev/null)) + + # ── Per-target passes ───────────────────────────────────────────────────── + # Each coverage-libfuzz / coverage-afl call copies its profdata into the + # organised tree (coverage/libfuzz/ or coverage/afl/) immediately after + # cargo-fuzz produces it. Because the two engines write to distinct + # directories there is no risk of one overwriting the other's data, + # regardless of the order in which the targets are processed. + if [ "{{ENGINE}}" = "all" ] || [ "{{ENGINE}}" = "libfuzz" ]; then + for target in "${TARGETS[@]}"; do + echo "=== coverage (libfuzz) $target ===" + just coverage-libfuzz "$target" || true + done + fi + if [ "{{ENGINE}}" = "all" ] || [ "{{ENGINE}}" = "afl" ]; then + for target in "${TARGETS[@]}"; do + echo "=== coverage (afl) $target ===" + just coverage-afl "$target" + done + fi + + # ── Merged summary report (libfuzz) ─────────────────────────────────────── + if [ "{{ENGINE}}" = "libfuzz" ] || [ "{{ENGINE}}" = "all" ]; then + echo "" + echo "=== libFuzzer summary report (all targets merged) ===" + SUMMARY_DIR="coverage/libfuzz/summary" + mkdir -p "$SUMMARY_DIR" + + PROFDATA_FILES=() + BINARY_ARGS=() + for t in "${TARGETS[@]}"; do + PD="coverage/libfuzz/$t/coverage.profdata" + BIN="target/${_HOST}/coverage/${_HOST}/release/$t" + [ -f "$PD" ] && PROFDATA_FILES+=("$PD") + [ -f "$BIN" ] && BINARY_ARGS+=("--object" "$BIN") + done + + if [ ${#PROFDATA_FILES[@]} -eq 0 ]; then + echo "No libFuzzer profdata found — skipping summary." + else + MERGED="${SUMMARY_DIR}/merged.profdata" + "$LLVM_PROFDATA" merge -sparse "${PROFDATA_FILES[@]}" -o "$MERGED" + + HTML_DIR="${SUMMARY_DIR}/html" + mkdir -p "$HTML_DIR" + # First binary is positional; the rest are --object flags. + FIRST_BIN="${BINARY_ARGS[1]}" # index 1 is the path after '--object' + REST_ARGS=("${BINARY_ARGS[@]:2}") + "$LLVM_COV" show \ + "$FIRST_BIN" \ + "${REST_ARGS[@]}" \ + --instr-profile="$MERGED" \ + --format=html \ + --output-dir="$HTML_DIR" \ + --ignore-filename-regex='\.cargo|rustc' + echo "libFuzzer summary HTML report: ${HTML_DIR}/index.html" + fi + fi + + # ── Merged summary report (afl) ─────────────────────────────────────────── + if [ "{{ENGINE}}" = "afl" ] || [ "{{ENGINE}}" = "all" ]; then + echo "" + echo "=== AFL++ corpus summary report (all targets merged) ===" + SUMMARY_DIR="coverage/afl/summary" + mkdir -p "$SUMMARY_DIR" + + PROFDATA_FILES=() + BINARY_ARGS=() + for t in "${TARGETS[@]}"; do + PD="coverage/afl/$t/coverage.profdata" + BIN="target/${_HOST}/coverage/${_HOST}/release/$t" + [ -f "$PD" ] && PROFDATA_FILES+=("$PD") + [ -f "$BIN" ] && BINARY_ARGS+=("--object" "$BIN") + done + + if [ ${#PROFDATA_FILES[@]} -eq 0 ]; then + echo "No AFL++ profdata found — skipping summary." + else + MERGED="${SUMMARY_DIR}/merged.profdata" + "$LLVM_PROFDATA" merge -sparse "${PROFDATA_FILES[@]}" -o "$MERGED" + + HTML_DIR="${SUMMARY_DIR}/html" + mkdir -p "$HTML_DIR" + FIRST_BIN="${BINARY_ARGS[1]}" + REST_ARGS=("${BINARY_ARGS[@]:2}") + "$LLVM_COV" show \ + "$FIRST_BIN" \ + "${REST_ARGS[@]}" \ + --instr-profile="$MERGED" \ + --format=html \ + --output-dir="$HTML_DIR" \ + --ignore-filename-regex='\.cargo|rustc' + echo "AFL++ corpus summary HTML report: ${HTML_DIR}/index.html" + fi + fi # ── Housekeeping ────────────────────────────────────────────────────────────── @@ -104,9 +654,21 @@ clean: clean-artifacts: rm -rf fuzz/artifacts/ -# Remove coverage reports generated by `cargo fuzz coverage` +# Remove all coverage reports. +# Also removes the temporary profdata that cargo-fuzz writes to fuzz/coverage/ +# and any stray .profraw files left by the instrumented binaries. clean-coverage: - rm -rf fuzz/coverage/ + rm -rf coverage/ fuzz/coverage/ + find . -name '*.profraw' -delete -# Remove everything: builds, artifacts, and coverage -clean-all: clean clean-artifacts clean-coverage +# Remove AFL++ output directories (crashes, hangs, queue). +# Note: the queue is also stored in corpus/afl/ via `just afl-corpus-sync`. +clean-afl: + rm -rf afl-output/ + +# Remove the corpus directories (libFuzzer and AFL). +clean-corpus: + rm -rf corpus/ + +# Remove everything: builds, artifacts, coverage, and AFL++ output (preserves corpus/) +clean-all: clean clean-artifacts clean-coverage clean-afl diff --git a/current_vs_alternative_approach.md b/current_vs_alternative_approach.md index 8d07ddf..34af3ac 100644 --- a/current_vs_alternative_approach.md +++ b/current_vs_alternative_approach.md @@ -131,14 +131,8 @@ The extension noted in [`docs/fuzzing.md`](docs/fuzzing.md:356) is: ## Decision-maker Recommendations -The current implementation is **well-architected and production-ready** for a protocol at this stage. Its [`fuzz_props`](fuzz_props/src/lib.rs) crate, typed `Arbitrary` wrappers, and `ProtocolInvariant` framework provide the right abstractions to add new targets and invariants incrementally. - **Highest-ROI next steps, in priority order:** -1. **The invariant framework is complete for the current target set** — three invariants are fully implemented and auto-run by [`assert_invariants()`](fuzz_props/src/invariants.rs:325): [`StateIsolationOnFailure`](fuzz_props/src/invariants.rs:60), [`BalanceConservation`](fuzz_props/src/invariants.rs:94), and [`FailedTxNonceStability`](fuzz_props/src/invariants.rs:130). Two further invariants ([`ReplayRejection`](fuzz_props/src/invariants.rs:169) and [`NonceIncrementCorrectness`](fuzz_props/src/invariants.rs:196)) are registered stubs; callers use the dedicated `assert_replay_rejection` and `assert_nonce_increment_correctness` helpers directly. The next step is to audit all 15 targets to confirm every applicable invariant is wired up, then add mutation tests via `cargo-mutants`. +1. **Add AFL++ as a parallel fuzzing lane** (`just fuzz-afl`) — zero corpus migration cost, discovers different mutation paths through the same targets as libFuzzer. -2. ✅ **The sequencer-vs-replayer differential target is implemented** — [`fuzz_sequencer_vs_replayer`](fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs) catches consensus-breaking state root divergence between the sequencer and replayer pipelines, unique to this protocol's architecture. - -3. **Add AFL++ as a parallel fuzzing lane** (`just fuzz-afl`) — zero corpus migration cost, discovers different mutation paths through the same targets as libFuzzer. - -4. **Add `cargo-mutants`** before any external security audit — proves the invariant assertions in [`fuzz_props/src/invariants.rs`](fuzz_props/src/invariants.rs) are actually capable of catching the bugs they claim to detect. \ No newline at end of file +2. **Add `cargo-mutants`** before any external security audit — proves the invariant assertions in [`fuzz_props/src/invariants.rs`](fuzz_props/src/invariants.rs) are actually capable of catching the bugs they claim to detect. \ No newline at end of file diff --git a/docs/fuzzing.md b/docs/fuzzing.md index 78e5535..bb870ca 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -9,14 +9,54 @@ directory that must be cloned separately). --- +## Architecture + +The fuzz workspace (`fuzz/`) is a single Cargo workspace that covers **both** fuzzing +engines via Cargo features. No separate Cargo manifest is needed. + +| | libFuzzer lane | AFL++ lane | +|---|---|---| +| **Build command** | `cargo fuzz build ` | `cd fuzz && cargo afl build --no-default-features --features fuzzer-afl --release --bin ` | +| **Run command** | `cargo fuzz run ` | `afl-fuzz -i fuzz/corpus/ -o afl-output/ -- fuzz/target/release/` | +| **Cargo feature** | `fuzzer-libfuzzer` (default) | `fuzzer-afl` | +| **Harness entry** | `::libfuzzer_sys::fuzz_target!(…)` | `fn main() { ::afl::fuzz!(…) }` | +| **`main()` presence** | Suppressed via `#![no_main]` | Required; provided by `afl::fuzz!` | +| **`fuzz/Cargo.toml`** | ✅ Source of truth | ✅ Same file — covers both lanes | + +The engine is selected at the call site via the `fuzz_props::fuzz_entry!` macro: + +```rust +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] + +fuzz_props::fuzz_entry!(|data: &[u8]| { + // … harness body … +}); +``` + +The `cfg` attributes in the macro expansion resolve against the **calling crate's** features +(`fuzz/`), not `fuzz_props`'s features. + +--- + ## Prerequisites ```bash -# Rust nightly is required by cargo-fuzz / libFuzzer +# libFuzzer lane rustup install nightly rustup component add llvm-tools-preview --toolchain nightly - cargo install cargo-fuzz + +# AFL++ lane (additional) +# macOS: +brew install afl-fuzz + +# Linux — build from source (apt packages are several major versions behind): +git clone https://github.com/AFLplusplus/AFLplusplus +cd AFLplusplus && make distrib && sudo make install +cd .. + +# Rust wrapper (all platforms): +cargo install cargo-afl ``` --- @@ -47,10 +87,10 @@ proof generation. The `just` recipes handle this automatically. ```bash # From lez-fuzzing/ -# Run all targets for 30 s each +# Run all targets for 30 s each (libFuzzer) just fuzz -# Run a specific target for 120 s +# Run a specific target for 120 s (libFuzzer) RISC0_DEV_MODE=1 cargo fuzz run fuzz_state_transition -- -max_total_time=120 # Run the saved corpus (regression mode, no mutations) @@ -95,12 +135,11 @@ This single command does four things automatically: |---|---| | Creates the corpus directory | `fuzz/corpus/fuzz_my_feature/` | | Writes a typed fuzz target template | `fuzz/fuzz_targets/fuzz_my_feature.rs` | -| Appends `[[bin]]` entry | `fuzz/Cargo.toml` | +| Appends `[[bin]]` entry to `fuzz/Cargo.toml` | Covers **both** the libFuzzer and AFL++ lanes | | Inserts target into every CI matrix + perf loop | `.github/workflows/fuzz.yml` | -The generated template uses `ArbNSSATransaction` from `fuzz_props::arbitrary_types` -so libfuzzer drives every field of `NSSATransaction` independently — no manual -`Unstructured` wiring required. +The generated template uses `fuzz_props::fuzz_entry!` and works with both engines +without modification. ### Step 2 — Implement the target @@ -110,13 +149,15 @@ function under test and any invariant assertions. Use the typed wrappers from structured input, or the proptest generators from [`fuzz_props::generators`](../fuzz_props/src/generators.rs) for richer strategies. -### Step 3 — Register the binary (automated) +### Step 3 — Automated registration (cargo-fuzz + CI) `just new-target` calls [`scripts/add_fuzz_target.py`](../scripts/add_fuzz_target.py) -which appends the `[[bin]]` entry to [`fuzz/Cargo.toml`](../fuzz/Cargo.toml) -automatically. Once present, `cargo fuzz list` (and therefore `just fuzz`, -`just fuzz-regression`, `just corpus-cmin`) pick up the target automatically — no -further Justfile edits required. +which: +- Appends the `[[bin]]` entry to [`fuzz/Cargo.toml`](../fuzz/Cargo.toml). + This **single entry** covers both the libFuzzer lane (`cargo fuzz build`) and + the AFL++ lane (`cargo afl build --no-default-features --features fuzzer-afl`). +- Inserts the target name into every strategy matrix and the perf-baseline shell + loop in [`.github/workflows/fuzz.yml`](../.github/workflows/fuzz.yml). > **Manual fallback:** if you create a target without `just new-target`, add the > entry yourself: @@ -129,21 +170,19 @@ further Justfile edits required. > bench = false > ``` -### Step 4 — Add to CI matrix (automated) - -`just new-target` also inserts `fuzz_my_feature` into every strategy matrix and the -perf-baseline shell loop in [`.github/workflows/fuzz.yml`](../.github/workflows/fuzz.yml) -automatically via `scripts/add_fuzz_target.py`. - -> **Manual fallback:** if you created the target without `just new-target`, add -> `- fuzz_my_feature` to the `target:` list in the three places shown in -> `.github/workflows/fuzz.yml` (smoke-fuzz, regression, perf-baseline). - -### Step 5 — Verify +### Step 4 — Verify ```bash +# Verify the libFuzzer build RISC0_DEV_MODE=1 cargo fuzz build fuzz_my_feature just fuzz-regression # runs the new target against its (empty) corpus + +# Verify the AFL++ build (same fuzz/Cargo.toml — no separate manifest needed) +cd fuzz && cargo afl build \ + --no-default-features \ + --features fuzzer-afl \ + --release \ + --bin fuzz_my_feature ``` ### Quick reference: what to touch @@ -152,12 +191,193 @@ just fuzz-regression # runs the new target against its (empty) corpus |---|---|---| | `fuzz/fuzz_targets/fuzz_.rs` | Create | ✅ `just new-target` | | `fuzz/corpus/fuzz_/` | Create | ✅ `just new-target` | -| `fuzz/Cargo.toml` | Add `[[bin]]` | ✅ `just new-target` | +| `fuzz/Cargo.toml` | Add `[[bin]]` (covers both lanes) | ✅ `just new-target` | | `Justfile` | Nothing — auto-discovers | ✅ automatic | | `.github/workflows/fuzz.yml` | Add to 3 matrix lists | ✅ `just new-target` | --- +## AFL++ Parallel Fuzzing Lane + +### Prerequisites + +Install AFL++ natively on your machine. + +> **Note on Linux package versions**: The `afl++` package in Debian stable (Bookworm) +> and Ubuntu LTS is several major versions behind the current AFL++ 4.x series and may +> be incompatible with `cargo-afl`. **Build from source** for a current version. + +```bash +# macOS — Homebrew keeps the formula up to date +brew install afl-fuzz + +# Linux — build from source (~5 min) +git clone https://github.com/AFLplusplus/AFLplusplus +cd AFLplusplus +make distrib # builds all components: afl-fuzz, afl-cc, afl-clang-fast, … +sudo make install +cd .. + +# Rust build wrapper (all platforms) +cargo install cargo-afl +``` + +> **macOS: run `afl-system-config` once before fuzzing** — AFL++ uses System V shared +> memory (`shmget`) to pass coverage bitmaps between the fuzzer and the target. macOS +> ships with very small defaults (`kern.sysv.shmmax = 4 MB`, `kern.sysv.shmmni = 32`) +> that are exhausted as soon as multiple AFL++ instances start in parallel, causing every +> run to abort immediately with: +> +> ``` +> [-] SYSTEM ERROR : shmget() failed, try running afl-system-config +> OS message : Invalid argument +> ``` +> +> Fix by running the AFL++ system-configuration helper once per boot (or after every +> macOS update): +> +> ```bash +> sudo afl-system-config +> ``` +> +> This raises `shmmax`, `shmmni`, `shmall`, and related limits to values suitable for +> parallel fuzzing. The change is not persistent across reboots, so re-run it after +> each restart. The `just fuzz-afl` and `just fuzz-afl-parallel` recipes **do not** +> call this automatically because it requires `sudo`. + +> **macOS: crash reporter must be disabled** — AFL++ detects the macOS `ReportCrash` +> daemon and aborts if it is active (it delays crash notifications and causes AFL++ to +> mis-classify crashes as timeouts). The `just fuzz-afl` and `just fuzz-afl-parallel` +> recipes disable it automatically for the duration of the run and re-enable it on exit +> (via a shell `trap`). You can also manage it manually: +> +> ```bash +> # Disable (run once before a long session) +> just afl-macos-setup +> +> # Re-enable afterward +> just afl-macos-teardown +> ``` +> +> Or use the raw `launchctl` commands shown in the AFL++ error message: +> +> ```bash +> SL=/System/Library; PL=com.apple.ReportCrash +> launchctl unload -w ${SL}/LaunchAgents/${PL}.plist +> sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist +> ``` + +### Build + +```bash +# All targets +just afl-build + +# Single target +just afl-build-target fuzz_state_transition +``` + +Both commands compile `fuzz/` with `--no-default-features --features fuzzer-afl`. +Output binaries land in `fuzz/target/release/`. + +### Run (single instance) + +```bash +# 120-second smoke run +just fuzz-afl fuzz_state_transition + +# Custom duration +just fuzz-afl fuzz_state_transition 600 +``` + +### Run (parallel) + +```bash +# 1 main + 3 secondary instances for 5 minutes +just fuzz-afl-parallel fuzz_state_transition 4 300 + +# AFL++ rule: always start the main instance first; +# secondary instances are started with -S flags automatically. +``` + +### Monitor + +```bash +just afl-status fuzz_state_transition +# … calls afl-whatsup afl-output/fuzz_state_transition +``` + +### Triage + +```bash +# Minimise a crash artifact to the smallest reproducing input +just afl-tmin fuzz_state_transition afl-output/fuzz_state_transition/default/crashes/id:000000,... + +# Pretty-print as Rust byte literal (for pasting into a unit test) +just afl-fmt afl-output/fuzz_state_transition/default/crashes/id:000000,... +``` + +### Sync queue to shared corpus + +```bash +# Copies afl-output/*/queue/id:* into fuzz/corpus// +# Run this after any AFL++ session to share findings with cargo-fuzz +just afl-corpus-sync +``` + +### How the shared harness works + +| Mechanism | libFuzzer | AFL++ | +|---|---|---| +| **Entry macro** | `::libfuzzer_sys::fuzz_target!(…)` | `::afl::fuzz!(…)` inside `fn main()` | +| **`no_main` suppression** | `#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)]` | Not applied (AFL++ needs a real `main`) | +| **Feature gate** | `cfg(feature = "fuzzer-libfuzzer")` | `cfg(feature = "fuzzer-afl")` | +| **Feature resolution** | Resolved at `fuzz/` (calling crate), not at `fuzz_props/` | Same | +| **`libfuzzer-sys` dep** | Optional, active under `fuzzer-libfuzzer` | Not compiled — avoids `main()` conflict | +| **`afl` dep** | Not compiled | Optional, active under `fuzzer-afl` | +| **Default build** | `default = ["fuzzer-libfuzzer"]` → `cargo fuzz` just works | Requires `--no-default-features --features fuzzer-afl` | + +The `fuzz_props::fuzz_entry!` macro defined in [`fuzz_props/src/lib.rs`](../fuzz_props/src/lib.rs) +expands to the right entry point based on the active feature: + +```rust +#[macro_export] +macro_rules! fuzz_entry { + (|$data:ident: &[u8]| $body:block) => { + #[cfg(feature = "fuzzer-libfuzzer")] + ::libfuzzer_sys::fuzz_target!(|$data: &[u8]| $body); + + #[cfg(feature = "fuzzer-afl")] + fn main() { + ::afl::fuzz!(|$data: &[u8]| $body); + } + }; +} +``` + +### CI (`.github/workflows/fuzz-afl.yml`) + +The nightly AFL++ CI workflow has two jobs: + +| Job | Triggers | Matrix | +|-----|----------|--------| +| `afl-smoke` | nightly + `workflow_dispatch` | 7 priority targets, 120 s each | +| `afl-coverage` | nightly, `needs: afl-smoke` | 3 key targets; LLVM HTML report | + +The smoke job: +1. Builds the target with `cargo afl build --no-default-features --features fuzzer-afl` +2. Runs `afl-fuzz` for 120 s in `aflplusplus/aflplusplus:v4.40c` container +3. Syncs new queue entries into `fuzz/corpus//` and opens a corpus PR +4. Uploads crashes/hangs as a workflow artifact + +The coverage job: +1. Downloads the smoke findings +2. Rebuilds with `RUSTFLAGS="-C instrument-coverage"` +3. Runs all corpus + queue inputs through the binary +4. Merges `.profraw` → `.profdata` → HTML report via `llvm-cov show` + +--- + ## Updating the LEZ Dependency `lez-fuzzing` reads LEZ source directly from `../logos-execution-zone`. To pick up LEZ @@ -186,18 +406,27 @@ just update-lez When `cargo fuzz` finds a crash it writes an artifact to `fuzz/artifacts/fuzz_/crash-`. -### Minimise +### Minimise (libFuzzer) ```bash # Produces a smaller input that still triggers the same crash just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123 ``` +### Minimise (AFL++) + +```bash +just afl-tmin fuzz_state_transition afl-output/fuzz_state_transition/default/crashes/id:000000,... +``` + ### Convert to a regression test ```bash -# Print the bytes as a Rust byte-literal (paste into a #[test]) +# libFuzzer: print bytes as a Rust byte-literal cargo fuzz fmt fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123 + +# AFL++: print bytes as a Rust byte-literal +just afl-fmt afl-output/fuzz_state_transition/default/crashes/id:000000,... ``` Add the minimised file to the corpus so CI always reproduces it: @@ -211,6 +440,42 @@ Open a PR. The `regression` CI job will permanently block re-introduction of thi --- +## Coverage Reports + +### Step 1 — libFuzzer coverage (via `cargo fuzz coverage`) + +```bash +# Generates coverage for a single target +cargo fuzz coverage fuzz_state_transition + +# Generates coverage for all targets +just coverage-all +``` + +Reports land in `fuzz/coverage//`. + +### Step 2 — AFL++ LLVM coverage + +Run after a successful AFL++ session (queue data in `afl-output//`): + +```bash +# Combines libFuzzer + AFL++ corpus into a single LLVM HTML report +just coverage fuzz_state_transition +``` + +This: +1. Runs `cargo fuzz coverage` (step 1) +2. Detects `afl-output/fuzz_state_transition/` and builds the target with + `RUSTFLAGS="-C instrument-coverage" cargo build --manifest-path fuzz/Cargo.toml --no-default-features --features fuzzer-afl --release` +3. Runs all AFL++ queue entries through the binary, collects `.profraw` files +4. Merges profiles with `llvm-profdata merge` and generates an HTML report with `llvm-cov show` +5. Writes the report to `coverage/afl/fuzz_state_transition/html/index.html` + +The AFL++ CI coverage job (`afl-coverage` in [`.github/workflows/fuzz-afl.yml`](../.github/workflows/fuzz-afl.yml)) +automates steps 2–5 and uploads the report as a workflow artifact. + +--- + ## Invariant Framework Shared invariants live in `fuzz_props/src/invariants.rs`. Each invariant implements @@ -242,6 +507,8 @@ Concrete invariants currently registered in `assert_invariants()`: > whose signer-account list is private to the `nssa` crate. The caller must derive signer > IDs from the transaction's witness set before consuming the diff, then call the standalone > `assert_nonce_increment_correctness(signer_ids, nonces_before, state_after)` helper. +> The `signer_account_ids()` helper in `fuzz_props::generators` extracts signer `AccountId`s +> from an `NSSATransaction`'s witness set. Additional invariants enforced **inline** in specific targets (not via `ProtocolInvariant`): @@ -289,6 +556,7 @@ fuzz target parameters for zero-boilerplate structured fuzzing. | `arb_fuzz_native_transfer()` | Correctly-signed native-transfer `NSSATransaction` referencing accounts from an `arbitrary_fuzz_state()` result; gives the fuzzer a path to successful state transitions | | `arbitrary_transaction()` | Structured `NSSATransaction` (`Public` or `ProgramDeployment`) from unstructured bytes via `ArbNSSATransaction` | | `arb_borsh_transaction_bytes()` | Raw Borsh bytes including invalid encodings | +| `signer_account_ids()` | Extracts `AccountId`s of all signers from an `NSSATransaction`'s witness set; used to derive signer IDs before `apply_state_diff` consumes the diff | | `arb_native_transfer_tx()` | Valid native-transfer `NSSATransaction` between known testnet genesis accounts (proptest strategy) | | `test_accounts()` | Returns `(AccountId, PrivateKey)` pairs from `testnet_initial_state` | | `arb_hashable_block_data()` | `HashableBlockData` with 0–8 valid native transfers (proptest strategy) | @@ -326,9 +594,12 @@ Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`: Recommended local settings for longer runs: ```bash -# Use all available cores +# libFuzzer — use all available cores RISC0_DEV_MODE=1 cargo fuzz run fuzz_state_transition \ -- -max_total_time=3600 -jobs=$(nproc) -workers=$(nproc) + +# AFL++ — parallel (1 main + N-1 secondary) +just fuzz-afl-parallel fuzz_state_transition $(nproc) 3600 ``` --- @@ -354,6 +625,6 @@ flag stubs out ZK proof generation and replaces it with a fast mock implementati |------|-------| | `PrivacyPreservingTransaction` coverage | Excluded from `fuzz_encoding_roundtrip` because its ZK receipt cannot be reconstructed in a fuzzing loop. A dedicated slow target with `RISC0_DEV_MODE=1` and `proptest` should be added after the current targets are stable | | `fuzz_validate_execute_consistency` new-account detection | If `execute_check_on_state` creates a brand-new account absent from both the genesis set and the diff, that state-widening will not be detected — full detection requires iterating all accounts in `V03State`, which the API does not currently expose | -| AFL++ integration | A `just fuzz-afl` recipe can be added later; the same corpus is compatible | | Differential testing (sequencer vs replayer) | ✅ Implemented — `fuzz_sequencer_vs_replayer` feeds the same block through the sequencer path (`validate_on_state` → `apply_state_diff`) and the replayer path (`execute_check_on_state`) and asserts identical state for all known accounts | +| AFL++ integration | ✅ Implemented — `just afl-build`, `just fuzz-afl`, `just fuzz-afl-parallel`; nightly CI in `.github/workflows/fuzz-afl.yml`; single `fuzz/Cargo.toml` covers both engines via feature flags | | LEZ version tracking | There is no submodule pin — `lez-fuzzing` reads `../logos-execution-zone` as checked out. Update that repo to a release tag or a tested commit, then run `just update-lez` (which does `git pull --ff-only`) and open a PR to bump it | diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 7173479..5ad1b08 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -37,6 +37,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "afl" +version = "0.15.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927cd71710d1a232519e2393470e8f74a178ae59367efe58fa122884bba35ca4" +dependencies = [ + "home", + "libc", + "rustc_version", + "xdg", +] + [[package]] name = "ahash" version = "0.8.12" @@ -1994,6 +2006,7 @@ dependencies = [ name = "fuzz" version = "0.1.0" dependencies = [ + "afl", "arbitrary", "borsh", "common", @@ -2300,6 +2313,15 @@ version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "019ece39bbefc17f13f677a690328cb978dbf6790e141a3c24e66372cb38588b" +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "hostname" version = "0.3.1" @@ -7398,6 +7420,12 @@ dependencies = [ "time", ] +[[package]] +name = "xdg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" + [[package]] name = "xml-rs" version = "0.8.28" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 726b805..d2d8e87 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -34,8 +34,14 @@ path = "fuzz_targets/fuzz_block_verification.rs" test = false bench = false +[features] +default = ["fuzzer-libfuzzer"] +fuzzer-libfuzzer = ["libfuzzer-sys", "fuzz_props/fuzzer-libfuzzer"] +fuzzer-afl = ["afl", "fuzz_props/fuzzer-afl"] + [dependencies] -libfuzzer-sys = "0.4" +libfuzzer-sys = { version = "0.4", optional = true } +afl = { version = "0.15", optional = true } arbitrary = { version = "1", features = ["derive"] } borsh = "1" nssa = { path = "../../logos-execution-zone/nssa" } diff --git a/fuzz/fuzz_targets/_template.rs b/fuzz/fuzz_targets/_template.rs index e4a4bcc..38e72d5 100644 --- a/fuzz/fuzz_targets/_template.rs +++ b/fuzz/fuzz_targets/_template.rs @@ -1,23 +1,9 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] +// use fuzz_props::arbitrary_types::*; +// use fuzz_props::generators::*; +// use fuzz_props::invariants::*; -use fuzz_props::arbitrary_types::ArbNSSATransaction; -use libfuzzer_sys::fuzz_target; - -fuzz_target!(|wrapped: ArbNSSATransaction| { - let tx = wrapped.0; - - // ── Stateless gate ──────────────────────────────────────────────────────── - // Remove this block to fuzz malformed / unsigned transactions too. - let Ok(tx) = tx.transaction_stateless_check() else { - return; - }; - - // ── Call the function under test ────────────────────────────────────────── - // Example: - // let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0); - // let result = tx.execute_check_on_state(&mut state, block_id, timestamp); - - // ── Assert invariants ───────────────────────────────────────────────────── - // Use fuzz_props::invariants::assert_invariants(&ctx) or inline assertions. - let _ = tx; // replace once the target body is implemented +fuzz_props::fuzz_entry!(|data: &[u8]| { + // TODO: implement harness body + let _ = data; }); diff --git a/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs b/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs index 6db12ec..7cd5fe8 100644 --- a/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs +++ b/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: `validate_on_state` → `apply_state_diff` split path vs //! `execute_check_on_state` direct path. //! @@ -33,14 +33,12 @@ use std::collections::HashSet; use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; use fuzz_props::arbitrary_types::ArbNSSATransaction; -use fuzz_props::generators::arbitrary_fuzz_state; +use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids}; use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness}; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. @@ -75,23 +73,7 @@ fuzz_target!(|data: &[u8]| { }; // ── Extract signer IDs and capture nonce snapshot before apply ──────────── - // Signer IDs are private to ValidatedStateDiff; derive them from the transaction's - // witness set before the diff is consumed by apply_state_diff. - let signer_ids: Vec = match &tx { - NSSATransaction::Public(pub_tx) => pub_tx - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::PrivacyPreserving(pp_tx) => pp_tx - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], - }; + let signer_ids = signer_account_ids(&tx); let nonces_before = NonceSnapshot( signer_ids .iter() diff --git a/fuzz/fuzz_targets/fuzz_block_verification.rs b/fuzz/fuzz_targets/fuzz_block_verification.rs index 2d8847b..0973bea 100644 --- a/fuzz/fuzz_targets/fuzz_block_verification.rs +++ b/fuzz/fuzz_targets/fuzz_block_verification.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: block hash integrity — three invariants unique to block-level validation. //! //! 1. **Hash integrity via `From` round-trip** — `HashableBlockData::from(block)` @@ -21,12 +21,11 @@ use arbitrary::{Arbitrary, Unstructured}; use common::block::HashableBlockData; use fuzz_props::arbitrary_types::ArbHashableBlockData; -use libfuzzer_sys::fuzz_target; use nssa::PrivateKey; const DUMMY_KEY_BYTES: [u8; 32] = [1u8; 32]; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); let Ok(wrap) = ArbHashableBlockData::arbitrary(&mut u) else { return; diff --git a/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs b/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs index cc9268f..936b0be 100644 --- a/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs +++ b/fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: encoding round-trip for all transaction types. //! //! Invariants exercised: @@ -18,10 +18,9 @@ use arbitrary::{Arbitrary, Unstructured}; use fuzz_props::arbitrary_types::{ArbProgramDeploymentTransaction, ArbPublicTransaction}; -use libfuzzer_sys::fuzz_target; use nssa::{ProgramDeploymentTransaction, PublicTransaction}; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // ── Test 1: PublicTransaction round-trip ────────────────────────────────── diff --git a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs index 299bab3..bb084de 100644 --- a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs +++ b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: multi-block transaction sequence with long-range invariants. //! //! Verifies properties that span an entire *sequence* of blocks: @@ -35,16 +35,14 @@ //! the total; only mint/burn bugs or token-inflation bugs would break it. use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; -use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; +use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids}; use fuzz_props::invariants::{ BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, assert_nonce_increment_correctness, assert_replay_rejection, }; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. @@ -120,22 +118,8 @@ fuzz_target!(|data: &[u8]| { // First verify every signer's nonce was incremented by exactly one, then // replay in the next block to confirm the nonce is permanently consumed. if let Ok(applied_tx) = result { - let signer_ids: Vec = match &applied_tx { - NSSATransaction::Public(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::PrivacyPreserving(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], - }; - assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); + let ids = signer_account_ids(&applied_tx); + assert_nonce_increment_correctness(&ids, &nonces_before, &state); assert_replay_rejection(applied_tx, &mut state, block_id + 1, timestamp + 1); } } diff --git a/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs b/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs index 8631f5c..03040ac 100644 --- a/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs +++ b/fuzz/fuzz_targets/fuzz_program_deployment_lifecycle.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: `V03State::transition_from_program_deployment_transaction`. //! //! The deployment path runs `ValidatedStateDiff::from_program_deployment_transaction` @@ -24,10 +24,9 @@ use arbitrary::{Arbitrary, Unstructured}; use fuzz_props::arbitrary_types::ArbProgramDeploymentTransaction; use fuzz_props::generators::arbitrary_fuzz_state; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. diff --git a/fuzz/fuzz_targets/fuzz_replay_prevention.rs b/fuzz/fuzz_targets/fuzz_replay_prevention.rs index 1aacfcb..1bc2d63 100644 --- a/fuzz/fuzz_targets/fuzz_replay_prevention.rs +++ b/fuzz/fuzz_targets/fuzz_replay_prevention.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: transaction replay prevention. //! //! Invariant: a transaction that is accepted in block N must be rejected when @@ -23,16 +23,16 @@ //! - **ReplayRejection** — accepted tx rejected on replay use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; -use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; -use fuzz_props::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, assert_nonce_increment_correctness, - assert_replay_rejection, +use fuzz_props::generators::{ + arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids, +}; +use fuzz_props::invariants::{ + BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, + assert_nonce_increment_correctness, assert_replay_rejection, }; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. @@ -96,21 +96,7 @@ fuzz_target!(|data: &[u8]| { // First verify every signer's nonce was incremented by exactly one, then // assert that replaying in the next block is rejected (nonce permanently consumed). if let Ok(applied_tx) = result { - let signer_ids: Vec = match &applied_tx { - NSSATransaction::Public(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::PrivacyPreserving(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], - }; + let signer_ids = signer_account_ids(&applied_tx); assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); assert_replay_rejection(applied_tx, &mut state, 2, 1); } diff --git a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs index 5cbc4a2..9b46905 100644 --- a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs +++ b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: sequencer vs replayer differential state-root equivalence. //! //! Feeds the same block of transactions through two independent state-transition @@ -40,10 +40,9 @@ use std::collections::HashSet; use arbitrary::{Arbitrary, Unstructured}; use common::transaction::{NSSATransaction, clock_invocation}; use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // ── Initial state ───────────────────────────────────────────────────────── diff --git a/fuzz/fuzz_targets/fuzz_signature_verification.rs b/fuzz/fuzz_targets/fuzz_signature_verification.rs index 83c3221..71db2ae 100644 --- a/fuzz/fuzz_targets/fuzz_signature_verification.rs +++ b/fuzz/fuzz_targets/fuzz_signature_verification.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: signature creation and verification. //! //! Invariants exercised: @@ -12,10 +12,9 @@ use arbitrary::{Arbitrary, Unstructured}; use fuzz_props::arbitrary_types::{ArbPrivateKey, ArbPublicKey, ArbSignature}; -use libfuzzer_sys::fuzz_target; use nssa::{PublicKey, Signature}; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // ── 1. Freshly signed message always verifies with the correct key ───────── diff --git a/fuzz/fuzz_targets/fuzz_state_diff_computation.rs b/fuzz/fuzz_targets/fuzz_state_diff_computation.rs index 410afb3..0c912fe 100644 --- a/fuzz/fuzz_targets/fuzz_state_diff_computation.rs +++ b/fuzz/fuzz_targets/fuzz_state_diff_computation.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: state diff isolation — bidirectional. //! //! Invariants: @@ -22,10 +22,9 @@ use arbitrary::{Arbitrary, Unstructured}; use common::transaction::NSSATransaction; use fuzz_props::arbitrary_types::ArbPublicTransaction; use fuzz_props::generators::arbitrary_fuzz_state; -use libfuzzer_sys::fuzz_target; use nssa::{V03State, ValidatedStateDiff}; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. diff --git a/fuzz/fuzz_targets/fuzz_state_serialization.rs b/fuzz/fuzz_targets/fuzz_state_serialization.rs index 535201c..d0cde75 100644 --- a/fuzz/fuzz_targets/fuzz_state_serialization.rs +++ b/fuzz/fuzz_targets/fuzz_state_serialization.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: `V03State` Borsh serialization/deserialization. //! //! The state blob is transmitted between nodes and persisted to disk, so a panic or @@ -22,10 +22,9 @@ //! place for a logic bug — and the fuzzer should be steered towards exercising //! the duplicate-nullifier code path. -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { // ── Invariant 1: NoPanic ────────────────────────────────────────────────── // `borsh::from_slice` must never panic. If it returns `Err`, we simply // return early; only structurally valid blobs proceed to the round-trip check. diff --git a/fuzz/fuzz_targets/fuzz_state_transition.rs b/fuzz/fuzz_targets/fuzz_state_transition.rs index 8dc3de0..4915237 100644 --- a/fuzz/fuzz_targets/fuzz_state_transition.rs +++ b/fuzz/fuzz_targets/fuzz_state_transition.rs @@ -1,16 +1,16 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; -use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; -use fuzz_props::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, assert_nonce_increment_correctness, - assert_replay_rejection, +use fuzz_props::generators::{ + arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids, +}; +use fuzz_props::invariants::{ + BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, + assert_nonce_increment_correctness, assert_replay_rejection, }; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state instead of always using the fixed @@ -95,21 +95,7 @@ fuzz_target!(|data: &[u8]| { // First verify every signer's nonce was incremented by exactly one, then // replay in the next block to confirm the nonce is permanently consumed. if let Ok(applied_tx) = result { - let signer_ids: Vec = match &applied_tx { - NSSATransaction::Public(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::PrivacyPreserving(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], - }; + let signer_ids = signer_account_ids(&applied_tx); assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); assert_replay_rejection(applied_tx, &mut state, block_id + 1, timestamp + 1); } diff --git a/fuzz/fuzz_targets/fuzz_stateless_verification.rs b/fuzz/fuzz_targets/fuzz_stateless_verification.rs index 3cb792e..767edf1 100644 --- a/fuzz/fuzz_targets/fuzz_stateless_verification.rs +++ b/fuzz/fuzz_targets/fuzz_stateless_verification.rs @@ -1,11 +1,10 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] use arbitrary::Unstructured; use common::transaction::NSSATransaction; use fuzz_props::generators::arbitrary_transaction; -use libfuzzer_sys::fuzz_target; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Path A: try to build a structured transaction from unstructured bytes diff --git a/fuzz/fuzz_targets/fuzz_transaction_decoding.rs b/fuzz/fuzz_targets/fuzz_transaction_decoding.rs index fae71e5..07a4302 100644 --- a/fuzz/fuzz_targets/fuzz_transaction_decoding.rs +++ b/fuzz/fuzz_targets/fuzz_transaction_decoding.rs @@ -1,12 +1,11 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] use common::{ block::{Block, HashableBlockData}, transaction::NSSATransaction, }; -use libfuzzer_sys::fuzz_target; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { // Attempt 1: decode as NSSATransaction and verify roundtrip if let Ok(tx) = borsh::from_slice::(data) { let re_encoded = borsh::to_vec(&tx).expect("re-encode of valid tx must succeed"); diff --git a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs index d9f1b72..b8a8b95 100644 --- a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs +++ b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: `validate_on_state` and `execute_check_on_state` consistency. //! //! Invariants: @@ -25,14 +25,12 @@ //! reachable by the fuzzer. use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; use fuzz_props::arbitrary_types::ArbNSSATransaction; -use fuzz_props::generators::arbitrary_fuzz_state; +use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids}; use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness}; -use libfuzzer_sys::fuzz_target; use nssa::V03State; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // Generate a fuzz-driven initial state. The state shape — account IDs, @@ -160,21 +158,7 @@ fuzz_target!(|data: &[u8]| { // consistency checks above: it catches bugs where validate_on_state and // execute_check_on_state agree (passing INVARIANT 1) but both increment // the wrong account's nonce, or skip the increment entirely. - let signer_ids: Vec = match &applied_tx { - NSSATransaction::Public(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::PrivacyPreserving(t) => t - .witness_set() - .signatures_and_public_keys() - .iter() - .map(|(_, pk)| nssa::AccountId::from(pk)) - .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], - }; + let signer_ids = signer_account_ids(&applied_tx); assert_nonce_increment_correctness(&signer_ids, &nonces_before, &exec_state); } (Err(_), Err(_)) => { diff --git a/fuzz/fuzz_targets/fuzz_witness_set_verification.rs b/fuzz/fuzz_targets/fuzz_witness_set_verification.rs index dc4102f..585cbb1 100644 --- a/fuzz/fuzz_targets/fuzz_witness_set_verification.rs +++ b/fuzz/fuzz_targets/fuzz_witness_set_verification.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] //! Fuzz target: `WitnessSet` authentication isolation for public transactions. //! //! The most security-critical property of `WitnessSet` is **message isolation**: @@ -23,10 +23,9 @@ use arbitrary::{Arbitrary, Unstructured}; use fuzz_props::arbitrary_types::{ArbPrivateKey, ArbPubTxMessage, ArbWitnessSet}; -use libfuzzer_sys::fuzz_target; use nssa::{PublicKey, public_transaction::WitnessSet}; -fuzz_target!(|data: &[u8]| { +fuzz_props::fuzz_entry!(|data: &[u8]| { let mut u = Unstructured::new(data); // ── Invariant 1: NoPanic on adversarial WitnessSet ──────────────────────── diff --git a/fuzz_props/Cargo.toml b/fuzz_props/Cargo.toml index ad96ec6..a6796bd 100644 --- a/fuzz_props/Cargo.toml +++ b/fuzz_props/Cargo.toml @@ -6,6 +6,10 @@ edition = "2024" [lints] workspace = true +[features] +fuzzer-libfuzzer = [] +fuzzer-afl = [] + [dependencies] nssa = { workspace = true } nssa_core = { workspace = true } diff --git a/fuzz_props/src/arbitrary_types.rs b/fuzz_props/src/arbitrary_types.rs index 6804c34..d920d6c 100644 --- a/fuzz_props/src/arbitrary_types.rs +++ b/fuzz_props/src/arbitrary_types.rs @@ -118,13 +118,10 @@ impl<'a> Arbitrary<'a> for ArbPublicKey { // rejection path in `is_valid_for` independently. let bytes = <[u8; 32]>::arbitrary(u)?; let pk = PublicKey::try_new(bytes).unwrap_or_else(|_| { - PublicKey::new_from_private_key( - &ArbPrivateKey::arbitrary(u) - .map(|w| w.0) - .unwrap_or_else(|_| { - PrivateKey::try_new([1_u8; 32]).expect("known-good seed") - }), - ) + PublicKey::new_from_private_key(&ArbPrivateKey::arbitrary(u).map_or_else( + |_| PrivateKey::try_new([1_u8; 32]).expect("known-good seed"), + |w| w.0, + )) }); Ok(Self(pk)) } @@ -145,11 +142,11 @@ impl<'a> Arbitrary<'a> for ArbPubTxMessage { let program_id: [u32; 8] = <[u32; 8]>::arbitrary(u)?; // Generate 0–7 accounts; nonces vector is given the same length. let len = (u8::arbitrary(u)? as usize) % 8; - let account_ids = (0..len) - .map(|_| ArbAccountId::arbitrary(u).map(|a| a.0)) + let account_ids = std::iter::repeat_with(|| ArbAccountId::arbitrary(u).map(|a| a.0)) + .take(len) .collect::>>()?; - let nonces = (0..len) - .map(|_| ArbNonce::arbitrary(u).map(|n| n.0)) + let nonces = std::iter::repeat_with(|| ArbNonce::arbitrary(u).map(|n| n.0)) + .take(len) .collect::>>()?; let instruction_data: Vec = Vec::::arbitrary(u)?; Ok(Self(Message::new_preserialized( @@ -174,9 +171,11 @@ impl<'a> Arbitrary<'a> for ArbWitnessSet { fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult { // 0–3 (signature, public_key) pairs let n = (u8::arbitrary(u)? as usize) % 4; - let pairs = (0..n) - .map(|_| Ok((ArbSignature::arbitrary(u)?.0, ArbPublicKey::arbitrary(u)?.0))) - .collect::>>()?; + let pairs = std::iter::repeat_with(|| { + Ok((ArbSignature::arbitrary(u)?.0, ArbPublicKey::arbitrary(u)?.0)) + }) + .take(n) + .collect::>>()?; Ok(Self(WitnessSet::from_raw_parts(pairs))) } } @@ -247,8 +246,8 @@ impl<'a> Arbitrary<'a> for ArbHashableBlockData { fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult { // 0–7 transactions per block let n = (u8::arbitrary(u)? as usize) % 8; - let transactions = (0..n) - .map(|_| ArbNSSATransaction::arbitrary(u).map(|t| t.0)) + let transactions = std::iter::repeat_with(|| ArbNSSATransaction::arbitrary(u).map(|t| t.0)) + .take(n) .collect::>>()?; Ok(Self(HashableBlockData { block_id: u64::arbitrary(u)?, diff --git a/fuzz_props/src/generators.rs b/fuzz_props/src/generators.rs index 4d2b165..def495f 100644 --- a/fuzz_props/src/generators.rs +++ b/fuzz_props/src/generators.rs @@ -6,6 +6,31 @@ use crate::arbitrary_types::{ArbAccountId, ArbNSSATransaction, ArbPrivateKey}; use proptest::prelude::*; use testnet_initial_state::initial_pub_accounts_private_keys; +// ── Signer account ID extraction ───────────────────────────────────────────── + +/// Extract the [`AccountId`]s of all signers from a transaction's +/// witness set. Used by fuzz targets that need to verify nonce +/// increments after `execute_check_on_state`. +#[must_use] +pub fn signer_account_ids(tx: &common::transaction::NSSATransaction) -> Vec { + use common::transaction::NSSATransaction; + match tx { + NSSATransaction::Public(pt) => pt + .witness_set() + .signatures_and_public_keys() + .iter() + .map(|(_, pk)| nssa::AccountId::from(pk)) + .collect(), + NSSATransaction::PrivacyPreserving(pt) => pt + .witness_set() + .signatures_and_public_keys() + .iter() + .map(|(_, pk)| nssa::AccountId::from(pk)) + .collect(), + NSSATransaction::ProgramDeployment(_) => vec![], + } +} + // ── Fuzz-driven state generation ───────────────────────────────────────────── /// An account with an arbitrary identifier, balance, and private key, @@ -28,15 +53,15 @@ pub struct FuzzAccount { /// has a shape controlled by the fuzzer rather than fixed at compile time. pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result> { let n = ((u8::arbitrary(u)? as usize) % 8) + 1; // 1..=8 - (0..n) - .map(|_| { - Ok(FuzzAccount { - account_id: ArbAccountId::arbitrary(u)?.0, - balance: u128::arbitrary(u)?, - private_key: ArbPrivateKey::arbitrary(u)?.0, - }) + std::iter::repeat_with(|| { + Ok(FuzzAccount { + account_id: ArbAccountId::arbitrary(u)?.0, + balance: u128::arbitrary(u)?, + private_key: ArbPrivateKey::arbitrary(u)?.0, }) - .collect() + }) + .take(n) + .collect() } /// Generate a native-transfer [`NSSATransaction`] between two accounts chosen @@ -91,8 +116,8 @@ prop_compose! { )( from_idx in 0..accounts.len(), to_idx in 0..accounts.len(), - nonce in 0u128..1_000u128, - amount in 0u128..10_000u128, + nonce in 0_u128..1_000_u128, + amount in 0_u128..10_000_u128, ) -> NSSATransaction { let (from_id, from_key) = &accounts[from_idx]; let (to_id, _) = &accounts[to_idx]; @@ -103,6 +128,7 @@ prop_compose! { } /// Return the test accounts from `testnet_initial_state` as `(AccountId, PrivateKey)` pairs. +#[must_use] pub fn test_accounts() -> Vec<(AccountId, PrivateKey)> { initial_pub_accounts_private_keys() .into_iter() @@ -144,9 +170,9 @@ prop_compose! { /// the state is left unchanged on rejection (StateIsolationOnFailure). pub fn arb_invalid_account_state_tx()( // Use a random 32-byte seed as a "phantom" account id not in genesis - phantom_id_bytes in proptest::array::uniform32(0u8..), + phantom_id_bytes in proptest::array::uniform32(0_u8..), amount in (u128::MAX / 2)..u128::MAX, // overflow-inducing amount - nonce in 0u128..10u128, + nonce in 0_u128..10_u128, ) -> NSSATransaction { let phantom_id = nssa::AccountId::new(phantom_id_bytes); // Attempt to sign with a key that has no matching on-chain account @@ -192,14 +218,14 @@ pub fn arb_duplicate_tx_sequence() -> impl Strategy pub fn arb_pathological_sequence() -> impl Strategy> { let accounts = test_accounts(); let n = accounts.len(); - proptest::collection::vec((0..n, 0..n, 0u128..5u128, any::()), 1..8_usize).prop_map( + proptest::collection::vec((0..n, 0..n, 0_u128..5_u128, any::()), 1..8_usize).prop_map( move |params| { params .into_iter() .map(|(from_idx, to_idx, nonce, zero_amount)| { let (from_id, from_key) = &accounts[from_idx]; let (to_id, _) = &accounts[to_idx]; - let amount = if zero_amount { 0u128 } else { u128::MAX }; // 0 or overflow + let amount = if zero_amount { 0_u128 } else { u128::MAX }; // 0 or overflow common::test_utils::create_transaction_native_token_transfer( *from_id, nonce, *to_id, amount, from_key, ) diff --git a/fuzz_props/src/invariants.rs b/fuzz_props/src/invariants.rs index 4f97886..b4d92bc 100644 --- a/fuzz_props/src/invariants.rs +++ b/fuzz_props/src/invariants.rs @@ -9,7 +9,7 @@ pub struct BalanceSnapshot(pub std::collections::HashMap) impl BalanceSnapshot { /// Capture current total balance over all known accounts. pub fn total(&self) -> u128 { - self.0.values().copied().fold(0u128, u128::saturating_add) + self.0.values().copied().fold(0_u128, u128::saturating_add) } } @@ -72,9 +72,8 @@ impl ProtocolInvariant for StateIsolationOnFailure { return Some(InvariantViolation { invariant: self.name(), message: format!( - "balance changed despite tx rejection: account {:?} had \ + "balance changed despite tx rejection: account {acc_id:?} had \ {expected_balance} before, {actual_balance} after", - acc_id, ), }); } @@ -106,7 +105,7 @@ impl ProtocolInvariant for BalanceConservation { .0 .keys() .map(|&id| ctx.state_after.get_account_by_id(id).balance) - .fold(0u128, u128::saturating_add); + .fold(0_u128, u128::saturating_add); if total_before != total_after { return Some(InvariantViolation { invariant: self.name(), @@ -142,10 +141,9 @@ impl ProtocolInvariant for FailedTxNonceStability { return Some(InvariantViolation { invariant: self.name(), message: format!( - "nonce changed despite tx rejection: account {:?} nonce was \ - {:?} before, {:?} after \ - (griefing attack — victim nonce permanently burned on failed tx)", - acc_id, expected_nonce, actual_nonce, + "nonce changed despite tx rejection: account {acc_id:?} nonce was \ + {expected_nonce:?} before, {actual_nonce:?} after \ + (griefing attack \u{2014} victim nonce permanently burned on failed tx)", ), }); } @@ -241,7 +239,7 @@ pub fn assert_replay_rejection( let replay = applied_tx.execute_check_on_state(state, next_block_id, next_timestamp); assert!( replay.is_err(), - "INVARIANT VIOLATION [ReplayRejection]: transaction accepted a second time — \ + "INVARIANT VIOLATION [ReplayRejection]: transaction accepted a second time \u{2014} \ nonce replay not prevented (replay block_id={next_block_id}, \ replay timestamp={next_timestamp})", ); @@ -298,15 +296,14 @@ pub fn assert_nonce_increment_correctness( nonce_before .0 .checked_add(1) - .expect("nonce overflow — signer nonce at u128::MAX"), + .expect("nonce overflow \u{2014} signer nonce at u128::MAX"), ); assert_eq!( nonce_after, expected, - "INVARIANT VIOLATION [NonceIncrementCorrectness]: signer account {:?} nonce \ + "INVARIANT VIOLATION [NonceIncrementCorrectness]: signer account {id:?} nonce \ not incremented by 1 after successful transaction \ - — before={:?}, expected={:?}, got={:?} \ + \u{2014} before={nonce_before:?}, expected={expected:?}, got={nonce_after:?} \ (apply_state_diff failed to increment nonce exactly once)", - id, nonce_before, expected, nonce_after, ); } } @@ -340,190 +337,3 @@ pub fn assert_invariants(ctx: &InvariantCtx<'_>) { } } } - -// ── Unit tests ──────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - use nssa::V03State; - - fn make_empty_state() -> V03State { - V03State::new_with_genesis_accounts(&[], vec![], 0) - } - - fn make_empty_snapshot() -> BalanceSnapshot { - BalanceSnapshot(std::collections::HashMap::new()) - } - - fn make_empty_nonce_snapshot() -> NonceSnapshot { - NonceSnapshot(std::collections::HashMap::new()) - } - - #[test] - fn invariant_state_isolation_on_failure_does_not_panic_on_error() { - let state = make_empty_state(); - let ctx = InvariantCtx { - state_before: &state, - state_after: &state, - execution_succeeded: false, - balances_before: make_empty_snapshot(), - nonces_before: make_empty_nonce_snapshot(), - }; - assert_invariants(&ctx); - } - - #[test] - fn invariant_replay_rejection_does_not_panic() { - let state = make_empty_state(); - let ctx = InvariantCtx { - state_before: &state, - state_after: &state, - execution_succeeded: true, - balances_before: make_empty_snapshot(), - nonces_before: make_empty_nonce_snapshot(), - }; - assert_invariants(&ctx); - } - - #[test] - fn balance_conservation_catches_inflation_on_success() { - // Arrange: one account with balance 100. - let acc_id = nssa::AccountId::new([1u8; 32]); - let state_before = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); - // Simulate execution that inflated the balance to 200. - let state_after = V03State::new_with_genesis_accounts(&[(acc_id, 200)], vec![], 0); - - let mut balances = std::collections::HashMap::new(); - balances.insert(acc_id, 100u128); - - let ctx = InvariantCtx { - state_before: &state_before, - state_after: &state_after, - execution_succeeded: true, - balances_before: BalanceSnapshot(balances), - nonces_before: make_empty_nonce_snapshot(), - }; - - let result = std::panic::catch_unwind(|| assert_invariants(&ctx)); - assert!(result.is_err(), "expected panic for balance inflation"); - } - - #[test] - fn nonce_increment_correctness_passes_with_no_signers() { - // Empty signer list — no accounts to check; trivially satisfies the invariant. - let state = make_empty_state(); - assert_nonce_increment_correctness(&[], &make_empty_nonce_snapshot(), &state); - } - - #[test] - fn nonce_increment_correctness_passes_when_signer_not_in_snapshot() { - // Signer ID is present in the list but absent from the snapshot — skipped. - let acc_id = nssa::AccountId::new([9u8; 32]); - let state = make_empty_state(); - // Empty snapshot → `continue` branch fires; no assertion is made. - assert_nonce_increment_correctness(&[acc_id], &make_empty_nonce_snapshot(), &state); - } - - #[test] - fn nonce_increment_correctness_catches_unchanged_nonce() { - // Arrange: signer has nonce 5 in the snapshot; the state returns Nonce(0) for the - // same account (genesis default). expected = Nonce(6), actual = Nonce(0) → VIOLATION. - let acc_id = nssa::AccountId::new([3u8; 32]); - let state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - let mut nonces = std::collections::HashMap::new(); - nonces.insert(acc_id, Nonce(5)); - - let result = std::panic::catch_unwind(|| { - assert_nonce_increment_correctness(&[acc_id], &NonceSnapshot(nonces), &state); - }); - assert!(result.is_err(), "expected panic for unchanged nonce"); - } - - #[test] - fn failed_tx_nonce_stability_catches_nonce_mutation() { - let acc_id = nssa::AccountId::new([2u8; 32]); - // before: nonce 5; after: nonce 6 (should not happen on failure) - let state_before = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); - let state_after = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); - - // We check the nonce snapshot directly; the states both return default nonce (0). - // Fake a discrepancy by inserting nonce=1 in the snapshot while state_after has nonce=0. - let mut nonces = std::collections::HashMap::new(); - // Nonce(1) in snapshot, but state_after will return Nonce(0). - nonces.insert(acc_id, Nonce(1)); - - let mut balances = std::collections::HashMap::new(); - balances.insert(acc_id, 100u128); - - let ctx = InvariantCtx { - state_before: &state_before, - state_after: &state_after, - execution_succeeded: false, - balances_before: BalanceSnapshot(balances), - nonces_before: NonceSnapshot(nonces), - }; - - let result = std::panic::catch_unwind(|| assert_invariants(&ctx)); - assert!( - result.is_err(), - "expected panic for nonce mutation on failure" - ); - } -} - -// ── ReplayRejection proptest suite ─────────────────────────────────────────── -// -// This suite constitutes the formal, reproducible exercise of the ReplayRejection -// invariant. It generates a realistic initial state and a correctly-signed -// native-transfer transaction, applies it once, and asserts that a second -// application is rejected. -// -// Run with: cargo test -p fuzz_props replay_rejection -#[cfg(test)] -mod replay_proptest { - use crate::generators::{arb_native_transfer_tx, test_accounts}; - use nssa::V03State; - use proptest::prelude::*; - - /// Build a `V03State` from the testnet accounts, assigning each a fixed - /// balance large enough for any reasonable transfer amount. - fn make_test_state() -> V03State { - let accounts = test_accounts(); - let init_accs: Vec<(nssa::AccountId, u128)> = accounts - .iter() - .map(|(id, _)| (*id, 1_000_000u128)) - .collect(); - V03State::new_with_genesis_accounts(&init_accs, vec![], 0) - } - - proptest! { - /// **ReplayRejection** — a transaction accepted in block N must be - /// rejected when replayed in block N+1, because the nonce is consumed - /// on first acceptance. - #[test] - fn replay_rejection_proptest(tx in arb_native_transfer_tx(test_accounts())) { - let mut state = make_test_state(); - - // Stateless gate — skip structurally invalid transactions (e.g. those - // whose public key does not match the declared sender). - let validated_tx = match tx.transaction_stateless_check() { - Ok(v) => v, - Err(_) => return Ok(()), - }; - - // First application — may fail for state-level reasons (e.g. sender - // has insufficient balance, wrong nonce). In that case there is - // nothing to replay. - let first_result = validated_tx.execute_check_on_state(&mut state, 1, 0); - - if let Ok(validated_tx) = first_result { - // Use the shared framework function. assert_replay_rejection uses - // assert!() rather than prop_assert!(); for structured proptest - // inputs the framework-level panic is equivalent. - super::assert_replay_rejection(validated_tx, &mut state, 2, 1); - } - } - } -} diff --git a/fuzz_props/src/lib.rs b/fuzz_props/src/lib.rs index 6bfa46e..6c45ffd 100644 --- a/fuzz_props/src/lib.rs +++ b/fuzz_props/src/lib.rs @@ -1,39 +1,89 @@ //! Fuzzing property library: invariant framework + input generators. -#![allow(clippy::missing_docs_in_private_items)] +#![allow( + clippy::missing_docs_in_private_items, + reason = "fuzz/test library; internal docs omitted for brevity" +)] +#![allow( + clippy::single_char_lifetime_names, + reason = "the `Arbitrary` trait uses `'a` and our impls must match its signature" +)] +#![allow( + clippy::exhaustive_structs, + reason = "fuzz-library newtype wrappers and test helpers; non_exhaustive would only add noise" +)] +#![allow( + clippy::missing_inline_in_public_items, + reason = "fuzz/test library; inlining hints have negligible effect here" +)] +#![allow( + clippy::question_mark_used, + reason = "`?` is the idiomatic Rust error-propagation operator in `Arbitrary` implementations" +)] +#![allow( + clippy::as_conversions, + reason = "u8 → usize for index arithmetic is safe and bounded in arbitrary contexts" +)] +#![allow( + clippy::integer_division_remainder_used, + reason = "modulo is the natural way to bound arbitrary u8 values to a range" +)] +#![allow( + clippy::arbitrary_source_item_ordering, + reason = "items are grouped logically rather than alphabetically for readability" +)] +#![allow( + clippy::iter_over_hash_type, + reason = "invariant checks iterate over all accounts; iteration order does not affect correctness" +)] +#![allow( + clippy::arithmetic_side_effects, + reason = "arithmetic is bounded by construction in test/fuzz helpers" +)] +#![allow( + clippy::integer_division, + reason = "u128::MAX / 2 is intentional for generating overflow-inducing test values" +)] +#![allow( + clippy::module_name_repetitions, + reason = "assert_invariants is the canonical, self-documenting name for this function" +)] +#![allow( + clippy::unused_trait_names, + reason = "named `Arbitrary` import needed to disambiguate from `proptest::arbitrary::Arbitrary` in generators.rs" +)] +#![allow( + clippy::let_underscore_must_use, + reason = "seed-generation IO errors are intentionally ignored in tests" +)] +#![allow( + clippy::let_underscore_untyped, + reason = "seed-generation IO errors are intentionally ignored in tests" +)] pub mod arbitrary_types; pub mod generators; pub mod invariants; -#[cfg(test)] -mod seed_gen { - use std::fs; - use std::path::Path; +/// Generates the fuzzer entry point for whichever engine this crate is +/// compiled with, selected via Cargo features: +/// +/// | Feature | Expansion | +/// |----------------------|-----------| +/// | `fuzzer-libfuzzer` | `libfuzzer_sys::fuzz_target!(…)` | +/// | `fuzzer-afl` | `fn main() { afl::fuzz!(…) }` | +#[macro_export] +macro_rules! fuzz_entry { + (|$data:ident: &[u8]| $body:block) => { + #[cfg(feature = "fuzzer-libfuzzer")] + ::libfuzzer_sys::fuzz_target!(|$data: &[u8]| $body); - #[test] - fn generate_seeds() { - let tx = common::test_utils::produce_dummy_empty_transaction(); - let bytes = borsh::to_vec(&tx).unwrap(); - - // CARGO_MANIFEST_DIR is lez-fuzzing/fuzz_props/ at compile time. - // Tests inherit the package directory as cwd, so we must use an - // absolute base rather than a bare relative path. - let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .expect("fuzz_props is one level below the workspace root"); - - let targets = [ - "fuzz/corpus/fuzz_transaction_decoding/seed_empty_tx", - "fuzz/corpus/fuzz_stateless_verification/seed_empty_tx", - "fuzz/corpus/fuzz_state_transition/seed_empty_tx", - ]; - for rel in &targets { - let p = workspace_root.join(rel); - if let Some(parent) = p.parent() { - fs::create_dir_all(parent).ok(); - } - fs::write(&p, &bytes).ok(); + #[cfg(feature = "fuzzer-afl")] + fn main() { + ::afl::fuzz!(|$data: &[u8]| $body); } - } + }; } + +#[cfg(test)] +mod tests; diff --git a/fuzz_props/src/tests.rs b/fuzz_props/src/tests.rs new file mode 100644 index 0000000..759db83 --- /dev/null +++ b/fuzz_props/src/tests.rs @@ -0,0 +1,3 @@ +mod invariants; +mod replay_proptest; +mod seed_gen; diff --git a/fuzz_props/src/tests/invariants.rs b/fuzz_props/src/tests/invariants.rs new file mode 100644 index 0000000..59c6217 --- /dev/null +++ b/fuzz_props/src/tests/invariants.rs @@ -0,0 +1,119 @@ +use crate::invariants::{ + BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, + assert_nonce_increment_correctness, +}; +use nssa::V03State; +use nssa_core::account::Nonce; + +fn make_empty_state() -> V03State { + V03State::new_with_genesis_accounts(&[], vec![], 0) +} + +fn make_empty_snapshot() -> BalanceSnapshot { + BalanceSnapshot(std::collections::HashMap::new()) +} + +fn make_empty_nonce_snapshot() -> NonceSnapshot { + NonceSnapshot(std::collections::HashMap::new()) +} + +#[test] +fn invariant_state_isolation_on_failure_does_not_panic_on_error() { + let state = make_empty_state(); + let ctx = InvariantCtx { + state_before: &state, + state_after: &state, + execution_succeeded: false, + balances_before: make_empty_snapshot(), + nonces_before: make_empty_nonce_snapshot(), + }; + assert_invariants(&ctx); +} + +#[test] +fn invariant_replay_rejection_does_not_panic() { + let state = make_empty_state(); + let ctx = InvariantCtx { + state_before: &state, + state_after: &state, + execution_succeeded: true, + balances_before: make_empty_snapshot(), + nonces_before: make_empty_nonce_snapshot(), + }; + assert_invariants(&ctx); +} + +#[test] +fn balance_conservation_catches_inflation_on_success() { + let acc_id = nssa::AccountId::new([1_u8; 32]); + let state_before = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + let state_after = V03State::new_with_genesis_accounts(&[(acc_id, 200)], vec![], 0); + + let mut balances = std::collections::HashMap::new(); + balances.insert(acc_id, 100_u128); + + let ctx = InvariantCtx { + state_before: &state_before, + state_after: &state_after, + execution_succeeded: true, + balances_before: BalanceSnapshot(balances), + nonces_before: make_empty_nonce_snapshot(), + }; + + let result = std::panic::catch_unwind(|| assert_invariants(&ctx)); + assert!(result.is_err(), "expected panic for balance inflation"); +} + +#[test] +fn nonce_increment_correctness_passes_with_no_signers() { + let state = make_empty_state(); + assert_nonce_increment_correctness(&[], &make_empty_nonce_snapshot(), &state); +} + +#[test] +fn nonce_increment_correctness_passes_when_signer_not_in_snapshot() { + let acc_id = nssa::AccountId::new([9_u8; 32]); + let state = make_empty_state(); + assert_nonce_increment_correctness(&[acc_id], &make_empty_nonce_snapshot(), &state); +} + +#[test] +fn nonce_increment_correctness_catches_unchanged_nonce() { + let acc_id = nssa::AccountId::new([3_u8; 32]); + let state = V03State::new_with_genesis_accounts(&[], vec![], 0); + + let mut nonces = std::collections::HashMap::new(); + nonces.insert(acc_id, Nonce(5)); + + let result = std::panic::catch_unwind(|| { + assert_nonce_increment_correctness(&[acc_id], &NonceSnapshot(nonces), &state); + }); + assert!(result.is_err(), "expected panic for unchanged nonce"); +} + +#[test] +fn failed_tx_nonce_stability_catches_nonce_mutation() { + let acc_id = nssa::AccountId::new([2_u8; 32]); + let state_before = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + let state_after = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + + let mut nonces = std::collections::HashMap::new(); + nonces.insert(acc_id, Nonce(1)); + + let mut balances = std::collections::HashMap::new(); + balances.insert(acc_id, 100_u128); + + let ctx = InvariantCtx { + state_before: &state_before, + state_after: &state_after, + execution_succeeded: false, + balances_before: BalanceSnapshot(balances), + nonces_before: NonceSnapshot(nonces), + }; + + let result = std::panic::catch_unwind(|| assert_invariants(&ctx)); + assert!( + result.is_err(), + "expected panic for nonce mutation on failure" + ); +} diff --git a/fuzz_props/src/tests/replay_proptest.rs b/fuzz_props/src/tests/replay_proptest.rs new file mode 100644 index 0000000..6be98a8 --- /dev/null +++ b/fuzz_props/src/tests/replay_proptest.rs @@ -0,0 +1,33 @@ +// Run with: cargo test -p fuzz_props replay_rejection +use crate::generators::{arb_native_transfer_tx, test_accounts}; +use nssa::V03State; +use proptest::prelude::*; + +fn make_test_state() -> V03State { + let accounts = test_accounts(); + let init_accs: Vec<(nssa::AccountId, u128)> = accounts + .iter() + .map(|(id, _)| (*id, 1_000_000_u128)) + .collect(); + V03State::new_with_genesis_accounts(&init_accs, vec![], 0) +} + +proptest! { + /// **ReplayRejection** \u{2014} a transaction accepted in block N must be + /// rejected when replayed in block N+1, because the nonce is consumed + /// on first acceptance. + #[test] + fn replay_rejection_proptest(tx in arb_native_transfer_tx(test_accounts())) { + let mut state = make_test_state(); + + // Skip structurally invalid transactions (e.g. mismatched public key / sender). + let Ok(validated_tx) = tx.transaction_stateless_check() else { return Ok(()) }; + + // First application may fail for state-level reasons; nothing to replay then. + let first_result = validated_tx.execute_check_on_state(&mut state, 1, 0); + + if let Ok(applied_tx) = first_result { + crate::invariants::assert_replay_rejection(applied_tx, &mut state, 2, 1); + } + } +} diff --git a/fuzz_props/src/tests/seed_gen.rs b/fuzz_props/src/tests/seed_gen.rs new file mode 100644 index 0000000..7548307 --- /dev/null +++ b/fuzz_props/src/tests/seed_gen.rs @@ -0,0 +1,25 @@ +use std::fs; +use std::path::Path; + +#[test] +fn generate_seeds() { + let tx = common::test_utils::produce_dummy_empty_transaction(); + let bytes = borsh::to_vec(&tx).unwrap(); + + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("fuzz_props is one level below the workspace root"); + + let targets = [ + "fuzz/corpus/fuzz_transaction_decoding/seed_empty_tx", + "fuzz/corpus/fuzz_stateless_verification/seed_empty_tx", + "fuzz/corpus/fuzz_state_transition/seed_empty_tx", + ]; + for rel in &targets { + let p = workspace_root.join(rel); + if let Some(parent) = p.parent() { + let _ = fs::create_dir_all(parent); + } + let _ = fs::write(&p, &bytes); + } +} diff --git a/scripts/add_fuzz_target.py b/scripts/add_fuzz_target.py index 52bd5a6..3dde883 100644 --- a/scripts/add_fuzz_target.py +++ b/scripts/add_fuzz_target.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Fully automates registering a new cargo-fuzz target. +"""Fully automates registering a new cargo-fuzz / AFL++ fuzz target. Usage: python3 scripts/add_fuzz_target.py @@ -7,12 +7,19 @@ Usage: Where TARGET_NAME is the full binary name, e.g. fuzz_my_feature. Actions performed: - 1. Appends a [[bin]] entry to fuzz/Cargo.toml + 1. Appends a [[bin]] entry to fuzz/Cargo.toml (one entry covers BOTH + the libFuzzer lane and the AFL++ lane — no separate Cargo.toml needed) 2. Inserts TARGET_NAME into every YAML matrix block in .github/workflows/fuzz.yml (smoke-fuzz, regression) 3. Inserts TARGET_NAME into the perf-baseline shell for-loop in .github/workflows/fuzz.yml +NOTE: A single fuzz/Cargo.toml is the source of truth for both engines. + - libFuzzer build: cargo fuzz build + - AFL++ build: cd fuzz && cargo afl build \\ + --no-default-features --features fuzzer-afl \\ + --release --bin + Run from the repository root. """ @@ -172,6 +179,24 @@ def main() -> None: append_cargo_bin(target, cargo_toml) insert_into_workflow(target, workflow) + # ── Print build instructions ────────────────────────────────────────────── + print() + print("Registration complete! Next steps:") + print() + print(" 1. Implement the harness body in:") + print(f" fuzz/fuzz_targets/{target}.rs") + print() + print(" 2. Verify the libFuzzer (cargo-fuzz) build:") + print(f" RISC0_DEV_MODE=1 cargo fuzz build {target}") + print() + print(" 3. Verify the AFL++ build (single shared fuzz/Cargo.toml):") + print(f" cd fuzz && cargo afl build \\") + print(f" --no-default-features --features fuzzer-afl \\") + print(f" --release --bin {target}") + print() + print(" 4. Run with libFuzzer: just fuzz-one", target) + print(" Run with AFL++: just fuzz-afl", target) + if __name__ == "__main__": main() From 88e780b865138c4bf112c94fa923266065c7ad8c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 11:40:38 +0800 Subject: [PATCH 02/18] fix: improve API around tx invariants --- current_vs_alternative_approach.md | 2 +- docs/fuzzing.md | 69 ++++--- .../fuzz_multi_block_state_sequence.rs | 50 ++--- fuzz/fuzz_targets/fuzz_replay_prevention.rs | 47 ++--- fuzz/fuzz_targets/fuzz_state_transition.rs | 42 ++-- fuzz_props/src/invariants.rs | 189 +++++++++++++----- fuzz_props/src/tests/invariants.rs | 2 +- 7 files changed, 230 insertions(+), 171 deletions(-) diff --git a/current_vs_alternative_approach.md b/current_vs_alternative_approach.md index 34af3ac..8498316 100644 --- a/current_vs_alternative_approach.md +++ b/current_vs_alternative_approach.md @@ -111,7 +111,7 @@ The extension noted in [`docs/fuzzing.md`](docs/fuzzing.md:356) is: | Execution time | Slow (recompile per mutation) | Continuous | | Output | Surviving mutants = assertion gaps | Crash artifacts | -**Decision-maker view**: `cargo-mutants` would **audit the invariant assertions themselves** — revealing if [`assert_invariants()`](fuzz_props/src/invariants.rs:325) has gaps. Three invariants are fully implemented: [`StateIsolationOnFailure`](fuzz_props/src/invariants.rs:60), [`BalanceConservation`](fuzz_props/src/invariants.rs:94), and [`FailedTxNonceStability`](fuzz_props/src/invariants.rs:130). Two are registry stubs: [`ReplayRejection`](fuzz_props/src/invariants.rs:169) and [`NonceIncrementCorrectness`](fuzz_props/src/invariants.rs:196) — each enforced via dedicated standalone helpers (`assert_replay_rejection`, `assert_nonce_increment_correctness`). This is a **complementary quality gate**, not a fuzzing replacement. Low cost (~1 day), highly useful before an external security audit. +**Decision-maker view**: `cargo-mutants` would **audit the invariant assertions themselves** — revealing if [`assert_invariants()`](fuzz_props/src/invariants.rs) has gaps. Three invariants are fully implemented and registered in `assert_invariants()`: [`StateIsolationOnFailure`](fuzz_props/src/invariants.rs:60), [`BalanceConservation`](fuzz_props/src/invariants.rs:94), and [`FailedTxNonceStability`](fuzz_props/src/invariants.rs:130). Two additional invariants — [`ReplayRejection`](fuzz_props/src/invariants.rs:167) and [`NonceIncrementCorrectness`](fuzz_props/src/invariants.rs:194) — are enforced exclusively via standalone helpers (`assert_replay_rejection`, `assert_nonce_increment_correctness`) and are **not** in the `assert_invariants()` registry; this is intentional because they require data consumed before `InvariantCtx` is built. This is a **complementary quality gate**, not a fuzzing replacement. Low cost (~1 day), highly useful before an external security audit. --- diff --git a/docs/fuzzing.md b/docs/fuzzing.md index bb870ca..c65bf7f 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -478,37 +478,50 @@ automates steps 2–5 and uploads the report as a workflow artifact. ## Invariant Framework -Shared invariants live in `fuzz_props/src/invariants.rs`. Each invariant implements -`ProtocolInvariant` and is automatically run by `assert_invariants()`. +Shared invariants live in `fuzz_props/src/invariants.rs`. There are two layers: -Concrete invariants currently registered in `assert_invariants()`: +### Primary API — `assert_tx_execution_invariants()` -| Invariant | Description | Implementation status | -|-----------|-------------|----------------------| -| `StateIsolationOnFailure` | Per-account balance must not change for any tracked account when a transaction is rejected | ✅ Fully implemented | -| `BalanceConservation` | Total balance of all known accounts must be conserved when a transaction succeeds | ✅ Fully implemented | -| `FailedTxNonceStability` | Every account's nonce must remain unchanged when a transaction is rejected | ✅ Fully implemented | -| `ReplayRejection` | An accepted transaction must be rejected when replayed | ⚠️ Registry stub — always returns `None` from `InvariantCtx`; use `assert_replay_rejection()` directly (see note below) | -| `NonceIncrementCorrectness` | Every signer account's nonce must be incremented by exactly one after a successful transaction | ⚠️ Registry stub — always returns `None` from `InvariantCtx`; use `assert_nonce_increment_correctness()` directly (see note below) | +For every fuzz target that calls `execute_check_on_state`, use the single unified entry +point. It enforces the five state-transition invariants in one call, routing by outcome: -> **Note on stub invariants:** `ReplayRejection` and `NonceIncrementCorrectness` cannot be -> fully exercised through `InvariantCtx` alone. Each requires information that is consumed -> before `InvariantCtx` is built: -> -> - **`ReplayRejection`**: `execute_check_on_state` returns the `NSSATransaction` on `Ok`, -> consuming `self`. Replaying it requires re-applying the returned transaction to the -> post-execution state — not possible via a shared `&InvariantCtx`. Use the standalone -> `assert_replay_rejection(applied_tx, state, next_block_id, next_timestamp)` helper -> immediately after each successful execution. The proptest suite `replay_rejection_proptest` -> in `fuzz_props/src/invariants.rs` provides reproducible structured coverage of this -> invariant. -> -> - **`NonceIncrementCorrectness`**: `apply_state_diff` consumes the `ValidatedStateDiff` -> whose signer-account list is private to the `nssa` crate. The caller must derive signer -> IDs from the transaction's witness set before consuming the diff, then call the standalone -> `assert_nonce_increment_correctness(signer_ids, nonces_before, state_after)` helper. -> The `signer_account_ids()` helper in `fuzz_props::generators` extracts signer `AccountId`s -> from an `NSSATransaction`'s witness set. +| Invariant | Active when | +|-----------|-------------| +| `StateIsolationOnFailure` | `execution_result` is `Err` | +| `FailedTxNonceStability` | `execution_result` is `Err` | +| `BalanceConservation` | `execution_result` is `Ok` | +| `NonceIncrementCorrectness` | `execution_result` is `Ok` | +| `ReplayRejection` | `execution_result` is `Ok` | + +```rust +let state_snapshot = state.clone(); +let result = tx.execute_check_on_state(&mut state, block_id, timestamp); + +assert_tx_execution_invariants( + &state_snapshot, + &mut state, + balances_before, + nonces_before, + result, + (block_id + 1, timestamp + 1), +); +``` + +One call. No standalone helpers to remember. + +### Registry API — `assert_invariants()` + `ProtocolInvariant` + +Each invariant is a zero-size struct implementing `ProtocolInvariant`; `assert_invariants()` +runs the registry and panics on the first violation. This lower-level API is used +internally by `assert_tx_execution_invariants` and is also available for targets where no +transaction is available for replay (e.g. pure state-serialization targets). + +```rust +// Only use assert_invariants() directly for non-execution contexts. +// For execute_check_on_state call sites, prefer assert_tx_execution_invariants(). +assert_invariants(&InvariantCtx { state_before, state_after, execution_succeeded, + balances_before, nonces_before }); +``` Additional invariants enforced **inline** in specific targets (not via `ProtocolInvariant`): diff --git a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs index bb084de..c1974f8 100644 --- a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs +++ b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs @@ -17,15 +17,14 @@ //! //! # Invariants //! -//! The following per-transaction invariants are checked via the shared framework -//! ([`fuzz_props::invariants::assert_invariants`]) on every iteration: +//! The following per-transaction invariants are checked via +//! [`fuzz_props::invariants::assert_tx_execution_invariants`] on every iteration: //! //! - **StateIsolationOnFailure** — balances unchanged on rejection. -//! - **BalanceConservation** — total balance conserved on success. //! - **FailedTxNonceStability** — nonces unchanged on rejection. -//! -//! In addition, [`assert_replay_rejection`] is called on every successful -//! transaction (per-block replay check). +//! - **BalanceConservation** — total balance conserved on success. +//! - **NonceIncrementCorrectness** — signer nonces each increment by exactly one on success. +//! - **ReplayRejection** — every successful transaction rejected on replay (per-block). //! //! The following multi-block aggregate invariant is checked **after** the loop: //! @@ -35,11 +34,8 @@ //! the total; only mint/burn bugs or token-inflation bugs would break it. use arbitrary::{Arbitrary, Unstructured}; -use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids}; -use fuzz_props::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, assert_nonce_increment_correctness, - assert_replay_rejection, -}; +use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; +use fuzz_props::invariants::{BalanceSnapshot, NonceSnapshot, assert_tx_execution_invariants}; use nssa::V03State; fuzz_props::fuzz_entry!(|data: &[u8]| { @@ -99,29 +95,19 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { let state_snapshot = state.clone(); let result = tx.execute_check_on_state(&mut state, block_id, timestamp); - let execution_succeeded = result.is_ok(); - // ── Shared invariant checks ─────────────────────────────────────────── - // Asserts per-transaction: - // • StateIsolationOnFailure — balances unchanged on rejection - // • BalanceConservation — total balance conserved on success - // • FailedTxNonceStability — nonces unchanged on rejection - assert_invariants(&InvariantCtx { - state_before: &state_snapshot, - state_after: &state, - execution_succeeded, + // ── All five protocol invariants ────────────────────────────────────── + // A single call enforces every invariant — no standalone helpers needed: + // On rejection: StateIsolationOnFailure + FailedTxNonceStability + // On success: BalanceConservation + NonceIncrementCorrectness + ReplayRejection + assert_tx_execution_invariants( + &state_snapshot, + &mut state, balances_before, - nonces_before: nonces_before.clone(), - }); - - // ── NonceIncrementCorrectness + ReplayRejection (per-block) ────────── - // First verify every signer's nonce was incremented by exactly one, then - // replay in the next block to confirm the nonce is permanently consumed. - if let Ok(applied_tx) = result { - let ids = signer_account_ids(&applied_tx); - assert_nonce_increment_correctness(&ids, &nonces_before, &state); - assert_replay_rejection(applied_tx, &mut state, block_id + 1, timestamp + 1); - } + nonces_before, + result, + (block_id + 1, timestamp + 1), + ); } // ── LongRangeBalanceConservation ────────────────────────────────────────── diff --git a/fuzz/fuzz_targets/fuzz_replay_prevention.rs b/fuzz/fuzz_targets/fuzz_replay_prevention.rs index 1bc2d63..bdeb1b8 100644 --- a/fuzz/fuzz_targets/fuzz_replay_prevention.rs +++ b/fuzz/fuzz_targets/fuzz_replay_prevention.rs @@ -14,22 +14,17 @@ //! //! # Invariants checked //! -//! The shared framework ([`assert_invariants`]) enforces per-transaction: +//! [`assert_tx_execution_invariants`] enforces all five invariants per transaction +//! in one call: //! - **StateIsolationOnFailure** — balances unchanged on rejection //! - **BalanceConservation** — total balance conserved on success //! - **FailedTxNonceStability** — nonces unchanged on rejection -//! -//! The dedicated [`assert_replay_rejection`] function enforces: +//! - **NonceIncrementCorrectness** — signer nonces each increment by exactly one on success //! - **ReplayRejection** — accepted tx rejected on replay use arbitrary::{Arbitrary, Unstructured}; -use fuzz_props::generators::{ - arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids, -}; -use fuzz_props::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, - assert_nonce_increment_correctness, assert_replay_rejection, -}; +use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; +use fuzz_props::invariants::{BalanceSnapshot, NonceSnapshot, assert_tx_execution_invariants}; use nssa::V03State; fuzz_props::fuzz_entry!(|data: &[u8]| { @@ -77,27 +72,17 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { // First application — may legitimately fail for state-level reasons. let result = tx.execute_check_on_state(&mut state, 1, 0); - let execution_succeeded = result.is_ok(); - // ── Shared invariant checks ─────────────────────────────────────────────── - // Asserts: - // • StateIsolationOnFailure — balances unchanged on rejection - // • BalanceConservation — total balance conserved on success - // • FailedTxNonceStability — nonces unchanged on rejection - assert_invariants(&InvariantCtx { - state_before: &state_snapshot, - state_after: &state, - execution_succeeded, + // ── All five protocol invariants ────────────────────────────────────────── + // A single call enforces every invariant — no standalone helpers needed: + // On rejection: StateIsolationOnFailure + FailedTxNonceStability + // On success: BalanceConservation + NonceIncrementCorrectness + ReplayRejection + assert_tx_execution_invariants( + &state_snapshot, + &mut state, balances_before, - nonces_before: nonces_before.clone(), - }); - - // ── NonceIncrementCorrectness + ReplayRejection ─────────────────────────── - // First verify every signer's nonce was incremented by exactly one, then - // assert that replaying in the next block is rejected (nonce permanently consumed). - if let Ok(applied_tx) = result { - let signer_ids = signer_account_ids(&applied_tx); - assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); - assert_replay_rejection(applied_tx, &mut state, 2, 1); - } + nonces_before, + result, + (2, 1), + ); }); diff --git a/fuzz/fuzz_targets/fuzz_state_transition.rs b/fuzz/fuzz_targets/fuzz_state_transition.rs index 4915237..b892436 100644 --- a/fuzz/fuzz_targets/fuzz_state_transition.rs +++ b/fuzz/fuzz_targets/fuzz_state_transition.rs @@ -1,13 +1,8 @@ #![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] use arbitrary::{Arbitrary, Unstructured}; -use fuzz_props::generators::{ - arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction, signer_account_ids, -}; -use fuzz_props::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, - assert_nonce_increment_correctness, assert_replay_rejection, -}; +use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; +use fuzz_props::invariants::{BalanceSnapshot, NonceSnapshot, assert_tx_execution_invariants}; use nssa::V03State; fuzz_props::fuzz_entry!(|data: &[u8]| { @@ -75,29 +70,18 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { // Snapshot state before execution for isolation checks. let state_snapshot = state.clone(); let result = tx.execute_check_on_state(&mut state, block_id, timestamp); - let execution_succeeded = result.is_ok(); - // ── Shared invariant checks ─────────────────────────────────────────── - // Asserts: - // • StateIsolationOnFailure — balances unchanged on rejection - // • BalanceConservation — total balance conserved on success - // • FailedTxNonceStability — nonces unchanged on rejection - assert_invariants(&InvariantCtx { - state_before: &state_snapshot, - state_after: &state, - execution_succeeded, + // ── All five protocol invariants ────────────────────────────────────── + // A single call enforces every invariant — no standalone helpers needed: + // On rejection: StateIsolationOnFailure + FailedTxNonceStability + // On success: BalanceConservation + NonceIncrementCorrectness + ReplayRejection + assert_tx_execution_invariants( + &state_snapshot, + &mut state, balances_before, - nonces_before: nonces_before.clone(), - }); - - // ── NonceIncrementCorrectness + ReplayRejection ─────────────────────── - // execute_check_on_state returns the NSSATransaction on Ok. - // First verify every signer's nonce was incremented by exactly one, then - // replay in the next block to confirm the nonce is permanently consumed. - if let Ok(applied_tx) = result { - let signer_ids = signer_account_ids(&applied_tx); - assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); - assert_replay_rejection(applied_tx, &mut state, block_id + 1, timestamp + 1); - } + nonces_before, + result, + (block_id + 1, timestamp + 1), + ); } }); diff --git a/fuzz_props/src/invariants.rs b/fuzz_props/src/invariants.rs index b4d92bc..13c6a41 100644 --- a/fuzz_props/src/invariants.rs +++ b/fuzz_props/src/invariants.rs @@ -155,56 +155,47 @@ impl ProtocolInvariant for FailedTxNonceStability { /// A successfully accepted transaction must be rejected when replayed. /// -/// # Note +/// # Enforcement /// -/// This invariant **cannot** be exercised through [`InvariantCtx`] alone because -/// the replay check requires re-applying the `NSSATransaction` that was consumed -/// by `execute_check_on_state`. The `ProtocolInvariant` impl here is a registry -/// placeholder only; it always returns `None`. +/// This invariant **cannot** be enforced through [`InvariantCtx`] because the replay +/// check requires re-applying the `NSSATransaction` that `execute_check_on_state` +/// consumes and returns on `Ok`. It is therefore **not registered** in +/// [`assert_invariants`]; calling `assert_invariants` alone does **not** cover +/// `ReplayRejection`. /// -/// Use the standalone [`assert_replay_rejection`] function instead, which accepts -/// the `NSSATransaction` returned on success and performs the replay inline. +/// Every fuzz target that performs state transitions **must** call the standalone +/// [`assert_replay_rejection`] function after each successful execution: +/// +/// ```rust,ignore +/// if let Ok(applied_tx) = result { +/// assert_replay_rejection(applied_tx, &mut state, block_id + 1, timestamp + 1); +/// } +/// ``` pub struct ReplayRejection; -impl ProtocolInvariant for ReplayRejection { - fn name(&self) -> &'static str { - "ReplayRejection" - } - - fn check(&self, _ctx: &InvariantCtx<'_>) -> Option { - // ReplayRejection cannot be fully exercised through InvariantCtx alone. - // Use `assert_replay_rejection(applied_tx, state, next_block_id, next_ts)` instead. - None - } -} - /// A successfully applied transaction must increment the nonce of every signer account /// by exactly one. /// -/// # Note +/// # Enforcement /// -/// This invariant **cannot** be exercised through [`InvariantCtx`] alone because -/// `InvariantCtx` does not carry a signer-ID list — that information is private to the -/// `nssa` crate and is consumed by `apply_state_diff` before it returns. The -/// `ProtocolInvariant` impl here is a registry placeholder only; it always returns `None`. +/// This invariant **cannot** be enforced through [`InvariantCtx`] because signer +/// account IDs are private to the `nssa` crate and are consumed by `apply_state_diff` +/// before the caller can observe them. It is therefore **not registered** in +/// [`assert_invariants`]; calling `assert_invariants` alone does **not** cover +/// `NonceIncrementCorrectness`. /// -/// Use the standalone [`assert_nonce_increment_correctness`] function instead, passing -/// the signer IDs derived from the transaction's witness set, the [`NonceSnapshot`] -/// captured before execution, and the post-execution state. +/// Every fuzz target that performs state transitions **must** call the standalone +/// [`assert_nonce_increment_correctness`] function after each successful execution, +/// passing signer IDs derived from the transaction's witness set: +/// +/// ```rust,ignore +/// if let Ok(applied_tx) = result { +/// let signer_ids = signer_account_ids(&applied_tx); +/// assert_nonce_increment_correctness(&signer_ids, &nonces_before, &state); +/// } +/// ``` pub struct NonceIncrementCorrectness; -impl ProtocolInvariant for NonceIncrementCorrectness { - fn name(&self) -> &'static str { - "NonceIncrementCorrectness" - } - - fn check(&self, _ctx: &InvariantCtx<'_>) -> Option { - // NonceIncrementCorrectness requires explicit signer_ids not available in InvariantCtx. - // Use `assert_nonce_increment_correctness(signer_ids, nonces_before, state_after)` instead. - None - } -} - // ── Standalone helpers ──────────────────────────────────────────────────────── /// Assert that a successfully-applied transaction is **rejected** when replayed. @@ -308,24 +299,124 @@ pub fn assert_nonce_increment_correctness( } } -// ── Dispatcher ─────────────────────────────────────────────────────────────── +// ── Dispatchers ─────────────────────────────────────────────────────────────── -/// Run every registered [`ProtocolInvariant`] and panic with a structured message -/// on the first violation. +/// Assert the five state-transition invariants for a single `execute_check_on_state` call. +/// +/// Covers the invariants that are defined over one transaction execution attempt — +/// both the failure-isolation properties and the success-outcome correctness properties. +/// All are enforced from a single call; no standalone helpers are needed: +/// +/// | Invariant | Active when | +/// |-----------|-------------| +/// | [`StateIsolationOnFailure`] | `execution_result` is `Err` | +/// | [`FailedTxNonceStability`] | `execution_result` is `Err` | +/// | [`BalanceConservation`] | `execution_result` is `Ok` | +/// | [`NonceIncrementCorrectness`] | `execution_result` is `Ok` | +/// | [`ReplayRejection`] | `execution_result` is `Ok` | +/// +/// # Parameters +/// +/// * `state_before` — clone of the state captured **before** `execute_check_on_state`. +/// * `state_after` — live state **after** execution (mutably borrowed for the replay attempt). +/// * `balances_before` — per-account balance snapshot captured before execution. +/// * `nonces_before` — per-account nonce snapshot captured before execution. +/// * `execution_result` — the `Result` returned by `execute_check_on_state`. +/// * `replay_context` — `(next_block_id, next_timestamp)` used for the mandatory replay attempt. +/// +/// # Usage +/// +/// ```rust,ignore +/// let state_snapshot = state.clone(); +/// let balances_before = BalanceSnapshot( +/// accounts.iter().map(|&(id, _)| (id, state.get_account_by_id(id).balance)).collect(), +/// ); +/// let nonces_before = NonceSnapshot( +/// accounts.iter().map(|&(id, _)| (id, state.get_account_by_id(id).nonce)).collect(), +/// ); +/// let result = tx.execute_check_on_state(&mut state, block_id, timestamp); +/// +/// assert_tx_execution_invariants( +/// &state_snapshot, +/// &mut state, +/// balances_before, +/// nonces_before, +/// result, +/// (block_id + 1, timestamp + 1), +/// ); +/// ``` +pub fn assert_tx_execution_invariants( + state_before: &V03State, + state_after: &mut V03State, + balances_before: BalanceSnapshot, + nonces_before: NonceSnapshot, + execution_result: Result, + replay_context: (u64, u64), +) { + let execution_succeeded = execution_result.is_ok(); + // Clone nonces_before before it is moved into InvariantCtx so the clone + // remains available for assert_nonce_increment_correctness on the success path. + let nonces_for_nonce_check = nonces_before.clone(); + + // ── Three InvariantCtx-based invariants ─────────────────────────────────── + // The shared reborrow of state_after ends when assert_invariants returns (NLL), + // after which state_after is available mutably again for the replay attempt. + assert_invariants(&InvariantCtx { + state_before, + state_after: &*state_after, + execution_succeeded, + balances_before, + nonces_before, + }); + + // ── Two success-only invariants ─────────────────────────────────────────── + if let Ok(applied_tx) = execution_result { + // Derive signer IDs from the witness set. ProgramDeployment has no signers. + let signer_ids: Vec = match &applied_tx { + NSSATransaction::Public(pt) => pt + .witness_set() + .signatures_and_public_keys() + .iter() + .map(|(_, pk)| nssa::AccountId::from(pk)) + .collect(), + NSSATransaction::PrivacyPreserving(pt) => pt + .witness_set() + .signatures_and_public_keys() + .iter() + .map(|(_, pk)| nssa::AccountId::from(pk)) + .collect(), + NSSATransaction::ProgramDeployment(_) => vec![], + }; + assert_nonce_increment_correctness(&signer_ids, &nonces_for_nonce_check, state_after); + let (next_block_id, next_timestamp) = replay_context; + assert_replay_rejection(applied_tx, state_after, next_block_id, next_timestamp); + } +} + +/// Run the three [`InvariantCtx`]-based invariants and panic on the first violation. /// /// Invariants checked: -/// - [`StateIsolationOnFailure`] — balances unchanged on rejection -/// - [`BalanceConservation`] — total balance conserved on success -/// - [`FailedTxNonceStability`] — nonces unchanged on rejection -/// - [`ReplayRejection`] — stub only; use [`assert_replay_rejection`] directly -/// - [`NonceIncrementCorrectness`] — stub only; use [`assert_nonce_increment_correctness`] directly +/// +/// | Invariant | Condition | +/// |-----------|-----------| +/// | [`StateIsolationOnFailure`] | balances unchanged on rejection | +/// | [`BalanceConservation`] | total balance conserved on success | +/// | [`FailedTxNonceStability`] | nonces unchanged on rejection | +/// +/// # Prefer [`assert_tx_execution_invariants`] for `execute_check_on_state` call sites +/// +/// [`ReplayRejection`] and [`NonceIncrementCorrectness`] are not checked here — they +/// require data unavailable inside [`InvariantCtx`]. Use [`assert_tx_execution_invariants`] +/// instead for any target that calls `execute_check_on_state`; it enforces all five +/// invariants in one call. +/// +/// Reserve `assert_invariants` for contexts where no transaction is available for +/// replay (e.g. pure state-serialization or encoding targets). pub fn assert_invariants(ctx: &InvariantCtx<'_>) { let invariants: &[&dyn ProtocolInvariant] = &[ &StateIsolationOnFailure, &BalanceConservation, &FailedTxNonceStability, - &ReplayRejection, - &NonceIncrementCorrectness, ]; for inv in invariants { if let Some(violation) = inv.check(ctx) { diff --git a/fuzz_props/src/tests/invariants.rs b/fuzz_props/src/tests/invariants.rs index 59c6217..178b934 100644 --- a/fuzz_props/src/tests/invariants.rs +++ b/fuzz_props/src/tests/invariants.rs @@ -31,7 +31,7 @@ fn invariant_state_isolation_on_failure_does_not_panic_on_error() { } #[test] -fn invariant_replay_rejection_does_not_panic() { +fn assert_invariants_does_not_panic_on_success_with_empty_state() { let state = make_empty_state(); let ctx = InvariantCtx { state_before: &state, From 3c8844068fabd575aec09b7f85abfee69501be99 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 11:52:10 +0800 Subject: [PATCH 03/18] fix: replace saturating_add to prevent overflows --- .../fuzz_multi_block_state_sequence.rs | 14 ++++++-- .../fuzz_validate_execute_consistency.rs | 14 ++++++-- fuzz_props/src/generators.rs | 12 ++++++- fuzz_props/src/invariants.rs | 33 +++++++++++++++++-- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs index c1974f8..0f263cc 100644 --- a/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs +++ b/fuzz/fuzz_targets/fuzz_multi_block_state_sequence.rs @@ -57,7 +57,12 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { let starting_total: u128 = init_accs .iter() .map(|&(id, _)| state.get_account_by_id(id).balance) - .fold(0u128, u128::saturating_add); + .try_fold(0u128, |acc, x| acc.checked_add(x)) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: initial sum of genesis account balances \ + exceeded u128::MAX — per-account balance cap in arbitrary_fuzz_state() should \ + prevent this; if triggered, the cap has been raised without updating this check", + ); // Apply up to 16 transactions across successive blocks. let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 16; @@ -119,7 +124,12 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { let ending_total: u128 = init_accs .iter() .map(|&(id, _)| state.get_account_by_id(id).balance) - .fold(0u128, u128::saturating_add); + .try_fold(0u128, |acc, x| acc.checked_add(x)) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: final sum of genesis account balances \ + exceeded u128::MAX — token-inflation bug that saturating_add would have \ + silently masked", + ); assert_eq!( starting_total, diff --git a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs index b8a8b95..75d8e68 100644 --- a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs +++ b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs @@ -141,11 +141,21 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { let total_before: u128 = known_ids .iter() .map(|id| state.get_account_by_id(*id).balance) - .fold(0u128, u128::saturating_add); + .try_fold(0u128, |acc, x| acc.checked_add(x)) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: pre-execution sum of known account \ + balances exceeded u128::MAX — token-inflation bug that saturating_add would \ + have silently masked", + ); let total_after: u128 = known_ids .iter() .map(|id| exec_state.get_account_by_id(*id).balance) - .fold(0u128, u128::saturating_add); + .try_fold(0u128, |acc, x| acc.checked_add(x)) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: post-execution sum of known account \ + balances exceeded u128::MAX — token-inflation bug that saturating_add would \ + have silently masked", + ); assert_eq!( total_before, total_after, diff --git a/fuzz_props/src/generators.rs b/fuzz_props/src/generators.rs index def495f..8d75966 100644 --- a/fuzz_props/src/generators.rs +++ b/fuzz_props/src/generators.rs @@ -51,12 +51,22 @@ pub struct FuzzAccount { /// /// Call this before generating transactions so the constructed [`nssa::V03State`] /// has a shape controlled by the fuzzer rather than fixed at compile time. +/// +/// # Balance cap +/// +/// Each account's balance is capped at `u128::MAX / 8`. With at most 8 accounts, this +/// guarantees the sum of all balances fits in a `u128` without overflow. Balance- +/// conservation checks can therefore use `checked_add` instead of `saturating_add` to +/// turn silent overflow into a detected violation, ruling out false-positive panics on +/// legitimate fuzz inputs. pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result> { let n = ((u8::arbitrary(u)? as usize) % 8) + 1; // 1..=8 std::iter::repeat_with(|| { Ok(FuzzAccount { account_id: ArbAccountId::arbitrary(u)?.0, - balance: u128::arbitrary(u)?, + // Divide by 8 so the sum of 8 accounts is at most u128::MAX, preventing + // false-positive checked_add panics that would mask real inflation bugs. + balance: u128::arbitrary(u)? / 8, private_key: ArbPrivateKey::arbitrary(u)?.0, }) }) diff --git a/fuzz_props/src/invariants.rs b/fuzz_props/src/invariants.rs index 13c6a41..b874879 100644 --- a/fuzz_props/src/invariants.rs +++ b/fuzz_props/src/invariants.rs @@ -7,9 +7,25 @@ use nssa_core::account::Nonce; pub struct BalanceSnapshot(pub std::collections::HashMap); impl BalanceSnapshot { - /// Capture current total balance over all known accounts. + /// Sum of all recorded account balances. + /// + /// # Panics + /// + /// Panics if the sum overflows `u128`. This indicates a token-inflation bug — i.e. + /// the protocol somehow created tokens past `u128::MAX` — and would have been silently + /// masked by `saturating_add`. The generator caps each account balance at + /// `u128::MAX / 8` so eight accounts never overflow; any overflow here is therefore + /// a genuine protocol violation, not a fuzzer artefact. + #[must_use] pub fn total(&self) -> u128 { - self.0.values().copied().fold(0_u128, u128::saturating_add) + self.0 + .values() + .copied() + .try_fold(0_u128, u128::checked_add) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: sum of account balances exceeded u128::MAX \ + \u{2014} token-inflation bug that saturating_add would have silently masked", + ) } } @@ -97,6 +113,12 @@ impl ProtocolInvariant for BalanceConservation { "BalanceConservation" } + // Overflow in the balance sum IS the violation; using `?` here would silently return + // `None` and skip the check, which is worse than the inflation bug it was meant to catch. + #[expect( + clippy::unwrap_in_result, + reason = "overflow panic is the intended signal" + )] fn check(&self, ctx: &InvariantCtx<'_>) -> Option { if ctx.execution_succeeded { let total_before = ctx.balances_before.total(); @@ -105,7 +127,12 @@ impl ProtocolInvariant for BalanceConservation { .0 .keys() .map(|&id| ctx.state_after.get_account_by_id(id).balance) - .fold(0_u128, u128::saturating_add); + .try_fold(0_u128, u128::checked_add) + .expect( + "INVARIANT VIOLATION [BalanceOverflow]: sum of post-execution account balances \ + exceeded u128::MAX \u{2014} token-inflation bug that saturating_add would \ + have silently masked", + ); if total_before != total_after { return Some(InvariantViolation { invariant: self.name(), From 839359546d85fe5684360a7ba181b4c189d863e4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 12:11:49 +0800 Subject: [PATCH 04/18] fix: unlock new cases with arbitrary block_id and timestamp --- fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs index 9b46905..1cf693a 100644 --- a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs +++ b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs @@ -57,10 +57,14 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { .map(|a| (a.account_id, a.balance)) .collect(); - // Fixed block context — both pipelines use identical block_id and timestamp - // so the only variable is the code path (sequencer vs replayer). - let block_id: u64 = 2; // block 1 is genesis; this is the first "real" block - let timestamp: u64 = 1_000; + // Both pipelines use the same block_id and timestamp, drawn from the fuzz corpus + // so the fuzzer can explore clock-dependent and block-ID-dependent code paths. + // The invariant is path-equivalence at every (block_id, timestamp); it does not + // require either value to be constant. If the protocol rejects block_id=0 or + // timestamp=0 as structurally invalid, the existing clock-failure guard below + // (lines ~130-133) will return early without panicking — no extra guard needed. + let block_id: u64 = u64::arbitrary(&mut u).unwrap_or(2); + let timestamp: u64 = u64::arbitrary(&mut u).unwrap_or(1_000); // Shared base state — cloned once for each pipeline. let base_state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0); From aceb12f054a96af88990e0f0e9de8ed9c4b64281 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 17:59:21 +0800 Subject: [PATCH 05/18] fix: run aggregated coverage instead --- .github/workflows/fuzz-afl.yml | 231 ++++++++++++++++++++++----------- .github/workflows/fuzz.yml | 2 +- 2 files changed, 154 insertions(+), 79 deletions(-) diff --git a/.github/workflows/fuzz-afl.yml b/.github/workflows/fuzz-afl.yml index 542af4b..60d31bd 100644 --- a/.github/workflows/fuzz-afl.yml +++ b/.github/workflows/fuzz-afl.yml @@ -5,7 +5,7 @@ on: - cron: "0 2 * * *" workflow_dispatch: push: - branches: [main] + branches: [main, feat-afl-fuzzing] env: RISC0_DEV_MODE: "1" @@ -239,32 +239,15 @@ jobs: if-no-files-found: ignore # ──────────────────────────────────────────────────────────────────────────── - # afl-coverage — LLVM coverage report for all 15 targets + # afl-coverage-aggregate — single HTML report merging all 15 targets # ──────────────────────────────────────────────────────────────────────────── - afl-coverage: - name: "AFL++ coverage — ${{ matrix.target }}" + afl-coverage-aggregate: + name: "AFL++ coverage — aggregated" runs-on: ubuntu-latest needs: afl-smoke - strategy: - fail-fast: false - matrix: - target: - - fuzz_apply_state_diff_split_path - - fuzz_block_verification - - fuzz_encoding_roundtrip - - fuzz_multi_block_state_sequence - - fuzz_program_deployment_lifecycle - - fuzz_replay_prevention - - fuzz_sequencer_vs_replayer - - fuzz_signature_verification - - fuzz_state_diff_computation - - fuzz_state_serialization - - fuzz_state_transition - - fuzz_stateless_verification - - fuzz_transaction_decoding - - fuzz_validate_execute_consistency - - fuzz_witness_set_verification + permissions: + contents: read steps: - name: Checkout repository @@ -283,86 +266,125 @@ jobs: with: components: llvm-tools-preview - - name: Download smoke findings for ${{ matrix.target }} + - name: Download all AFL smoke findings uses: actions/download-artifact@v4 with: - name: afl-findings-${{ matrix.target }} - path: . + pattern: afl-findings-* + path: afl-artifacts/ + merge-multiple: false continue-on-error: true # no crashes/hangs/queue is fine - - name: Extract AFL findings tarball + - name: Extract all AFL findings tarballs run: | - TARGET="${{ matrix.target }}" - TARBALL="afl-findings-${TARGET}.tar.gz" - if [ -f "$TARBALL" ]; then - tar -xzf "$TARBALL" - fi + for tarball in afl-artifacts/*/afl-findings-*.tar.gz; do + [ -f "$tarball" ] || continue + tar -xzf "$tarball" + done - - name: Build with LLVM instrumented coverage + - name: Build all fuzz targets with LLVM coverage instrumentation env: RUSTFLAGS: "-C instrument-coverage" RISC0_DEV_MODE: "1" run: | - # Build with the libfuzzer harness: libFuzzer accepts corpus files as - # positional arguments, runs each through the fuzz closure once, then - # exits — LLVM coverage counters (-C instrument-coverage) are flushed - # to the .profraw file on exit regardless of the fuzzer runtime used. - cargo build \ - --manifest-path fuzz/Cargo.toml \ - --no-default-features \ - --features fuzzer-libfuzzer \ - --release \ - --bin ${{ matrix.target }} + TARGETS=( + fuzz_apply_state_diff_split_path + fuzz_block_verification + fuzz_encoding_roundtrip + fuzz_multi_block_state_sequence + fuzz_program_deployment_lifecycle + fuzz_replay_prevention + fuzz_sequencer_vs_replayer + fuzz_signature_verification + fuzz_state_diff_computation + fuzz_state_serialization + fuzz_state_transition + fuzz_stateless_verification + fuzz_transaction_decoding + fuzz_validate_execute_consistency + fuzz_witness_set_verification + ) + for TARGET in "${TARGETS[@]}"; do + cargo build \ + --manifest-path fuzz/Cargo.toml \ + --no-default-features \ + --features fuzzer-libfuzzer \ + --release \ + --bin "$TARGET" + done - - name: Run corpus + queue entries through instrumented binary + - name: Run all corpus and queue entries through instrumented binaries run: | - TARGET="${{ matrix.target }}" - BINARY="fuzz/target/release/${TARGET}" - PROFRAW_DIR="coverage/afl/${TARGET}/profraw" + TARGETS=( + fuzz_apply_state_diff_split_path + fuzz_block_verification + fuzz_encoding_roundtrip + fuzz_multi_block_state_sequence + fuzz_program_deployment_lifecycle + fuzz_replay_prevention + fuzz_sequencer_vs_replayer + fuzz_signature_verification + fuzz_state_diff_computation + fuzz_state_serialization + fuzz_state_transition + fuzz_stateless_verification + fuzz_transaction_decoding + fuzz_validate_execute_consistency + fuzz_witness_set_verification + ) + PROFRAW_DIR="coverage/afl/aggregated/profraw" mkdir -p "$PROFRAW_DIR" idx=0 - - # AFL corpus (checked-in, accumulated from prior runs) - for f in corpus/afl/${TARGET}/*; do - [ -f "$f" ] || continue - LLVM_PROFILE_FILE="${PROFRAW_DIR}/${idx}.profraw" "$BINARY" "$f" 2>/dev/null || true - idx=$((idx + 1)) - done - - # AFL++ queue entries from today's smoke run (downloaded artifact) - for instance_dir in afl-output/${TARGET}/*/; do - QUEUE="${instance_dir}queue" - [ -d "$QUEUE" ] || continue - for f in "$QUEUE"/id:*; do + for TARGET in "${TARGETS[@]}"; do + BINARY="fuzz/target/release/${TARGET}" + # Checked-in libFuzzer corpus + for f in corpus/libfuzz/${TARGET}/*; do [ -f "$f" ] || continue - LLVM_PROFILE_FILE="${PROFRAW_DIR}/${idx}.profraw" "$BINARY" "$f" 2>/dev/null || true + LLVM_PROFILE_FILE="${PROFRAW_DIR}/${TARGET}_${idx}.profraw" \ + "$BINARY" "$f" 2>/dev/null || true idx=$((idx + 1)) done + # Checked-in AFL corpus + for f in corpus/afl/${TARGET}/*; do + [ -f "$f" ] || continue + LLVM_PROFILE_FILE="${PROFRAW_DIR}/${TARGET}_${idx}.profraw" \ + "$BINARY" "$f" 2>/dev/null || true + idx=$((idx + 1)) + done + # AFL++ queue entries from today's smoke run + for instance_dir in afl-output/${TARGET}/*/; do + QUEUE="${instance_dir}queue" + [ -d "$QUEUE" ] || continue + for f in "$QUEUE"/id:*; do + [ -f "$f" ] || continue + LLVM_PROFILE_FILE="${PROFRAW_DIR}/${TARGET}_${idx}.profraw" \ + "$BINARY" "$f" 2>/dev/null || true + idx=$((idx + 1)) + done + done done - echo "Ran ${idx} inputs through ${TARGET}" + echo "Total inputs processed across all targets: ${idx}" - - name: Merge raw profiles + - name: Merge all profiles into one combined profdata run: | - TARGET="${{ matrix.target }}" - PROFRAW_DIR="coverage/afl/${TARGET}/profraw" - PROFDATA="coverage/afl/${TARGET}/merged.profdata" + PROFRAW_DIR="coverage/afl/aggregated/profraw" + PROFDATA="coverage/afl/aggregated/merged.profdata" SYSROOT="$(rustc --print sysroot)" HOST_TRIPLE="$(rustc -vV | awk '/^host:/{print $2}')" LLVM_PROFDATA="${SYSROOT}/lib/rustlib/${HOST_TRIPLE}/bin/llvm-profdata" shopt -s nullglob files=("${PROFRAW_DIR}"/*.profraw) if [ ${#files[@]} -eq 0 ]; then - echo "No .profraw files found — skipping merge." + echo "No .profraw files found — nothing to aggregate." exit 0 fi + mkdir -p "$(dirname "$PROFDATA")" "$LLVM_PROFDATA" merge -sparse "${files[@]}" -o "$PROFDATA" + echo "Merged ${#files[@]} profraw files → $PROFDATA" - - name: Generate HTML coverage report + - name: Generate aggregated HTML coverage report run: | - TARGET="${{ matrix.target }}" - BINARY="fuzz/target/release/${TARGET}" - PROFDATA="coverage/afl/${TARGET}/merged.profdata" - HTML_DIR="coverage/afl/${TARGET}/html" + PROFDATA="coverage/afl/aggregated/merged.profdata" + HTML_DIR="coverage/afl/aggregated/html" SYSROOT="$(rustc --print sysroot)" HOST_TRIPLE="$(rustc -vV | awk '/^host:/{print $2}')" LLVM_COV="${SYSROOT}/lib/rustlib/${HOST_TRIPLE}/bin/llvm-cov" @@ -371,17 +393,70 @@ jobs: exit 0 fi mkdir -p "$HTML_DIR" + TARGETS=( + fuzz_apply_state_diff_split_path + fuzz_block_verification + fuzz_encoding_roundtrip + fuzz_multi_block_state_sequence + fuzz_program_deployment_lifecycle + fuzz_replay_prevention + fuzz_sequencer_vs_replayer + fuzz_signature_verification + fuzz_state_diff_computation + fuzz_state_serialization + fuzz_state_transition + fuzz_stateless_verification + fuzz_transaction_decoding + fuzz_validate_execute_consistency + fuzz_witness_set_verification + ) + # llvm-cov show: first binary is a positional arg; the rest use --object + first=1 + OBJECT_FLAGS=() + for TARGET in "${TARGETS[@]}"; do + BINARY="fuzz/target/release/${TARGET}" + [ -f "$BINARY" ] || continue + if [ $first -eq 1 ]; then + OBJECT_FLAGS+=("$BINARY") + first=0 + else + OBJECT_FLAGS+=("--object" "$BINARY") + fi + done + if [ ${#OBJECT_FLAGS[@]} -eq 0 ]; then + echo "No instrumented binaries found — skipping report." + exit 0 + fi "$LLVM_COV" show \ - "$BINARY" \ + "${OBJECT_FLAGS[@]}" \ --instr-profile="$PROFDATA" \ --format=html \ --output-dir="$HTML_DIR" \ --ignore-filename-regex='\.cargo|rustc' - echo "Coverage report: ${HTML_DIR}/index.html" + echo "Aggregated coverage report written to ${HTML_DIR}/index.html" - - name: Upload coverage report artifact + - name: Write GitHub Step Summary + if: always() + run: | + PROFDATA="coverage/afl/aggregated/merged.profdata" + HTML_DIR="coverage/afl/aggregated/html" + { + echo "## AFL++ Aggregated Coverage Report" + echo "" + if [ -f "${HTML_DIR}/index.html" ]; then + echo "✅ HTML report generated successfully." + elif [ -f "$PROFDATA" ]; then + echo "⚠️ profdata exists but HTML generation may have failed." + else + echo "❌ No profdata found — no coverage data to report." + fi + echo "" + echo "Download the \`afl-coverage-aggregated\` artifact to browse the full HTML report." + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload aggregated coverage report uses: actions/upload-artifact@v4 with: - name: afl-coverage-${{ matrix.target }} - path: coverage/afl/${{ matrix.target }}/html/ - if-no-files-found: ignore + name: afl-coverage-aggregated + path: coverage/afl/aggregated/html/ + if-no-files-found: warn diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 8253824..4deb722 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -5,7 +5,7 @@ on: - cron: "0 2 * * *" workflow_dispatch: push: - branches: [main] + branches: [main, feat-afl-fuzzing] env: RISC0_DEV_MODE: "1" From e0720cbceb3ae50dda62653ccd5e1b931b103911 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 20:41:11 +0800 Subject: [PATCH 06/18] test: initial mutants for props and protocol --- .github/workflows/mutants.yml | 216 +++++++++++++++++++++++++++++++++ .gitignore | 7 ++ Cargo.toml | 16 +++ Justfile | 103 ++++++++++++++++ scripts/mutants-corpus-test.sh | 53 ++++++++ 5 files changed, 395 insertions(+) create mode 100644 .github/workflows/mutants.yml create mode 100644 scripts/mutants-corpus-test.sh diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml new file mode 100644 index 0000000..337d92e --- /dev/null +++ b/.github/workflows/mutants.yml @@ -0,0 +1,216 @@ +name: Mutation Testing + +# ── When to run ─────────────────────────────────────────────────────────────── +# Plane A (fuzz_props invariants) runs on every PR that touches harness code. +# Plane B (LEZ protocol vs corpus) is slow (minutes per mutant × many mutants) +# so it only runs on a weekly schedule or on manual dispatch. +on: + pull_request: + paths: + - "fuzz_props/**" + - "fuzz/fuzz_targets/**" + - ".github/workflows/mutants.yml" + schedule: + - cron: "0 4 * * 1" # 04:00 UTC every Monday + workflow_dispatch: + +env: + RISC0_DEV_MODE: "1" + CARGO_TERM_COLOR: always + +jobs: + # ── Plane A: mutate fuzz_props (invariant harness) ──────────────────────── + # Oracle: cargo test -p fuzz_props --release + # Fast (~30–120 s total). Blocks PRs if any invariant-check logic is + # under-tested. + mutants-harness: + name: Mutants — fuzz_props invariants + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + - name: Install stable Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install logos-blockchain-circuits + uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: mutants-harness-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install cargo-mutants + run: cargo install cargo-mutants --locked + + # workspace.metadata.cargo-mutants in Cargo.toml sets: + # additional_cargo_args = ["--release"] + # exclude_globs = ["fuzz/fuzz_targets/**"] + # timeout_multiplier = 3.0 + - name: Run mutation tests on fuzz_props + run: | + cargo mutants \ + --package fuzz_props \ + --output mutants-harness.out + + - name: Upload mutants report + if: always() + uses: actions/upload-artifact@v4 + with: + name: mutants-harness-report + path: mutants-harness.out/ + + - name: Write GitHub Step Summary + if: always() + run: | + MISSED=$(wc -l < mutants-harness.out/missed.txt 2>/dev/null | tr -d ' ' || echo 0) + CAUGHT=$(wc -l < mutants-harness.out/caught.txt 2>/dev/null | tr -d ' ' || echo 0) + { + echo "## Mutation Testing — \`fuzz_props\` invariants" + echo "" + echo "| Result | Count |" + echo "|--------|-------|" + echo "| ✅ Caught | ${CAUGHT} |" + echo "| ❌ Survived | ${MISSED} |" + echo "" + if [ "${MISSED}" -gt 0 ]; then + echo "### Surviving mutants (invariant-checker gaps)" + echo '```' + cat mutants-harness.out/missed.txt 2>/dev/null || true + echo '```' + echo "" + echo "> Each surviving mutant represents a mutation in the invariant-checking" + echo "> code that \`cargo test -p fuzz_props\` did not detect." + echo "> Add a property-test that specifically exercises that code path." + else + echo "> All mutants caught — invariant-checking logic is fully covered." + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Fail if any mutations survived + run: | + if [ -s mutants-harness.out/missed.txt ]; then + echo "ERROR: surviving mutants found in fuzz_props — see artifact and Step Summary" + cat mutants-harness.out/missed.txt + exit 1 + fi + + # ── Plane B: mutate LEZ protocol code, oracle = corpus regression ───────── + # Each mutant: rebuild nssa/common + replay all 15 fuzz corpora (-runs=0). + # Surviving mutants = protocol bugs the committed corpus has never caught. + # Runs on schedule (weekly Monday) or manual workflow_dispatch only. + mutants-protocol: + name: Mutants — LEZ protocol vs corpus + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' + + steps: + - uses: actions/checkout@v4 + + - name: Checkout logos-execution-zone + uses: ./.github/actions/checkout-lez + + # cargo-fuzz requires nightly. + - name: Install Rust nightly toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools-preview + + - name: Install logos-blockchain-circuits + uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: mutants-protocol-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install cargo-fuzz and cargo-mutants + run: | + cargo install cargo-fuzz --locked + cargo install cargo-mutants --locked + + - name: Make corpus-regression wrapper executable + run: chmod +x scripts/mutants-corpus-test.sh + + # Build all 15 fuzz targets once before the mutation loop so that each + # mutant only needs to rebuild the mutated crate, not the fuzz harness. + - name: Pre-build fuzz targets + run: | + for target in \ + fuzz_transaction_decoding fuzz_stateless_verification \ + fuzz_state_transition fuzz_block_verification \ + fuzz_encoding_roundtrip fuzz_signature_verification \ + fuzz_replay_prevention fuzz_state_diff_computation \ + fuzz_validate_execute_consistency fuzz_state_serialization \ + fuzz_witness_set_verification fuzz_program_deployment_lifecycle \ + fuzz_apply_state_diff_split_path fuzz_multi_block_state_sequence \ + fuzz_sequencer_vs_replayer; do + cargo fuzz build "${target}" + done + + # cargo-mutants is invoked from the LEZ workspace (sibling directory). + # FUZZ_REPO is exported so the wrapper script can locate the corpus and + # fuzz directory without relying on relative paths. + - name: Run mutation tests against LEZ (nssa + common) + env: + FUZZ_REPO: ${{ github.workspace }} + working-directory: logos-execution-zone + run: | + cargo mutants \ + --package nssa \ + --package common \ + --in-place \ + --test-command "${{ github.workspace }}/scripts/mutants-corpus-test.sh" \ + --output "${{ github.workspace }}/mutants-protocol.out" \ + --timeout-multiplier 5.0 + + - name: Upload mutants report + if: always() + uses: actions/upload-artifact@v4 + with: + name: mutants-protocol-report + path: mutants-protocol.out/ + + - name: Write GitHub Step Summary + if: always() + run: | + MISSED=$(wc -l < mutants-protocol.out/missed.txt 2>/dev/null | tr -d ' ' || echo 0) + CAUGHT=$(wc -l < mutants-protocol.out/caught.txt 2>/dev/null | tr -d ' ' || echo 0) + { + echo "## Mutation Testing — LEZ protocol vs committed corpus" + echo "" + echo "| Result | Count |" + echo "|--------|-------|" + echo "| ✅ Caught by corpus | ${CAUGHT} |" + echo "| ❌ Survived (corpus gap) | ${MISSED} |" + echo "" + if [ "${MISSED}" -gt 0 ]; then + echo "### Surviving mutants (corpus gaps — protocol bugs not yet reached)" + echo '```' + cat mutants-protocol.out/missed.txt 2>/dev/null || true + echo '```' + echo "" + echo "> For each surviving mutant:" + echo "> 1. Run \`cargo fuzz run \` targeting the mutated function." + echo "> 2. Save the crashing input to \`corpus/libfuzz//\`." + echo "> 3. Commit the corpus entry — the next run will show \`CAUGHT\`." + else + echo "> All mutants caught — committed corpus covers all tested mutation points." + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 0a1758b..48506a6 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,13 @@ fuzz/coverage/ .DS_Store **/.DS_Store +# ── cargo-mutants outputs ───────────────────────────────────────────────────── +# Local mutation-testing reports (caught.txt, missed.txt, etc.) +# Created by `just mutants-harness` and `just mutants-protocol`. +mutants.out/ +mutants-harness.out/ +mutants-protocol.out/ + # ── Misc ────────────────────────────────────────────────────────────────────── # Performance baseline output from `just perf-baseline` or CI perf_baseline.txt diff --git a/Cargo.toml b/Cargo.toml index af287fc..39d3287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,3 +84,19 @@ hmac-sha512 = "1.1.7" itertools = "0.14.0" risc0-build = "3.0.5" logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } + +# ── cargo-mutants configuration (Plane A: mutate fuzz_props invariants) ─────── +# Run with --release to match CI timing. +# fuzz/fuzz_targets/** entry-points use fuzz_entry!() macros that are not +# reachable via `cargo test`; mutations there produce false survivors. +[workspace.metadata.cargo-mutants] +additional_cargo_args = ["--release"] +exclude_globs = ["fuzz/fuzz_targets/**"] +# RISC0 release builds are slower than typical crates; give each mutant extra time. +timeout_multiplier = 3.0 +# The workspace uses path dependencies outside its own directory +# (../logos-execution-zone/*). cargo-mutants normally copies the workspace to a +# temp directory, but the copy does not include the sibling LEZ directory, so the +# build fails immediately. --in-place mutates the original source files in-place +# and avoids the copy, letting cargo resolve ../logos-execution-zone as usual. +in_place = true diff --git a/Justfile b/Justfile index 7bfb9be..7c6563a 100644 --- a/Justfile +++ b/Justfile @@ -643,6 +643,109 @@ coverage-all ENGINE="all": fi fi +# ── Mutation testing ────────────────────────────────────────────────────────── +# +# Prerequisites (install once): +# cargo install cargo-mutants +# +# Two planes — run them independently: +# +# Plane A (fast, ~1-5 min): mutates fuzz_props invariant logic. +# Oracle: cargo test -p fuzz_props --release +# Run on every PR that touches fuzz_props/ or fuzz/fuzz_targets/. +# +# Plane B (slow, ~hours): mutates LEZ protocol code (nssa, common). +# Oracle: all 15 fuzz targets replayed against their committed corpus. +# Run weekly or manually to find corpus gaps. + +# Plane A — mutation testing of fuzz_props invariant implementations. +# +# Mutates every function in fuzz_props and checks whether `cargo test -p fuzz_props +# --release` catches the mutation. Surviving mutants identify invariant-checker +# logic that the property tests do not fully exercise. +# +# Workspace metadata in Cargo.toml configures --release, exclude_globs, and +# timeout_multiplier automatically; no extra flags are needed here. +# +# Output: mutants.out/ (human-readable report, also printed to stdout) +mutants-harness: + cargo mutants --package fuzz_props + +# Plane B — mutation testing of the LEZ protocol code against the committed corpus. +# +# Mutates nssa and common in the logos-execution-zone sibling workspace and uses +# scripts/mutants-corpus-test.sh as the oracle. The oracle replays all 15 +# committed libFuzzer corpora (cargo fuzz run -runs=0) against each mutant. +# +# A mutant that SURVIVES means there is no corpus input that triggers the +# relevant protocol invariant at that mutation point — a corpus gap worth +# investigating with a longer fuzz run. +# +# Prerequisites: +# - logos-execution-zone cloned at ../logos-execution-zone +# - cargo-fuzz installed (cargo install cargo-fuzz) +# - cargo-mutants installed (cargo install cargo-mutants --locked) +# +# PACKAGES selects which LEZ crates to mutate (space-separated). +# Default covers the two highest-value protocol crates. +# +# Output report: mutants-protocol.out/ in the repository root. +mutants-protocol PACKAGES="nssa common": + #!/bin/bash + set -euo pipefail + REPO_DIR="$(pwd)" + + if [ ! -d "${REPO_DIR}/../logos-execution-zone" ]; then + echo "ERROR: logos-execution-zone not found at ../logos-execution-zone" + exit 1 + fi + LEZ_DIR="$(cd "${REPO_DIR}/../logos-execution-zone" && pwd)" + + # Build --package flags (one per crate name) + PKG_FLAGS=() + for pkg in {{PACKAGES}}; do + PKG_FLAGS+=(--package "$pkg") + done + + echo "=== Plane B: mutating [{{PACKAGES}}] in logos-execution-zone ===" + echo " Oracle: scripts/mutants-corpus-test.sh (corpus regression, -runs=0)" + echo " Report: ${REPO_DIR}/mutants-protocol.out/" + echo "" + + # cargo-mutants must be run from inside the target workspace. + # FUZZ_REPO tells the oracle script where to find the corpus and fuzz/ dir. + # --output puts the report in our repo root so it's easy to browse/commit. + # --in-place is required because LEZ depends on path crates outside its own + # directory (e.g. the Rust standard toolchain); without it cargo-mutants copies + # the workspace to a temp dir where those relative paths would not resolve. + cd "$LEZ_DIR" + FUZZ_REPO="$REPO_DIR" \ + cargo mutants \ + "${PKG_FLAGS[@]}" \ + --in-place \ + --test-command "${REPO_DIR}/scripts/mutants-corpus-test.sh" \ + --output "${REPO_DIR}/mutants-protocol.out" \ + --timeout-multiplier 5.0 + + echo "" + echo "=== Mutation report summary ===" + MISSED_FILE="${REPO_DIR}/mutants-protocol.out/missed.txt" + CAUGHT_FILE="${REPO_DIR}/mutants-protocol.out/caught.txt" + MISSED=$(wc -l < "$MISSED_FILE" 2>/dev/null | tr -d ' ' || echo 0) + CAUGHT=$(wc -l < "$CAUGHT_FILE" 2>/dev/null | tr -d ' ' || echo 0) + echo "Caught: ${CAUGHT} | Survived: ${MISSED}" + echo "" + if [ "${MISSED}" -gt 0 ]; then + echo "Surviving mutants (corpus gaps):" + cat "$MISSED_FILE" || true + echo "" + echo "For each surviving mutant: run 'just fuzz ' targeting the" + echo "mutated function, add the crashing input to corpus/libfuzz//," + echo "then re-run 'just mutants-protocol' to confirm it is now CAUGHT." + else + echo "All mutants caught — corpus covers all tested mutation points." + fi + # ── Housekeeping ────────────────────────────────────────────────────────────── # Remove all Cargo build artefacts (workspace + fuzz sub-crate) diff --git a/scripts/mutants-corpus-test.sh b/scripts/mutants-corpus-test.sh new file mode 100644 index 0000000..1bfdead --- /dev/null +++ b/scripts/mutants-corpus-test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Plane-B mutation-testing oracle. +# +# Called by `cargo mutants --test-command` from *inside* the logos-execution-zone +# workspace directory after each source mutation. Replays the committed +# libFuzzer corpus against every fuzz target (cargo fuzz run -runs=0). +# +# Exit behaviour (used by cargo-mutants to classify each mutant): +# exit 0 → all corpus replays passed → mutant SURVIVED (corpus gap) +# exit ≠0 → at least one replay panicked → mutant CAUGHT (corpus covers it) +# +# Environment variables: +# FUZZ_REPO absolute path to the lez-fuzzing repository root. +# Defaults to the directory one level above this script. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FUZZ_REPO="${FUZZ_REPO:-"$(cd "${SCRIPT_DIR}/.." && pwd)"}" + +CORPUS_ROOT="${FUZZ_REPO}/corpus/libfuzz" +FUZZ_DIR="${FUZZ_REPO}/fuzz" + +targets=( + fuzz_transaction_decoding + fuzz_stateless_verification + fuzz_state_transition + fuzz_block_verification + fuzz_encoding_roundtrip + fuzz_signature_verification + fuzz_replay_prevention + fuzz_state_diff_computation + fuzz_validate_execute_consistency + fuzz_state_serialization + fuzz_witness_set_verification + fuzz_program_deployment_lifecycle + fuzz_apply_state_diff_split_path + fuzz_multi_block_state_sequence + fuzz_sequencer_vs_replayer +) + +for target in "${targets[@]}"; do + corpus="${CORPUS_ROOT}/${target}" + mkdir -p "${corpus}" + + # -runs=0 → replay every file in the corpus directory exactly once, then exit. + # A panic (invariant violation) causes cargo fuzz to exit non-zero, which + # propagates through this script and causes cargo-mutants to mark the mutant + # as CAUGHT. + cargo fuzz run "${target}" \ + --fuzz-dir "${FUZZ_DIR}" \ + "${corpus}" \ + -- -runs=0 +done From 1da53a9566d1fa375387f948b00314b0f7366aa8 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 20:50:45 +0800 Subject: [PATCH 07/18] fix: resolve path to LEZ --- .github/workflows/mutants.yml | 1 + Justfile | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index 337d92e..08d0ee2 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -61,6 +61,7 @@ jobs: run: | cargo mutants \ --package fuzz_props \ + --in-place \ --output mutants-harness.out - name: Upload mutants report diff --git a/Justfile b/Justfile index 7c6563a..a2ad2aa 100644 --- a/Justfile +++ b/Justfile @@ -665,11 +665,15 @@ coverage-all ENGINE="all": # logic that the property tests do not fully exercise. # # Workspace metadata in Cargo.toml configures --release, exclude_globs, and -# timeout_multiplier automatically; no extra flags are needed here. +# timeout_multiplier automatically. # -# Output: mutants.out/ (human-readable report, also printed to stdout) +# --in-place is mandatory: fuzz_props depends on LEZ crates via relative path +# (../logos-execution-zone/...) — without it cargo-mutants copies the workspace +# to /tmp and the copy cannot resolve those relative paths. +# +# Output: mutants-harness.out/ (human-readable report also printed to stdout) mutants-harness: - cargo mutants --package fuzz_props + cargo mutants --package fuzz_props --in-place --output mutants-harness.out # Plane B — mutation testing of the LEZ protocol code against the committed corpus. # From ee7b3b0f69357ea3b8e9675fa14c6864364cbb08 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 May 2026 21:27:48 +0800 Subject: [PATCH 08/18] fix: mutants-protocol invocation --- .github/workflows/mutants.yml | 42 ++++++++++++++++++++++----------- Justfile | 44 +++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index 08d0ee2..1c8e70c 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -165,21 +165,37 @@ jobs: cargo fuzz build "${target}" done - # cargo-mutants is invoked from the LEZ workspace (sibling directory). - # FUZZ_REPO is exported so the wrapper script can locate the corpus and - # fuzz directory without relying on relative paths. + # cargo-mutants >=24 dropped --test-command; intercept "cargo test" with a + # fake cargo wrapper that runs the corpus oracle instead. cargo-mutants is + # called as a direct binary (not through `cargo`) so the CARGO env var we + # set is respected rather than being overridden by cargo's process launch. - name: Run mutation tests against LEZ (nssa + common) - env: - FUZZ_REPO: ${{ github.workspace }} - working-directory: logos-execution-zone run: | - cargo mutants \ - --package nssa \ - --package common \ - --in-place \ - --test-command "${{ github.workspace }}/scripts/mutants-corpus-test.sh" \ - --output "${{ github.workspace }}/mutants-protocol.out" \ - --timeout-multiplier 5.0 + REAL_CARGO="$(command -v cargo)" + FAKE_CARGO="$(mktemp /tmp/fake-cargo-XXXXXX)" + # Intercept the test *execution* phase only; forward the build phase + # (cargo test --no-run) to the real cargo so mutants are compiled. + # cargo-mutants uses: + # Build phase: cargo test --no-run --verbose --package=... + # Test phase: cargo test --verbose --package=... + printf '#!/bin/bash\n_has_no_run=false\nfor _a in "$@"; do [ "$_a" = "--no-run" ] && _has_no_run=true && break; done\nif [ "${1:-}" = "test" ] && [ "$_has_no_run" = "false" ]; then\n FUZZ_REPO="%s" exec "%s"\nelse\n exec "%s" "$@"\nfi\n' \ + "${{ github.workspace }}" \ + "${{ github.workspace }}/scripts/mutants-corpus-test.sh" \ + "$REAL_CARGO" > "$FAKE_CARGO" + chmod +x "$FAKE_CARGO" + # cargo install places cargo-mutants next to cargo in the same bin dir. + MUTANTS_BIN="$(command -v cargo-mutants 2>/dev/null || echo "$(dirname "$REAL_CARGO")/cargo-mutants")" + cd "${{ github.workspace }}/logos-execution-zone" + # cargo-mutants is a Cargo plugin; when invoked directly (not via + # `cargo mutants`) we must supply "mutants" as argv[1] ourselves. + CARGO="$FAKE_CARGO" \ + "$MUTANTS_BIN" mutants \ + --package nssa \ + --package common \ + --in-place \ + --output "${{ github.workspace }}/mutants-protocol.out" \ + --timeout-multiplier 5.0 + rm -f "$FAKE_CARGO" - name: Upload mutants report if: always() diff --git a/Justfile b/Justfile index a2ad2aa..1e36971 100644 --- a/Justfile +++ b/Justfile @@ -722,14 +722,50 @@ mutants-protocol PACKAGES="nssa common": # --in-place is required because LEZ depends on path crates outside its own # directory (e.g. the Rust standard toolchain); without it cargo-mutants copies # the workspace to a temp dir where those relative paths would not resolve. + # + # cargo-mutants >=24 dropped --test-command and only supports --test-tool cargo|nextest. + # Work around: create a fake `cargo` wrapper that intercepts `cargo test` and + # runs the corpus oracle instead; every other sub-command is delegated to the + # real cargo. We call the cargo-mutants binary directly so that cargo's own + # process launch doesn't override the CARGO env var back to the real binary. + REAL_CARGO="$(command -v cargo)" + FAKE_CARGO=$(mktemp /tmp/fake-cargo-XXXXXX) + FAKE_CARGO_LOG=$(mktemp /tmp/fake-cargo-log-XXXXXX.txt) + trap 'rm -f "$FAKE_CARGO" "$FAKE_CARGO_LOG"' EXIT + # The fake cargo intercepts the test *execution* phase only. + # cargo-mutants drives two kinds of "cargo test" invocations: + # Build phase: cargo test --no-run --verbose --package=... (compile only) + # Test phase: cargo test --verbose --package=... (run tests) + # The oracle must only replace the test execution phase; the build phase + # must be forwarded to the real cargo so mutants are actually compiled. + printf '#!/bin/bash\necho "FAKE_CARGO: $*" >> "%s"\n_has_no_run=false\nfor _a in "$@"; do [ "$_a" = "--no-run" ] && _has_no_run=true && break; done\nif [ "${1:-}" = "test" ] && [ "$_has_no_run" = "false" ]; then\n FUZZ_REPO="%s" exec "%s"\nelse\n exec "%s" "$@"\nfi\n' \ + "$FAKE_CARGO_LOG" \ + "$REPO_DIR" \ + "${REPO_DIR}/scripts/mutants-corpus-test.sh" \ + "$REAL_CARGO" > "$FAKE_CARGO" + chmod +x "$FAKE_CARGO" + + # Locate the cargo-mutants binary (installed by `cargo install cargo-mutants`). + MUTANTS_BIN="$(command -v cargo-mutants 2>/dev/null || true)" + if [ -z "$MUTANTS_BIN" ]; then + MUTANTS_BIN="$(dirname "$REAL_CARGO")/cargo-mutants" + fi + if [ ! -x "$MUTANTS_BIN" ]; then + echo "ERROR: cargo-mutants not found. Install with: cargo install cargo-mutants --locked" + exit 1 + fi + + # cargo-mutants is a Cargo plugin. When invoked via `cargo mutants`, Cargo + # automatically prepends "mutants" as argv[1]. When we invoke the binary + # directly (to keep our CARGO env override alive), we must supply it ourselves. cd "$LEZ_DIR" - FUZZ_REPO="$REPO_DIR" \ - cargo mutants \ + CARGO="$FAKE_CARGO" \ + "$MUTANTS_BIN" mutants \ "${PKG_FLAGS[@]}" \ --in-place \ - --test-command "${REPO_DIR}/scripts/mutants-corpus-test.sh" \ --output "${REPO_DIR}/mutants-protocol.out" \ - --timeout-multiplier 5.0 + --timeout-multiplier 5.0 \ + || { echo "--- fake-cargo invocations ---"; cat "$FAKE_CARGO_LOG"; exit 1; } echo "" echo "=== Mutation report summary ===" From 477edb48bbdcb59fcdfb06287973f069b41622bc Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Jun 2026 12:40:24 +0800 Subject: [PATCH 09/18] fix: permission and start directory --- scripts/mutants-corpus-test.sh | 7 +++++++ 1 file changed, 7 insertions(+) mode change 100644 => 100755 scripts/mutants-corpus-test.sh diff --git a/scripts/mutants-corpus-test.sh b/scripts/mutants-corpus-test.sh old mode 100644 new mode 100755 index 1bfdead..cd95d55 --- a/scripts/mutants-corpus-test.sh +++ b/scripts/mutants-corpus-test.sh @@ -38,6 +38,13 @@ targets=( fuzz_sequencer_vs_replayer ) +# cargo-fuzz requires the nightly toolchain (-Zsanitizer=address etc.). +# When this script is called by `cargo-mutants` the working directory is the +# LEZ workspace (logos-execution-zone/), whose rust-toolchain.toml pins the +# stable 1.x compiler. Change to the fuzzing repo so that rustup resolves +# the nightly toolchain from lez-fuzzing/rust-toolchain.toml instead. +cd "${FUZZ_REPO}" + for target in "${targets[@]}"; do corpus="${CORPUS_ROOT}/${target}" mkdir -p "${corpus}" From a11f5540406bf819f48ac5508d55da504d6d5d54 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 10:04:26 +0800 Subject: [PATCH 10/18] chore: prevent PR file list display for corpus files --- .gitattributes | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e69de29 From 415e427e889747e401563b006e7738ace3c86cdf Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 10:08:28 +0800 Subject: [PATCH 11/18] test: add crash artifacts for regression testing --- .../fuzz_encoding_roundtrip/regression_0001 | 0 .../regression_0002 | 1 + .../libfuzz/fuzz_state_transition/regression_0003 | 0 .../fuzz_witness_set_verification/regression_0004 | Bin 0 -> 71 bytes .../fuzz_witness_set_verification/regression_0005 | Bin 0 -> 70 bytes 5 files changed, 1 insertion(+) create mode 100644 corpus/libfuzz/fuzz_encoding_roundtrip/regression_0001 create mode 100644 corpus/libfuzz/fuzz_multi_block_state_sequence/regression_0002 create mode 100644 corpus/libfuzz/fuzz_state_transition/regression_0003 create mode 100644 corpus/libfuzz/fuzz_witness_set_verification/regression_0004 create mode 100644 corpus/libfuzz/fuzz_witness_set_verification/regression_0005 diff --git a/corpus/libfuzz/fuzz_encoding_roundtrip/regression_0001 b/corpus/libfuzz/fuzz_encoding_roundtrip/regression_0001 new file mode 100644 index 0000000..e69de29 diff --git a/corpus/libfuzz/fuzz_multi_block_state_sequence/regression_0002 b/corpus/libfuzz/fuzz_multi_block_state_sequence/regression_0002 new file mode 100644 index 0000000..8c7e5a6 --- /dev/null +++ b/corpus/libfuzz/fuzz_multi_block_state_sequence/regression_0002 @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/corpus/libfuzz/fuzz_state_transition/regression_0003 b/corpus/libfuzz/fuzz_state_transition/regression_0003 new file mode 100644 index 0000000..e69de29 diff --git a/corpus/libfuzz/fuzz_witness_set_verification/regression_0004 b/corpus/libfuzz/fuzz_witness_set_verification/regression_0004 new file mode 100644 index 0000000000000000000000000000000000000000..327f06de5ea4130275f753c8d3b553d59f00379e GIT binary patch literal 71 qcmZ=cG<`Y)2yhsHDG)JzdeU@8I1eJSefsq2BB*jKKp}`;hUoyv(+zR} literal 0 HcmV?d00001 diff --git a/corpus/libfuzz/fuzz_witness_set_verification/regression_0005 b/corpus/libfuzz/fuzz_witness_set_verification/regression_0005 new file mode 100644 index 0000000000000000000000000000000000000000..a5d82fa63689e4d9f5cd8cd0d01c97ff240ee8ce GIT binary patch literal 70 pcmZ=cG<`Y)1A{1tfB<$F1DzHDsuG!=G@TJ5!7_a+SSye>9RLm<2toh= literal 0 HcmV?d00001 From 2adc49136104a6cac704229237e629e9e573edc7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 11:03:44 +0800 Subject: [PATCH 12/18] fix: recommit with corpus exclusion --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index e69de29..86ed182 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1 @@ +corpus/** linguist-generated=true From ccd08aed6f10d204ed7fb2407d87741a7a28f37b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 11:12:26 +0800 Subject: [PATCH 13/18] chore: synchronize with latest lee introduction --- Cargo.lock | 836 ++++++++++++----- Cargo.toml | 12 +- Justfile | 9 +- README.md | 2 +- docs/fuzzing.md | 14 +- fuzz/Cargo.lock | 840 +++++++++++++----- fuzz/Cargo.toml | 8 +- .../fuzz_apply_state_diff_split_path.rs | 4 +- .../fuzz_sequencer_vs_replayer.rs | 4 +- .../fuzz_state_diff_computation.rs | 6 +- .../fuzz_stateless_verification.rs | 4 +- .../fuzz_targets/fuzz_transaction_decoding.rs | 10 +- .../fuzz_validate_execute_consistency.rs | 4 +- fuzz_props/src/arbitrary_types.rs | 22 +- fuzz_props/src/generators.rs | 38 +- fuzz_props/src/invariants.rs | 20 +- 16 files changed, 1329 insertions(+), 504 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9050d10..51a670b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -134,7 +140,7 @@ dependencies = [ "ark-std 0.4.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "sha2", ] @@ -154,7 +160,7 @@ dependencies = [ "ark-std 0.5.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "fnv", "merlin", "sha2", @@ -220,7 +226,7 @@ dependencies = [ "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", @@ -240,7 +246,7 @@ dependencies = [ "ark-serialize 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools 0.13.0", "num-bigint", @@ -402,7 +408,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive 0.4.2", "ark-std 0.4.0", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -415,7 +421,7 @@ dependencies = [ "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -578,8 +584,6 @@ checksum = "86887daca11d02e0b04f37a9cb81888aae881397fb48ff66494e356aea97554a" dependencies = [ "itertools 0.10.5", "lazy_static", - "rand 0.8.5", - "serde", ] [[package]] @@ -749,7 +753,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ "fastrand", - "gloo-timers", "tokio", ] @@ -855,7 +858,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -882,19 +885,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "bonsai-sdk" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a381a5f681e536070483826412fcfcd6f6637921717c6aa0a3759926899ee9c2" -dependencies = [ - "duplicate", - "maybe-async", - "reqwest", - "serde", - "thiserror 2.0.18", -] - [[package]] name = "borsh" version = "1.6.1" @@ -919,6 +909,14 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bridge_core" +version = "0.1.0" +dependencies = [ + "lee_core", + "serde", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1081,9 +1079,15 @@ name = "clock_core" version = "0.1.0" dependencies = [ "borsh", - "nssa_core", + "lee_core", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" @@ -1103,10 +1107,10 @@ dependencies = [ "borsh", "clock_core", "hex", + "lee", + "lee_core", "log", "logos-blockchain-common-http-client", - "nssa", - "nssa_core", "serde", "serde_with", "sha2", @@ -1140,6 +1144,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-str" version = "0.4.3" @@ -1209,6 +1219,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1268,7 +1287,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -1280,6 +1301,15 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1289,7 +1319,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "serde", @@ -1400,7 +1430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 1.0.109", ] [[package]] @@ -1409,11 +1439,21 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -1520,11 +1560,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", + "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1543,7 +1593,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1586,17 +1636,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" -[[package]] -name = "duplicate" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" -dependencies = [ - "heck", - "proc-macro2", - "proc-macro2-diagnostics", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -1609,13 +1648,13 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", - "digest", + "der 0.7.10", + "digest 0.10.7", "elliptic-curve", "rfc6979", "serdect", "signature", - "spki", + "spki 0.7.3", ] [[package]] @@ -1624,7 +1663,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "serde", "signature", ] @@ -1676,12 +1715,12 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array 0.14.7", "group", "pem-rfc7468", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", "serdect", @@ -1755,7 +1794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1789,10 +1828,21 @@ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" name = "faucet_core" version = "0.1.0" dependencies = [ - "nssa_core", + "lee_core", "serde", ] +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "ff" version = "0.13.1" @@ -1809,12 +1859,32 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1995,8 +2065,8 @@ dependencies = [ "arbitrary", "borsh", "common", - "nssa", - "nssa_core", + "lee", + "lee_core", "proptest", "testnet_initial_state", ] @@ -2059,6 +2129,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2073,18 +2144,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.13.0" @@ -2275,7 +2334,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2363,6 +2422,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ + "ctutils", "typenum", ] @@ -2824,6 +2884,26 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.1", + "rand_core 0.10.1", +] + [[package]] name = "key_protocol" version = "0.1.0" @@ -2836,8 +2916,9 @@ dependencies = [ "hmac-sha512", "itertools 0.14.0", "k256", - "nssa", - "nssa_core", + "lee", + "lee_core", + "ml-kem", "rand 0.8.5", "serde", "sha2", @@ -2882,6 +2963,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lee" +version = "0.1.0" +dependencies = [ + "anyhow", + "borsh", + "bridge_core", + "clock_core", + "faucet_core", + "hex", + "k256", + "lee_core", + "log", + "rand 0.8.5", + "risc0-binfmt", + "risc0-build", + "risc0-zkvm", + "serde", + "serde_with", + "sha2", + "thiserror 2.0.18", +] + +[[package]] +name = "lee_core" +version = "0.1.0" +dependencies = [ + "base58", + "borsh", + "bytemuck", + "bytesize", + "chacha20", + "ml-kem", + "risc0-zkvm", + "serde", + "serde_with", + "thiserror 2.0.18", +] + [[package]] name = "libc" version = "0.2.184" @@ -3342,7 +3462,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos-blockchain-blend-crypto" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "blake2", "logos-blockchain-groth16", @@ -3350,13 +3470,13 @@ dependencies = [ "logos-blockchain-poseidon2", "logos-blockchain-utils", "rs-merkle-tree", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "logos-blockchain-blend-message" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "blake2", "derivative", @@ -3367,11 +3487,12 @@ dependencies = [ "logos-blockchain-core", "logos-blockchain-groth16", "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", "logos-blockchain-utils", "serde", "serde-big-array", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", "zeroize", ] @@ -3379,7 +3500,7 @@ dependencies = [ [[package]] name = "logos-blockchain-blend-proofs" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ed25519-dalek", "generic-array 1.3.5", @@ -3388,17 +3509,18 @@ dependencies = [ "logos-blockchain-groth16", "logos-blockchain-pol", "logos-blockchain-poq", + "logos-blockchain-poseidon2", "logos-blockchain-utils", "num-bigint", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "logos-blockchain-chain-broadcast-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "derivative", @@ -3414,7 +3536,7 @@ dependencies = [ [[package]] name = "logos-blockchain-chain-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", @@ -3436,25 +3558,95 @@ dependencies = [ "serde", "serde_with", "strum", - "thiserror 1.0.69", + "thiserror 2.0.18", + "time", "tokio", "tracing", "tracing-futures", ] +[[package]] +name = "logos-blockchain-circuits-build" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "dirs", + "fd-lock", + "flate2", + "tar", + "ureq", +] + +[[package]] +name = "logos-blockchain-circuits-common" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poc-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-pol-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poq-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + [[package]] name = "logos-blockchain-circuits-prover" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-circuits-utils", "tempfile", ] +[[package]] +name = "logos-blockchain-circuits-signature-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-types" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "bytes", + "libc", +] + [[package]] name = "logos-blockchain-circuits-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "dirs", ] @@ -3462,10 +3654,11 @@ dependencies = [ [[package]] name = "logos-blockchain-common-http-client" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "futures", "hex", + "log", "logos-blockchain-chain-broadcast-service", "logos-blockchain-chain-service", "logos-blockchain-core", @@ -3475,14 +3668,15 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", + "tokio-util", "url", ] [[package]] name = "logos-blockchain-core" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "bincode", @@ -3507,20 +3701,21 @@ dependencies = [ "rpds", "serde", "strum", - "thiserror 1.0.69", + "thiserror 2.0.18", + "time", "tracing", ] [[package]] name = "logos-blockchain-cryptarchia-engine" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-pol", "logos-blockchain-utils", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -3529,7 +3724,7 @@ dependencies = [ [[package]] name = "logos-blockchain-cryptarchia-sync" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "bytes", "futures", @@ -3540,7 +3735,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -3548,7 +3743,7 @@ dependencies = [ [[package]] name = "logos-blockchain-groth16" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-bn254 0.4.0", "ark-ec 0.4.2", @@ -3566,7 +3761,7 @@ dependencies = [ [[package]] name = "logos-blockchain-http-api-common" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "axum", "logos-blockchain-core", @@ -3574,14 +3769,19 @@ dependencies = [ "logos-blockchain-tracing", "serde", "serde_json", + "serde_urlencoded", "serde_with", + "time", "tracing", + "url", + "utoipa", + "validator", ] [[package]] name = "logos-blockchain-key-management-system-keys" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", @@ -3607,7 +3807,7 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-macros" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "proc-macro2", "quote", @@ -3617,7 +3817,7 @@ dependencies = [ [[package]] name = "logos-blockchain-ledger" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "derivative", "logos-blockchain-blend-crypto", @@ -3636,14 +3836,14 @@ dependencies = [ "rpds", "serde", "serde_arrays", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-libp2p" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "backon", @@ -3654,6 +3854,7 @@ dependencies = [ "igd-next 0.16.2", "libp2p", "logos-blockchain-cryptarchia-sync", + "logos-blockchain-log-targets", "logos-blockchain-utils", "multiaddr", "natpmp", @@ -3662,16 +3863,34 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", "zerocopy", ] +[[package]] +name = "logos-blockchain-log-targets" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "logos-blockchain-log-targets-macros", +] + +[[package]] +name = "logos-blockchain-log-targets-macros" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "logos-blockchain-mmr" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", @@ -3684,13 +3903,14 @@ dependencies = [ [[package]] name = "logos-blockchain-network-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", "logos-blockchain-core", "logos-blockchain-cryptarchia-sync", "logos-blockchain-libp2p", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "overwatch", "rand 0.8.5", @@ -3704,48 +3924,52 @@ dependencies = [ [[package]] name = "logos-blockchain-poc" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "logos-blockchain-circuits-poc-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", - "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-pol" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "astro-float", + "logos-blockchain-circuits-pol-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", + "logos-blockchain-proofs-error", "logos-blockchain-utils", - "logos-blockchain-witness-generator", "num-bigint", "num-traits", "serde", "serde_json", - "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-poq" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "logos-blockchain-circuits-poq-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", "logos-blockchain-pol", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", @@ -3756,7 +3980,7 @@ dependencies = [ [[package]] name = "logos-blockchain-poseidon2" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", @@ -3764,10 +3988,21 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "logos-blockchain-proofs-error" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "logos-blockchain-services-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", @@ -3775,24 +4010,25 @@ dependencies = [ "overwatch", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-storage-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", "futures", "logos-blockchain-core", "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "overwatch", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -3800,12 +4036,13 @@ dependencies = [ [[package]] name = "logos-blockchain-time-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", "log", "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "logos-blockchain-utils", "overwatch", @@ -3822,8 +4059,10 @@ dependencies = [ [[package]] name = "logos-blockchain-tracing" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "flate2", + "logos-blockchain-log-targets", "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-http", @@ -3846,7 +4085,7 @@ dependencies = [ [[package]] name = "logos-blockchain-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "blake2", @@ -3857,13 +4096,15 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", + "serde_yaml", + "thiserror 2.0.18", "time", ] [[package]] name = "logos-blockchain-utxotree" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", @@ -3871,27 +4112,21 @@ dependencies = [ "num-bigint", "rpds", "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "logos-blockchain-witness-generator" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" -dependencies = [ - "tempfile", + "thiserror 2.0.18", ] [[package]] name = "logos-blockchain-zksign" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-signature-sys", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", "logos-blockchain-poseidon2", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", @@ -3965,17 +4200,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "memchr" version = "2.8.0" @@ -3989,7 +4213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", - "keccak", + "keccak 0.1.6", "rand_core 0.6.4", "zeroize", ] @@ -4021,6 +4245,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.0" @@ -4032,6 +4266,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-kem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8 0.11.0", + "rand_core 0.10.1", + "sha3", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "moka" version = "0.12.15" @@ -4228,10 +4487,10 @@ dependencies = [ "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", - "digest", + "digest 0.10.7", "generic-array 0.14.7", "hex", - "keccak", + "keccak 0.1.6", "log", "rand 0.8.5", "zeroize", @@ -4274,51 +4533,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "nssa" -version = "0.1.0" -dependencies = [ - "anyhow", - "borsh", - "clock_core", - "faucet_core", - "hex", - "k256", - "log", - "nssa_core", - "rand 0.8.5", - "risc0-binfmt", - "risc0-build", - "risc0-zkvm", - "serde", - "serde_with", - "sha2", - "thiserror 2.0.18", -] - -[[package]] -name = "nssa_core" -version = "0.1.0" -dependencies = [ - "base58", - "borsh", - "bytemuck", - "bytesize", - "chacha20", - "k256", - "risc0-zkvm", - "serde", - "serde_with", - "thiserror 2.0.18", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4651,9 +4872,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -4662,8 +4883,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", ] [[package]] @@ -4753,6 +4984,30 @@ dependencies = [ "toml_edit 0.25.11+spec-1.1.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -4784,18 +5039,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", -] - [[package]] name = "prometheus-client" version = "0.22.3" @@ -5057,6 +5300,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -5156,7 +5405,6 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "futures-channel", "futures-core", "futures-util", "h2", @@ -5369,7 +5617,7 @@ dependencies = [ "borsh", "bytemuck", "cfg-if", - "digest", + "digest 0.10.7", "hex", "hex-literal", "metal", @@ -5391,7 +5639,6 @@ checksum = "22b7eafb5d85be59cbd9da83f662cf47d834f1b836e14f675d1530b12c666867" dependencies = [ "anyhow", "bincode", - "bonsai-sdk", "borsh", "bytemuck", "bytes", @@ -5480,16 +5727,16 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "signature", - "spki", + "spki 0.7.3", "subtle", "zeroize", ] @@ -5568,7 +5815,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5577,6 +5824,7 @@ version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -5697,9 +5945,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.10", "generic-array 0.14.7", - "pkcs8", + "pkcs8 0.10.2", "serdect", "subtle", "zeroize", @@ -5839,6 +6087,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.2.0" @@ -5857,7 +6118,17 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] @@ -5891,10 +6162,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -5940,7 +6217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5956,7 +6233,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", ] [[package]] @@ -6014,6 +6301,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -6094,6 +6387,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -6104,7 +6408,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6113,8 +6417,8 @@ version = "0.1.0" dependencies = [ "common", "key_protocol", - "nssa", - "nssa_core", + "lee", + "lee_core", "serde", ] @@ -6466,11 +6770,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber 0.3.23", @@ -6683,6 +6988,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -6701,6 +7012,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -6714,12 +7054,42 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "uuid" version = "1.23.1" @@ -6731,6 +7101,36 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling 0.20.11", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "valuable" version = "0.1.1" @@ -7362,6 +7762,16 @@ dependencies = [ "time", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xml-rs" version = "0.8.28" diff --git a/Cargo.toml b/Cargo.toml index 39d3287..f6b0861 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,11 +52,13 @@ unsafe_code = "deny" [workspace.dependencies] # ── LEZ crates — expects logos-execution-zone/ to be cloned at ../logos-execution-zone ── -nssa = { path = "../logos-execution-zone/nssa" } -nssa_core = { path = "../logos-execution-zone/nssa/core" } -common = { path = "../logos-execution-zone/common" } -key_protocol = { path = "../logos-execution-zone/key_protocol" } -testnet_initial_state = { path = "../logos-execution-zone/testnet_initial_state" } +# LEZ reorganised its directory layout; the package= key keeps the old dependency +# alias so that fuzz_props source code (use nssa::...) compiles unchanged. +nssa = { path = "../logos-execution-zone/lee/state_machine", package = "lee" } +nssa_core = { path = "../logos-execution-zone/lee/state_machine/core", package = "lee_core" } +common = { path = "../logos-execution-zone/lez/common" } +key_protocol = { path = "../logos-execution-zone/lee/key_protocol" } +testnet_initial_state = { path = "../logos-execution-zone/lez/testnet_initial_state" } token_core = { path = "../logos-execution-zone/programs/token/core" } test_program_methods = { path = "../logos-execution-zone/test_program_methods" } diff --git a/Justfile b/Justfile index 1e36971..e552881 100644 --- a/Justfile +++ b/Justfile @@ -788,10 +788,13 @@ mutants-protocol PACKAGES="nssa common": # ── Housekeeping ────────────────────────────────────────────────────────────── -# Remove all Cargo build artefacts (workspace + fuzz sub-crate) +# Remove all Cargo build artefacts (workspace + fuzz sub-crate + logos-execution-zone) +# Each command is prefixed with `-` so that a missing sibling workspace (LEZ not cloned) +# does not abort the recipe — cargo clean still removes whatever targets are present. clean: - cargo clean - cargo clean --manifest-path fuzz/Cargo.toml + -cargo clean + -cargo clean --manifest-path fuzz/Cargo.toml + -cargo clean --manifest-path ../logos-execution-zone/Cargo.toml # Remove libFuzzer crash/timeout artifacts for all targets (corpus is kept) clean-artifacts: diff --git a/README.md b/README.md index e95c320..082fa38 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ just fuzz-props | Target | Protocol layer | Entry point | |--------|---------------|-------------| -| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`NSSATransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` | +| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`LeeTransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` | | `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic + idempotency | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` | | `fuzz_state_transition` | `V03State` transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state | `fuzz/fuzz_targets/fuzz_state_transition.rs` | | `fuzz_block_verification` | Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment | `fuzz/fuzz_targets/fuzz_block_verification.rs` | diff --git a/docs/fuzzing.md b/docs/fuzzing.md index c65bf7f..3f8133b 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -103,7 +103,7 @@ just fuzz-regression | Target | What it fuzzes | Entry point | |--------|---------------|-------------| -| `fuzz_transaction_decoding` | Borsh decoding of `NSSATransaction`, `Block`, and `HashableBlockData`; roundtrip re-encoding of successfully decoded transactions | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` | +| `fuzz_transaction_decoding` | Borsh decoding of `LeeTransaction`, `Block`, and `HashableBlockData`; roundtrip re-encoding of successfully decoded transactions | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` | | `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic on arbitrary bytes; idempotency — a transaction that passes the check must pass it again | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` | | `fuzz_state_transition` | `execute_check_on_state()` across up to 8 transactions with fuzz-driven initial state and monotonically-advancing block context; asserts **StateIsolationOnFailure** (balances unchanged on rejection), **BalanceConservation** (total balance unchanged on success), and **ReplayRejection** (nonce consumed on first acceptance) | `fuzz/fuzz_targets/fuzz_state_transition.rs` | | `fuzz_block_verification` | Three block-hash invariants: **HashRoundTrip** (`HashableBlockData::from(Block)` is lossless), **HashPreimage** (block_id, prev_block_hash, timestamp each individually affect the hash), **TxOrderCommitment** (reversing the transaction list changes the hash) | `fuzz/fuzz_targets/fuzz_block_verification.rs` | @@ -558,19 +558,19 @@ fuzz target parameters for zero-boilerplate structured fuzzing. | `ArbWitnessSet` | `WitnessSet` (0–3 `(Signature, PublicKey)` pairs; mixes valid and invalid) | | `ArbPublicTransaction` | `PublicTransaction` (composed from `ArbPubTxMessage` + `ArbWitnessSet`) | | `ArbProgramDeploymentTransaction` | `ProgramDeploymentTransaction` (arbitrary bytecode) | -| `ArbHashableBlockData` | `HashableBlockData` (0–7 `ArbNSSATransaction` entries, random header fields) | -| `ArbNSSATransaction` | `NSSATransaction` (`Public` or `ProgramDeployment` variant; `PrivacyPreserving` excluded) | +| `ArbHashableBlockData` | `HashableBlockData` (0–7 `ArbLeeTransaction` entries, random header fields) | +| `ArbLeeTransaction` | `LeeTransaction` (`Public` or `ProgramDeployment` variant; `PrivacyPreserving` excluded) | ### `fuzz_props::generators` (libFuzzer helpers + proptest strategies) | Generator | Covers | |-----------|--------| | `arbitrary_fuzz_state()` | 1–8 fuzz-driven accounts with arbitrary IDs, balances, and private keys; used by `fuzz_state_transition`, `fuzz_replay_prevention`, `fuzz_validate_execute_consistency`, `fuzz_state_diff_computation` | -| `arb_fuzz_native_transfer()` | Correctly-signed native-transfer `NSSATransaction` referencing accounts from an `arbitrary_fuzz_state()` result; gives the fuzzer a path to successful state transitions | -| `arbitrary_transaction()` | Structured `NSSATransaction` (`Public` or `ProgramDeployment`) from unstructured bytes via `ArbNSSATransaction` | +| `arb_fuzz_native_transfer()` | Correctly-signed native-transfer `LeeTransaction` referencing accounts from an `arbitrary_fuzz_state()` result; gives the fuzzer a path to successful state transitions | +| `arbitrary_transaction()` | Structured `LeeTransaction` (`Public` or `ProgramDeployment`) from unstructured bytes via `ArbLeeTransaction` | | `arb_borsh_transaction_bytes()` | Raw Borsh bytes including invalid encodings | -| `signer_account_ids()` | Extracts `AccountId`s of all signers from an `NSSATransaction`'s witness set; used to derive signer IDs before `apply_state_diff` consumes the diff | -| `arb_native_transfer_tx()` | Valid native-transfer `NSSATransaction` between known testnet genesis accounts (proptest strategy) | +| `signer_account_ids()` | Extracts `AccountId`s of all signers from an `LeeTransaction`'s witness set; used to derive signer IDs before `apply_state_diff` consumes the diff | +| `arb_native_transfer_tx()` | Valid native-transfer `LeeTransaction` between known testnet genesis accounts (proptest strategy) | | `test_accounts()` | Returns `(AccountId, PrivateKey)` pairs from `testnet_initial_state` | | `arb_hashable_block_data()` | `HashableBlockData` with 0–8 valid native transfers (proptest strategy) | | `arb_invalid_account_state_tx()` | Phantom accounts + overflow amounts — expected to be rejected (IS-3) | diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 5ad1b08..413470e 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -146,7 +152,7 @@ dependencies = [ "ark-std 0.4.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "sha2", ] @@ -166,7 +172,7 @@ dependencies = [ "ark-std 0.5.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "fnv", "merlin", "sha2", @@ -232,7 +238,7 @@ dependencies = [ "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", "num-bigint", "num-traits", @@ -252,7 +258,7 @@ dependencies = [ "ark-serialize 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools 0.13.0", "num-bigint", @@ -414,7 +420,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive 0.4.2", "ark-std 0.4.0", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -427,7 +433,7 @@ dependencies = [ "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "num-bigint", ] @@ -590,8 +596,6 @@ checksum = "86887daca11d02e0b04f37a9cb81888aae881397fb48ff66494e356aea97554a" dependencies = [ "itertools 0.10.5", "lazy_static", - "rand 0.8.5", - "serde", ] [[package]] @@ -761,7 +765,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ "fastrand", - "gloo-timers", "tokio", ] @@ -867,7 +870,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -894,19 +897,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "bonsai-sdk" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a381a5f681e536070483826412fcfcd6f6637921717c6aa0a3759926899ee9c2" -dependencies = [ - "duplicate", - "maybe-async", - "reqwest", - "serde", - "thiserror 2.0.18", -] - [[package]] name = "borsh" version = "1.6.1" @@ -931,6 +921,14 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bridge_core" +version = "0.1.0" +dependencies = [ + "lee_core", + "serde", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1095,9 +1093,15 @@ name = "clock_core" version = "0.1.0" dependencies = [ "borsh", - "nssa_core", + "lee_core", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" @@ -1117,10 +1121,10 @@ dependencies = [ "borsh", "clock_core", "hex", + "lee", + "lee_core", "log", "logos-blockchain-common-http-client", - "nssa", - "nssa_core", "serde", "serde_with", "sha2", @@ -1154,6 +1158,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-str" version = "0.4.3" @@ -1223,6 +1233,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1282,7 +1301,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -1294,6 +1315,15 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1303,7 +1333,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "serde", @@ -1414,7 +1444,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 1.0.109", ] [[package]] @@ -1423,11 +1453,21 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -1534,11 +1574,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", + "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1557,7 +1607,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1600,17 +1650,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" -[[package]] -name = "duplicate" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" -dependencies = [ - "heck", - "proc-macro2", - "proc-macro2-diagnostics", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -1623,13 +1662,13 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", - "digest", + "der 0.7.10", + "digest 0.10.7", "elliptic-curve", "rfc6979", "serdect", "signature", - "spki", + "spki 0.7.3", ] [[package]] @@ -1638,7 +1677,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "serde", "signature", ] @@ -1690,12 +1729,12 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array 0.14.7", "group", "pem-rfc7468", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "sec1", "serdect", @@ -1769,7 +1808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1803,10 +1842,21 @@ checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" name = "faucet_core" version = "0.1.0" dependencies = [ - "nssa_core", + "lee_core", "serde", ] +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "ff" version = "0.13.1" @@ -1823,12 +1873,32 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2011,9 +2081,9 @@ dependencies = [ "borsh", "common", "fuzz_props", + "lee", + "lee_core", "libfuzzer-sys", - "nssa", - "nssa_core", "testnet_initial_state", ] @@ -2024,8 +2094,8 @@ dependencies = [ "arbitrary", "borsh", "common", - "nssa", - "nssa_core", + "lee", + "lee_core", "proptest", "testnet_initial_state", ] @@ -2088,6 +2158,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2102,18 +2173,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.13.0" @@ -2304,7 +2363,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2401,6 +2460,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ + "ctutils", "typenum", ] @@ -2872,6 +2932,26 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "kem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" +dependencies = [ + "crypto-common 0.2.1", + "rand_core 0.10.1", +] + [[package]] name = "key_protocol" version = "0.1.0" @@ -2884,8 +2964,9 @@ dependencies = [ "hmac-sha512", "itertools 0.14.0", "k256", - "nssa", - "nssa_core", + "lee", + "lee_core", + "ml-kem", "rand 0.8.5", "serde", "sha2", @@ -2930,6 +3011,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lee" +version = "0.1.0" +dependencies = [ + "anyhow", + "borsh", + "bridge_core", + "clock_core", + "faucet_core", + "hex", + "k256", + "lee_core", + "log", + "rand 0.8.5", + "risc0-binfmt", + "risc0-build", + "risc0-zkvm", + "serde", + "serde_with", + "sha2", + "thiserror 2.0.18", +] + +[[package]] +name = "lee_core" +version = "0.1.0" +dependencies = [ + "base58", + "borsh", + "bytemuck", + "bytesize", + "chacha20", + "ml-kem", + "risc0-zkvm", + "serde", + "serde_with", + "thiserror 2.0.18", +] + [[package]] name = "libc" version = "0.2.184" @@ -3400,7 +3520,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos-blockchain-blend-crypto" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "blake2", "logos-blockchain-groth16", @@ -3408,13 +3528,13 @@ dependencies = [ "logos-blockchain-poseidon2", "logos-blockchain-utils", "rs-merkle-tree", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "logos-blockchain-blend-message" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "blake2", "derivative", @@ -3425,11 +3545,12 @@ dependencies = [ "logos-blockchain-core", "logos-blockchain-groth16", "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", "logos-blockchain-utils", "serde", "serde-big-array", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", "zeroize", ] @@ -3437,7 +3558,7 @@ dependencies = [ [[package]] name = "logos-blockchain-blend-proofs" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ed25519-dalek", "generic-array 1.3.5", @@ -3446,17 +3567,18 @@ dependencies = [ "logos-blockchain-groth16", "logos-blockchain-pol", "logos-blockchain-poq", + "logos-blockchain-poseidon2", "logos-blockchain-utils", "num-bigint", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "logos-blockchain-chain-broadcast-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "derivative", @@ -3472,7 +3594,7 @@ dependencies = [ [[package]] name = "logos-blockchain-chain-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", @@ -3494,25 +3616,95 @@ dependencies = [ "serde", "serde_with", "strum", - "thiserror 1.0.69", + "thiserror 2.0.18", + "time", "tokio", "tracing", "tracing-futures", ] +[[package]] +name = "logos-blockchain-circuits-build" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "dirs", + "fd-lock", + "flate2", + "tar", + "ureq", +] + +[[package]] +name = "logos-blockchain-circuits-common" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poc-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-pol-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poq-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + [[package]] name = "logos-blockchain-circuits-prover" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-circuits-utils", "tempfile", ] +[[package]] +name = "logos-blockchain-circuits-signature-sys" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-types" +version = "0.5.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?rev=2e79ac30831d89e6a349720c08d5b8b9978970e0#2e79ac30831d89e6a349720c08d5b8b9978970e0" +dependencies = [ + "bytes", + "libc", +] + [[package]] name = "logos-blockchain-circuits-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "dirs", ] @@ -3520,10 +3712,11 @@ dependencies = [ [[package]] name = "logos-blockchain-common-http-client" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "futures", "hex", + "log", "logos-blockchain-chain-broadcast-service", "logos-blockchain-chain-service", "logos-blockchain-core", @@ -3533,14 +3726,15 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", + "tokio-util", "url", ] [[package]] name = "logos-blockchain-core" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "bincode", @@ -3565,20 +3759,21 @@ dependencies = [ "rpds", "serde", "strum", - "thiserror 1.0.69", + "thiserror 2.0.18", + "time", "tracing", ] [[package]] name = "logos-blockchain-cryptarchia-engine" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-pol", "logos-blockchain-utils", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -3587,7 +3782,7 @@ dependencies = [ [[package]] name = "logos-blockchain-cryptarchia-sync" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "bytes", "futures", @@ -3598,7 +3793,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -3606,7 +3801,7 @@ dependencies = [ [[package]] name = "logos-blockchain-groth16" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-bn254 0.4.0", "ark-ec 0.4.2", @@ -3624,7 +3819,7 @@ dependencies = [ [[package]] name = "logos-blockchain-http-api-common" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "axum", "logos-blockchain-core", @@ -3632,14 +3827,19 @@ dependencies = [ "logos-blockchain-tracing", "serde", "serde_json", + "serde_urlencoded", "serde_with", + "time", "tracing", + "url", + "utoipa", + "validator", ] [[package]] name = "logos-blockchain-key-management-system-keys" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", @@ -3665,7 +3865,7 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-macros" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "proc-macro2", "quote", @@ -3675,7 +3875,7 @@ dependencies = [ [[package]] name = "logos-blockchain-ledger" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "derivative", "logos-blockchain-blend-crypto", @@ -3694,14 +3894,14 @@ dependencies = [ "rpds", "serde", "serde_arrays", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-libp2p" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "backon", @@ -3712,6 +3912,7 @@ dependencies = [ "igd-next 0.16.2", "libp2p", "logos-blockchain-cryptarchia-sync", + "logos-blockchain-log-targets", "logos-blockchain-utils", "multiaddr", "natpmp", @@ -3720,16 +3921,34 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", "zerocopy", ] +[[package]] +name = "logos-blockchain-log-targets" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "logos-blockchain-log-targets-macros", +] + +[[package]] +name = "logos-blockchain-log-targets-macros" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "logos-blockchain-mmr" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", @@ -3742,13 +3961,14 @@ dependencies = [ [[package]] name = "logos-blockchain-network-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", "logos-blockchain-core", "logos-blockchain-cryptarchia-sync", "logos-blockchain-libp2p", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "overwatch", "rand 0.8.5", @@ -3762,48 +3982,52 @@ dependencies = [ [[package]] name = "logos-blockchain-poc" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "logos-blockchain-circuits-poc-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", - "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-pol" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "astro-float", + "logos-blockchain-circuits-pol-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", + "logos-blockchain-proofs-error", "logos-blockchain-utils", - "logos-blockchain-witness-generator", "num-bigint", "num-traits", "serde", "serde_json", - "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-poq" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "logos-blockchain-circuits-poq-sys", "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", "logos-blockchain-pol", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", @@ -3814,7 +4038,7 @@ dependencies = [ [[package]] name = "logos-blockchain-poseidon2" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", @@ -3822,10 +4046,21 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "logos-blockchain-proofs-error" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" +dependencies = [ + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "serde_json", + "thiserror 2.0.18", +] + [[package]] name = "logos-blockchain-services-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", @@ -3833,24 +4068,25 @@ dependencies = [ "overwatch", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", ] [[package]] name = "logos-blockchain-storage-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "bytes", "futures", "logos-blockchain-core", "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "overwatch", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -3858,12 +4094,13 @@ dependencies = [ [[package]] name = "logos-blockchain-time-service" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "futures", "log", "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", "logos-blockchain-tracing", "logos-blockchain-utils", "overwatch", @@ -3880,8 +4117,10 @@ dependencies = [ [[package]] name = "logos-blockchain-tracing" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ + "flate2", + "logos-blockchain-log-targets", "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-http", @@ -3904,7 +4143,7 @@ dependencies = [ [[package]] name = "logos-blockchain-utils" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "async-trait", "blake2", @@ -3915,13 +4154,15 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", + "serde_yaml", + "thiserror 2.0.18", "time", ] [[package]] name = "logos-blockchain-utxotree" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", @@ -3929,27 +4170,21 @@ dependencies = [ "num-bigint", "rpds", "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "logos-blockchain-witness-generator" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" -dependencies = [ - "tempfile", + "thiserror 2.0.18", ] [[package]] name = "logos-blockchain-zksign" version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=db9a8d821c1b20f29b03d02072817150cf969b8e#db9a8d821c1b20f29b03d02072817150cf969b8e" dependencies = [ "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-signature-sys", + "logos-blockchain-circuits-types", "logos-blockchain-circuits-utils", "logos-blockchain-groth16", "logos-blockchain-poseidon2", - "logos-blockchain-witness-generator", + "logos-blockchain-proofs-error", "num-bigint", "serde", "serde_json", @@ -4023,17 +4258,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "memchr" version = "2.8.0" @@ -4047,7 +4271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", - "keccak", + "keccak 0.1.6", "rand_core 0.6.4", "zeroize", ] @@ -4079,6 +4303,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.0" @@ -4090,6 +4324,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-kem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" +dependencies = [ + "hybrid-array", + "kem", + "module-lattice", + "pkcs8 0.11.0", + "rand_core 0.10.1", + "sha3", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "moka" version = "0.12.15" @@ -4286,10 +4545,10 @@ dependencies = [ "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", - "digest", + "digest 0.10.7", "generic-array 0.14.7", "hex", - "keccak", + "keccak 0.1.6", "log", "rand 0.8.5", "zeroize", @@ -4332,51 +4591,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "nssa" -version = "0.1.0" -dependencies = [ - "anyhow", - "borsh", - "clock_core", - "faucet_core", - "hex", - "k256", - "log", - "nssa_core", - "rand 0.8.5", - "risc0-binfmt", - "risc0-build", - "risc0-zkvm", - "serde", - "serde_with", - "sha2", - "thiserror 2.0.18", -] - -[[package]] -name = "nssa_core" -version = "0.1.0" -dependencies = [ - "base58", - "borsh", - "bytemuck", - "bytesize", - "chacha20", - "k256", - "risc0-zkvm", - "serde", - "serde_with", - "thiserror 2.0.18", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4709,9 +4930,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -4720,8 +4941,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", ] [[package]] @@ -4811,6 +5042,30 @@ dependencies = [ "toml_edit 0.25.11+spec-1.1.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -4842,18 +5097,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", -] - [[package]] name = "prometheus-client" version = "0.22.3" @@ -5115,6 +5358,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -5214,7 +5463,6 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", - "futures-channel", "futures-core", "futures-util", "h2", @@ -5427,7 +5675,7 @@ dependencies = [ "borsh", "bytemuck", "cfg-if", - "digest", + "digest 0.10.7", "hex", "hex-literal", "metal", @@ -5449,7 +5697,6 @@ checksum = "22b7eafb5d85be59cbd9da83f662cf47d834f1b836e14f675d1530b12c666867" dependencies = [ "anyhow", "bincode", - "bonsai-sdk", "borsh", "bytemuck", "bytes", @@ -5538,16 +5785,16 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", "signature", - "spki", + "spki 0.7.3", "subtle", "zeroize", ] @@ -5626,7 +5873,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5635,6 +5882,7 @@ version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -5755,9 +6003,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.10", "generic-array 0.14.7", - "pkcs8", + "pkcs8 0.10.2", "serdect", "subtle", "zeroize", @@ -5897,6 +6145,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.2.0" @@ -5915,7 +6176,17 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] @@ -5949,10 +6220,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -5998,7 +6275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6014,7 +6291,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", ] [[package]] @@ -6072,6 +6359,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -6152,6 +6445,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -6162,7 +6466,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6171,8 +6475,8 @@ version = "0.1.0" dependencies = [ "common", "key_protocol", - "nssa", - "nssa_core", + "lee", + "lee_core", "serde", ] @@ -6524,11 +6828,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber 0.3.23", @@ -6741,6 +7046,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -6759,6 +7070,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -6772,12 +7112,42 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "uuid" version = "1.23.1" @@ -6789,6 +7159,36 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling 0.20.11", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "valuable" version = "0.1.1" @@ -7420,6 +7820,16 @@ dependencies = [ "time", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xdg" version = "3.0.0" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d2d8e87..de3cb48 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -44,11 +44,11 @@ libfuzzer-sys = { version = "0.4", optional = true } afl = { version = "0.15", optional = true } arbitrary = { version = "1", features = ["derive"] } borsh = "1" -nssa = { path = "../../logos-execution-zone/nssa" } -nssa_core = { path = "../../logos-execution-zone/nssa/core" } -common = { path = "../../logos-execution-zone/common" } +nssa = { path = "../../logos-execution-zone/lee/state_machine", package = "lee" } +nssa_core = { path = "../../logos-execution-zone/lee/state_machine/core", package = "lee_core" } +common = { path = "../../logos-execution-zone/lez/common" } fuzz_props = { path = "../fuzz_props" } -testnet_initial_state = { path = "../../logos-execution-zone/testnet_initial_state" } +testnet_initial_state = { path = "../../logos-execution-zone/lez/testnet_initial_state" } [profile.release] debug = true diff --git a/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs b/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs index 7cd5fe8..3564cae 100644 --- a/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs +++ b/fuzz/fuzz_targets/fuzz_apply_state_diff_split_path.rs @@ -33,7 +33,7 @@ use std::collections::HashSet; use arbitrary::{Arbitrary, Unstructured}; -use fuzz_props::arbitrary_types::ArbNSSATransaction; +use fuzz_props::arbitrary_types::ArbLeeTransaction; use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids}; use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness}; use nssa::V03State; @@ -52,7 +52,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { .collect(); // Generate and stateless-check a transaction. - let tx_raw = match ArbNSSATransaction::arbitrary(&mut u) { + let tx_raw = match ArbLeeTransaction::arbitrary(&mut u) { Ok(w) => w.0, Err(_) => return, }; diff --git a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs index 1cf693a..9f20dda 100644 --- a/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs +++ b/fuzz/fuzz_targets/fuzz_sequencer_vs_replayer.rs @@ -38,7 +38,7 @@ use std::collections::HashSet; use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::{NSSATransaction, clock_invocation}; +use common::transaction::{LeeTransaction, clock_invocation}; use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction}; use nssa::V03State; @@ -81,7 +81,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { // Accepted transaction list — populated here, consumed by the replayer phase // so that both pipelines process exactly the same set of transactions. - let mut accepted_txs: Vec = Vec::new(); + let mut accepted_txs: Vec = Vec::new(); let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 8; diff --git a/fuzz/fuzz_targets/fuzz_state_diff_computation.rs b/fuzz/fuzz_targets/fuzz_state_diff_computation.rs index 0c912fe..e252046 100644 --- a/fuzz/fuzz_targets/fuzz_state_diff_computation.rs +++ b/fuzz/fuzz_targets/fuzz_state_diff_computation.rs @@ -19,7 +19,7 @@ //! specific account shapes such as zero balance or `u128::MAX` — are reachable. use arbitrary::{Arbitrary, Unstructured}; -use common::transaction::NSSATransaction; +use common::transaction::LeeTransaction; use fuzz_props::arbitrary_types::ArbPublicTransaction; use fuzz_props::generators::arbitrary_fuzz_state; use nssa::{V03State, ValidatedStateDiff}; @@ -47,7 +47,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { // Collect the set of accounts the transaction declares it will touch. // `affected_public_account_ids()` returns owned data so `pub_tx` remains // available for both `from_public_transaction` (borrow) and the later move - // into `NSSATransaction::Public`. + // into `LeeTransaction::Public`. let affected = pub_tx.affected_public_account_ids(); match ValidatedStateDiff::from_public_transaction(&pub_tx, &state, 1, 0) { @@ -77,7 +77,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { // we do not panic on a structurally malformed transaction. let mut exec_state = state.clone(); // `pub_tx` is moved here; it is no longer borrowed after this point. - let tx_for_exec = NSSATransaction::Public(pub_tx); + let tx_for_exec = LeeTransaction::Public(pub_tx); if let Ok(checked_tx) = tx_for_exec.transaction_stateless_check() { if checked_tx.execute_check_on_state(&mut exec_state, 1, 0).is_ok() { for acc_id in &affected { diff --git a/fuzz/fuzz_targets/fuzz_stateless_verification.rs b/fuzz/fuzz_targets/fuzz_stateless_verification.rs index 767edf1..830317c 100644 --- a/fuzz/fuzz_targets/fuzz_stateless_verification.rs +++ b/fuzz/fuzz_targets/fuzz_stateless_verification.rs @@ -1,7 +1,7 @@ #![cfg_attr(feature = "fuzzer-libfuzzer", no_main)] use arbitrary::Unstructured; -use common::transaction::NSSATransaction; +use common::transaction::LeeTransaction; use fuzz_props::generators::arbitrary_transaction; fuzz_props::fuzz_entry!(|data: &[u8]| { @@ -22,7 +22,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { } // Path B: raw decode first, then check — must never panic - if let Ok(tx) = borsh::from_slice::(data) { + if let Ok(tx) = borsh::from_slice::(data) { let _ = tx.transaction_stateless_check(); } }); diff --git a/fuzz/fuzz_targets/fuzz_transaction_decoding.rs b/fuzz/fuzz_targets/fuzz_transaction_decoding.rs index 07a4302..bfa5423 100644 --- a/fuzz/fuzz_targets/fuzz_transaction_decoding.rs +++ b/fuzz/fuzz_targets/fuzz_transaction_decoding.rs @@ -2,19 +2,19 @@ use common::{ block::{Block, HashableBlockData}, - transaction::NSSATransaction, + transaction::LeeTransaction, }; fuzz_props::fuzz_entry!(|data: &[u8]| { - // Attempt 1: decode as NSSATransaction and verify roundtrip - if let Ok(tx) = borsh::from_slice::(data) { + // Attempt 1: decode as LeeTransaction and verify roundtrip + if let Ok(tx) = borsh::from_slice::(data) { let re_encoded = borsh::to_vec(&tx).expect("re-encode of valid tx must succeed"); - let tx2 = borsh::from_slice::(&re_encoded) + let tx2 = borsh::from_slice::(&re_encoded) .expect("second decode of re-encoded tx must succeed"); assert_eq!( re_encoded, borsh::to_vec(&tx2).unwrap(), - "NSSATransaction roundtrip encoding divergence" + "LeeTransaction roundtrip encoding divergence" ); } diff --git a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs index 75d8e68..522154d 100644 --- a/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs +++ b/fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs @@ -25,7 +25,7 @@ //! reachable by the fuzzer. use arbitrary::{Arbitrary, Unstructured}; -use fuzz_props::arbitrary_types::ArbNSSATransaction; +use fuzz_props::arbitrary_types::ArbLeeTransaction; use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids}; use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness}; use nssa::V03State; @@ -47,7 +47,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| { .collect(); // Generate the transaction from the remaining fuzz bytes. - let tx = match ArbNSSATransaction::arbitrary(&mut u) { + let tx = match ArbLeeTransaction::arbitrary(&mut u) { Ok(w) => w.0, Err(_) => return, }; diff --git a/fuzz_props/src/arbitrary_types.rs b/fuzz_props/src/arbitrary_types.rs index d920d6c..20ea843 100644 --- a/fuzz_props/src/arbitrary_types.rs +++ b/fuzz_props/src/arbitrary_types.rs @@ -2,7 +2,7 @@ //! //! **No changes to `../logos-execution-zone` are required.** //! -//! The Rust orphan rule forbids `impl Arbitrary for NSSATransaction` when both +//! The Rust orphan rule forbids `impl Arbitrary for LeeTransaction` when both //! the trait and the type come from external crates. Using newtypes (`ArbXxx`) //! sidesteps the restriction entirely. //! @@ -10,10 +10,10 @@ //! //! ```rust,ignore //! #![no_main] -//! use fuzz_props::arbitrary_types::ArbNSSATransaction; +//! use fuzz_props::arbitrary_types::ArbLeeTransaction; //! use libfuzzer_sys::fuzz_target; //! -//! fuzz_target!(|wrapped: ArbNSSATransaction| { +//! fuzz_target!(|wrapped: ArbLeeTransaction| { //! let tx = wrapped.0; //! let Ok(valid_tx) = tx.transaction_stateless_check() else { return; }; //! // … @@ -21,7 +21,7 @@ //! ``` use arbitrary::{Arbitrary, Result as ArbResult, Unstructured}; -use common::{HashType, block::HashableBlockData, transaction::NSSATransaction}; +use common::{HashType, block::HashableBlockData, transaction::LeeTransaction}; use nssa::{ AccountId, PrivateKey, PublicKey, Signature, program_deployment_transaction::ProgramDeploymentTransaction, @@ -210,24 +210,24 @@ impl<'a> Arbitrary<'a> for ArbProgramDeploymentTransaction { } } -// ── NSSATransaction ─────────────────────────────────────────────────────────── +// ── LeeTransaction ─────────────────────────────────────────────────────────── // `PrivacyPreservingTransaction` is intentionally excluded: it embeds a risc0 // ZK receipt that cannot be generated inside a hot fuzzing loop. This matches // the known limitation documented in `docs/fuzzing.md`. -/// Newtype wrapper providing [`Arbitrary`] for [`NSSATransaction`]. +/// Newtype wrapper providing [`Arbitrary`] for [`LeeTransaction`]. /// /// Generates `Public` and `ProgramDeployment` variants only. #[derive(Debug)] -pub struct ArbNSSATransaction(pub NSSATransaction); +pub struct ArbLeeTransaction(pub LeeTransaction); -impl<'a> Arbitrary<'a> for ArbNSSATransaction { +impl<'a> Arbitrary<'a> for ArbLeeTransaction { fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult { match u8::arbitrary(u)? % 2 { - 0 => Ok(Self(NSSATransaction::Public( + 0 => Ok(Self(LeeTransaction::Public( ArbPublicTransaction::arbitrary(u)?.0, ))), - _ => Ok(Self(NSSATransaction::ProgramDeployment( + _ => Ok(Self(LeeTransaction::ProgramDeployment( ArbProgramDeploymentTransaction::arbitrary(u)?.0, ))), } @@ -246,7 +246,7 @@ impl<'a> Arbitrary<'a> for ArbHashableBlockData { fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult { // 0–7 transactions per block let n = (u8::arbitrary(u)? as usize) % 8; - let transactions = std::iter::repeat_with(|| ArbNSSATransaction::arbitrary(u).map(|t| t.0)) + let transactions = std::iter::repeat_with(|| ArbLeeTransaction::arbitrary(u).map(|t| t.0)) .take(n) .collect::>>()?; Ok(Self(HashableBlockData { diff --git a/fuzz_props/src/generators.rs b/fuzz_props/src/generators.rs index 8d75966..d11df5a 100644 --- a/fuzz_props/src/generators.rs +++ b/fuzz_props/src/generators.rs @@ -1,8 +1,8 @@ use arbitrary::{Arbitrary, Unstructured}; -use common::{block::HashableBlockData, transaction::NSSATransaction}; +use common::{block::HashableBlockData, transaction::LeeTransaction}; use nssa::{AccountId, PrivateKey}; -use crate::arbitrary_types::{ArbAccountId, ArbNSSATransaction, ArbPrivateKey}; +use crate::arbitrary_types::{ArbAccountId, ArbLeeTransaction, ArbPrivateKey}; use proptest::prelude::*; use testnet_initial_state::initial_pub_accounts_private_keys; @@ -12,22 +12,22 @@ use testnet_initial_state::initial_pub_accounts_private_keys; /// witness set. Used by fuzz targets that need to verify nonce /// increments after `execute_check_on_state`. #[must_use] -pub fn signer_account_ids(tx: &common::transaction::NSSATransaction) -> Vec { - use common::transaction::NSSATransaction; +pub fn signer_account_ids(tx: &common::transaction::LeeTransaction) -> Vec { + use common::transaction::LeeTransaction; match tx { - NSSATransaction::Public(pt) => pt + LeeTransaction::Public(pt) => pt .witness_set() .signatures_and_public_keys() .iter() .map(|(_, pk)| nssa::AccountId::from(pk)) .collect(), - NSSATransaction::PrivacyPreserving(pt) => pt + LeeTransaction::PrivacyPreserving(pt) => pt .witness_set() .signatures_and_public_keys() .iter() .map(|(_, pk)| nssa::AccountId::from(pk)) .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], + LeeTransaction::ProgramDeployment(_) => vec![], } } @@ -74,7 +74,7 @@ pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result) -> arbitrary::Result, accounts: &[FuzzAccount], -) -> arbitrary::Result { +) -> arbitrary::Result { if accounts.is_empty() { return Err(arbitrary::Error::IncorrectFormat); } @@ -112,9 +112,9 @@ pub fn arb_fuzz_native_transfer( // ── Arbitrary (for libFuzzer targets) ──────────────────────────────────────── -/// Generate a structurally plausible `NSSATransaction` from unstructured bytes. -pub fn arbitrary_transaction(u: &mut Unstructured<'_>) -> arbitrary::Result { - ArbNSSATransaction::arbitrary(u).map(|w| w.0) +/// Generate a structurally plausible `LeeTransaction` from unstructured bytes. +pub fn arbitrary_transaction(u: &mut Unstructured<'_>) -> arbitrary::Result { + ArbLeeTransaction::arbitrary(u).map(|w| w.0) } // ── proptest strategies ─────────────────────────────────────────────────────── @@ -128,7 +128,7 @@ prop_compose! { to_idx in 0..accounts.len(), nonce in 0_u128..1_000_u128, amount in 0_u128..10_000_u128, - ) -> NSSATransaction { + ) -> LeeTransaction { let (from_id, from_key) = &accounts[from_idx]; let (to_id, _) = &accounts[to_idx]; common::test_utils::create_transaction_native_token_transfer( @@ -146,11 +146,11 @@ pub fn test_accounts() -> Vec<(AccountId, PrivateKey)> { .collect() } -/// Strategy: raw bytes that are valid borsh encodings of `NSSATransaction`. +/// Strategy: raw bytes that are valid borsh encodings of `LeeTransaction`. pub fn arb_borsh_transaction_bytes() -> impl Strategy> { any::>().prop_map(|bytes| { // Either pass through raw bytes OR encode a known dummy transaction - if borsh::from_slice::(&bytes).is_ok() { + if borsh::from_slice::(&bytes).is_ok() { bytes } else { borsh::to_vec(&common::test_utils::produce_dummy_empty_transaction()).unwrap() @@ -183,7 +183,7 @@ prop_compose! { phantom_id_bytes in proptest::array::uniform32(0_u8..), amount in (u128::MAX / 2)..u128::MAX, // overflow-inducing amount nonce in 0_u128..10_u128, - ) -> NSSATransaction { + ) -> LeeTransaction { let phantom_id = nssa::AccountId::new(phantom_id_bytes); // Attempt to sign with a key that has no matching on-chain account let signing_key = nssa::PrivateKey::try_new(phantom_id_bytes) @@ -204,11 +204,11 @@ prop_compose! { /// attack candidates) and some are re-ordered permutations of a valid sequence. /// Used in proptest-level tests and as a seed generator for the state-transition /// fuzz target. -pub fn arb_duplicate_tx_sequence() -> impl Strategy> { +pub fn arb_duplicate_tx_sequence() -> impl Strategy> { let accounts = test_accounts(); proptest::collection::vec(arb_native_transfer_tx(accounts), 1..5_usize).prop_flat_map(|txs| { // Build a sequence that: original | duplicates | reversed - let duped: Vec = txs + let duped: Vec = txs .iter() .cloned() .chain(txs.iter().cloned()) // append exact duplicates @@ -225,7 +225,7 @@ pub fn arb_duplicate_tx_sequence() -> impl Strategy /// - self-transfers (sender == recipient), /// - max-nonce wrapping, /// - alternating valid / invalid transactions to test partial-batch isolation. -pub fn arb_pathological_sequence() -> impl Strategy> { +pub fn arb_pathological_sequence() -> impl Strategy> { let accounts = test_accounts(); let n = accounts.len(); proptest::collection::vec((0..n, 0..n, 0_u128..5_u128, any::()), 1..8_usize).prop_map( diff --git a/fuzz_props/src/invariants.rs b/fuzz_props/src/invariants.rs index b874879..d520e30 100644 --- a/fuzz_props/src/invariants.rs +++ b/fuzz_props/src/invariants.rs @@ -1,4 +1,4 @@ -use common::transaction::NSSATransaction; +use common::transaction::LeeTransaction; use nssa::V03State; use nssa_core::account::Nonce; @@ -185,7 +185,7 @@ impl ProtocolInvariant for FailedTxNonceStability { /// # Enforcement /// /// This invariant **cannot** be enforced through [`InvariantCtx`] because the replay -/// check requires re-applying the `NSSATransaction` that `execute_check_on_state` +/// check requires re-applying the `LeeTransaction` that `execute_check_on_state` /// consumes and returns on `Ok`. It is therefore **not registered** in /// [`assert_invariants`]; calling `assert_invariants` alone does **not** cover /// `ReplayRejection`. @@ -235,7 +235,7 @@ pub struct NonceIncrementCorrectness; /// /// # Why a standalone function? /// -/// `execute_check_on_state` consumes the `NSSATransaction` and returns it on `Ok`, +/// `execute_check_on_state` consumes the `LeeTransaction` and returns it on `Ok`, /// so the transaction is not available as a shared reference inside [`InvariantCtx`]. /// This function accepts ownership of the returned transaction and performs the /// replay in-place. @@ -249,7 +249,7 @@ pub struct NonceIncrementCorrectness; /// } /// ``` pub fn assert_replay_rejection( - applied_tx: NSSATransaction, + applied_tx: LeeTransaction, state: &mut V03State, next_block_id: u64, next_timestamp: u64, @@ -270,7 +270,7 @@ pub fn assert_replay_rejection( /// passing the signer IDs derived from the transaction's witness set, the [`NonceSnapshot`] /// captured **before** execution, and the post-execution state. /// -/// For a `NSSATransaction::Public(tx)`, derive signer IDs as: +/// For a `LeeTransaction::Public(tx)`, derive signer IDs as: /// /// ```rust,ignore /// let signer_ids: Vec = tx @@ -281,7 +281,7 @@ pub fn assert_replay_rejection( /// .collect(); /// ``` /// -/// For `NSSATransaction::ProgramDeployment`, there are no signers; pass an empty slice. +/// For `LeeTransaction::ProgramDeployment`, there are no signers; pass an empty slice. /// /// # Why a standalone function? /// @@ -377,7 +377,7 @@ pub fn assert_tx_execution_invariants( state_after: &mut V03State, balances_before: BalanceSnapshot, nonces_before: NonceSnapshot, - execution_result: Result, + execution_result: Result, replay_context: (u64, u64), ) { let execution_succeeded = execution_result.is_ok(); @@ -400,19 +400,19 @@ pub fn assert_tx_execution_invariants( if let Ok(applied_tx) = execution_result { // Derive signer IDs from the witness set. ProgramDeployment has no signers. let signer_ids: Vec = match &applied_tx { - NSSATransaction::Public(pt) => pt + LeeTransaction::Public(pt) => pt .witness_set() .signatures_and_public_keys() .iter() .map(|(_, pk)| nssa::AccountId::from(pk)) .collect(), - NSSATransaction::PrivacyPreserving(pt) => pt + LeeTransaction::PrivacyPreserving(pt) => pt .witness_set() .signatures_and_public_keys() .iter() .map(|(_, pk)| nssa::AccountId::from(pk)) .collect(), - NSSATransaction::ProgramDeployment(_) => vec![], + LeeTransaction::ProgramDeployment(_) => vec![], }; assert_nonce_increment_correctness(&signer_ids, &nonces_for_nonce_check, state_after); let (next_block_id, next_timestamp) = replay_context; From a8d0355b9fcb329371e011f80f295271a5060615 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 18:19:32 +0800 Subject: [PATCH 14/18] fix: address mutants found in harness --- fuzz_props/src/tests.rs | 2 + fuzz_props/src/tests/arbitrary_types_test.rs | 158 ++++++++++++++++ fuzz_props/src/tests/generators_test.rs | 135 ++++++++++++++ fuzz_props/src/tests/invariants.rs | 184 ++++++++++++++++++- 4 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 fuzz_props/src/tests/arbitrary_types_test.rs create mode 100644 fuzz_props/src/tests/generators_test.rs diff --git a/fuzz_props/src/tests.rs b/fuzz_props/src/tests.rs index 759db83..af7a7e6 100644 --- a/fuzz_props/src/tests.rs +++ b/fuzz_props/src/tests.rs @@ -1,3 +1,5 @@ +mod arbitrary_types_test; +mod generators_test; mod invariants; mod replay_proptest; mod seed_gen; diff --git a/fuzz_props/src/tests/arbitrary_types_test.rs b/fuzz_props/src/tests/arbitrary_types_test.rs new file mode 100644 index 0000000..fc8c57a --- /dev/null +++ b/fuzz_props/src/tests/arbitrary_types_test.rs @@ -0,0 +1,158 @@ +//! Tests that detect mutations in `arbitrary_types.rs`. +//! +//! # Design rationale +//! +//! `arbitrary::Unstructured::fill_buffer` reads bytes from the **front** of the buffer +//! and pads with zeros when the buffer is exhausted — it never returns an error. As a +//! result, the total number of items generated by `take(n)` always equals `n` regardless +//! of buffer size. This makes count-based tests the most reliable mutation detectors. +//! +//! For types that expose their length through public APIs we check the count directly. +//! For `ArbPubTxMessage`, whose inner [`nssa::public_transaction::Message`] is opaque, +//! we use the borsh-serialised size of a wrapping [`LeeTransaction::Public`] as a proxy. + +use crate::arbitrary_types::{ + ArbHashableBlockData, ArbLeeTransaction, ArbPubTxMessage, ArbPublicTransaction, ArbWitnessSet, +}; +use arbitrary::{Arbitrary, Unstructured}; +use common::transaction::LeeTransaction; + +#[test] +fn arb_lee_transaction_zero_byte_selects_public() { + // fill_buffer reads from the front, so the first byte consumed = 0. + let buf = vec![0_u8; 4096]; + let mut u = Unstructured::new(&buf); + let arb = ArbLeeTransaction::arbitrary(&mut u).expect("should succeed"); + assert!( + matches!(arb.0, LeeTransaction::Public(_)), + "expected Public variant: with first byte=0 and `% 2`, arm 0 (Public) is selected" + ); +} + +#[test] +fn arb_lee_transaction_byte4_selects_public() { + // Place 4 as the first byte (variant selector); rest are zeros. + let mut buf = vec![0_u8; 4096]; + buf[0] = 4; + let mut u = Unstructured::new(&buf); + let arb = ArbLeeTransaction::arbitrary(&mut u).expect("should succeed"); + assert!( + matches!(arb.0, LeeTransaction::Public(_)), + "expected Public variant: `4 % 2 = 0` \u{2192} arm 0; \ + mutant `4 / 2 = 2` or `4 + 2 = 6` maps to `_` \u{2192} ProgramDeployment" + ); +} + +/// Generates from all-1 bytes: `1 % 2 = 1` -> `_` -> `ProgramDeployment`. +#[test] +fn arb_lee_transaction_one_byte_selects_program_deployment() { + let buf = vec![1_u8; 4096]; + let mut u = Unstructured::new(&buf); + let arb = ArbLeeTransaction::arbitrary(&mut u).expect("should succeed"); + assert!( + matches!(arb.0, LeeTransaction::ProgramDeployment(_)), + "expected ProgramDeployment variant with first byte=1" + ); +} + +/// Generates `ArbHashableBlockData` from all-255 bytes and asserts `transactions.len() <= 7`. +#[test] +fn arb_hashable_block_data_tx_count_bounded() { + let buf = vec![255_u8; 50_000]; + let mut u = Unstructured::new(&buf); + let arb = ArbHashableBlockData::arbitrary(&mut u) + .expect("ArbHashableBlockData::arbitrary should succeed with a large all-255 buffer"); + assert!( + arb.0.transactions.len() <= 7, + "expected at most 7 transactions (% 8), got {} \ + (mutation: % replaced by / or + on line 248 of arbitrary_types.rs)", + arb.0.transactions.len() + ); +} + +/// Generates `ArbWitnessSet` from all-255 bytes and asserts pair count <= 3. +#[test] +fn arb_witness_set_pair_count_bounded() { + let buf = vec![255_u8; 50_000]; + let mut u = Unstructured::new(&buf); + let arb = ArbWitnessSet::arbitrary(&mut u) + .expect("ArbWitnessSet::arbitrary should succeed with a large all-255 buffer"); + let pair_count = arb.0.signatures_and_public_keys().len(); + assert!( + pair_count <= 3, + "expected at most 3 witness pairs (% 4), got {pair_count} \ + (mutation: % replaced by / or + on line 173 of arbitrary_types.rs)" + ); +} + +/// Checks the borsh-encoded size of a `LeeTransaction::Public` wrapping an +/// `ArbPubTxMessage` generated from a buffer where the len-selector byte = 255. +#[test] +fn arb_pub_tx_message_account_count_bounded_via_borsh() { + // Bytes 0-31: zeros for program_id ([u32; 8] via fill_buffer reads 32 bytes). + // Byte 32: 255 — this is the len-selector byte. 255 % 8 = 7 (correct) vs 255 / 8 = 31 (mutant). + // Bytes 33+: zeros so Vec (instruction_data) produces 0 elements (last byte = 0). + let mut buf = vec![0_u8; 2000]; + buf[32] = 255; + + let mut u = Unstructured::new(&buf); + let msg = + ArbPubTxMessage::arbitrary(&mut u).expect("ArbPubTxMessage::arbitrary should succeed"); + + // Wrap in a real PublicTransaction to enable borsh serialisation. + let mut u_witness = Unstructured::new(&[0_u8; 10]); + let witness = ArbWitnessSet::arbitrary(&mut u_witness) + .expect("ArbWitnessSet::arbitrary should succeed with zero bytes (n=0)"); + + let tx = LeeTransaction::Public(nssa::public_transaction::PublicTransaction::new( + msg.0, witness.0, + )); + let borsh_bytes = borsh::to_vec(&tx).expect("borsh serialization should succeed"); + + // With 7 accounts the message borsh-encodes to ~380 bytes; the whole transaction to ~400 bytes. + // With 31 accounts the message encodes to ~1540 bytes. + // Using 800 as a conservative threshold clearly separates the two cases. + assert!( + borsh_bytes.len() < 800, + "borsh-encoded size {} bytes exceeds threshold: too many accounts in message \ + (% 8 may have been replaced with / 8 or + 8 on line 144 of arbitrary_types.rs)", + borsh_bytes.len() + ); +} + +/// Additional check: with an all-zero buffer the `ArbPubTxMessage` generates a +/// message with 0 accounts (`0 % 8 = 0`). This verifies the zero case. +#[test] +fn arb_pub_tx_message_zero_accounts_with_zero_selector() { + // All zeros: program_id = all 0, len selector byte = 0. + // 0 % 8 = 0 (correct), 0 + 8 = 8 (+ mutant). + let buf = vec![0_u8; 500]; + let mut u = Unstructured::new(&buf); + let msg = + ArbPubTxMessage::arbitrary(&mut u).expect("ArbPubTxMessage::arbitrary should succeed"); + + let mut u_witness = Unstructured::new(&[0_u8; 10]); + let witness = ArbWitnessSet::arbitrary(&mut u_witness).expect("witness should succeed"); + + let tx = LeeTransaction::Public(nssa::public_transaction::PublicTransaction::new( + msg.0, witness.0, + )); + let borsh_bytes = borsh::to_vec(&tx).expect("borsh serialization should succeed"); + + // With 0 accounts borsh size is minimal (~50 bytes for empty message + envelope). + // With 8 accounts (+ mutant) borsh size > 400 bytes. + assert!( + borsh_bytes.len() < 300, + "borsh-encoded size {} bytes too large for zero-account message \ + (% 8 may have been replaced with + 8 on line 144 of arbitrary_types.rs)", + borsh_bytes.len() + ); +} + +/// Verifies that `ArbPublicTransaction::arbitrary` completes without error. +#[test] +fn arb_public_transaction_smoke() { + let buf = vec![0_u8; 4096]; + let mut u = Unstructured::new(&buf); + let _ = ArbPublicTransaction::arbitrary(&mut u).expect("should succeed"); +} diff --git a/fuzz_props/src/tests/generators_test.rs b/fuzz_props/src/tests/generators_test.rs new file mode 100644 index 0000000..44c7b13 --- /dev/null +++ b/fuzz_props/src/tests/generators_test.rs @@ -0,0 +1,135 @@ +//! Tests that detect mutations in `generators.rs`. + +use arbitrary::Unstructured; +use nssa::{AccountId, PrivateKey}; + +use crate::generators::{ + FuzzAccount, arb_fuzz_native_transfer, arbitrary_fuzz_state, signer_account_ids, test_accounts, +}; + +/// Verifies that `signer_account_ids` returns a **non-empty** list for a properly signed +/// public transaction. +#[test] +fn signer_ids_nonempty_for_signed_public_tx() { + let accounts = test_accounts(); + let (from_id, from_key) = &accounts[0]; + let (to_id, _) = &accounts[1]; + + let tx = common::test_utils::create_transaction_native_token_transfer( + *from_id, 0, // nonce 0 — genesis nonce for the account + *to_id, 100, from_key, + ); + + let ids = signer_account_ids(&tx); + assert!( + !ids.is_empty(), + "signer_account_ids must return at least one ID for a signed public transaction \ + (mutation: function body replaced with vec![])" + ); +} + +/// Verifies that the returned signer ID matches the account that actually signed the +/// transaction — not a default/zeroed account ID. +#[test] +fn signer_ids_contains_the_signing_account() { + let accounts = test_accounts(); + let (from_id, from_key) = &accounts[0]; + let (to_id, _) = &accounts[1]; + + let tx = common::test_utils::create_transaction_native_token_transfer( + *from_id, 0, *to_id, 100, from_key, + ); + + let ids = signer_account_ids(&tx); + assert!( + ids.contains(from_id), + "signer_account_ids must contain the account ID of the private key that signed \ + the transaction; got {ids:?} but expected it to contain {from_id:?}" + ); +} + +#[test] +fn fuzz_state_never_empty() { + let buf = vec![0_u8; 1000]; + let mut u = Unstructured::new(&buf); + let accounts = arbitrary_fuzz_state(&mut u).expect("should succeed"); + assert!( + !accounts.is_empty(), + "arbitrary_fuzz_state must return at least 1 account (n = 1..=8); \ + returned 0 \u{2014} mutation: `+ 1` replaced by `* 1` or `Ok(vec![])`" + ); +} + +#[test] +fn fuzz_state_count_uses_modulo_not_div_or_add() { + // fill_buffer reads from the front; the first byte is the n-selector. + let mut buf = vec![0_u8; 1000]; + buf[0] = 8; // selector byte: 8 % 8 = 0, +1 -> n=1 | 8 / 8 = 1, +1 -> n=2 | 8 + 8 = 16, +1 -> n=17 + let mut u = Unstructured::new(&buf); + let accounts = arbitrary_fuzz_state(&mut u).expect("should succeed"); + assert_eq!( + accounts.len(), + 1, + "with selector byte=8: (8 % 8) + 1 = 1 account; \ + mutation `% \u{2192} /` gives (8/8)+1=2; mutation `% \u{2192} +` gives (8+8)+1=17" + ); +} + +/// Verifies that each account's balance is <= `u128::MAX / 8`. +#[test] +fn fuzz_state_balances_bounded_by_max_div_8() { + let buf = vec![255_u8; 10_000]; + let mut u = Unstructured::new(&buf); + // With correct division, this must NOT overflow (no panic). + let accounts = arbitrary_fuzz_state(&mut u) + .expect("should succeed \u{2014} no overflow with correct / 8 implementation"); + + let max_balance = u128::MAX / 8; + for acc in &accounts { + assert!( + acc.balance <= max_balance, + "account balance {} exceeds u128::MAX/8={} \u{2014} \ + mutation: `/ 8` replaced by `* 8` (overflow) or `% 8`", + acc.balance, + max_balance + ); + } + + // Ensures the `% 8` mutation is caught: with u128::MAX bytes, correct `/` gives a + // large balance (u128::MAX/8 ~= 3.4e37), while `%` gives only 0-7. + let has_large_balance = accounts.iter().any(|a| a.balance > 7); + assert!( + has_large_balance, + "expected at least one account with balance > 7 \u{2014} \ + mutation: `/ 8` replaced by `% 8` (balance capped at 7)" + ); +} + +#[test] +fn native_transfer_index_uses_modulo_not_div_add() { + let accounts = vec![ + FuzzAccount { + account_id: AccountId::new([1_u8; 32]), + balance: 1_000_000, + private_key: PrivateKey::try_new([1_u8; 32]).expect("scalar 1 is a valid private key"), + }, + FuzzAccount { + account_id: AccountId::new([2_u8; 32]), + balance: 1_000_000, + private_key: PrivateKey::try_new([2_u8; 32]).expect("scalar 2 is a valid private key"), + }, + ]; + + // All-0xFF bytes: the from_idx byte = 255, to_idx byte = 255. + // 255 % 2 = 1 (in-bounds), 255 / 2 = 127 (out-of-bounds), 255 + 2 = 257 (out-of-bounds). + let buf = vec![0xFF_u8; 500]; + let mut u = Unstructured::new(&buf); + + // With the mutated `/ 2` or `+ 2`, `accounts[127]` or `accounts[257]` panics. + let result = arb_fuzz_native_transfer(&mut u, &accounts); + assert!( + result.is_ok(), + "arb_fuzz_native_transfer should succeed with valid modulo-bounded indices; \ + mutation: `% accounts.len()` replaced by `/ accounts.len()` or `+ accounts.len()`" + ); +} diff --git a/fuzz_props/src/tests/invariants.rs b/fuzz_props/src/tests/invariants.rs index 178b934..1d799ab 100644 --- a/fuzz_props/src/tests/invariants.rs +++ b/fuzz_props/src/tests/invariants.rs @@ -1,7 +1,10 @@ +use crate::generators::test_accounts; use crate::invariants::{ - BalanceSnapshot, InvariantCtx, NonceSnapshot, assert_invariants, - assert_nonce_increment_correctness, + BalanceConservation, BalanceSnapshot, FailedTxNonceStability, InvariantCtx, NonceSnapshot, + ProtocolInvariant, StateIsolationOnFailure, assert_invariants, + assert_nonce_increment_correctness, assert_replay_rejection, assert_tx_execution_invariants, }; +use common::transaction::LeeTransaction; use nssa::V03State; use nssa_core::account::Nonce; @@ -117,3 +120,180 @@ fn failed_tx_nonce_stability_catches_nonce_mutation() { "expected panic for nonce mutation on failure" ); } + +/// Verifies that `BalanceSnapshot::total` returns the correct arithmetical sum. +#[test] +fn balance_snapshot_total_is_correct_sum() { + let mut map = std::collections::HashMap::new(); + map.insert(nssa::AccountId::new([1_u8; 32]), 100_u128); + map.insert(nssa::AccountId::new([2_u8; 32]), 200_u128); + map.insert(nssa::AccountId::new([3_u8; 32]), 700_u128); + let snap = BalanceSnapshot(map); + assert_eq!( + snap.total(), + 1000, + "BalanceSnapshot::total must sum all balances" + ); +} + +/// Ensures `total()` is non-zero when accounts have positive balances. +/// +/// Together with `balance_snapshot_total_is_correct_sum`, this forms a pair that +/// catches the `replace total with 0` mutation even when the expected sum is zero +/// in other tests. +#[test] +fn balance_snapshot_total_nonzero_for_positive_balances() { + let mut map = std::collections::HashMap::new(); + map.insert(nssa::AccountId::new([42_u8; 32]), 1_u128); + let snap = BalanceSnapshot(map); + assert_ne!( + snap.total(), + 0, + "BalanceSnapshot::total must not return 0 when accounts have positive balances \ + (mutation: replaced with literal 0)" + ); +} + +/// Verifies that `StateIsolationOnFailure::name` returns a non-empty, non-"xyzzy" string. +#[test] +fn state_isolation_name_is_nonempty_and_not_placeholder() { + let inv = StateIsolationOnFailure; + let name = inv.name(); + assert!( + !name.is_empty(), + "StateIsolationOnFailure::name must not be empty" + ); + assert_ne!( + name, "xyzzy", + "StateIsolationOnFailure::name must not be 'xyzzy'" + ); + assert_eq!(name, "StateIsolationOnFailure"); +} + +/// Verifies that `BalanceConservation::name` returns a non-empty, non-"xyzzy" string. +#[test] +fn balance_conservation_name_is_nonempty_and_not_placeholder() { + let inv = BalanceConservation; + let name = inv.name(); + assert!( + !name.is_empty(), + "BalanceConservation::name must not be empty" + ); + assert_ne!( + name, "xyzzy", + "BalanceConservation::name must not be 'xyzzy'" + ); + assert_eq!(name, "BalanceConservation"); +} + +/// Verifies that `FailedTxNonceStability::name` returns a non-empty, non-"xyzzy" string. +#[test] +fn failed_tx_nonce_stability_name_is_nonempty_and_not_placeholder() { + let inv = FailedTxNonceStability; + let name = inv.name(); + assert!( + !name.is_empty(), + "FailedTxNonceStability::name must not be empty" + ); + assert_ne!( + name, "xyzzy", + "FailedTxNonceStability::name must not be 'xyzzy'" + ); + assert_eq!(name, "FailedTxNonceStability"); +} + +/// Verifies that `StateIsolationOnFailure::check` returns `Some` when execution failed and +/// the balance in `state_after` differs from `balances_before`. +#[test] +fn state_isolation_check_detects_balance_change_on_failure() { + let acc_id = nssa::AccountId::new([1_u8; 32]); + // State has balance 100 for acc_id. + let state = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + + // balances_before claims balance was 50, but state_after (== state) has 100. + let mut balances = std::collections::HashMap::new(); + balances.insert(acc_id, 50_u128); + + let ctx = InvariantCtx { + state_before: &state, + state_after: &state, + execution_succeeded: false, // failure → isolation invariant is active + balances_before: BalanceSnapshot(balances), + nonces_before: make_empty_nonce_snapshot(), + }; + + let inv = StateIsolationOnFailure; + let result = inv.check(&ctx); + assert!( + result.is_some(), + "StateIsolationOnFailure::check must return Some violation when \ + state_after balance (100) differs from balances_before (50) on a failed tx \ + (mutations: replace with None; delete !; replace != with ==)" + ); +} + +/// Verifies that `assert_replay_rejection` panics when the replayed transaction is +/// accepted (i.e. NOT rejected — a genuine invariant violation). +#[test] +fn assert_replay_rejection_panics_when_replay_not_rejected() { + let accounts = test_accounts(); + let (from_id, from_key) = &accounts[0]; + let (to_id, _) = &accounts[1]; + + // Build a state that contains the sender account with nonce 0 and sufficient balance. + let genesis: Vec<(nssa::AccountId, u128)> = accounts + .iter() + .map(|(id, _)| (*id, 10_000_000_u128)) + .collect(); + let mut state = V03State::new_with_genesis_accounts(&genesis, vec![], 0); + + // Create a valid, signed transaction with nonce 0 (the initial nonce in state). + let tx = common::test_utils::create_transaction_native_token_transfer( + *from_id, 0, *to_id, 100, from_key, + ); + + // We do NOT apply the tx first. The state nonce is still 0, so calling + // execute_check_on_state would SUCCEED — making this a "successful replay". + // assert_replay_rejection is supposed to panic here (INVARIANT VIOLATION [ReplayRejection]). + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + assert_replay_rejection(tx, &mut state, 0, 0); + })); + + assert!( + result.is_err(), + "assert_replay_rejection must panic when the replayed tx is accepted \ + (mutation: replace function body with () \u{2014} no-op skips the check)" + ); +} + +/// Verifies that `assert_tx_execution_invariants` is NOT a no-op by providing a +/// context that violates `StateIsolationOnFailure` and expecting a panic. +#[test] +fn assert_tx_execution_invariants_is_not_noop() { + let acc_id = nssa::AccountId::new([5_u8; 32]); + // Both state_before and state_after have the account at balance 100. + let state_before = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + let mut state_after = V03State::new_with_genesis_accounts(&[(acc_id, 100)], vec![], 0); + + // Lie: claim balance was 50 before. State_after shows 100. + // With execution_succeeded=false, StateIsolationOnFailure detects the discrepancy. + let mut balances = std::collections::HashMap::new(); + balances.insert(acc_id, 50_u128); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + assert_tx_execution_invariants( + &state_before, + &mut state_after, + BalanceSnapshot(balances), + make_empty_nonce_snapshot(), + Err::("simulated failure"), + (1, 1), + ); + })); + + assert!( + result.is_err(), + "assert_tx_execution_invariants must panic on a StateIsolationOnFailure violation \ + (mutation: replace entire function body with () \u{2014} no-op skips all invariant checks)" + ); +} From fd95df7c6f59bdb38b5734ae658e043f596cd20b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Jun 2026 18:55:53 +0800 Subject: [PATCH 15/18] fix: test in harness --- fuzz_props/src/tests/invariants.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fuzz_props/src/tests/invariants.rs b/fuzz_props/src/tests/invariants.rs index 1d799ab..18f79e6 100644 --- a/fuzz_props/src/tests/invariants.rs +++ b/fuzz_props/src/tests/invariants.rs @@ -255,8 +255,10 @@ fn assert_replay_rejection_panics_when_replay_not_rejected() { // We do NOT apply the tx first. The state nonce is still 0, so calling // execute_check_on_state would SUCCEED — making this a "successful replay". // assert_replay_rejection is supposed to panic here (INVARIANT VIOLATION [ReplayRejection]). + // block_id=0 is the genesis block; transactions are only valid from block_id=1 onwards, + // so use (1, 0) to ensure execute_check_on_state accepts the tx (triggering the panic). let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - assert_replay_rejection(tx, &mut state, 0, 0); + assert_replay_rejection(tx, &mut state, 1, 0); })); assert!( From 1bb51acd8796d31833ffbd2ed106a5bb6c8ea893 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 6 Jun 2026 00:31:48 +0800 Subject: [PATCH 16/18] fix: make the test platform neutral --- fuzz_props/src/tests/invariants.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/fuzz_props/src/tests/invariants.rs b/fuzz_props/src/tests/invariants.rs index 18f79e6..0d62fb3 100644 --- a/fuzz_props/src/tests/invariants.rs +++ b/fuzz_props/src/tests/invariants.rs @@ -240,25 +240,28 @@ fn assert_replay_rejection_panics_when_replay_not_rejected() { let (from_id, from_key) = &accounts[0]; let (to_id, _) = &accounts[1]; - // Build a state that contains the sender account with nonce 0 and sufficient balance. let genesis: Vec<(nssa::AccountId, u128)> = accounts .iter() .map(|(id, _)| (*id, 10_000_000_u128)) .collect(); - let mut state = V03State::new_with_genesis_accounts(&genesis, vec![], 0); - // Create a valid, signed transaction with nonce 0 (the initial nonce in state). let tx = common::test_utils::create_transaction_native_token_transfer( *from_id, 0, *to_id, 100, from_key, ); + let validated = tx + .transaction_stateless_check() + .expect("test setup: transaction must pass stateless validation"); + let mut scratch_state = V03State::new_with_genesis_accounts(&genesis, vec![], 0); + let applied_tx = validated + .execute_check_on_state(&mut scratch_state, 1, 1) + .expect("test setup: first execution must succeed (block_id=1, timestamp=1)"); - // We do NOT apply the tx first. The state nonce is still 0, so calling - // execute_check_on_state would SUCCEED — making this a "successful replay". - // assert_replay_rejection is supposed to panic here (INVARIANT VIOLATION [ReplayRejection]). - // block_id=0 is the genesis block; transactions are only valid from block_id=1 onwards, - // so use (1, 0) to ensure execute_check_on_state accepts the tx (triggering the panic). + // Replay `applied_tx` (nonce 0) against a FRESH state still at nonce 0. + // The nonce matches → execute_check_on_state ACCEPTS the replay — a protocol + // violation that assert_replay_rejection must detect and panic on. + let mut fresh_state = V03State::new_with_genesis_accounts(&genesis, vec![], 0); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - assert_replay_rejection(tx, &mut state, 1, 0); + assert_replay_rejection(applied_tx, &mut fresh_state, 1, 1); })); assert!( From 2b951f733b5683abda19c5c8b2841f2bc0917f73 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 6 Jun 2026 00:50:52 +0800 Subject: [PATCH 17/18] fix: add missing feature --- Cargo.lock | 790 ++++++++++++++++++++++++++++++++++++++++-- fuzz_props/Cargo.toml | 1 + 2 files changed, 769 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51a670b..899caad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,33 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "cpp_demangle", + "fallible-iterator", + "gimli", + "memmap2", + "object", + "rustc-demangle", + "smallvec", + "typed-arena", +] + [[package]] name = "adler2" version = "2.0.1" @@ -209,7 +236,7 @@ dependencies = [ "fnv", "hashbrown 0.15.5", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "zeroize", @@ -228,7 +255,7 @@ dependencies = [ "derivative", "digest 0.10.7", "itertools 0.10.5", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "rustc_version", @@ -249,7 +276,7 @@ dependencies = [ "digest 0.10.7", "educe", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "zeroize", @@ -281,7 +308,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -294,7 +321,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -370,7 +397,7 @@ dependencies = [ "ark-relations 0.5.1", "ark-std 0.5.0", "educe", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "tracing", @@ -409,7 +436,7 @@ dependencies = [ "ark-serialize-derive 0.4.2", "ark-std 0.4.0", "digest 0.10.7", - "num-bigint", + "num-bigint 0.4.6", ] [[package]] @@ -422,7 +449,7 @@ dependencies = [ "ark-std 0.5.0", "arrayvec", "digest 0.10.7", - "num-bigint", + "num-bigint 0.4.6", ] [[package]] @@ -651,6 +678,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -852,6 +888,18 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1015,6 +1063,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -1201,6 +1251,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1228,6 +1287,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1237,6 +1302,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + [[package]] name = "crossbeam-epoch" version = "0.9.18" @@ -1463,7 +1538,7 @@ dependencies = [ "asn1-rs", "displaydoc", "nom 7.1.3", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "rusticata-macros", ] @@ -1575,6 +1650,15 @@ dependencies = [ "crypto-common 0.2.1", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1630,6 +1714,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "downloader" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac1e888d6830712d565b2f3a974be3200be9296bc1b03db8251a4cbf18a4a34" +dependencies = [ + "digest 0.10.7", + "futures", + "rand 0.8.5", + "reqwest", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "dtoa" version = "1.0.11" @@ -1761,6 +1859,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "enum-ordinalize" version = "4.3.2" @@ -1787,6 +1905,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + [[package]] name = "errno" version = "0.3.14" @@ -1818,6 +1947,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.4.1" @@ -1849,10 +1984,28 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ + "bitvec", + "byteorder", + "ff_derive", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1933,6 +2086,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.32" @@ -2071,6 +2230,30 @@ dependencies = [ "testnet_initial_state", ] +[[package]] +name = "gdbstub" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "log", + "managed", + "num-traits", + "pastey", +] + +[[package]] +name = "gdbstub_arch" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c02bfe7bd65f42bcda751456869dfa1eb2bd1c36e309b9ec27f4888d41cf258" +dependencies = [ + "gdbstub", + "num-traits", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2144,6 +2327,23 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "indexmap 2.14.0", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "group" version = "0.13.0" @@ -2174,6 +2374,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2233,6 +2442,20 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -2761,6 +2984,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + [[package]] name = "ipconfig" version = "0.3.4" @@ -2799,6 +3031,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2848,6 +3089,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.95" @@ -3008,6 +3259,26 @@ version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "liblzma" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a60851d15cd8c5346eca4ab8babff585be2ae4bc8097c067291d3ffe2add3b6" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.16" @@ -3428,7 +3699,7 @@ checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" dependencies = [ "ark-bn254 0.5.0", "ark-ff 0.5.0", - "num-bigint", + "num-bigint 0.4.6", "thiserror 1.0.69", ] @@ -3511,7 +3782,7 @@ dependencies = [ "logos-blockchain-poq", "logos-blockchain-poseidon2", "logos-blockchain-utils", - "num-bigint", + "num-bigint 0.4.6", "serde", "thiserror 2.0.18", "zeroize", @@ -3697,7 +3968,7 @@ dependencies = [ "logos-blockchain-utxotree", "multiaddr", "nom 8.0.0", - "num-bigint", + "num-bigint 0.4.6", "rpds", "serde", "strum", @@ -3752,7 +4023,7 @@ dependencies = [ "ark-serialize 0.4.2", "generic-array 1.3.5", "hex", - "num-bigint", + "num-bigint 0.4.6", "serde", "serde_json", "thiserror 2.0.18", @@ -3793,7 +4064,7 @@ dependencies = [ "logos-blockchain-poseidon2", "logos-blockchain-utils", "logos-blockchain-zksign", - "num-bigint", + "num-bigint 0.4.6", "rand_core 0.6.4", "serde", "subtle", @@ -3831,7 +4102,7 @@ dependencies = [ "logos-blockchain-pol", "logos-blockchain-utils", "logos-blockchain-utxotree", - "num-bigint", + "num-bigint 0.4.6", "rand 0.8.5", "rpds", "serde", @@ -3932,7 +4203,7 @@ dependencies = [ "logos-blockchain-circuits-utils", "logos-blockchain-groth16", "logos-blockchain-proofs-error", - "num-bigint", + "num-bigint 0.4.6", "serde", "serde_json", "tracing", @@ -3951,7 +4222,7 @@ dependencies = [ "logos-blockchain-groth16", "logos-blockchain-proofs-error", "logos-blockchain-utils", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "serde", "serde_json", @@ -3970,7 +4241,7 @@ dependencies = [ "logos-blockchain-groth16", "logos-blockchain-pol", "logos-blockchain-proofs-error", - "num-bigint", + "num-bigint 0.4.6", "serde", "serde_json", "thiserror 2.0.18", @@ -3985,7 +4256,7 @@ dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", "jf-poseidon2", - "num-bigint", + "num-bigint 0.4.6", ] [[package]] @@ -4109,7 +4380,7 @@ dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", "logos-blockchain-poseidon2", - "num-bigint", + "num-bigint 0.4.6", "rpds", "serde", "thiserror 2.0.18", @@ -4127,7 +4398,7 @@ dependencies = [ "logos-blockchain-groth16", "logos-blockchain-poseidon2", "logos-blockchain-proofs-error", - "num-bigint", + "num-bigint 0.4.6", "serde", "serde_json", "thiserror 2.0.18", @@ -4159,6 +4430,64 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "malachite" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbdf9cb251732db30a7200ebb6ae5d22fe8e11397364416617d2c2cf0c51cb5" +dependencies = [ + "malachite-base", + "malachite-float", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +dependencies = [ + "hashbrown 0.14.5", + "itertools 0.11.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-float" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9d20db1c73759c1377db7b27575df6f2eab7368809dd62c0a715dc1bcc39f7" +dependencies = [ + "itertools 0.11.0", + "malachite-base", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-nz" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" +dependencies = [ + "itertools 0.11.0", + "libm", + "malachite-base", +] + +[[package]] +name = "malachite-q" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f235d5747b1256b47620f5640c2a17a88c7569eebdf27cd9cb130e1a619191" +dependencies = [ + "itertools 0.11.0", + "malachite-base", + "malachite-nz", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -4168,6 +4497,12 @@ dependencies = [ "libc", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "match-lookup" version = "0.1.2" @@ -4200,12 +4535,31 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "merlin" version = "3.0.0" @@ -4376,6 +4730,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", + "rayon", +] + [[package]] name = "netdev" version = "0.31.0" @@ -4542,6 +4912,17 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4568,12 +4949,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -4625,6 +5026,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "nvtx" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2e855e8019f99e4b94ac33670eb4e4f570a2e044f3749a0b2c7f83b841e52c" +dependencies = [ + "cc", +] + [[package]] name = "objc" version = "0.2.7" @@ -4634,6 +5044,17 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -4815,6 +5236,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + [[package]] name = "pem" version = "3.0.6" @@ -4897,6 +5324,12 @@ dependencies = [ "spki 0.8.0", ] +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + [[package]] name = "polling" version = "3.11.0" @@ -4929,6 +5362,15 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postcard" version = "1.1.3" @@ -4938,6 +5380,7 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", + "heapless", "serde", ] @@ -5136,6 +5579,20 @@ dependencies = [ "prost 0.13.5", ] +[[package]] +name = "puffin" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9dae7b05c02ec1a6bc9bcf20d8bc64a7dcbf57934107902a872014899b741f" +dependencies = [ + "anyhow", + "byteorder", + "cfg-if", + "itertools 0.10.5", + "once_cell", + "parking_lot", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -5241,6 +5698,12 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -5315,6 +5778,32 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rcgen" version = "0.13.2" @@ -5469,6 +5958,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuffer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" + [[package]] name = "risc0-binfmt" version = "3.0.4" @@ -5515,6 +6010,20 @@ dependencies = [ "tempfile", ] +[[package]] +name = "risc0-build-kernel" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaaa3e04c71e4244354dd9e3f8b89378cfecfbb03f9c72de4e2e7e0482b30c9a" +dependencies = [ + "cc", + "directories", + "hex", + "rayon", + "sha2", + "tempfile", +] + [[package]] name = "risc0-circuit-keccak" version = "4.0.5" @@ -5523,14 +6032,34 @@ checksum = "5f543c60287fece797a5da4209384ab1bfebd9644fcfe591e11b1aa85f1a02f8" dependencies = [ "anyhow", "bytemuck", + "cfg-if", + "keccak 0.1.6", + "liblzma", "paste", + "rayon", "risc0-binfmt", + "risc0-circuit-keccak-sys", "risc0-circuit-recursion", "risc0-core", + "risc0-sys", "risc0-zkp", "tracing", ] +[[package]] +name = "risc0-circuit-keccak-sys" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eae53a7bf1c09828dfd46ed5c942cefbf4bef3c4400f6758001569a834c462" +dependencies = [ + "cc", + "derive_more", + "glob", + "risc0-build-kernel", + "risc0-core", + "risc0-sys", +] + [[package]] name = "risc0-circuit-recursion" version = "4.0.4" @@ -5539,11 +6068,33 @@ checksum = "2347e909c6b2a65584b5898f3802eec5b8c1b4b45329edfdd8587b6a04dd3357" dependencies = [ "anyhow", "bytemuck", + "cfg-if", + "downloader", "hex", + "lazy-regex", "metal", + "rand 0.9.3", + "rayon", + "risc0-circuit-recursion-sys", "risc0-core", + "risc0-sys", "risc0-zkp", + "serde", + "sha2", "tracing", + "zip", +] + +[[package]] +name = "risc0-circuit-recursion-sys" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132be7ccca4b39ec957cc37d5083ca2aee171407922352002c9812452a890b39" +dependencies = [ + "glob", + "risc0-build-kernel", + "risc0-core", + "risc0-sys", ] [[package]] @@ -5555,13 +6106,43 @@ dependencies = [ "anyhow", "bit-vec", "bytemuck", + "byteorder", + "cfg-if", "derive_more", + "enum-map", + "gdbstub", + "gdbstub_arch", + "malachite", + "num-derive", + "num-traits", "paste", + "postcard", + "rand 0.9.3", + "rayon", + "ringbuffer", "risc0-binfmt", + "risc0-circuit-rv32im-sys", "risc0-core", + "risc0-sys", "risc0-zkp", "serde", + "smallvec", "tracing", + "wide", +] + +[[package]] +name = "risc0-circuit-rv32im-sys" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d677ec41e475534e18e58889ef0626dcdabf5e918804ef847da0c0bbf300b3" +dependencies = [ + "cc", + "derive_more", + "glob", + "risc0-build-kernel", + "risc0-core", + "risc0-sys", ] [[package]] @@ -5571,6 +6152,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b956a976b8ce4713694dcc6c370b522a42ccef4ba45da5b6e57dbf26cdb7b1" dependencies = [ "bytemuck", + "nvtx", + "puffin", "rand_core 0.9.5", ] @@ -5587,12 +6170,28 @@ dependencies = [ "ark-groth16 0.5.0", "ark-serialize 0.5.0", "bytemuck", + "cfg-if", "hex", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "risc0-binfmt", + "risc0-core", "risc0-zkp", + "rzup", "serde", + "serde_json", + "tempfile", + "tracing", +] + +[[package]] +name = "risc0-sys" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960c8295fbb87e1e73e332f8f7de2fba0252377575042d9d3e9a4eb50a38e078" +dependencies = [ + "anyhow", + "risc0-build-kernel", ] [[package]] @@ -5618,12 +6217,18 @@ dependencies = [ "bytemuck", "cfg-if", "digest 0.10.7", + "ff", "hex", "hex-literal", "metal", + "ndarray", + "parking_lot", "paste", + "rand 0.9.3", "rand_core 0.9.5", + "rayon", "risc0-core", + "risc0-sys", "risc0-zkvm-platform", "serde", "sha2", @@ -5637,15 +6242,27 @@ version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b7eafb5d85be59cbd9da83f662cf47d834f1b836e14f675d1530b12c666867" dependencies = [ + "addr2line", "anyhow", "bincode", "borsh", "bytemuck", "bytes", "derive_more", + "elf", + "enum-map", + "gdbstub", + "gdbstub_arch", + "gimli", "hex", + "keccak 0.1.6", "lazy-regex", + "num-bigint 0.4.6", + "num-traits", + "object", "prost 0.13.5", + "rand 0.9.3", + "rayon", "risc0-binfmt", "risc0-build", "risc0-circuit-keccak", @@ -5657,6 +6274,7 @@ dependencies = [ "risc0-zkp", "risc0-zkvm-platform", "rrs-lib", + "rustc-demangle", "rzup", "semver", "serde", @@ -5664,6 +6282,7 @@ dependencies = [ "stability", "tempfile", "tracing", + "typetag", ] [[package]] @@ -5781,6 +6400,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.2" @@ -5872,6 +6497,15 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -5908,6 +6542,15 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "schemars" version = "0.9.0" @@ -6225,6 +6868,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -6387,6 +7033,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.46" @@ -6927,12 +7579,58 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "typetag" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a897b12c6c1151ad0b138b8db50252dc301f93bc3b027db05eec82aeed298c" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf808357c6ed7e13ba0f3277ec8d8f21b2d501274895104263985330c726c1c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "uint" version = "0.10.0" @@ -7316,6 +8014,16 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.1" @@ -7733,6 +8441,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -7924,8 +8641,37 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.14.0", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/fuzz_props/Cargo.toml b/fuzz_props/Cargo.toml index a6796bd..7e6ccc7 100644 --- a/fuzz_props/Cargo.toml +++ b/fuzz_props/Cargo.toml @@ -21,3 +21,4 @@ testnet_initial_state = { workspace = true } [dev-dependencies] proptest = "1.4" +nssa = { workspace = true, features = ["prove"] } From 9d18c9f3468b48a9b0a4b2e349565ab3c47600bf Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 7 Jun 2026 08:31:09 +0800 Subject: [PATCH 18/18] fix: update mutants-protocol recipe --- Justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Justfile b/Justfile index e552881..949f913 100644 --- a/Justfile +++ b/Justfile @@ -654,7 +654,7 @@ coverage-all ENGINE="all": # Oracle: cargo test -p fuzz_props --release # Run on every PR that touches fuzz_props/ or fuzz/fuzz_targets/. # -# Plane B (slow, ~hours): mutates LEZ protocol code (nssa, common). +# Plane B (slow, ~hours): mutates LEZ protocol code (lee, common). # Oracle: all 15 fuzz targets replayed against their committed corpus. # Run weekly or manually to find corpus gaps. @@ -677,7 +677,7 @@ mutants-harness: # Plane B — mutation testing of the LEZ protocol code against the committed corpus. # -# Mutates nssa and common in the logos-execution-zone sibling workspace and uses +# Mutates lee and common in the logos-execution-zone sibling workspace and uses # scripts/mutants-corpus-test.sh as the oracle. The oracle replays all 15 # committed libFuzzer corpora (cargo fuzz run -runs=0) against each mutant. # @@ -694,7 +694,7 @@ mutants-harness: # Default covers the two highest-value protocol crates. # # Output report: mutants-protocol.out/ in the repository root. -mutants-protocol PACKAGES="nssa common": +mutants-protocol PACKAGES="lee common": #!/bin/bash set -euo pipefail REPO_DIR="$(pwd)"