From 9c7409fbe7bdec091c1b6aa73455b0b755a8a198 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 20 Apr 2026 14:09:10 +0800 Subject: [PATCH] fix: docs update --- README.md | 37 +++++++++++++++++++++---- docs/fuzzing.md | 72 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 548c3c7..336a74f 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,28 @@ lez-fuzzing/ ├── Justfile # Turn-key entry-points ├── rust-toolchain.toml # Pins Rust nightly (required by cargo-fuzz) ├── .gitignore +├── scripts/ +│ └── add_fuzz_target.py # Automates new-target scaffolding (called by just new-target) ├── fuzz_props/ # Shared invariant framework + input generators │ ├── Cargo.toml │ └── src/ │ ├── lib.rs -│ ├── invariants.rs # ProtocolInvariant trait + concrete invariants -│ └── generators.rs # Arbitrary / proptest strategies +│ ├── 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/ +│ │ ├── _template.rs # Template for just new-target │ │ ├── fuzz_transaction_decoding.rs │ │ ├── fuzz_stateless_verification.rs │ │ ├── fuzz_state_transition.rs -│ │ └── fuzz_block_verification.rs +│ │ ├── fuzz_block_verification.rs +│ │ ├── fuzz_encoding_roundtrip.rs +│ │ ├── fuzz_signature_verification.rs +│ │ ├── fuzz_replay_prevention.rs +│ │ ├── fuzz_state_diff_computation.rs +│ │ └── fuzz_validate_execute_consistency.rs │ └── corpus/ # Curated seed inputs (one dir per target) ├── .github/ │ └── workflows/ @@ -106,6 +115,11 @@ just fuzz-props | `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` | +| `fuzz_encoding_roundtrip` | Borsh encode→decode→encode round-trip identity | `fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs` | +| `fuzz_signature_verification` | Signature creation + verification correctness and no-panic | `fuzz/fuzz_targets/fuzz_signature_verification.rs` | +| `fuzz_replay_prevention` | Transaction nonce replay rejection | `fuzz/fuzz_targets/fuzz_replay_prevention.rs` | +| `fuzz_state_diff_computation` | `ValidatedStateDiff` scope isolation (only declared accounts mutated) | `fuzz/fuzz_targets/fuzz_state_diff_computation.rs` | +| `fuzz_validate_execute_consistency` | `validate_on_state` / `execute_check_on_state` agreement + diff accuracy | `fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs` | --- @@ -137,6 +151,19 @@ cp fuzz/artifacts/fuzz_state_transition/crash-abc123-minimised \ --- +## Adding a New Target + +```bash +# Scaffold everything automatically (corpus dir, .rs file, Cargo.toml entry, CI matrix entry) +just new-target my_feature # creates fuzz_my_feature +``` + +`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) and inserts the target +into every strategy matrix in [`.github/workflows/fuzz.yml`](.github/workflows/fuzz.yml). + +--- + ## Housekeeping ```bash @@ -154,8 +181,8 @@ 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`) | +| `smoke-fuzz` (matrix, 9 targets) | Builds + runs each target for 60 s | +| `regression` (matrix, 9 targets) | 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` | diff --git a/docs/fuzzing.md b/docs/fuzzing.md index 6abefbe..f5a0a29 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -63,10 +63,15 @@ just fuzz-regression | 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_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` | +| `fuzz_encoding_roundtrip` | `decode(encode(tx)) == Ok(tx)` and `encode(decode(encode(tx))) == encode(tx)` for `PublicTransaction` and `ProgramDeploymentTransaction` | `fuzz/fuzz_targets/fuzz_encoding_roundtrip.rs` | +| `fuzz_signature_verification` | Signature correctness (sign→verify), no-panic on random bytes, cross-key soundness | `fuzz/fuzz_targets/fuzz_signature_verification.rs` | +| `fuzz_replay_prevention` | A tx accepted in block N must be rejected when replayed in block N+1 (nonce consumed) | `fuzz/fuzz_targets/fuzz_replay_prevention.rs` | +| `fuzz_state_diff_computation` | `ValidatedStateDiff` only modifies accounts declared in `affected_public_account_ids()` | `fuzz/fuzz_targets/fuzz_state_diff_computation.rs` | +| `fuzz_validate_execute_consistency` | `validate_on_state` and `execute_check_on_state` must agree on success/failure and produce identical state changes | `fuzz/fuzz_targets/fuzz_validate_execute_consistency.rs` | --- @@ -205,6 +210,13 @@ Open a PR. The `regression` CI job will permanently block re-introduction of thi Shared invariants live in `fuzz_props/src/invariants.rs`. Each invariant implements `ProtocolInvariant` and is automatically run by `assert_invariants()`. +Concrete invariants currently registered: + +| Invariant | Description | +|-----------|-------------| +| `StateIsolationOnFailure` | Account balances must not change when a transaction is rejected | +| `ReplayRejection` | An accepted transaction must be rejected when replayed (see `fuzz_replay_prevention`) | + To add a new invariant: 1. Add a zero-size struct implementing `ProtocolInvariant`. @@ -213,6 +225,32 @@ To add a new invariant: --- +## Input Generators + +The `fuzz_props` crate provides two layers of input generation: + +### `fuzz_props::arbitrary_types` (libFuzzer / `Arbitrary`) + +Typed wrappers that implement `Arbitrary` for LEZ structs — `ArbNSSATransaction`, +`ArbPublicTransaction`, `ArbProgramDeploymentTransaction`, `ArbPrivateKey`, +`ArbPublicKey`, `ArbSignature`, etc. Use these directly as fuzz target parameters +for zero-boilerplate structured fuzzing. + +### `fuzz_props::generators` (proptest strategies + libFuzzer helpers) + +| Generator | Covers | +|-----------|--------| +| `arbitrary_transaction()` | Best-effort structured `NSSATransaction` from unstructured bytes, falls back to raw Borsh decode | +| `arb_borsh_transaction_bytes()` | Raw Borsh bytes including invalid encodings (IS-2) | +| `arb_native_transfer_tx()` | Valid native-transfer `NSSATransaction` between known genesis accounts | +| `test_accounts()` | Returns `(AccountId, PrivateKey)` pairs from `testnet_initial_state` | +| `arb_hashable_block_data()` | `HashableBlockData` with 0–8 valid native transfers | +| `arb_invalid_account_state_tx()` | Phantom accounts + overflow amounts — expected to be rejected (IS-3) | +| `arb_duplicate_tx_sequence()` | Duplicated + re-ordered transaction sequences (IS-4) | +| `arb_pathological_sequence()` | Zero-value, self-transfer, max-nonce inputs (IS-5) | + +--- + ## Performance Baseline Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`: @@ -223,6 +261,14 @@ Measured on a 4-core x86_64 Linux runner with `RISC0_DEV_MODE=1`: | `fuzz_stateless_verification` | ~30 000 exec/sec | | `fuzz_state_transition` | ~5 000 exec/sec | | `fuzz_block_verification` | ~50 000 exec/sec | +| `fuzz_encoding_roundtrip` | ~150 000 exec/sec | +| `fuzz_signature_verification` | ~20 000 exec/sec | +| `fuzz_replay_prevention` | ~5 000 exec/sec | +| `fuzz_state_diff_computation` | ~10 000 exec/sec | +| `fuzz_validate_execute_consistency` | ~3 000 exec/sec | + +> Numbers for the five newer targets are rough estimates; run `just perf-baseline` +> locally or check the `perf-baseline` CI artifact for up-to-date measurements. Recommended local settings for longer runs: @@ -249,27 +295,13 @@ flag stubs out ZK proof generation and replaces it with a fast mock implementati --- -## 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 | +| `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 | +| `StateIsolationOnFailure` balance check | The invariant body is a placeholder — full balance extraction from `V03State` should be implemented once the API for iterating all accounts is confirmed | +| `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 until `V03State` exposes account iteration | | 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. | +| Differential testing (sequencer vs replayer) | Add a 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 |