Roman Zajic aa0c8b7c4c
Merge pull request #20 from logos-blockchain/fix-add-timeout-stateful-lockstep
fix: Add timeout to model based stateful lockstep
2026-07-14 09:46:28 +02:00
2026-07-09 12:50:46 +02:00
2026-07-06 15:24:30 +02:00
2026-07-06 15:24:30 +02:00
2026-07-02 15:04:31 +02:00
2026-06-09 23:19:53 +08:00
2026-06-25 13:15:33 +08:00
2026-06-25 13:15:33 +08:00
2026-04-13 16:03:20 +08:00
2026-06-12 14:30:19 +08:00
2026-07-03 10:19:28 +02:00
2026-04-13 16:03:20 +08:00

Lez-fuzzing

Coverage-guided fuzzing & adversarial testing infrastructure for the Logos Execution Zone (LEZ) protocol.

Rust Fuzzing Mutation testing License


📂 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
│       ├── arbitrary_types.rs  # Arbitrary impl wrappers for LEZ types (libFuzzer)
│       ├── invariants.rs       # ProtocolInvariant trait + concrete invariants
│       └── generators.rs       # Arbitrary / proptest strategies
├── fuzz/                   # cargo-fuzz crate (own [workspace] sentinel)
│   ├── Cargo.toml
│   ├── fuzz_targets/       # 23 targets total — see table below
│   │   ├── _template.rs    # Template for `just new-target`
│   │   └── fuzz_*.rs
│   └── corpus/             # Curated seed inputs (one dir per target)
├── .github/
│   └── workflows/
│       ├── fuzz.yml        # CI: smoke-fuzz · regression · proptest · perf (libFuzzer)
│       ├── fuzz-afl.yml    # CI: AFL++ lane
│       ├── mutants.yml     # CI: mutation testing (cargo-mutants)
│       └── lint.yml        # CI: fmt + clippy
├── scripts/
│   └── add_fuzz_target.py  # Automates new-target scaffolding (called by just new-target)
└── docs/
    ├── fuzzing.md              # Full developer guide
    └── mutants-not-fuzzable.md # Policy + mutant→test mapping

The LEZ codebase is consumed as a sibling directory — clone logos-execution-zone next to this repository so the ../ path deps resolve:

parent/
├── lez-fuzzing/            ← this repo
└── logos-execution-zone/   ← LEZ codebase (path deps resolve via ../)

🚀 Quick Start

1. Prerequisites

rustup install nightly
rustup component add llvm-tools-preview --toolchain nightly
cargo install cargo-fuzz
cargo install just            # optional but recommended

2. Setup

# 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

3. Run the fuzz targets

# 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

Important

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
1 fuzz_transaction_decoding Borsh decoding of all tx/block types (LeeTransaction, Block, HashableBlockData) with roundtrip re-encoding
2 fuzz_stateless_verification transaction_stateless_check() no-panic + idempotency
3 fuzz_state_transition V03State transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state
4 fuzz_block_verification Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment
5 fuzz_encoding_roundtrip Borsh encode→decode→encode round-trip identity + canonical encoding for PublicTransaction and ProgramDeploymentTransaction
6 fuzz_signature_verification Signature correctness (sign→verify), no-panic on random bytes, cross-key soundness
7 fuzz_replay_prevention Transaction nonce replay rejection with fuzz-driven initial state
8 fuzz_state_diff_computation ValidatedStateDiff forward containment + reverse completeness (bidirectional isolation check)
9 fuzz_validate_execute_consistency validate_on_state / execute_check_on_state agreement + diff accuracy + BalanceConservation
10 fuzz_state_serialization V03State Borsh decode no-panic + StateSerializationRoundtrip idempotency + NullifierDeduplication (NullifierSet hand-written impl)
11 fuzz_witness_set_verification WitnessSet::is_valid_for no-panic + CorrectVerification (sign→verify) + MessageIsolation (witness set for msg A rejected on msg B)
12 fuzz_program_deployment_lifecycle V03State::transition_from_program_deployment_transaction no-panic + BalanceIsolation (deployment must not move tokens) + StateIsolationOnFailure
13 fuzz_apply_state_diff_split_path SplitPathEquivalence: validate_on_state + apply_state_diff == execute_check_on_state for all known accounts (balance, nonce, data, program_owner); NonceIncrementCorrectness
14 fuzz_multi_block_state_sequence LongRangeBalanceConservation across up to 16 blocks + FailedTxNonceStability (nonce must not change on rejection) + PerBlockReplayRejection
15 fuzz_sequencer_vs_replayer Differential: sequencer path (validate_on_stateapply_state_diff) vs replayer path (execute_check_on_state) — SequencerReplayerEquivalence + ReplayerAcceptsAllSequencerTxs + ClockConsistency
16 fuzz_merkle_tree Commitment Merkle tree via the commitment set: ProofSome · ProofValid (leaf + auth path recomputes the root) · NonMembershipNone · IndicesSequential
17 fuzz_transaction_properties Transaction property invariants: HashDeterministic/HashNonDefault, SignerIds derived from witness keys & non-empty, AffectedAccountsContainSigners, PublicDiffNonEmptyOnSuccess
18 fuzz_privacy_preserving_witness privacy_preserving_transaction::WitnessSet: CorrectVerification (witness for msg A passes signatures_are_valid_for(A)) + MessageIsolation + SignerIdsMatchWitnessKeys
19 fuzz_encoding_privacy_preserving Privacy-preserving encoding: MessageEncodingRoundtrip + TxEncodingDeterministic/NonEmpty
20 fuzz_nullifier_set_roundtrip NullifierSet Borsh serialisation: NullifierSetRoundtrip (decode→encode identity for the hand-written impl)
21 fuzz_privacy_preserving_state_transition Path B — NSSATransaction::PrivacyPreserving through execute_check_on_state with a dev-mode passing proof: reaches commitment/nullifier checks 56 + apply_state_diff. Asserts no-panic, StateIsolationOnFailure, PrivateStateIsolationOnFailure, CommitmentInsertion, NonceIncrementCorrectness, PostStateApplied, ReplayRejection (balance conservation intentionally not asserted — the fake proof bypasses the circuit guarantee)
22 fuzz_transaction_ordering_independence Transaction ordering-independence on the shielded path: builds a nullifier-conflicting pair (two distinct privacy-preserving txs declaring the same nullifier) and applies it in both orders on independent clones of a seeded state, at an identical (block_id, timestamp). Asserts NoDoubleSpend (neither ordering accepts both — the shared nullifier is spendable at most once) and OrderIndependentAcceptance (the count of accepted txs is the same in both orderings). The nullifier check is enforced by the state machine, not the circuit, so the dev-mode fake proof does not mask it. Requires RISC0_DEV_MODE=1
23 fuzz_stateful_model_lockstep Model-based stateful lockstep: steps an independent hand-written reference model of (balance, nonce) per account — reimplementing native-transfer semantics without calling nssa — in lockstep with the real V03State over a generated schedule of Transfer/AdvanceBlock/ReplayLast commands. Asserts ModelAcceptanceAgreement (real accept iff model predicts accept) and ModelStateAgreement (every account's real balance & nonce equals the model) after every command. The only target with a predictive oracle that pins the exact expected outcome per operation, catching history-dependent acceptance/state drift a self-consistent-but-wrong machine would pass

Each target lives at fuzz/fuzz_targets/<name>.rs.


🧬 Corpus Management

# 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

# 1. Minimise a crash artifact
just fuzz-tmin fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123

# 2. Print the bytes as a Rust literal (for a regression #[test])
cargo fuzz fmt fuzz_state_transition fuzz/artifacts/fuzz_state_transition/crash-abc123

# 3. 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

Adding a New Target

# Scaffold everything automatically (corpus dir, .rs file, Cargo.toml entry)
just new-target my_feature   # creates fuzz_my_feature

just new-target calls scripts/add_fuzz_target.py, which appends the [[bin]] entry to fuzz/Cargo.toml — the single source of truth. Every workflow and script derives its target list from that file at runtime (the CI matrices and build loops via the resolve-targets composite action, and scripts/mutants-corpus-test.sh via an inline parse), so no CI edits are needed. The only manual step is a prose row in the target tables of README.md and docs/fuzzing.md; scripts/check_target_inventory.py (run in CI) fails the build if either table drifts from fuzz/Cargo.toml.


🧹 Housekeeping

Command Removes
just clean Cargo build artefacts (target/ and fuzz/target/)
just clean-artifacts fuzz/artifacts/ (crash/timeout inputs)
just clean-coverage fuzz/coverage/ (LLVM coverage reports)
just clean-all All of the above

⚙️ CI

GitHub Actions runs these workflows on every push/PR and nightly:

Workflow What it does
fuzz.ymlsmoke-fuzz (matrix) Builds + runs each libFuzzer target for 60 s
fuzz.ymlregression (matrix) Replays the saved corpus (-runs=0)
fuzz.ymlproptest cargo test -p fuzz_props --release
fuzz.ymlperf-baseline (nightly only) Measures exec/sec per target, uploads perf_baseline.txt
fuzz-afl.yml AFL++ lane over the same targets/corpus
mutants.yml Mutation testing (cargo-mutants)
lint.yml Formatting + Clippy

📖 Documentation

The full developer guide — how to add new targets, interpret crashes, update the LEZ sibling clone, and tune performance — lives in docs/fuzzing.md.


📜 License

Licensed under the MIT License.

Description
Adversarial testing/fuzzing infrastructure for LEZ
Readme
Languages
Rust 83.5%
Just 13.5%
Python 2.2%
Shell 0.8%