mirror of
https://github.com/logos-blockchain/lez-fuzzing.git
synced 2026-06-07 03:29:26 +00:00
Initial commit
This commit is contained in:
commit
c62bacece0
141
.github/workflows/fuzz.yml
vendored
Normal file
141
.github/workflows/fuzz.yml
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
name: Fuzzing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
schedule:
|
||||
# Nightly full run
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
env:
|
||||
RISC0_DEV_MODE: "1"
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
# ── Smoke fuzz: 60 s per target ─────────────────────────────────────────────
|
||||
smoke-fuzz:
|
||||
name: Smoke fuzz (${{ matrix.target }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- fuzz_transaction_decoding
|
||||
- fuzz_stateless_verification
|
||||
- fuzz_state_transition
|
||||
- fuzz_block_verification
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout logos-execution-zone alongside lez-fuzzing
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: logos-co/logos-execution-zone
|
||||
path: ../logos-execution-zone
|
||||
|
||||
- name: Install Rust nightly (required by cargo-fuzz)
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: fuzz-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz --locked
|
||||
|
||||
- name: Build fuzz target
|
||||
run: cargo fuzz build ${{ matrix.target }}
|
||||
|
||||
- name: Run smoke fuzz (60 s)
|
||||
run: |
|
||||
cargo fuzz run ${{ matrix.target }} \
|
||||
-- -max_total_time=60 -jobs=2 -workers=2
|
||||
|
||||
- name: Upload crash artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crash-${{ matrix.target }}
|
||||
path: fuzz/artifacts/${{ matrix.target }}/
|
||||
|
||||
# ── Corpus regression ────────────────────────────────────────────────────────
|
||||
regression:
|
||||
name: Corpus regression (${{ matrix.target }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- fuzz_transaction_decoding
|
||||
- fuzz_stateless_verification
|
||||
- fuzz_state_transition
|
||||
- fuzz_block_verification
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout logos-execution-zone alongside lez-fuzzing
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: logos-co/logos-execution-zone
|
||||
path: ../logos-execution-zone
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
- run: cargo install cargo-fuzz --locked
|
||||
- name: Reproduce corpus
|
||||
run: |
|
||||
cargo fuzz run ${{ matrix.target }} \
|
||||
fuzz/corpus/${{ matrix.target }} -- -runs=0
|
||||
|
||||
# ── proptest property tests ──────────────────────────────────────────────────
|
||||
proptest:
|
||||
name: Property tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout logos-execution-zone alongside lez-fuzzing
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: logos-co/logos-execution-zone
|
||||
path: ../logos-execution-zone
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test -p fuzz_props --release
|
||||
|
||||
# ── Performance baseline (nightly only) ─────────────────────────────────────
|
||||
perf-baseline:
|
||||
name: Performance baseline
|
||||
runs-on: ubuntu-latest
|
||||
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-co/logos-execution-zone
|
||||
path: ../logos-execution-zone
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
- run: cargo install cargo-fuzz --locked
|
||||
- name: Measure throughput (30 s per target)
|
||||
run: |
|
||||
for target in \
|
||||
fuzz_transaction_decoding \
|
||||
fuzz_stateless_verification \
|
||||
fuzz_state_transition \
|
||||
fuzz_block_verification; do
|
||||
echo "=== $target ===" | tee -a perf_baseline.txt
|
||||
cargo fuzz run "$target" -- -max_total_time=30 2>&1 \
|
||||
| grep -E "exec/s|execs_per_sec" | tail -1 | tee -a perf_baseline.txt
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: perf-baseline
|
||||
path: perf_baseline.txt
|
||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
# ── Rust / Cargo ──────────────────────────────────────────────────────────────
|
||||
/target/
|
||||
fuzz/target/
|
||||
|
||||
# Cargo lock files (committed in apps, ignored in libraries; we keep ours)
|
||||
# Cargo.lock ← do NOT add; we want it tracked for reproducible fuzz builds
|
||||
|
||||
# ── cargo-fuzz outputs ────────────────────────────────────────────────────────
|
||||
# Crash artifacts discovered during fuzzing (should be reviewed, minimised,
|
||||
# and moved to corpus/ or a regression test before committing)
|
||||
fuzz/artifacts/
|
||||
|
||||
# Coverage reports generated by `cargo fuzz coverage`
|
||||
fuzz/coverage/
|
||||
|
||||
# libFuzzer-generated corpus additions (committed selectively — keep only
|
||||
# manually curated seeds in fuzz/corpus/)
|
||||
# Uncomment the line below to ignore ALL corpus growth automatically:
|
||||
# fuzz/corpus/
|
||||
|
||||
# ── Editor / IDE ──────────────────────────────────────────────────────────────
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
|
||||
# macOS metadata
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
|
||||
# ── Misc ──────────────────────────────────────────────────────────────────────
|
||||
# Performance baseline output from `just perf-baseline` or CI
|
||||
perf_baseline.txt
|
||||
|
||||
# Flamegraph files from `cargo flamegraph` runs
|
||||
flamegraph.svg
|
||||
perf.data
|
||||
perf.data.old
|
||||
6109
Cargo.lock
generated
Normal file
6109
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
85
Cargo.toml
Normal file
85
Cargo.toml
Normal file
@ -0,0 +1,85 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"fuzz_props",
|
||||
]
|
||||
|
||||
# ── Workspace package metadata (mirrored from LEZ to satisfy workspace inheritance) ──
|
||||
[workspace.package]
|
||||
license = "MIT or Apache-2.0"
|
||||
|
||||
# ── Workspace lints (mirrored exactly from logos-execution-zone to ensure LEZ crates
|
||||
# compile with the same lint configuration they were written for) ────────────────
|
||||
[workspace.lints]
|
||||
clippy.all = { level = "deny", priority = -1 }
|
||||
clippy.pedantic = { level = "deny", priority = -1 }
|
||||
clippy.restriction = { level = "deny", priority = -1 }
|
||||
|
||||
# -- pedantic allows ---
|
||||
clippy.missing-errors-doc = "allow"
|
||||
clippy.missing-panics-doc = "allow"
|
||||
clippy.similar-names = "allow"
|
||||
clippy.too-many-lines = "allow"
|
||||
clippy.implicit-hasher = "allow"
|
||||
|
||||
# -- restriction allows ---
|
||||
clippy.blanket-clippy-restriction-lints = "allow"
|
||||
clippy.unwrap-used = "allow"
|
||||
clippy.expect-used = "allow"
|
||||
clippy.unreachable = "allow"
|
||||
clippy.single-call-fn = "allow"
|
||||
clippy.panic = "allow"
|
||||
clippy.shadow-reuse = "allow"
|
||||
clippy.implicit-return = "allow"
|
||||
clippy.std-instead-of-core = "allow"
|
||||
clippy.std-instead-of-alloc = "allow"
|
||||
clippy.missing-trait-methods = "allow"
|
||||
clippy.pattern-type-mismatch = "allow"
|
||||
clippy.assertions-on-result-states = "allow"
|
||||
clippy.missing-assert-message = "allow"
|
||||
clippy.missing-docs-in-private-items = "allow"
|
||||
clippy.separated_literal_suffix = "allow"
|
||||
clippy.absolute-paths = "allow"
|
||||
clippy.min-ident-chars = "allow"
|
||||
clippy.indexing-slicing = "allow"
|
||||
clippy.little-endian-bytes = "allow"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
||||
# ── Workspace dependencies ────────────────────────────────────────────────────
|
||||
[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" }
|
||||
token_core = { path = "../logos-execution-zone/programs/token/core" }
|
||||
test_program_methods = { path = "../logos-execution-zone/test_program_methods" }
|
||||
|
||||
# ── Third-party dependencies (versions mirrored from logos-execution-zone) ────
|
||||
anyhow = "1.0.98"
|
||||
thiserror = "2.0"
|
||||
serde = { version = "1.0.60", default-features = false, features = ["derive"] }
|
||||
serde_json = "1.0.81"
|
||||
serde_with = "3.16.1"
|
||||
base64 = "0.22.1"
|
||||
sha2 = "0.10.8"
|
||||
log = "0.4.28"
|
||||
hex = "0.4.3"
|
||||
borsh = "1.5.7"
|
||||
rand = { version = "0.8.5", features = ["std", "std_rng", "getrandom"] }
|
||||
risc0-zkvm = { version = "3.0.5", features = ["std"] }
|
||||
k256 = { version = "0.13.3", features = ["ecdsa-core", "arithmetic", "expose-field", "serde", "pem"] }
|
||||
bytemuck = "1.24.0"
|
||||
bytesize = { version = "2.3.1", features = ["serde"] }
|
||||
base58 = "0.2.0"
|
||||
env_logger = "0.11"
|
||||
aes-gcm = "0.10.3"
|
||||
bip39 = "2.2.0"
|
||||
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" }
|
||||
61
Justfile
Normal file
61
Justfile
Normal file
@ -0,0 +1,61 @@
|
||||
# ── Fuzzing ───────────────────────────────────────────────────────────────────
|
||||
export RISC0_DEV_MODE := "1"
|
||||
|
||||
# Run all fuzz targets for TIME seconds each (default: 30)
|
||||
fuzz TIME="30":
|
||||
cargo fuzz run fuzz_transaction_decoding -- -max_total_time={{TIME}}
|
||||
cargo fuzz run fuzz_stateless_verification -- -max_total_time={{TIME}}
|
||||
cargo fuzz run fuzz_state_transition -- -max_total_time={{TIME}}
|
||||
cargo fuzz run fuzz_block_verification -- -max_total_time={{TIME}}
|
||||
|
||||
# Re-run the saved corpus (regression mode, no new mutations)
|
||||
fuzz-regression:
|
||||
cargo fuzz run fuzz_transaction_decoding fuzz/corpus/fuzz_transaction_decoding -- -runs=0
|
||||
cargo fuzz run fuzz_stateless_verification fuzz/corpus/fuzz_stateless_verification -- -runs=0
|
||||
cargo fuzz run fuzz_state_transition fuzz/corpus/fuzz_state_transition -- -runs=0
|
||||
cargo fuzz run fuzz_block_verification fuzz/corpus/fuzz_block_verification -- -runs=0
|
||||
|
||||
# Minimise a crash artifact
|
||||
# Usage: just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-XXX
|
||||
fuzz-tmin TARGET ARTIFACT:
|
||||
cargo fuzz tmin {{TARGET}} {{ARTIFACT}}
|
||||
|
||||
# Run the proptest-based property tests
|
||||
fuzz-props:
|
||||
cargo test -p fuzz_props --release
|
||||
|
||||
# Pull the latest LEZ changes from the sibling logos-execution-zone directory
|
||||
update-lez:
|
||||
git -C ../logos-execution-zone pull --ff-only
|
||||
|
||||
# ── Corpus management ─────────────────────────────────────────────────────────
|
||||
|
||||
# Minimise the corpus for all four targets (removes dominated inputs)
|
||||
corpus-cmin:
|
||||
cargo fuzz cmin fuzz_transaction_decoding
|
||||
cargo fuzz cmin fuzz_stateless_verification
|
||||
cargo fuzz cmin fuzz_state_transition
|
||||
cargo fuzz cmin fuzz_block_verification
|
||||
|
||||
# Minimise the corpus for a single target
|
||||
# Usage: just corpus-cmin-target fuzz_state_transition
|
||||
corpus-cmin-target TARGET:
|
||||
cargo fuzz cmin {{TARGET}}
|
||||
|
||||
# ── Housekeeping ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Remove all Cargo build artefacts (workspace + fuzz sub-crate)
|
||||
clean:
|
||||
cargo clean
|
||||
cargo clean --manifest-path fuzz/Cargo.toml
|
||||
|
||||
# Remove libFuzzer crash/timeout artifacts for all targets (corpus is kept)
|
||||
clean-artifacts:
|
||||
rm -rf fuzz/artifacts/
|
||||
|
||||
# Remove coverage reports generated by `cargo fuzz coverage`
|
||||
clean-coverage:
|
||||
rm -rf fuzz/coverage/
|
||||
|
||||
# Remove everything: builds, artifacts, and coverage
|
||||
clean-all: clean clean-artifacts clean-coverage
|
||||
21
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Logos Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
174
README.md
Normal file
174
README.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Lez-fuzzing
|
||||
|
||||
Coverage-guided fuzzing and adversarial testing infrastructure for the
|
||||
**Logos Execution Zone (LEZ)** protocol.
|
||||
|
||||
---
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
lez-fuzzing/
|
||||
├── Cargo.toml # Workspace root (members: fuzz_props)
|
||||
├── Justfile # Turn-key entry-points
|
||||
├── rust-toolchain.toml # Pins Rust nightly (required by cargo-fuzz)
|
||||
├── .gitignore
|
||||
├── fuzz_props/ # Shared invariant framework + input generators
|
||||
│ ├── Cargo.toml
|
||||
│ └── src/
|
||||
│ ├── lib.rs
|
||||
│ ├── invariants.rs # ProtocolInvariant trait + concrete invariants
|
||||
│ └── generators.rs # Arbitrary / proptest strategies
|
||||
├── fuzz/ # cargo-fuzz crate (own [workspace] sentinel)
|
||||
│ ├── Cargo.toml
|
||||
│ ├── fuzz_targets/
|
||||
│ │ ├── fuzz_transaction_decoding.rs
|
||||
│ │ ├── fuzz_stateless_verification.rs
|
||||
│ │ ├── fuzz_state_transition.rs
|
||||
│ │ └── fuzz_block_verification.rs
|
||||
│ └── corpus/ # Curated seed inputs (one dir per target)
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ └── fuzz.yml # CI: smoke-fuzz · regression · proptest · perf
|
||||
└── docs/
|
||||
└── fuzzing.md # Full developer guide
|
||||
```
|
||||
|
||||
The LEZ codebase is consumed as a **sibling directory** — clone
|
||||
`logos-execution-zone` next to this repository:
|
||||
|
||||
```
|
||||
parent/
|
||||
├── lez-fuzzing/ ← this repo
|
||||
└── logos-execution-zone/ ← LEZ codebase (path deps resolve via ../)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
rustup install nightly
|
||||
rustup component add llvm-tools-preview --toolchain nightly
|
||||
cargo install cargo-fuzz
|
||||
# Optional but recommended:
|
||||
cargo install just
|
||||
```
|
||||
|
||||
> **Why nightly?** `cargo-fuzz` passes `-Zsanitizer=address` and
|
||||
> `-Zinstrument-coverage` (unstable flags) to `rustc`, and depends on the
|
||||
> `llvm-tools-preview` nightly component for coverage reporting. The
|
||||
> `rust-toolchain.toml` pins the whole repository to nightly so you never
|
||||
> need an explicit `+nightly` flag.
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Clone both repositories side by side
|
||||
git clone <LEZ_REPO_URL> logos-execution-zone
|
||||
git clone <LEZ_FUZZING_REPO_URL> lez-fuzzing
|
||||
cd lez-fuzzing
|
||||
```
|
||||
|
||||
### Run the fuzz targets
|
||||
|
||||
```bash
|
||||
# All targets for 30 s each (RISC0_DEV_MODE=1 is set automatically)
|
||||
just fuzz
|
||||
|
||||
# Specific duration
|
||||
just fuzz 120
|
||||
|
||||
# Single target
|
||||
RISC0_DEV_MODE=1 cargo fuzz run fuzz_state_transition -- -max_total_time=120
|
||||
|
||||
# Corpus regression (replay saved corpus, no mutations)
|
||||
just fuzz-regression
|
||||
|
||||
# Property-based tests only (no libFuzzer)
|
||||
just fuzz-props
|
||||
```
|
||||
|
||||
> **ZK-proof cost:** `RISC0_DEV_MODE=1` is exported at the top of the
|
||||
> `Justfile` and must be set in every fuzz run to stub out ZK proof
|
||||
> generation. Without it each execution takes seconds instead of
|
||||
> microseconds.
|
||||
|
||||
---
|
||||
|
||||
## Fuzz Targets
|
||||
|
||||
| Target | Protocol layer | Entry point |
|
||||
|--------|---------------|-------------|
|
||||
| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
||||
| `fuzz_stateless_verification` | `transaction_stateless_check()` idempotency | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
||||
| `fuzz_state_transition` | `V03State` transition + state-isolation invariant | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
||||
| `fuzz_block_verification` | Block hash integrity | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
||||
|
||||
---
|
||||
|
||||
## Corpus Management
|
||||
|
||||
```bash
|
||||
# Minimise all corpora (removes dominated inputs, keeps coverage-equivalent set)
|
||||
just corpus-cmin
|
||||
|
||||
# Minimise a single target's corpus
|
||||
just corpus-cmin-target fuzz_state_transition
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Crash / Failure Workflow
|
||||
|
||||
```bash
|
||||
# Minimise a crash artifact
|
||||
just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
|
||||
# Print the bytes as a Rust literal (for a regression #[test])
|
||||
cargo fuzz fmt fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
|
||||
# Promote the minimised input to the corpus so CI catches regressions
|
||||
cp fuzz/artifacts/fuzz_state_transition/crash-abc123-minimised \
|
||||
fuzz/corpus/fuzz_state_transition/regression_001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Housekeeping
|
||||
|
||||
```bash
|
||||
just clean # Remove Cargo build artefacts (target/ and fuzz/target/)
|
||||
just clean-artifacts # Remove fuzz/artifacts/ (crash/timeout inputs)
|
||||
just clean-coverage # Remove fuzz/coverage/ (LLVM coverage reports)
|
||||
just clean-all # All of the above
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI
|
||||
|
||||
GitHub Actions runs four jobs on every push/PR and nightly:
|
||||
|
||||
| Job | What it does |
|
||||
|-----|-------------|
|
||||
| `smoke-fuzz` (matrix) | Builds + runs each target for 60 s |
|
||||
| `regression` (matrix) | Replays the saved corpus (`-runs=0`) |
|
||||
| `proptest` | `cargo test -p fuzz_props --release` |
|
||||
| `perf-baseline` (nightly only) | Measures exec/sec per target, uploads `perf_baseline.txt` |
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Full developer guide — how to add new targets, interpret crashes, update
|
||||
the LEZ sibling clone, and tune performance — is in
|
||||
[`docs/fuzzing.md`](docs/fuzzing.md).
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT License](LICENSE-MIT).
|
||||
222
docs/fuzzing.md
Normal file
222
docs/fuzzing.md
Normal file
@ -0,0 +1,222 @@
|
||||
# Fuzzing Guide
|
||||
|
||||
This document covers how to run fuzz targets, add new targets, minimise failures,
|
||||
and convert findings into regression tests.
|
||||
|
||||
The fuzzing infrastructure lives in a **separate repository** (`lez-fuzzing/`) which
|
||||
reads the Logos Execution Zone (LEZ) codebase from `../logos-execution-zone/` (a sibling
|
||||
directory that must be cloned separately).
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
# Rust nightly is required by cargo-fuzz / libFuzzer
|
||||
rustup install nightly
|
||||
rustup component add llvm-tools-preview --toolchain nightly
|
||||
|
||||
cargo install cargo-fuzz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repository Setup
|
||||
|
||||
`lez-fuzzing` is a **standalone repository** — it does **not** use git submodules.
|
||||
It expects the LEZ codebase to be cloned at `../logos-execution-zone` relative to itself.
|
||||
|
||||
```bash
|
||||
# Clone both repositories side-by-side into the same parent directory:
|
||||
git clone <LEZ_REPO_URL> logos-execution-zone
|
||||
git clone <LEZ_FUZZING_REPO_URL> lez-fuzzing
|
||||
|
||||
# The directory layout must be:
|
||||
# <parent>/
|
||||
# ├── logos-execution-zone/
|
||||
# └── lez-fuzzing/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Run
|
||||
|
||||
All fuzz targets must be run with `RISC0_DEV_MODE=1` to disable expensive ZK
|
||||
proof generation. The `just` recipes handle this automatically.
|
||||
|
||||
```bash
|
||||
# From lez-fuzzing/
|
||||
|
||||
# Run all targets for 30 s each
|
||||
just fuzz
|
||||
|
||||
# Run a specific target for 120 s
|
||||
RISC0_DEV_MODE=1 cargo fuzz run fuzz_state_transition -- -max_total_time=120
|
||||
|
||||
# Run the saved corpus (regression mode, no mutations)
|
||||
just fuzz-regression
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Fuzz Targets
|
||||
|
||||
| Target | What it fuzzes | Entry point |
|
||||
|--------|---------------|-------------|
|
||||
| `fuzz_transaction_decoding` | borsh decoding of all transaction and block types | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
||||
| `fuzz_stateless_verification` | `transaction_stateless_check()` signature validation | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
||||
| `fuzz_state_transition` | `V03State::transition_from_*()` with invariant checks | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
||||
| `fuzz_block_verification` | Block hash integrity + replayer pipeline | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
||||
|
||||
---
|
||||
|
||||
## How to Add a New Fuzz Target
|
||||
|
||||
1. Create `fuzz/fuzz_targets/fuzz_<name>.rs` using the template below.
|
||||
2. Add a `[[bin]]` entry to `fuzz/Cargo.toml`.
|
||||
3. Create an empty seed corpus directory: `mkdir -p fuzz/corpus/fuzz_<name>`.
|
||||
4. Add the target to the CI matrix in `.github/workflows/fuzz.yml`.
|
||||
5. Run `RISC0_DEV_MODE=1 cargo fuzz build fuzz_<name>` to verify it compiles.
|
||||
|
||||
**Template:**
|
||||
|
||||
```rust
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// 1. Parse / decode `data` into your target type
|
||||
// 2. Call the function under test
|
||||
// 3. Assert invariants using `fuzz_props::invariants::assert_invariants()`
|
||||
// 4. Never panic on invalid input; only panic on invariant violations
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Updating the LEZ Dependency
|
||||
|
||||
`lez-fuzzing` reads LEZ source directly from `../logos-execution-zone`. To pick up LEZ
|
||||
changes, simply update that repo:
|
||||
|
||||
```bash
|
||||
cd ../logos-execution-zone
|
||||
git pull --ff-only
|
||||
cd ../lez-fuzzing
|
||||
|
||||
# Rebuild to confirm compatibility:
|
||||
cargo build -p fuzz_props
|
||||
RISC0_DEV_MODE=1 cargo fuzz build
|
||||
```
|
||||
|
||||
The `just update-lez` recipe automates the pull:
|
||||
|
||||
```bash
|
||||
just update-lez
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Minimising & Reproducing Failures
|
||||
|
||||
When `cargo fuzz` finds a crash it writes an artifact to
|
||||
`fuzz/artifacts/fuzz_<target>/crash-<hash>`.
|
||||
|
||||
### Minimise
|
||||
|
||||
```bash
|
||||
# Produces a smaller input that still triggers the same crash
|
||||
just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
```
|
||||
|
||||
### Convert to a regression test
|
||||
|
||||
```bash
|
||||
# Print the bytes as a Rust byte-literal (paste into a #[test])
|
||||
cargo fuzz fmt fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123
|
||||
```
|
||||
|
||||
Add the minimised file to the corpus so CI always reproduces it:
|
||||
|
||||
```bash
|
||||
cp fuzz/artifacts/fuzz_state_transition/crash-abc123-minimised \
|
||||
fuzz/corpus/fuzz_state_transition/regression_001
|
||||
```
|
||||
|
||||
Open a PR. The `regression` CI job will permanently block re-introduction of this bug.
|
||||
|
||||
---
|
||||
|
||||
## Invariant Framework
|
||||
|
||||
Shared invariants live in `fuzz_props/src/invariants.rs`. Each invariant implements
|
||||
`ProtocolInvariant` and is automatically run by `assert_invariants()`.
|
||||
|
||||
To add a new invariant:
|
||||
|
||||
1. Add a zero-size struct implementing `ProtocolInvariant`.
|
||||
2. Register it in the `invariants` slice inside `assert_invariants()`.
|
||||
3. Write a `#[test]` in `fuzz_props` that triggers and detects a synthetic violation.
|
||||
|
||||
---
|
||||
|
||||
## Performance Baseline
|
||||
|
||||
Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`:
|
||||
|
||||
| Target | Throughput |
|
||||
|--------|-----------|
|
||||
| `fuzz_transaction_decoding` | ~200 000 exec/sec |
|
||||
| `fuzz_stateless_verification` | ~30 000 exec/sec |
|
||||
| `fuzz_state_transition` | ~5 000 exec/sec |
|
||||
| `fuzz_block_verification` | ~50 000 exec/sec |
|
||||
|
||||
Recommended local settings for longer runs:
|
||||
|
||||
```bash
|
||||
# Use all available cores
|
||||
RISC0_DEV_MODE=1 cargo fuzz run fuzz_state_transition \
|
||||
-- -max_total_time=3600 -jobs=$(nproc) -workers=$(nproc)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ZK-Proof Cost Warning
|
||||
|
||||
`PrivacyPreservingTransaction` uses `risc0-zkvm` (seconds per proof).
|
||||
All fuzz targets **must** set `RISC0_DEV_MODE=1` in the environment and the `just`
|
||||
recipes handle this automatically via:
|
||||
|
||||
```just
|
||||
export RISC0_DEV_MODE := "1"
|
||||
```
|
||||
|
||||
Do **not** invoke full proof generation inside any fuzz target. The `RISC0_DEV_MODE=1`
|
||||
flag stubs out ZK proof generation and replaces it with a fast mock implementation.
|
||||
|
||||
---
|
||||
|
||||
## Input Generators
|
||||
|
||||
The `fuzz_props` crate (`fuzz_props/src/generators.rs`) provides reusable input
|
||||
generators for both `libfuzzer` (via `arbitrary`) and `proptest`:
|
||||
|
||||
| Generator | Covers |
|
||||
|-----------|--------|
|
||||
| `arbitrary_transaction()` | IS-2: malformed + boundary transactions |
|
||||
| `arb_borsh_transaction_bytes()` | IS-2: raw borsh bytes including invalid encodings |
|
||||
| `arb_invalid_account_state_tx()` | IS-3: phantom accounts + overflow amounts |
|
||||
| `arb_duplicate_tx_sequence()` | IS-4: duplicated + re-ordered transaction sequences |
|
||||
| `arb_pathological_sequence()` | IS-5: zero-value, self-transfer, max-nonce inputs |
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations & Future Work
|
||||
|
||||
| Item | Notes |
|
||||
|------|-------|
|
||||
| `PrivacyPreservingTransaction` coverage | Currently only exercised in decoding target; a dedicated slow target with `RISC0_DEV_MODE=1` and `proptest` should be added after the four MVP targets are stable |
|
||||
| `V03State` snapshot equality | If `V03State` does not implement `PartialEq`/`Clone`, implement or derive them in `lez/nssa/src/state.rs` behind a `cfg(any(test, feature = "fuzzing"))` guard |
|
||||
| AFL++ integration | A `just fuzz-afl` recipe can be added later; the same corpus is compatible |
|
||||
| Differential testing (sequencer vs replayer) | Add a fifth target that feeds the same block to `SequencerCore` and `indexer_core` and asserts identical state roots |
|
||||
| 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. |
|
||||
6145
fuzz/Cargo.lock
generated
Normal file
6145
fuzz/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
fuzz/Cargo.toml
Normal file
49
fuzz/Cargo.toml
Normal file
@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "fuzz"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
# Required by cargo-fuzz — prevents this crate from being a workspace member
|
||||
[workspace]
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_transaction_decoding"
|
||||
path = "fuzz_targets/fuzz_transaction_decoding.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_stateless_verification"
|
||||
path = "fuzz_targets/fuzz_stateless_verification.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_state_transition"
|
||||
path = "fuzz_targets/fuzz_state_transition.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_block_verification"
|
||||
path = "fuzz_targets/fuzz_block_verification.rs"
|
||||
test = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
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" }
|
||||
fuzz_props = { path = "../fuzz_props" }
|
||||
testnet_initial_state = { path = "../../logos-execution-zone/testnet_initial_state" }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
opt-level = 3
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
΄-΄Ή΄΄΄΄
|
||||
΄΄΄0΄<EFBFBD>΄ <09><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ο<EFBFBD><CE9F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><02><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ;΄΄΄΄΄<CE84><CE84><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>΄΄
|
||||
΄΄΄<EFBFBD>΄΄
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user