Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
99e481e
fix(ol/stf): mod.rs inside src/tests does not need #[cfg(test)]
krsnapaudel May 8, 2026
882adef
test(ol/stf): add behavior fixtures for STF tests
krsnapaudel May 8, 2026
3e965ea
refactor(ol/stf): use fixture helpers in DA preseal tests
krsnapaudel May 13, 2026
ff6582c
test(ol/stf): reuse terminal manifest helpers in chain tests
krsnapaudel May 8, 2026
8bd7a1b
fix(ol/stf): reject SAU updates with unconsumed proofs
krsnapaudel Apr 28, 2026
6cb11ca
test(ol/stf): add direct SAU proof exhaustion invariants
krsnapaudel Apr 25, 2026
44da5ba
test(ol/stf): revive body-root mismatch verification test
krsnapaudel Apr 25, 2026
97bdebf
test(ol/stf): pin mid-block partial execution semantics
krsnapaudel Apr 25, 2026
e5ba682
refactor(ol/stf): split tests into themed modules
krsnapaudel Apr 25, 2026
63eb01c
test(ol/stf): assert failed SAU updates leave state unchanged
krsnapaudel Apr 27, 2026
59b9e6a
test(ol/stf): add positive controls to tamper tests
krsnapaudel Apr 27, 2026
36f28d7
test(ol/stf): complete partial assertions in existing tests
krsnapaudel Apr 27, 2026
5eb2932
test(ol/stf): pin bridge gateway silent-drop behavior
krsnapaudel Apr 27, 2026
6a2b007
test(ol/stf): assert SAU output messages reach snark inboxes
krsnapaudel Apr 27, 2026
3389560
test(ol/stf): reject overflowing SAU transfer sums
krsnapaudel Apr 27, 2026
8e654ec
test(ol/stf): harden SAU zero-balance boundary assertions
krsnapaudel Apr 27, 2026
29d45f4
test(ol/stf): cover multi-SAU same-block scenarios
krsnapaudel Apr 27, 2026
7e5f0a8
test(ol/stf): reject replayed SAU updates across blocks
krsnapaudel Apr 27, 2026
8cfed36
test(ol/stf): pin TxConstraints min/max slot boundaries
krsnapaudel Apr 27, 2026
960fd93
test(ol/stf): reject proofs for wrong ledger reference claims
krsnapaudel Apr 27, 2026
feb5ee4
test(ol/stf): cover SAU and log boundary limits
krsnapaudel Apr 27, 2026
98c9a12
test(ol/stf): cover ASM manifest processing boundaries
krsnapaudel Apr 27, 2026
1788897
test(ol/stf): cover chain processing error branches
krsnapaudel Apr 27, 2026
2c23b2f
test(ol/stf): cover GAM structural validation
krsnapaudel Apr 27, 2026
8b1458e
test(ol/stf): cover logs root padding parity
krsnapaudel Apr 27, 2026
1fb011b
test(ol/stf): cover epoch preseal diff verification
krsnapaudel Apr 27, 2026
d9bd651
test(ol/stf): cover dependent SAU update sequences
krsnapaudel Apr 27, 2026
37bb194
test(ol/stf): reject reordered inbox message processing
krsnapaudel Apr 27, 2026
603a1e9
test(ol/stf): add STF stress coverage
krsnapaudel Apr 27, 2026
42a182f
test(ol/stf): cover block terminality verification
krsnapaudel Apr 28, 2026
35e4b12
test(ol/stf): cover SAU target type and sequence bounds
krsnapaudel Apr 28, 2026
f9a2d01
test(ol/stf): cover staging-layer STF execution paths
krsnapaudel Apr 28, 2026
8bfdecd
test(ol/stf): cover epoch-diff verification happy path
krsnapaudel Apr 28, 2026
4d6250c
fix: tests outside of ol/stf should use make_genesis_state
krsnapaudel May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions bin/prover-perf/src/programs/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use strata_identifiers::Buf64;
use strata_ledger_types::IStateAccessor;
use strata_ol_chain_types_new::{OLBlock, SignedOLBlockHeader};
use strata_ol_da::{GlobalStateDiff, LedgerDiff, OLDaPayloadV1, StateDiff};
use strata_ol_stf::test_utils::{build_empty_chain, create_test_genesis_state};
use strata_ol_stf::test_utils::{build_empty_chain, make_genesis_state};
use strata_proofimpl_checkpoint::program::{CheckpointProgram, CheckpointProverInput};
use tracing::info;
use zkaleido::{ExecutionSummary, ZkVmHost, ZkVmProgram};
Expand All @@ -26,15 +26,15 @@ const SLOTS_PER_EPOCH: u64 = 9;
const NUM_BLOCKS: usize = 10;

fn prepare_checkpoint_input() -> CheckpointProverInput {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let mut blocks = build_empty_chain(&mut state, NUM_BLOCKS, SLOTS_PER_EPOCH)
.expect("build_empty_chain should succeed");

// First block is the parent (genesis); remaining blocks are the proving batch.
let parent = blocks.remove(0).into_header();

// Rebuild start_state: execute just the genesis block to get state after genesis.
let mut start_state = create_test_genesis_state();
let mut start_state = make_genesis_state();
let _ = build_empty_chain(&mut start_state, 1, SLOTS_PER_EPOCH)
.expect("build_empty_chain should succeed");

Expand Down
14 changes: 6 additions & 8 deletions crates/consensus-logic/src/fcm/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ mod tests {
use strata_ol_state_support_types::MemoryStateBaseLayer;
use strata_ol_state_types::OLState;
use strata_ol_stf::{
test_utils::{create_test_genesis_state, execute_block},
test_utils::{execute_block, make_genesis_state},
BlockComponents, BlockInfo, CompletedBlock,
};
use strata_predicate::PredicateKey;
Expand Down Expand Up @@ -954,7 +954,7 @@ mod tests {
}

fn execute_test_genesis() -> (ExecutedBlock, MemoryStateBaseLayer) {
let mut genesis_state = create_test_genesis_state();
let mut genesis_state = make_genesis_state();
let genesis_manifest = AsmManifest::new(
1,
L1BlockId::from(Buf32::zero()),
Expand Down Expand Up @@ -1404,7 +1404,7 @@ mod tests {
#[tokio::test]
async fn stub_storage_round_trips_executed_blocks_and_epochs() {
let storage = StubFcmStorage::new();
let state = create_test_genesis_state().state().clone();
let state = make_genesis_state().state().clone();
let block = make_storage_block(0, OLBlockId::from(Buf32::zero()));
let blkid = block.header().compute_blkid();
let commitment = OLBlockCommitment::new(block.header().slot(), blkid);
Expand Down Expand Up @@ -1461,14 +1461,12 @@ mod tests {

ctx.storage().put_executed_block(
genesis,
create_test_genesis_state().state().clone(),
make_genesis_state().state().clone(),
BlockStatus::Valid,
);
ctx.storage().put_ol_block(block);
ctx.storage().put_toplevel_ol_state(
block_commitment,
create_test_genesis_state().state().clone(),
);
ctx.storage()
.put_toplevel_ol_state(block_commitment, make_genesis_state().state().clone());
ctx.storage().put_canonical_epoch_commitment(genesis_epoch);

let mut fcm_state = init_fcm_service_state(PredicateKey::always_accept(), ctx.clone())
Expand Down
23 changes: 21 additions & 2 deletions crates/ledger-types/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use strata_acct_types::{AccountId, AccountSerial, AccountTypeId, AcctError, BitcoinAmount};
use strata_codec::CodecError;
use strata_identifiers::{Epoch, OLTxId, Slot};
use strata_identifiers::{Epoch, L1Height, OLTxId, Slot};
use strata_snark_acct_types::Seqno;
use thiserror::Error;

Expand Down Expand Up @@ -84,7 +84,10 @@ pub type ExecResult<T> = Result<T, ExecError>;
#[derive(Debug, Error)]
pub enum ExecError {
#[error("header epoch does not match state epoch (header {0}, state {1})")]
EpochMismatch(Epoch, Epoch),
HeaderEpochMismatch(Epoch, Epoch),

#[error("context epoch does not match state epoch (context {0}, state {1})")]
ContextEpochMismatch(Epoch, Epoch),

/// Signature is invalid, for some purpose.
#[error("signature for {0} is invalid")]
Expand Down Expand Up @@ -113,6 +116,22 @@ pub enum ExecError {
#[error("chain integrity invalid")]
ChainIntegrity,

#[error("ASM manifest height mismatch at index {index} (expected {expected}, actual {actual})")]
AsmManifestHeightMismatch {
expected: L1Height,
actual: L1Height,
index: usize,
},

#[error("ASM manifest height overflow")]
AsmManifestHeightOverflow,

#[error("epoch overflow")]
EpochOverflow,

#[error("slot overflow")]
SlotOverflow,

#[error("tried to interact with nonexistent account ({0:?})")]
UnknownAccount(AccountId),

Expand Down
4 changes: 2 additions & 2 deletions crates/ol/da/src/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ impl<S: IStateAccessorMut> DaScheme<S> for OLDaSchemeV1 {
mod tests {
use strata_da_framework::DaCounter;
use strata_ledger_types::IStateAccessor;
use strata_ol_stf::test_utils::create_test_genesis_state;
use strata_ol_stf::test_utils::make_genesis_state;

use super::*;
use crate::{GlobalStateDiff, LedgerDiff, StateDiff};

#[test]
fn test_ol_da_scheme_v1_apply_updates_global_slot() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let start_slot = state.cur_slot();

let diff = StateDiff::new(
Expand Down
12 changes: 6 additions & 6 deletions crates/ol/da/src/types/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ mod tests {
use strata_identifiers::AccountSerial;
use strata_ledger_types::{IStateAccessor, IStateAccessorMut, NewAccountData};
use strata_ol_state_support_types::MemoryStateBaseLayer;
use strata_ol_stf::test_utils::create_test_genesis_state;
use strata_ol_stf::test_utils::make_genesis_state;
use strata_predicate::PredicateKey;

use super::*;
Expand Down Expand Up @@ -376,7 +376,7 @@ mod tests {

#[test]
fn test_ol_state_diff_poll_context_rejects_existing_new_account() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let account_id = test_account_id(2);
let new_acct = NewAccountData::new(BitcoinAmount::from_sat(10), NewAccountTypeState::Empty);
state
Expand All @@ -403,7 +403,7 @@ mod tests {

#[test]
fn test_ol_state_diff_apply_updates_balance() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let account_id = test_account_id(3);
let new_acct =
NewAccountData::new(BitcoinAmount::from_sat(1_000), NewAccountTypeState::Empty);
Expand Down Expand Up @@ -436,7 +436,7 @@ mod tests {

#[test]
fn test_ol_state_diff_apply_decreases_balance() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let account_id = test_account_id(30);
let new_acct =
NewAccountData::new(BitcoinAmount::from_sat(2_000), NewAccountTypeState::Empty);
Expand Down Expand Up @@ -468,7 +468,7 @@ mod tests {

#[test]
fn test_ol_state_diff_apply_updates_limbo_funds() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
assert_eq!(state.limbo_funds(), BitcoinAmount::from_sat(0));

let global_diff = GlobalStateDiff::new(
Expand Down Expand Up @@ -496,7 +496,7 @@ mod tests {

#[test]
fn test_ol_state_diff_apply_snark_seqno() {
let mut state = create_test_genesis_state();
let mut state = make_genesis_state();
let account_id = test_account_id(4);
let new_acct = NewAccountData::new(
BitcoinAmount::from_sat(500),
Expand Down
1 change: 1 addition & 0 deletions crates/ol/stf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tracing.workspace = true
[dev-dependencies]
ssz_primitives.workspace = true
ssz_types.workspace = true
strata-asm-proto-checkpoint-types.workspace = true
strata-ol-params.workspace = true
strata-ol-state-support-types.workspace = true
strata-predicate.workspace = true
Expand Down
14 changes: 10 additions & 4 deletions crates/ol/stf/src/chain_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ pub fn process_epoch_initial<S: IStateAccessor>(
let state_cur_epoch = state.cur_epoch();
let block_cur_epoch = context.cur_epoch();
if block_cur_epoch != state_cur_epoch {
return Err(ExecError::ChainIntegrity);
return Err(ExecError::ContextEpochMismatch(
block_cur_epoch,
state_cur_epoch,
));
}

// 3. Insert the previous terminal info into the MMR.
Expand All @@ -49,15 +52,18 @@ pub fn process_block_start<S: IStateAccessorMut>(
// also error out on this when constructing blocks?
let header_epoch = context.epoch();
if let Some(ph) = context.parent_header() {
let exp_epoch = ph.epoch() + ph.is_terminal() as u32;
let exp_epoch = ph
.epoch()
.checked_add(u32::from(ph.is_terminal()))
.ok_or(ExecError::EpochOverflow)?;
if context.epoch() != exp_epoch {
return Err(ExecError::IncorrectEpoch(
ph.epoch(),
context.epoch(),
ph.is_terminal(),
));
}
let exp_slot = ph.slot() + 1;
let exp_slot = ph.slot().checked_add(1).ok_or(ExecError::SlotOverflow)?;
if context.slot() != exp_slot {
return Err(ExecError::IncorrectSlot {
expected: exp_slot,
Expand All @@ -71,7 +77,7 @@ pub fn process_block_start<S: IStateAccessorMut>(
// 2. Make sure that the current state epoch matches the header's epoch.
let state_epoch = state.cur_epoch();
if header_epoch != state_epoch {
return Err(ExecError::EpochMismatch(header_epoch, state_epoch));
return Err(ExecError::HeaderEpochMismatch(header_epoch, state_epoch));
}

// 3. Update the global state's current slot to match the block's slot
Expand Down
11 changes: 11 additions & 0 deletions crates/ol/stf/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ impl<'b> BlockContext<'b> {
}
}

#[cfg(test)]
pub(crate) fn new_unchecked(
block_info: &'b BlockInfo,
parent_header: Option<&'b OLBlockHeader>,
) -> Self {
Self {
block_info,
parent_header,
}
}

pub fn block_info(&self) -> &BlockInfo {
self.block_info
}
Expand Down
38 changes: 35 additions & 3 deletions crates/ol/stf/src/manifest_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,19 @@ pub fn process_block_manifests<S: IStateAccessorMut>(
for (i, mf) in mf_cont.manifests().iter().enumerate() {
// New manifests in a segment are strictly after the state's current
// last seen height.
let real_height = orig_l1_height + i as u32 + 1;
let real_height = next_manifest_height(orig_l1_height, i)?;
if mf.height() != real_height {
warn!(
expected_height = real_height,
got_height = mf.height(),
index = i,
"asm manifest height mismatch",
);
return Err(ExecError::ChainIntegrity);
return Err(ExecError::AsmManifestHeightMismatch {
expected: real_height,
actual: mf.height(),
index: i,
});
}
trace!(
height = real_height,
Expand All @@ -75,7 +79,9 @@ pub fn process_block_manifests<S: IStateAccessorMut>(
}

// 2. Finally, we can update the epoch to get it ready for the next epoch.
let new_epoch = terminating_epoch + 1;
let new_epoch = terminating_epoch
.checked_add(1)
.ok_or(ExecError::EpochOverflow)?;
info!(
from_epoch = terminating_epoch,
to_epoch = new_epoch,
Expand All @@ -87,6 +93,14 @@ pub fn process_block_manifests<S: IStateAccessorMut>(
Ok(())
}

fn next_manifest_height(last_l1_height: L1Height, index: usize) -> ExecResult<L1Height> {
let offset = L1Height::try_from(index).map_err(|_| ExecError::AsmManifestHeightOverflow)?;
last_l1_height
.checked_add(offset)
.and_then(|height| height.checked_add(1))
.ok_or(ExecError::AsmManifestHeightOverflow)
}

fn process_asm_manifest<S: IStateAccessorMut>(
state: &mut S,
real_height: L1Height,
Expand Down Expand Up @@ -283,3 +297,21 @@ fn process_ee_predicate_key_update<S: IStateAccessorMut>(

Ok(())
}
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn next_manifest_height_rejects_l1_height_overflow() {
assert_eq!(next_manifest_height(0, 0).expect("height should fit"), 1);

assert!(matches!(
next_manifest_height(L1Height::MAX, 0),
Err(ExecError::AsmManifestHeightOverflow)
));
assert!(matches!(
next_manifest_height(L1Height::MAX - 1, 1),
Err(ExecError::AsmManifestHeightOverflow)
));
}
}
44 changes: 44 additions & 0 deletions crates/ol/stf/src/proof_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,48 @@ mod tests {
"Sp1Groth16 verifier feature must be enabled for OL STF replay"
);
}

#[test]
fn test_is_all_done_requires_consuming_all_proofs() {
let acc_proofs: Vec<RawMerkleProof> = (0..2).map(|_| RawMerkleProof::new_zero()).collect();
let pred_proofs: Vec<ProofSatisfier> = (0..2)
.map(|i| ProofSatisfier {
proof: vec![i as u8]
.try_into()
.expect("proof should not exceed capacity"),
})
.collect();

let tx_proofs = TxProofs::new(
Some(ProofSatisfierList {
proofs: pred_proofs
.try_into()
.expect("proofs should not exceed capacity"),
}),
Some(RawMerkleProofList {
proofs: acc_proofs
.try_into()
.expect("proofs should not exceed capacity"),
}),
);
let mut tracker = TxProofsTracker::from_txproofs(&tx_proofs);

// Consume one from each stream and ensure exhaustion is still false.
tracker
.inc_next_acc_proof()
.expect("first acc inc should work");
tracker
.inc_next_pred_proof()
.expect("first pred inc should work");
assert!(!tracker.is_all_done(), "one proof in each stream remains");

// Consume remaining proofs and ensure tracker now reports exhausted.
tracker
.inc_next_acc_proof()
.expect("second acc inc should work");
tracker
.inc_next_pred_proof()
.expect("second pred inc should work");
assert!(tracker.is_all_done(), "all proofs should be consumed");
}
}
Loading
Loading