mirror of
https://github.com/logos-blockchain/lez-fuzzing.git
synced 2026-07-02 07:49:45 +00:00
Merge pull request #9 from logos-blockchain/chore-sync-with-lez
chore: Sync with LEZ - circuits installation
This commit is contained in:
commit
ee5d5e4e68
2
.github/actions/checkout-lez/action.yml
vendored
2
.github/actions/checkout-lez/action.yml
vendored
@ -14,7 +14,7 @@ inputs:
|
||||
`just update-lez`, replace this SHA, and open a PR. The scheduled
|
||||
lez-compat workflow overrides this with `main` to detect upstream drift.
|
||||
required: false
|
||||
default: dac429a94af932b0c827544fff8b9de85b83e6f3
|
||||
default: e37876a64028a335eb693198a1ed6a0e875ec5b4
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
8
.github/workflows/corpus-update.yml
vendored
8
.github/workflows/corpus-update.yml
vendored
@ -80,10 +80,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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 }}
|
||||
- uses: ./.github/actions/setup-libfuzzer
|
||||
|
||||
- name: Build fuzz target
|
||||
@ -134,10 +130,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- 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 }}
|
||||
- uses: ./.github/actions/setup-afl
|
||||
|
||||
- name: Build AFL++ target
|
||||
|
||||
15
.github/workflows/fuzz-afl.yml
vendored
15
.github/workflows/fuzz-afl.yml
vendored
@ -75,11 +75,6 @@ jobs:
|
||||
- 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: Set up AFL++ toolchain
|
||||
uses: ./.github/actions/setup-afl
|
||||
|
||||
@ -270,11 +265,6 @@ jobs:
|
||||
- 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:
|
||||
@ -376,11 +366,6 @@ jobs:
|
||||
- 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:
|
||||
|
||||
17
.github/workflows/fuzz.yml
vendored
17
.github/workflows/fuzz.yml
vendored
@ -64,11 +64,6 @@ jobs:
|
||||
target
|
||||
key: fuzz-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install logos-blockchain-circuits
|
||||
uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/setup-libfuzzer
|
||||
|
||||
- name: Build fuzz target
|
||||
@ -236,10 +231,6 @@ jobs:
|
||||
- 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 }}
|
||||
- uses: ./.github/actions/setup-libfuzzer
|
||||
- name: Reproduce corpus
|
||||
run: |
|
||||
@ -256,10 +247,6 @@ jobs:
|
||||
- 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
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: cargo test -p fuzz_props --release
|
||||
- name: Run tests which require RISC0_DEV_MODE off.
|
||||
run: env -u RISC0_DEV_MODE cargo test -p fuzz_props --release synthesized_proof_is_rejected_without_dev_mode
|
||||
@ -273,10 +260,6 @@ jobs:
|
||||
- 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 }}
|
||||
- uses: ./.github/actions/setup-libfuzzer
|
||||
- name: Measure throughput (30 s per target)
|
||||
run: |
|
||||
|
||||
8
.github/workflows/lez-compat.yml
vendored
8
.github/workflows/lez-compat.yml
vendored
@ -8,6 +8,9 @@ on:
|
||||
schedule:
|
||||
- cron: "0 4 * * *"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- chore-sync-with-lez
|
||||
|
||||
env:
|
||||
RISC0_DEV_MODE: "1"
|
||||
@ -39,11 +42,6 @@ jobs:
|
||||
target
|
||||
key: lez-compat-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install logos-blockchain-circuits
|
||||
uses: ./logos-execution-zone/.github/actions/install-logos-blockchain-circuits
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build shared fuzz harness against upstream main
|
||||
run: cargo build -p fuzz_props --release
|
||||
|
||||
|
||||
5
.github/workflows/lint.yml
vendored
5
.github/workflows/lint.yml
vendored
@ -53,11 +53,6 @@ jobs:
|
||||
- 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 nightly toolchain with clippy
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
|
||||
10
.github/workflows/mutants.yml
vendored
10
.github/workflows/mutants.yml
vendored
@ -36,11 +36,6 @@ jobs:
|
||||
- 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:
|
||||
@ -127,11 +122,6 @@ jobs:
|
||||
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:
|
||||
|
||||
570
Cargo.lock
generated
570
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -59,8 +59,10 @@ nssa_core = { path = "../logos-execution-zone/lee/state_machine/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" }
|
||||
programs = { path = "../logos-execution-zone/lez/programs" }
|
||||
system_accounts = { path = "../logos-execution-zone/lez/system_accounts" }
|
||||
token_core = { path = "../logos-execution-zone/lez/programs/token/core" }
|
||||
test_program_methods = { path = "../logos-execution-zone/test_programs", package = "test_programs" }
|
||||
|
||||
# ── Third-party dependencies (versions mirrored from logos-execution-zone) ────
|
||||
anyhow = "1.0.98"
|
||||
|
||||
BIN
corpus/libfuzz/fuzz_state_transition/seed_empty_tx
generated
Normal file
BIN
corpus/libfuzz/fuzz_state_transition/seed_empty_tx
generated
Normal file
Binary file not shown.
BIN
corpus/libfuzz/fuzz_stateless_verification/seed_empty_tx
generated
Normal file
BIN
corpus/libfuzz/fuzz_stateless_verification/seed_empty_tx
generated
Normal file
Binary file not shown.
BIN
corpus/libfuzz/fuzz_transaction_decoding/seed_empty_tx
generated
Normal file
BIN
corpus/libfuzz/fuzz_transaction_decoding/seed_empty_tx
generated
Normal file
Binary file not shown.
1436
fuzz/Cargo.lock
generated
1436
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,8 @@ nssa_core = { path = "../../logos-execution-zone/lee/state_machine/core", pa
|
||||
common = { path = "../../logos-execution-zone/lez/common" }
|
||||
fuzz_props = { path = "../fuzz_props" }
|
||||
testnet_initial_state = { path = "../../logos-execution-zone/lez/testnet_initial_state" }
|
||||
programs = { path = "../../logos-execution-zone/lez/programs" }
|
||||
system_accounts = { path = "../../logos-execution-zone/lez/system_accounts" }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
@ -36,7 +36,6 @@ use arbitrary::{Arbitrary, Unstructured};
|
||||
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;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -60,7 +59,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
return;
|
||||
};
|
||||
|
||||
let state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// ── Split path: validate → apply ─────────────────────────────────────────
|
||||
// `validate_on_state` borrows `tx`; the transaction is still usable after.
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
//! compute_digest_for_path(c, proof) → canonical leaf→root recomputation
|
||||
//! ```
|
||||
//!
|
||||
//! Inserting commitments via `V03State::new_with_genesis_accounts` therefore
|
||||
//! Inserting commitments via `fuzz_props::genesis::genesis_state` therefore
|
||||
//! drives `insert`, `root`/`root_index`, `get_authentication_path_for`, `depth`,
|
||||
//! `get_node`/`set_node`, and — once the count exceeds the genesis capacity (32)
|
||||
//! — `reallocate_to_double_capacity` and `prev_power_of_two`.
|
||||
@ -47,7 +47,6 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use nssa::V03State;
|
||||
use nssa_core::{
|
||||
Commitment, Nullifier,
|
||||
account::{Account, AccountId},
|
||||
@ -78,7 +77,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let commitments: Vec<Commitment> = pairs.iter().map(|(c, _)| c.clone()).collect();
|
||||
|
||||
// Genesis inserts DUMMY_COMMITMENT at index 0, then our commitments at 1..=N.
|
||||
let state = V03State::new_with_genesis_accounts(&[], pairs, 0);
|
||||
let state = fuzz_props::genesis::genesis_state(&[], pairs);
|
||||
let digest = state.commitment_set_digest();
|
||||
|
||||
let mut indices: Vec<usize> = Vec::with_capacity(commitments.len());
|
||||
|
||||
@ -36,7 +36,6 @@
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
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]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -51,7 +50,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.map(|a| (a.account_id, a.balance))
|
||||
.collect();
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let mut state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Record starting balances for the long-range conservation check.
|
||||
let starting_total: u128 = init_accs
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
//! A single `\x00` seed is sufficient — Part 1 uses fixed inputs and catches the
|
||||
//! `delete-!` mutation without fuzz-driven state.
|
||||
|
||||
use nssa::{Account, AccountId, V03State, system_faucet_account_id};
|
||||
use nssa::{Account, AccountId, V03State};
|
||||
use nssa_core::{Commitment, Nullifier};
|
||||
use system_accounts::faucet_account_id;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
// ── Part 1: State with nullifiers — Borsh round-trip ─────────────────────
|
||||
@ -37,10 +38,9 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let comm2 = Commitment::new(&AccountId::new([0x22_u8; 32]), &Account::default());
|
||||
|
||||
// Build a state that holds two nullifiers in its private state.
|
||||
let state = V03State::new_with_genesis_accounts(
|
||||
&[(system_faucet_account_id(), 0)],
|
||||
let state = fuzz_props::genesis::genesis_state(
|
||||
&[(faucet_account_id(), 0)],
|
||||
vec![(comm1, null1), (comm2, null2)],
|
||||
0,
|
||||
);
|
||||
|
||||
// Serialise the state:
|
||||
|
||||
@ -36,7 +36,7 @@ use fuzz_props::invariants::{
|
||||
StateIsolationOnFailure, assert_nonce_increment_correctness, assert_replay_rejection,
|
||||
};
|
||||
use fuzz_props::privacy::arb_privacy_preserving_tx;
|
||||
use nssa::{AccountId, V03State};
|
||||
use nssa::AccountId;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -50,7 +50,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.iter()
|
||||
.map(|a| (a.account_id, a.balance))
|
||||
.collect();
|
||||
let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let mut state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Apply a short sequence so multi-transaction state evolution (commitment growth,
|
||||
// signer-nonce advance) is exercised. Each transaction's proof is synthesised against
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use fuzz_props::arbitrary_types::ArbProgramDeploymentTransaction;
|
||||
use fuzz_props::generators::arbitrary_fuzz_state;
|
||||
use nssa::V03State;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -46,7 +45,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
};
|
||||
let tx = tx_wrap.0;
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let mut state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Capture per-account state snapshots before the deployment attempt.
|
||||
let balances_before: Vec<u128> = init_accs
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
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]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -39,7 +38,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.iter()
|
||||
.map(|a| (a.account_id, a.balance))
|
||||
.collect();
|
||||
let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let mut state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Mix correlated transactions (correctly signed, referencing a fuzz account)
|
||||
// with random ones. Correlated transactions have a higher chance of being
|
||||
|
||||
@ -40,7 +40,6 @@ use std::collections::HashSet;
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use common::transaction::{LeeTransaction, clock_invocation};
|
||||
use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction};
|
||||
use nssa::V03State;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -67,7 +66,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
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);
|
||||
let base_state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Track all account IDs touched by accepted transactions so we can compare
|
||||
// them across both pipelines after the full block is applied.
|
||||
|
||||
@ -22,7 +22,7 @@ use arbitrary::{Arbitrary, Unstructured};
|
||||
use common::transaction::LeeTransaction;
|
||||
use fuzz_props::arbitrary_types::ArbPublicTransaction;
|
||||
use fuzz_props::generators::arbitrary_fuzz_state;
|
||||
use nssa::{V03State, ValidatedStateDiff};
|
||||
use nssa::ValidatedStateDiff;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -36,7 +36,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.iter()
|
||||
.map(|a| (a.account_id, a.balance))
|
||||
.collect();
|
||||
let state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Generate the public transaction from remaining fuzz bytes.
|
||||
let pub_tx = match ArbPublicTransaction::arbitrary(&mut u) {
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
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]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -22,7 +21,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.collect();
|
||||
|
||||
// Construct the initial state
|
||||
let mut state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let mut state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Generate up to 8 transactions and apply them
|
||||
let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 8;
|
||||
|
||||
@ -9,10 +9,9 @@ use common::transaction::LeeTransaction;
|
||||
use fuzz_props::arbitrary_types::ArbPrivateKey;
|
||||
use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state};
|
||||
use nssa::{
|
||||
AccountId, PrivateKey, PublicKey, ValidatedStateDiff, V03State,
|
||||
AccountId, PrivateKey, PublicKey, ValidatedStateDiff,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
PublicTransaction,
|
||||
program::Program,
|
||||
};
|
||||
use nssa_core::account::Nonce;
|
||||
|
||||
@ -33,7 +32,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
|
||||
let nonces = vec![Nonce::from(0_u128), Nonce::from(0_u128)];
|
||||
let message = Message::try_new(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
programs::authenticated_transfer().id(),
|
||||
vec![addr1, addr2],
|
||||
nonces,
|
||||
1337_u64,
|
||||
@ -139,7 +138,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
if signer_addr != other1 && signer_addr != other2 {
|
||||
let nonces = vec![Nonce::from(0_u128)];
|
||||
if let Ok(msg) = Message::try_new(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
programs::authenticated_transfer().id(),
|
||||
vec![other1, other2],
|
||||
nonces,
|
||||
7_u64,
|
||||
@ -173,7 +172,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
.iter()
|
||||
.map(|a| (a.account_id, a.balance))
|
||||
.collect();
|
||||
let state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
let Ok(tx) = arb_fuzz_native_transfer(&mut u, &fuzz_accs) else {
|
||||
return;
|
||||
@ -235,7 +234,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
|
||||
let nonces = vec![Nonce::from(0_u128)];
|
||||
if let Ok(msg) = Message::try_new(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
programs::authenticated_transfer().id(),
|
||||
vec![addr],
|
||||
nonces,
|
||||
42_u64,
|
||||
|
||||
@ -28,7 +28,6 @@ use arbitrary::{Arbitrary, Unstructured};
|
||||
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;
|
||||
|
||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
let mut u = Unstructured::new(data);
|
||||
@ -55,7 +54,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||
// Stateless gate — skip structurally malformed transactions.
|
||||
let Ok(tx) = tx.transaction_stateless_check() else { return; };
|
||||
|
||||
let state = V03State::new_with_genesis_accounts(&init_accs, vec![], 0);
|
||||
let state = fuzz_props::genesis::genesis_state(&init_accs, vec![]);
|
||||
|
||||
// Capture nonces of all known accounts before execution so that
|
||||
// assert_nonce_increment_correctness can verify the +1 step on success.
|
||||
|
||||
@ -21,6 +21,10 @@ risc0-zkvm = { workspace = true }
|
||||
proptest = "1.4"
|
||||
arbitrary = { version = "1", features = ["derive"] }
|
||||
testnet_initial_state = { workspace = true }
|
||||
# Reproduce LEZ genesis (builtin programs + system accounts) in `genesis_state`,
|
||||
# which LEZ moved out of the state machine into these crates.
|
||||
programs = { workspace = true }
|
||||
system_accounts = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.4"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use common::{block::HashableBlockData, transaction::LeeTransaction};
|
||||
use nssa::{AccountId, PrivateKey};
|
||||
use nssa::{AccountId, PrivateKey, PublicKey};
|
||||
|
||||
use crate::arbitrary_types::{ArbAccountId, ArbLeeTransaction, ArbPrivateKey};
|
||||
use crate::arbitrary_types::{ArbLeeTransaction, ArbPrivateKey};
|
||||
use proptest::prelude::*;
|
||||
use testnet_initial_state::initial_pub_accounts_private_keys;
|
||||
|
||||
@ -31,16 +31,29 @@ pub fn signer_account_ids(tx: &common::transaction::LeeTransaction) -> Vec<nssa:
|
||||
}
|
||||
}
|
||||
|
||||
/// The public-account [`AccountId`] that a transaction signed with `key` will have as its
|
||||
/// signer — i.e. exactly what the validator derives from the witness set.
|
||||
///
|
||||
/// Centralises the `AccountId::from(&PublicKey::new_from_private_key(key))` derivation that
|
||||
/// funded-account generation and the privacy synthesiser both depend on, so the funded
|
||||
/// account and its signer never drift apart again.
|
||||
#[must_use]
|
||||
pub fn account_id_for_key(key: &PrivateKey) -> AccountId {
|
||||
AccountId::from(&PublicKey::new_from_private_key(key))
|
||||
}
|
||||
|
||||
// ── Fuzz-driven state generation ─────────────────────────────────────────────
|
||||
|
||||
/// An account with an arbitrary identifier, balance, and private key,
|
||||
/// generated entirely from unstructured fuzzer bytes.
|
||||
/// An account with a fuzz-driven balance and private key, plus the [`AccountId`]
|
||||
/// **derived from that key**.
|
||||
///
|
||||
/// Using random account IDs (rather than the fixed `testnet_initial_state` set)
|
||||
/// exposes state-dependent bugs that only manifest with specific account shapes —
|
||||
/// for example: zero balance, [`u128::MAX`] balance, or a nonce at the
|
||||
/// wrap-around boundary. The [`PrivateKey`] field lets downstream generators
|
||||
/// produce correctly-signed transfers referencing accounts present in this state.
|
||||
/// Deriving `account_id` from `private_key` (rather than drawing it independently)
|
||||
/// is what makes the funded account and its signer the *same* account: a transfer
|
||||
/// signed by `private_key` is then authorized to spend `account_id`, so downstream
|
||||
/// generators like [`arb_fuzz_native_transfer`] can actually reach the **successful**
|
||||
/// state-transition path instead of always being rejected as unauthorized. The key
|
||||
/// is still fuzz-driven, so account shapes (zero balance, [`u128::MAX`] balance,
|
||||
/// nonce wrap-around) remain controlled by the fuzzer.
|
||||
pub struct FuzzAccount {
|
||||
pub account_id: AccountId,
|
||||
pub balance: u128,
|
||||
@ -65,9 +78,9 @@ pub struct FuzzAccount {
|
||||
/// The cap above is only sound if every generated balance survives genesis construction
|
||||
/// unchanged. Two failure modes break that:
|
||||
///
|
||||
/// * **Reserved system accounts.** [`nssa::V03State::new_with_genesis_accounts`] inserts
|
||||
/// the faucet account (`balance = u128::MAX`) and bridge account *after* the supplied
|
||||
/// genesis accounts, overwriting any generated account whose ID collides. A fuzzer that
|
||||
/// * **Reserved system accounts.** [`crate::genesis::genesis_state`] inserts the faucet
|
||||
/// account (`balance = u128::MAX`) and bridge account *after* the supplied genesis
|
||||
/// accounts, overwriting any generated account whose ID collides. A fuzzer that
|
||||
/// lands on the faucet ID would make a caller read back `u128::MAX` instead of the capped
|
||||
/// balance it generated, overflowing the conservation sum — a harness false positive, not
|
||||
/// a protocol bug.
|
||||
@ -81,19 +94,21 @@ pub struct FuzzAccount {
|
||||
/// accounts are returned (an empty state is a valid degenerate case).
|
||||
pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<FuzzAccount>> {
|
||||
let reserved = [
|
||||
nssa::system_faucet_account_id(),
|
||||
nssa::system_bridge_account_id(),
|
||||
system_accounts::faucet_account_id(),
|
||||
system_accounts::bridge_account_id(),
|
||||
];
|
||||
let n = ((u8::arbitrary(u)? as usize) % 8) + 1; // 1..=8
|
||||
|
||||
let mut seen = std::collections::HashSet::with_capacity(n);
|
||||
let mut accounts = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let account_id = ArbAccountId::arbitrary(u)?.0;
|
||||
let private_key = ArbPrivateKey::arbitrary(u)?.0;
|
||||
// Derive the account id from the key so the funded account *is* the signer;
|
||||
// otherwise every "biased-valid" transfer is unauthorized and rejected.
|
||||
let account_id = account_id_for_key(&private_key);
|
||||
// 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.
|
||||
let balance = u128::arbitrary(u)? / 8;
|
||||
let private_key = ArbPrivateKey::arbitrary(u)?.0;
|
||||
|
||||
// Skip IDs that genesis would overwrite (reserved system accounts) or that would
|
||||
// collapse on insertion (duplicates); see the doc comment above.
|
||||
|
||||
64
fuzz_props/src/genesis.rs
Normal file
64
fuzz_props/src/genesis.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Genesis state construction for fuzz targets and tests.
|
||||
//!
|
||||
//! LEZ moved builtin-program and system-account assembly out of the state machine
|
||||
//! (the former `V03State::new_with_genesis_accounts`) into the `programs` /
|
||||
//! `system_accounts` crates. [`genesis_state`] reproduces that genesis setup so fuzz
|
||||
//! targets and tests can build a realistic starting state from arbitrary account data.
|
||||
|
||||
use nssa::{Account, AccountId, V03State};
|
||||
use nssa_core::{Commitment, Nullifier};
|
||||
|
||||
/// Build a genesis [`V03State`] from the given public account balances and private accounts.
|
||||
///
|
||||
/// Mirrors the former `V03State::new_with_genesis_accounts(balances, private_accounts, 0)`:
|
||||
/// every public account is owned by the authenticated-transfer program, the faucet/bridge/clock
|
||||
/// system accounts are present, and the eight builtin programs are registered. The genesis
|
||||
/// timestamp is fixed at 0, matching `system_accounts::clock_account()`'s default (every former
|
||||
/// caller passed `0`).
|
||||
#[must_use]
|
||||
pub fn genesis_state(
|
||||
balances: &[(AccountId, u128)],
|
||||
private_accounts: Vec<(Commitment, Nullifier)>,
|
||||
) -> V03State {
|
||||
let public_accounts = balances
|
||||
.iter()
|
||||
.map(|&(account_id, balance)| {
|
||||
(
|
||||
account_id,
|
||||
Account {
|
||||
program_owner: programs::authenticated_transfer().id(),
|
||||
balance,
|
||||
..Account::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.chain([
|
||||
(
|
||||
system_accounts::faucet_account_id(),
|
||||
system_accounts::faucet_account(),
|
||||
),
|
||||
(
|
||||
system_accounts::bridge_account_id(),
|
||||
system_accounts::bridge_account(),
|
||||
),
|
||||
])
|
||||
.chain(
|
||||
system_accounts::clock_account_ids()
|
||||
.into_iter()
|
||||
.map(|clock_id| (clock_id, system_accounts::clock_account())),
|
||||
);
|
||||
|
||||
V03State::new()
|
||||
.with_public_accounts(public_accounts)
|
||||
.with_private_accounts(private_accounts)
|
||||
.with_programs([
|
||||
programs::authenticated_transfer(),
|
||||
programs::token(),
|
||||
programs::amm(),
|
||||
programs::clock(),
|
||||
programs::ata(),
|
||||
programs::vault(),
|
||||
programs::faucet(),
|
||||
programs::bridge(),
|
||||
])
|
||||
}
|
||||
@ -68,6 +68,7 @@
|
||||
|
||||
pub mod arbitrary_types;
|
||||
pub mod generators;
|
||||
pub mod genesis;
|
||||
pub mod invariants;
|
||||
pub mod privacy;
|
||||
|
||||
|
||||
@ -40,8 +40,7 @@
|
||||
use arbitrary::{Arbitrary, Result as ArbResult, Unstructured};
|
||||
use borsh::to_vec as borsh_to_vec;
|
||||
use nssa::{
|
||||
AccountId, PRIVACY_PRESERVING_CIRCUIT_ID, PrivacyPreservingTransaction, PrivateKey, PublicKey,
|
||||
V03State,
|
||||
AccountId, PRIVACY_PRESERVING_CIRCUIT_ID, PrivacyPreservingTransaction, PrivateKey, V03State,
|
||||
privacy_preserving_transaction::{
|
||||
Message as PPMessage, WitnessSet as PPWitnessSet, circuit::Proof,
|
||||
},
|
||||
@ -54,7 +53,7 @@ use nssa_core::{
|
||||
};
|
||||
use risc0_zkvm::{FakeReceipt, InnerReceipt, ReceiptClaim};
|
||||
|
||||
use crate::generators::FuzzAccount;
|
||||
use crate::generators::{FuzzAccount, account_id_for_key};
|
||||
|
||||
/// Synthesise a [`Proof`] that **passes** `Proof::is_valid_for` for `message` against
|
||||
/// `state`, under `RISC0_DEV_MODE`.
|
||||
@ -193,8 +192,9 @@ pub fn arb_privacy_preserving_tx(
|
||||
) -> ArbResult<PrivacyPreservingTransaction> {
|
||||
// ── Signers ──────────────────────────────────────────────────────────────────────
|
||||
// 0..=3 distinct signers drawn from the keyed fuzz accounts. A signer's public-account
|
||||
// id is `AccountId::from(&its_public_key)` — exactly what the validator derives from the
|
||||
// witness set — and is independent of `FuzzAccount.account_id`.
|
||||
// id is `account_id_for_key(key)` — exactly what the validator derives from the witness
|
||||
// set. Since `arbitrary_fuzz_state` now derives `FuzzAccount.account_id` the same way,
|
||||
// this id also equals that account's `account_id`, so the funded account is the signer.
|
||||
let max_signers = accounts.len().min(3);
|
||||
let n_signers = if max_signers == 0 {
|
||||
0
|
||||
@ -205,7 +205,7 @@ pub fn arb_privacy_preserving_tx(
|
||||
let mut signer_ids: Vec<AccountId> = Vec::with_capacity(n_signers);
|
||||
for _ in 0..n_signers {
|
||||
let key = &accounts[(u8::arbitrary(u)? as usize) % accounts.len()].private_key;
|
||||
let id = AccountId::from(&PublicKey::new_from_private_key(key));
|
||||
let id = account_id_for_key(key);
|
||||
if signer_ids.contains(&id) {
|
||||
continue; // keep signer ids distinct so `nonces` stays 1:1 with `keys`
|
||||
}
|
||||
|
||||
@ -98,8 +98,8 @@ fn fuzz_state_excludes_reserved_system_ids() {
|
||||
// would read back a balance the cap never produced, overflowing conservation sums.
|
||||
// The generator must therefore never emit a reserved system ID.
|
||||
let reserved = [
|
||||
nssa::system_faucet_account_id(),
|
||||
nssa::system_bridge_account_id(),
|
||||
system_accounts::faucet_account_id(),
|
||||
system_accounts::bridge_account_id(),
|
||||
];
|
||||
let buf = distinct_byte_buffer(10_000);
|
||||
let mut u = Unstructured::new(&buf);
|
||||
|
||||
@ -9,7 +9,7 @@ use nssa::V03State;
|
||||
use nssa_core::account::Nonce;
|
||||
|
||||
fn make_empty_state() -> V03State {
|
||||
V03State::new_with_genesis_accounts(&[], vec![], 0)
|
||||
crate::genesis::genesis_state(&[], vec![])
|
||||
}
|
||||
|
||||
fn make_empty_snapshot() -> BalanceSnapshot {
|
||||
@ -49,8 +49,8 @@ fn assert_invariants_does_not_panic_on_success_with_empty_state() {
|
||||
#[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 state_before = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
let state_after = crate::genesis::genesis_state(&[(acc_id, 200)], vec![]);
|
||||
|
||||
let mut balances = std::collections::HashMap::new();
|
||||
balances.insert(acc_id, 100_u128);
|
||||
@ -83,7 +83,7 @@ fn nonce_increment_correctness_passes_when_signer_not_in_snapshot() {
|
||||
#[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 state = crate::genesis::genesis_state(&[], vec![]);
|
||||
|
||||
let mut nonces = std::collections::HashMap::new();
|
||||
nonces.insert(acc_id, Nonce(5));
|
||||
@ -97,8 +97,8 @@ fn nonce_increment_correctness_catches_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 state_before = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
let state_after = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
|
||||
let mut nonces = std::collections::HashMap::new();
|
||||
nonces.insert(acc_id, Nonce(1));
|
||||
@ -208,7 +208,7 @@ fn failed_tx_nonce_stability_name_is_nonempty_and_not_placeholder() {
|
||||
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);
|
||||
let state = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
|
||||
// balances_before claims balance was 50, but state_after (== state) has 100.
|
||||
let mut balances = std::collections::HashMap::new();
|
||||
@ -251,7 +251,7 @@ fn assert_replay_rejection_panics_when_replay_not_rejected() {
|
||||
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 mut scratch_state = crate::genesis::genesis_state(&genesis, vec![]);
|
||||
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)");
|
||||
@ -259,7 +259,7 @@ fn assert_replay_rejection_panics_when_replay_not_rejected() {
|
||||
// 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 mut fresh_state = crate::genesis::genesis_state(&genesis, vec![]);
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
assert_replay_rejection(applied_tx, &mut fresh_state, 1, 1);
|
||||
}));
|
||||
@ -277,8 +277,8 @@ fn assert_replay_rejection_panics_when_replay_not_rejected() {
|
||||
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);
|
||||
let state_before = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
let mut state_after = crate::genesis::genesis_state(&[(acc_id, 100)], vec![]);
|
||||
|
||||
// Lie: claim balance was 50 before. State_after shows 100.
|
||||
// With execution_succeeded=false, StateIsolationOnFailure detects the discrepancy.
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::privacy::{
|
||||
arb_account, arb_privacy_preserving_tx, arb_validity_window, synthesize_passing_proof,
|
||||
};
|
||||
use nssa::privacy_preserving_transaction::{Message as PPMessage, WitnessSet as PPWitnessSet};
|
||||
use nssa::{AccountId, PrivacyPreservingTransaction, PrivateKey, V03State};
|
||||
use nssa::{AccountId, PrivacyPreservingTransaction, PrivateKey};
|
||||
use nssa_core::Commitment;
|
||||
use nssa_core::account::Account;
|
||||
use nssa_core::program::{BlockValidityWindow, TimestampValidityWindow};
|
||||
@ -25,7 +25,7 @@ fn synthesized_proof_reaches_checks_5_6_and_applies() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
let mut state = crate::genesis::genesis_state(&[], vec![]);
|
||||
|
||||
// No signers and a single fresh commitment: checks 1–3 are vacuous/trivially met, so
|
||||
// the only way to reach checks 5–6 is for the synthesised proof to pass check 4.
|
||||
@ -84,7 +84,7 @@ fn synthesized_proof_is_rejected_without_dev_mode() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
let mut state = crate::genesis::genesis_state(&[], vec![]);
|
||||
|
||||
// Same well-formed message as the positive test: checks 1–3 are vacuous/trivially met, so a
|
||||
// rejection can only come from check 4 (proof verification) failing on the fake receipt.
|
||||
@ -303,7 +303,7 @@ fn arb_privacy_preserving_tx_generator_invariants() {
|
||||
.collect();
|
||||
let genesis: Vec<(AccountId, u128)> =
|
||||
accounts.iter().map(|a| (a.account_id, a.balance)).collect();
|
||||
let state = V03State::new_with_genesis_accounts(&genesis, vec![], 0);
|
||||
let state = crate::genesis::genesis_state(&genesis, vec![]);
|
||||
|
||||
let known_ids: std::collections::HashSet<AccountId> =
|
||||
accounts.iter().map(|a| a.account_id).collect();
|
||||
|
||||
@ -9,7 +9,7 @@ fn make_test_state() -> V03State {
|
||||
.iter()
|
||||
.map(|(id, _)| (*id, 1_000_000_u128))
|
||||
.collect();
|
||||
V03State::new_with_genesis_accounts(&init_accs, vec![], 0)
|
||||
crate::genesis::genesis_state(&init_accs, vec![])
|
||||
}
|
||||
|
||||
proptest! {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user