fix: targets and props

This commit is contained in:
Roman 2026-04-24 12:04:20 +08:00
parent 9c7409fbe7
commit f411509eb2
No known key found for this signature in database
GPG Key ID: 583BDF43C238B83E
5 changed files with 33 additions and 33 deletions

View File

@ -18,12 +18,10 @@ fuzz_target!(|data: &[u8]| {
let recomputed2 = hashable.block_hash();
assert_eq!(recomputed, recomputed2, "block_hash() is not deterministic");
// Log divergence between stored and recomputed hash for coverage guidance.
// We do NOT assert equality because adversarially-crafted fuzz inputs can
// store an arbitrary hash field without matching the body content.
let stored_hash = block.header.hash;
if stored_hash == recomputed {
// Hashes match — this is the expected case for a valid sequencer-produced block
let _ = stored_hash;
}
// We intentionally do NOT assert that the stored header hash equals the
// recomputed one: adversarially-crafted fuzz inputs can store an arbitrary
// hash field that does not match the body content, and that is a valid input
// for the purpose of this target (which only tests hash stability, not
// block validity).
let _ = (block.header.hash, recomputed);
});

View File

@ -21,7 +21,7 @@ fuzz_target!(|data: &[u8]| {
// Generate up to 8 transactions and apply them
let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 8;
for _ in 0..n_txs {
for i in 0..n_txs {
let Ok(tx) = arbitrary_transaction(&mut u) else {
break;
};
@ -34,8 +34,12 @@ fuzz_target!(|data: &[u8]| {
// Clone state before to detect state leakage on failure
let state_snapshot = state.clone();
let block_id: u64 = 1;
let timestamp: u64 = 0;
// Advance block_id and timestamp each iteration so the state machine
// sees a realistic monotonically-increasing context. Using the same
// block_id=1 / timestamp=0 for every tx hides bugs that only manifest
// when the block context changes across a multi-transaction sequence.
let block_id: u64 = 1 + u64::from(i);
let timestamp: u64 = u64::from(i);
let result = tx.execute_check_on_state(&mut state, block_id, timestamp);
if result.is_err() {

View File

@ -13,7 +13,7 @@ fuzz_target!(|data: &[u8]| {
let tx2 = borsh::from_slice::<NSSATransaction>(&re_encoded)
.expect("second decode of re-encoded tx must succeed");
assert_eq!(
borsh::to_vec(&tx).unwrap(),
re_encoded,
borsh::to_vec(&tx2).unwrap(),
"NSSATransaction roundtrip encoding divergence"
);

View File

@ -1,6 +1,8 @@
use arbitrary::{Arbitrary, Unstructured};
use common::{block::HashableBlockData, transaction::NSSATransaction};
use nssa::{AccountId, PrivateKey};
use crate::arbitrary_types::ArbNSSATransaction;
use proptest::prelude::*;
use testnet_initial_state::initial_pub_accounts_private_keys;
@ -9,26 +11,15 @@ use testnet_initial_state::initial_pub_accounts_private_keys;
/// A best-effort attempt to create a structurally plausible `NSSATransaction`
/// from unstructured bytes. Falls back to raw borsh decoding.
pub fn arbitrary_transaction(u: &mut Unstructured<'_>) -> arbitrary::Result<NSSATransaction> {
// Prefer structured generation; raw decode as fallback
// Prefer structured generation (via Arbitrary impls); raw borsh decode as fallback.
if bool::arbitrary(u)? {
let raw = Vec::<u8>::arbitrary(u)?;
borsh::from_slice::<NSSATransaction>(&raw).map_err(|_| arbitrary::Error::IncorrectFormat)
} else {
// Generate a minimal empty public tx using known test keys
let signing_key = PrivateKey::try_new([u8::arbitrary(u)?; 32])
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let message = nssa::public_transaction::Message::try_new(
program_id,
vec![],
vec![],
u128::arbitrary(u)?,
)
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
let witness = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
Ok(NSSATransaction::Public(nssa::PublicTransaction::new(
message, witness,
)))
// Use the full ArbNSSATransaction generator, which produces both Public and
// ProgramDeployment variants with realistic account IDs, nonces, and witness sets —
// far richer than the previous degenerate single-byte key / empty-message path.
ArbNSSATransaction::arbitrary(u).map(|w| w.0)
}
}

View File

@ -44,11 +44,18 @@ impl ProtocolInvariant for StateIsolationOnFailure {
fn check(&self, ctx: &InvariantCtx<'_>) -> Option<InvariantViolation> {
if ctx.result.is_err() {
// Capture snapshot totals for comparison
let _before_total = ctx.balances_before.total();
let _state_after = ctx.state_after;
// TODO: implement actual balance extraction from V03State once API is confirmed
// (use state_after.get_account_by_id per known account and compare with before)
for (acc_id, &expected_balance) in &ctx.balances_before.0 {
let actual_balance = ctx.state_after.get_account_by_id(*acc_id).balance;
if actual_balance != expected_balance {
return Some(InvariantViolation {
invariant: self.name(),
message: format!(
"balance changed despite tx rejection: account {:?} had {expected_balance} before, {actual_balance} after",
acc_id,
),
});
}
}
}
None
}