fix: sync with updated LEZ API

This commit is contained in:
Roman 2026-06-25 13:15:33 +08:00
parent aeec0e275a
commit e4c4d1eca7
No known key found for this signature in database
GPG Key ID: 583BDF43C238B83E
26 changed files with 911 additions and 1259 deletions

View File

@ -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: 64f8444a67e5cc93fb755f9c9292034bed2a657d
runs:
using: composite

570
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

Binary file not shown.

1436
fuzz/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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.

View File

@ -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());

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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,

View File

@ -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.

View File

@ -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"

64
fuzz_props/src/genesis.rs Normal file
View 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(),
])
}

View File

@ -68,6 +68,7 @@
pub mod arbitrary_types;
pub mod generators;
pub mod genesis;
pub mod invariants;
pub mod privacy;

View File

@ -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.

View File

@ -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 13 are vacuous/trivially met, so
// the only way to reach checks 56 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 13 are vacuous/trivially met, so a
// rejection can only come from check 4 (proof verification) failing on the fake receipt.
@ -268,7 +268,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 mut rng = Rng::new();
let mut buf = vec![0_u8; 8192];

View File

@ -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! {