diff --git a/crates/blockchain/state_transition/tests/stf_spectests.rs b/crates/blockchain/state_transition/tests/stf_spectests.rs index 8a92fd4e..458cc2c0 100644 --- a/crates/blockchain/state_transition/tests/stf_spectests.rs +++ b/crates/blockchain/state_transition/tests/stf_spectests.rs @@ -53,6 +53,20 @@ fn run(path: &Path) -> datatest_stable::Result<()> { match (result, test.post) { (Ok(_), Some(expected_post)) => { compare_post_states(&post_state, &expected_post, &block_registry)?; + // Hardening: the full post-state hash_tree_root must equal the + // fixture's `postStateRoot`. The per-field `post` checks only + // pin the fields the fixture chose to enumerate; this catches + // any state field they omit. + if let Some(expected_root) = test.post_state_root { + let actual_root = post_state.hash_tree_root(); + if actual_root != expected_root { + return Err(format!( + "Test '{name}' post-state root mismatch: \ + expected {expected_root:?}, got {actual_root:?}" + ) + .into()); + } + } } (Ok(_), None) => { return Err( diff --git a/crates/blockchain/state_transition/tests/types.rs b/crates/blockchain/state_transition/tests/types.rs index 0e766a69..262785b0 100644 --- a/crates/blockchain/state_transition/tests/types.rs +++ b/crates/blockchain/state_transition/tests/types.rs @@ -33,11 +33,10 @@ pub struct StateTransitionTest { pub pre: TestState, pub blocks: Vec, pub post: Option, - /// Expected post-state `hash_tree_root`, present alongside `post`. Captured - /// only so `deny_unknown_fields` accepts it; the per-field `post` checks - /// already pin the post-state. + /// Expected post-state `hash_tree_root`, present alongside `post`. Asserted + /// against the full post-state root after the per-field `post` checks, so + /// any state field those checks don't enumerate is still pinned. #[serde(rename = "postStateRoot")] - #[allow(dead_code)] pub post_state_root: Option, /// Expected rejection reason for negative cases. Captured only so /// `deny_unknown_fields` accepts it; failure is asserted via a missing