From 1eb48de797efdf971b8f22f338925351efabe426 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 27 Oct 2025 09:21:28 -0700 Subject: [PATCH 01/22] Add support for pre naka blocks Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 10 + .../chainstate/nakamoto/coordinator/mod.rs | 41 +- .../src/chainstate/nakamoto/tests/node.rs | 69 ++ stackslib/src/chainstate/tests/consensus.rs | 311 ++++----- stackslib/src/chainstate/tests/mod.rs | 603 +++++++++++++++--- ...nsensus__append_stx_transfers_success.snap | 4 +- ...error_expression_stack_depth_too_deep.snap | 14 +- ...nsensus__successfully_deploy_and_call.snap | 70 +- stackslib/src/net/tests/mod.rs | 25 +- 9 files changed, 797 insertions(+), 350 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index f25b2e5fc20..26f22dab0dd 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1732,6 +1732,16 @@ impl< Ok(None) } + /// A helper function for exposing the private process_new_pox_anchor_test function + #[cfg(test)] + pub fn process_new_pox_anchor_test( + &mut self, + block_id: BlockHeaderHash, + already_processed_burn_blocks: &mut HashSet, + ) -> Result, Error> { + self.process_new_pox_anchor(block_id, already_processed_burn_blocks) + } + /// Process a new PoX anchor block, possibly resulting in the PoX history being unwound and /// replayed through a different sequence of consensus hashes. If the new anchor block causes /// the node to reach a prepare-phase that elects a network-affirmed anchor block that we don't diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index ce736616418..9fd2ae262df 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -363,28 +363,27 @@ pub fn load_nakamoto_reward_set( provider: &U, ) -> Result, Error> { let cycle_start_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); - let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), cycle_start_height)? - .unwrap_or_else(|| { - panic!( - "FATAL: no epoch defined for burn height {}", - cycle_start_height - ) - }); - - // Find the first Stacks block in this reward cycle's preceding prepare phase. - // This block will have invoked `.signers.stackerdb-set-signer-slots()` with the reward set. - // Note that we may not have processed it yet. But, if we do find it, then it's - // unique (and since Nakamoto Stacks blocks are processed in order, the anchor block - // cannot change later). - let first_epoch30_reward_cycle = burnchain - .block_height_to_reward_cycle(epoch_at_height.start_height) - .expect("FATAL: no reward cycle for epoch 3.0 start height"); - - if !epoch_at_height - .epoch_id - .uses_nakamoto_reward_set(reward_cycle, first_epoch30_reward_cycle) - { + .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {cycle_start_height}")); + let is_pre_naka_epoch = if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { + true + } else { + let epoch_30 = + SortitionDB::get_stacks_epoch_by_epoch_id(sort_db.conn(), &StacksEpochId::Epoch30)? + .unwrap_or_else(|| panic!("FATAL: no Nakamoto epoch defined")); + // Find the first Stacks block in this reward cycle's preceding prepare phase. + // This block will have invoked `.signers.stackerdb-set-signer-slots()` with the reward set. + // Note that we may not have processed it yet. But, if we do find it, then it's + // unique (and since Nakamoto Stacks blocks are processed in order, the anchor block + // cannot change later). + let first_epoch30_reward_cycle = burnchain + .block_height_to_reward_cycle(epoch_30.start_height) + .expect("FATAL: no reward cycle for epoch 3.0 start height"); + !epoch_at_height + .epoch_id + .uses_nakamoto_reward_set(reward_cycle, first_epoch30_reward_cycle) + }; + if is_pre_naka_epoch { // in epoch 2.5, and in the first reward cycle of epoch 3.0, the reward set can *only* be found in the sortition DB. // The nakamoto chain-processing rules aren't active yet, so we can't look for the reward // cycle info in the nakamoto chain state. diff --git a/stackslib/src/chainstate/nakamoto/tests/node.rs b/stackslib/src/chainstate/nakamoto/tests/node.rs index 5cc36e4bb91..20448a4215f 100644 --- a/stackslib/src/chainstate/nakamoto/tests/node.rs +++ b/stackslib/src/chainstate/nakamoto/tests/node.rs @@ -1061,6 +1061,75 @@ impl TestStacksNode { Ok((block, size, cost)) } + /// Insert a staging pre-Nakamoto block and microblocks + /// then process them as the next ready block + /// NOTE: Will panic if called with unprocessed staging + /// blocks already in the queue. + pub fn process_pre_nakamoto_next_ready_block<'a>( + stacks_node: &mut TestStacksNode, + sortdb: &mut SortitionDB, + miner: &mut TestMiner, + tenure_id_consensus_hash: &ConsensusHash, + coord: &mut ChainsCoordinator< + 'a, + TestEventObserver, + (), + OnChainRewardSetProvider<'a, TestEventObserver>, + (), + (), + BitcoinIndexer, + >, + block: &StacksBlock, + microblocks: &[StacksMicroblock], + ) -> Result, ChainstateError> { + // First append the block to the staging blocks + { + let ic = sortdb.index_conn(); + let tip = SortitionDB::get_canonical_burn_chain_tip(&ic).unwrap(); + stacks_node + .chainstate + .preprocess_stacks_epoch(&ic, &tip, block, microblocks) + .unwrap(); + } + + let canonical_sortition_tip = coord.canonical_sortition_tip.clone().expect( + "FAIL: processing a new Stacks block, but don't have a canonical sortition tip", + ); + let mut sort_tx = sortdb.tx_begin_at_tip(); + let res = stacks_node + .chainstate + .process_next_staging_block(&mut sort_tx, coord.dispatcher) + .map(|(epoch_receipt, _)| epoch_receipt)?; + sort_tx.commit()?; + if let Some(block_receipt) = res.as_ref() { + let in_sortition_set = coord + .sortition_db + .is_stacks_block_in_sortition_set( + &canonical_sortition_tip, + &block_receipt.header.anchored_header.block_hash(), + ) + .unwrap(); + if in_sortition_set { + let block_hash = block_receipt.header.anchored_header.block_hash(); + // Was this block sufficiently confirmed by the prepare phase that it was a PoX + // anchor block? And if we're in epoch 2.1, does it match the heaviest-confirmed + // block-commit in the burnchain DB, and is it affirmed by the majority of the + // network? + if let Some(pox_anchor) = coord + .sortition_db + .is_stacks_block_pox_anchor(&block_hash, &canonical_sortition_tip) + .unwrap() + { + debug!("Discovered PoX anchor block {block_hash} off of canonical sortition tip {canonical_sortition_tip}"); + coord + .process_new_pox_anchor_test(pox_anchor, &mut HashSet::new()) + .unwrap(); + } + } + } + Ok(res) + } + /// Insert a staging Nakamoto block as a pushed block and /// then process it as the next ready block /// NOTE: Will panic if called with unprocessed staging diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 851ded28e55..bd2bf71a888 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -17,14 +17,11 @@ use std::sync::LazyLock; use clarity::boot_util::boot_code_addr; use clarity::codec::StacksMessageCodec; -use clarity::consts::{ - CHAIN_ID_TESTNET, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, - PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, - PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, - PEER_VERSION_EPOCH_3_3, STACKS_EPOCH_MAX, +use clarity::consts::CHAIN_ID_TESTNET; +use clarity::types::chainstate::{ + StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey, TrieHash, }; -use clarity::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey, TrieHash}; -use clarity::types::{StacksEpoch, StacksEpochId}; +use clarity::types::StacksEpochId; use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum}; use clarity::util::secp256k1::MessageSignature; use clarity::vm::ast::stack_depth_checker::AST_CALL_STACK_DEPTH_BUFFER; @@ -41,15 +38,13 @@ use crate::chainstate::stacks::db::{ClarityTx, StacksChainState, StacksEpochRece use crate::chainstate::stacks::events::TransactionOrigin; use crate::chainstate::stacks::tests::TestStacksNode; use crate::chainstate::stacks::{ - Error as ChainstateError, StacksTransaction, TenureChangeCause, TransactionContractCall, - TransactionPayload, TransactionSmartContract, MINER_BLOCK_CONSENSUS_HASH, - MINER_BLOCK_HEADER_HASH, + Error as ChainstateError, StacksTransaction, TransactionContractCall, TransactionPayload, + TransactionSmartContract, MINER_BLOCK_CONSENSUS_HASH, MINER_BLOCK_HEADER_HASH, }; use crate::chainstate::tests::TestChainstate; use crate::core::test_util::{ make_contract_call, make_contract_publish_versioned, make_stacks_transfer_tx, to_addr, }; -use crate::core::{EpochList, BLOCK_LIMIT_MAINNET_21}; use crate::net::tests::NakamotoBootPlan; /// The epochs to test for consensus are the current and upcoming epochs. @@ -116,13 +111,13 @@ impl ContractConsensusTest<'_> { /// Generates and executes the given transaction in a new block. /// Increases the nonce if the transaction succeeds. - fn append_tx_block(&mut self, tx_spec: &TestTxSpec) -> ExpectedResult { + fn append_tx_block(&mut self, tx_spec: &TestTxSpec, is_naka_block: bool) -> ExpectedResult { let tx = self.tx_factory.generate_tx(tx_spec); let block = TestBlock { transactions: vec![tx], }; - let result = self.consensus_test.append_block(block); + let result = self.consensus_test.append_block(block, is_naka_block); if let ExpectedResult::Success(_) = result { self.tx_factory.increase_nonce_for_tx(tx_spec); @@ -195,7 +190,13 @@ impl ContractConsensusTest<'_> { // Create epoch blocks by pairing each epoch with its corresponding transactions let mut results = vec![]; all_epochs.into_iter().for_each(|epoch| { - self.consensus_test.advance_to_epoch(epoch); + // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers + let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); + self.consensus_test + .chain + .advance_into_epoch(&private_key, epoch); + + let is_naka_block = epoch >= StacksEpochId::Epoch30; if deploy_epochs.contains(&epoch) { let clarity_versions = clarity_versions_for_epoch(epoch); let epoch_name = format!("Epoch{}", epoch.to_string().replace(".", "_")); @@ -205,24 +206,30 @@ impl ContractConsensusTest<'_> { version.to_string().replace(" ", "") ); contract_names.push(name.clone()); - let result = self.append_tx_block(&TestTxSpec::ContractDeploy { - sender, - name: &name, - code: contract_code, - clarity_version: Some(*version), - }); + let result = self.append_tx_block( + &TestTxSpec::ContractDeploy { + sender, + name: &name, + code: contract_code, + clarity_version: Some(*version), + }, + is_naka_block, + ); results.push(result); }); } if call_epochs.contains(&epoch) { contract_names.iter().for_each(|contract_name| { - let result = self.append_tx_block(&TestTxSpec::ContractCall { - sender, - contract_addr: &contract_addr, - contract_name, - function_name, - args: function_args, - }); + let result = self.append_tx_block( + &TestTxSpec::ContractCall { + sender, + contract_addr: &contract_addr, + contract_name, + function_name, + args: function_args, + }, + is_naka_block, + ); results.push(result); }); } @@ -549,97 +556,6 @@ impl TestTxFactory { } } -fn epoch_3_0_onwards(first_burnchain_height: u64) -> EpochList { - info!("StacksEpoch unit_test first_burn_height = {first_burnchain_height}"); - - EpochList::new(&[ - StacksEpoch { - epoch_id: StacksEpochId::Epoch10, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_1_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch20, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch2_05, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_05, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch21, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_1, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch22, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_2, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch23, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_3, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch24, - start_height: 0, - end_height: 0, - block_limit: ExecutionCost::max_value(), - network_epoch: PEER_VERSION_EPOCH_2_4, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch25, - start_height: 0, - end_height: first_burnchain_height, - block_limit: BLOCK_LIMIT_MAINNET_21, - network_epoch: PEER_VERSION_EPOCH_2_5, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch30, - start_height: first_burnchain_height, - end_height: first_burnchain_height + 1, - block_limit: BLOCK_LIMIT_MAINNET_21, - network_epoch: PEER_VERSION_EPOCH_3_0, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch31, - start_height: first_burnchain_height + 1, - end_height: first_burnchain_height + 2, - block_limit: BLOCK_LIMIT_MAINNET_21, - network_epoch: PEER_VERSION_EPOCH_3_1, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch32, - start_height: first_burnchain_height + 2, - end_height: first_burnchain_height + 3, - block_limit: BLOCK_LIMIT_MAINNET_21, - network_epoch: PEER_VERSION_EPOCH_3_2, - }, - StacksEpoch { - epoch_id: StacksEpochId::Epoch33, - start_height: first_burnchain_height + 3, - end_height: STACKS_EPOCH_MAX, - block_limit: BLOCK_LIMIT_MAINNET_21, - network_epoch: PEER_VERSION_EPOCH_3_3, - }, - ]) -} - /// Custom serializer for `Option` to improve snapshot readability. /// This avoids large diffs in snapshots due to code body changes and focuses on key fields. fn serialize_opt_tx_payload( @@ -778,64 +694,23 @@ pub struct ConsensusTest<'a> { impl ConsensusTest<'_> { /// Creates a new `ConsensusTest` with the given test name and initial balances. pub fn new(test_name: &str, initial_balances: Vec<(PrincipalData, u64)>) -> Self { - // Set up chainstate to start at Epoch 3.0 - // We don't really ever want the reward cycle to force a new signer set... - // so for now just set the cycle length to a high value (100) + // Set up chainstate to support Naka. let mut boot_plan = NakamotoBootPlan::new(test_name) - .with_pox_constants(100, 3) + .with_pox_constants(7, 1) .with_initial_balances(initial_balances) .with_private_key(FAUCET_PRIV_KEY.clone()); - let epochs = epoch_3_0_onwards( - (boot_plan.pox_constants.pox_4_activation_height - + boot_plan.pox_constants.reward_cycle_length - + 1) as u64, - ); + boot_plan.pox_constants.reward_cycle_length = 5; + boot_plan.pox_constants.prepare_length = 2; + let first_burnchain_height = (boot_plan.pox_constants.pox_4_activation_height + + boot_plan.pox_constants.reward_cycle_length + + 1) as u64; + let epochs = TestChainstate::all_epochs(first_burnchain_height); boot_plan = boot_plan.with_epochs(epochs); - let chain = boot_plan.boot_nakamoto_chainstate(None); - + let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); Self { chain } } - /// Advances the chainstate to the specified epoch. Creating a tenure change block per burn block height - pub fn advance_to_epoch(&mut self, target_epoch: StacksEpochId) { - let burn_block_height = self.chain.get_burn_block_height(); - let mut current_epoch = - SortitionDB::get_stacks_epoch(self.chain.sortdb().conn(), burn_block_height) - .unwrap() - .unwrap() - .epoch_id; - assert!(current_epoch <= target_epoch, "Chainstate is already at a higher epoch than the target. Current epoch: {current_epoch}. Target epoch: {target_epoch}"); - while current_epoch < target_epoch { - let (burn_ops, mut tenure_change, miner_key) = self - .chain - .begin_nakamoto_tenure(TenureChangeCause::BlockFound); - let (_, header_hash, consensus_hash) = self.chain.next_burnchain_block(burn_ops); - let vrf_proof = self.chain.make_nakamoto_vrf_proof(miner_key); - - tenure_change.tenure_consensus_hash = consensus_hash.clone(); - tenure_change.burn_view_consensus_hash = consensus_hash.clone(); - let tenure_change_tx = self.chain.miner.make_nakamoto_tenure_change(tenure_change); - let coinbase_tx = self.chain.miner.make_nakamoto_coinbase(None, vrf_proof); - - let blocks_and_sizes = self - .chain - .make_nakamoto_tenure(tenure_change_tx, coinbase_tx, Some(0)) - .unwrap(); - assert_eq!( - blocks_and_sizes.len(), - 1, - "Mined more than one Nakamoto block" - ); - let burn_block_height = self.chain.get_burn_block_height(); - current_epoch = - SortitionDB::get_stacks_epoch(self.chain.sortdb().conn(), burn_block_height) - .unwrap() - .unwrap() - .epoch_id; - } - } - - /// Appends a single block to the chain and returns the result. + /// Appends a single block to the chain as a Nakamoto block and returns the result. /// /// This method takes a [`TestBlock`] containing a list of transactions, constructs /// a fully valid [`NakamotoBlock`], processes it against the current chainstate. @@ -847,7 +722,7 @@ impl ConsensusTest<'_> { /// # Returns /// /// A [`ExpectedResult`] with the outcome of the block processing. - pub fn append_block(&mut self, block: TestBlock) -> ExpectedResult { + fn append_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { debug!("--------- Running block {block:?} ---------"); let (nakamoto_block, block_size) = self.construct_nakamoto_block(block); let mut sortdb = self.chain.sortdb.take().unwrap(); @@ -882,6 +757,77 @@ impl ConsensusTest<'_> { ExpectedResult::create_from(remapped_result, expected_marf) } + /// Appends a single block to the chain as a Pre-Nakamoto block and returns the result. + /// + /// This method takes a [`TestBlock`] containing a list of transactions, constructs + /// a fully valid [`StacksBlock`], processes it against the current chainstate. + /// + /// # Arguments + /// + /// * `block` - The test block to be processed and appended to the chain. + /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// + /// # Returns + /// + /// A [`ExpectedResult`] with the outcome of the block processing. + fn append_pre_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { + let (ch, bh) = + SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) + .unwrap(); + let tip_id = StacksBlockId::new(&ch, &bh); + let (burn_ops, stacks_block, microblocks) = + self.chain.make_tenure_with_txs(&block.transactions); + let (_, _, consensus_hash) = self.chain.next_burnchain_block(burn_ops); + + debug!( + "--------- Processing Pre-Nakamoto block ---------"; + "block" => ?stacks_block + ); + + let mut stacks_node = self.chain.stacks_node.take().unwrap(); + let mut sortdb = self.chain.sortdb.take().unwrap(); + let expected_marf = stacks_block.header.state_index_root; + let res = TestStacksNode::process_pre_nakamoto_next_ready_block( + &mut stacks_node, + &mut sortdb, + &mut self.chain.miner, + &ch, + &mut self.chain.coord, + &stacks_block, + µblocks, + ); + debug!( + "--------- Processed Pre-Nakamoto block ---------"; + "block" => ?stacks_block + ); + let remapped_result = res.map(|receipt| receipt.unwrap()); + // Restore chainstate for the next block + self.chain.sortdb = Some(sortdb); + self.chain.stacks_node = Some(stacks_node); + ExpectedResult::create_from(remapped_result, expected_marf) + } + + /// Appends a single block to the chain and returns the result. + /// + /// This method takes a [`TestBlock`] containing a list of transactions, whether the epoch [`is_naka_epoch`] , + /// constructing a fully valid [`StacksBlock`] or [`NakamotoBlock`] accordingly, processes it against the current chainstate. + /// + /// # Arguments + /// + /// * `block` - The test block to be processed and appended to the chain. + /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// + /// # Returns + /// + /// A [`ExpectedResult`] with the outcome of the block processing. + pub fn append_block(&mut self, block: TestBlock, is_naka_epoch: bool) -> ExpectedResult { + if is_naka_epoch { + self.append_nakamoto_block(block) + } else { + self.append_pre_nakamoto_block(block) + } + } + /// Executes a full test plan by processing blocks across multiple epochs. /// /// This function serves as the primary test runner. It iterates through the @@ -902,20 +848,11 @@ impl ConsensusTest<'_> { epoch_blocks: HashMap>, ) -> Vec { // Validate blocks - for (epoch_id, blocks) in epoch_blocks.iter() { - assert!( - !matches!( - *epoch_id, - StacksEpochId::Epoch10 - | StacksEpochId::Epoch20 - | StacksEpochId::Epoch2_05 - | StacksEpochId::Epoch21 - | StacksEpochId::Epoch22 - | StacksEpochId::Epoch23 - | StacksEpochId::Epoch24 - | StacksEpochId::Epoch25 - ), - "Pre-Nakamoto Tenures are not Supported" + for (epoch_id, blocks) in &epoch_blocks { + assert_ne!( + *epoch_id, + StacksEpochId::Epoch10, + "Epoch10 is not supported" ); assert!( !blocks.is_empty(), @@ -933,10 +870,12 @@ impl ConsensusTest<'_> { "--------- Processing epoch {epoch:?} with {} blocks ---------", blocks.len() ); - self.advance_to_epoch(epoch); + // Use the miner key to prevent messing with FAUCET nonces. + let miner_key = self.chain.miner.nakamoto_miner_key(); + self.chain.advance_into_epoch(&miner_key, epoch); for block in blocks { - results.push(self.append_block(block)); + results.push(self.append_block(block, epoch >= StacksEpochId::Epoch30)); } } results diff --git a/stackslib/src/chainstate/tests/mod.rs b/stackslib/src/chainstate/tests/mod.rs index 7b2dcfdb5cc..440e7c34d25 100644 --- a/stackslib/src/chainstate/tests/mod.rs +++ b/stackslib/src/chainstate/tests/mod.rs @@ -16,6 +16,12 @@ pub mod consensus; use std::fs; +use clarity::consts::{ + PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, + PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, + PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, + PEER_VERSION_EPOCH_3_3, STACKS_EPOCH_MAX, +}; use clarity::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, }; @@ -53,9 +59,12 @@ use crate::chainstate::stacks::boot::test::{get_parent_tip, make_pox_4_lockup_ch use crate::chainstate::stacks::db::{StacksChainState, *}; use crate::chainstate::stacks::tests::*; use crate::chainstate::stacks::{Error as ChainstateError, StacksMicroblockHeader, *}; -use crate::core::{EpochList, StacksEpoch, StacksEpochExtension, BOOT_BLOCK_HASH}; +use crate::core::{ + EpochList, StacksEpoch, StacksEpochExtension, BLOCK_LIMIT_MAINNET_21, BOOT_BLOCK_HASH, +}; use crate::net::relay::Relayer; use crate::net::test::TestEventObserver; +use crate::net::tests::NakamotoBootPlan; use crate::util_lib::boot::{boot_code_test_addr, boot_code_tx_auth}; use crate::util_lib::signed_structured_data::pox4::{ make_pox_4_signer_key_signature, Pox4SignatureTopic, @@ -195,6 +204,32 @@ impl<'a> TestChainstate<'a> { StacksEpoch::unit_test_pre_2_05(config.burnchain.first_block_height) }); + if let Some(epoch_30) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { + assert!(config.current_block < epoch_30.start_height, "Cannot use a Nakamoto chainstate if bootstrapped to a burn block height ({}) greater than or equal to the Epoch 3.0 activation height ({}).", config.current_block, epoch_30.start_height); + let epoch_25 = config + .epochs + .as_ref() + .expect("Epoch configuration missing") + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch25) + .expect("Must specify an Epoch25 start_height to use Nakamoto"); + let epoch_25_reward_cycle = config + .burnchain + .block_height_to_reward_cycle(epoch_25.start_height) + .expect("Failed to determine reward cycle of epoch 2.5"); + let epoch_30_reward_cycle = config + .burnchain + .block_height_to_reward_cycle(epoch_30.start_height) + .expect("Failed to determine reward cycle of Epoch 3.0"); + let epoch_25_in_prepare_phase = + config.burnchain.is_in_prepare_phase(epoch_25.start_height); + + assert_ne!(epoch_25_reward_cycle, epoch_30_reward_cycle, "Cannot activate Epoch 2.5 and Epoch 3.0 in the same reward cycle. Examine your bootstrap setup."); + if epoch_25_reward_cycle.saturating_add(1) == epoch_30_reward_cycle { + assert!(!epoch_25_in_prepare_phase, "Must activate Epoch 2.5 prior to the prepare phase in which Epoch 3.0 is activated. Examine your bootstrap setup."); + } + } + let mut sortdb = SortitionDB::connect( &config.burnchain.get_db_path(), config.burnchain.first_block_height, @@ -363,55 +398,126 @@ impl<'a> TestChainstate<'a> { } } - // Advances a TestChainstate to the Nakamoto epoch - pub fn advance_to_nakamoto_epoch(&mut self, private_key: &StacksPrivateKey, nonce: &mut usize) { - let addr = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(private_key)); - let default_pox_addr = - PoxAddress::from_legacy(AddressHashMode::SerializeP2PKH, addr.bytes().clone()); + /// Advances the chainstate to the specified epoch boundary by creating a tenure change block per burn block height. + /// Panics if already past the target epoch activation height. + pub fn advance_to_epoch_boundary( + &mut self, + private_key: &StacksPrivateKey, + target_epoch: StacksEpochId, + ) { + let mut burn_block_height = self.get_burn_block_height(); + let mut target_height = self + .config + .epochs + .as_ref() + .expect("Epoch configuration missing") + .iter() + .find(|e| e.epoch_id == target_epoch) + .expect("Target epoch not found") + .start_height; - let mut sortition_height = self.get_burn_block_height(); - debug!("\n\n======================"); - debug!("PoxConstants = {:#?}", &self.config.burnchain.pox_constants); - debug!("tip = {sortition_height}"); - debug!("========================\n\n"); + assert!( + burn_block_height <= target_height, + "Already advanced past target epoch ({target_epoch}) activation height ({target_height}). Current burn block height: {burn_block_height}." + ); + target_height = target_height.saturating_sub(1); + + debug!("Advancing to epoch {target_epoch} boundary at {target_height}. Current burn block height: {burn_block_height}"); let epoch_25_height = self .config .epochs .as_ref() - .unwrap() + .expect("Epoch configuration missing") .iter() - .find(|e| e.epoch_id == StacksEpochId::Epoch25) - .unwrap() - .start_height; + .find_map(|e| { + if e.epoch_id == StacksEpochId::Epoch25 { + Some(e.start_height) + } else { + None + } + }) + .unwrap_or(u64::MAX); let epoch_30_height = self .config .epochs .as_ref() - .unwrap() + .expect("Epoch configuration missing") .iter() - .find(|e| e.epoch_id == StacksEpochId::Epoch30) - .unwrap() - .start_height; + .find_map(|e| { + if e.epoch_id == StacksEpochId::Epoch30 { + Some(e.start_height) + } else { + None + } + }) + .unwrap_or(u64::MAX); - // Advance to just past PoX-4 instantiation - let mut blocks_produced = false; - while sortition_height <= epoch_25_height { - self.tenure_with_txs(&[], nonce); - sortition_height = self.get_burn_block_height(); - blocks_produced = true; - } + let epoch_30_reward_cycle = self + .config + .burnchain + .block_height_to_reward_cycle(epoch_30_height) + .unwrap_or(u64::MAX); - // Ensure at least one block is produced before PoX-4 lockups - if !blocks_produced { - self.tenure_with_txs(&[], nonce); - sortition_height = self.get_burn_block_height(); + let mut mined_pox_4_lockup = false; + let current_epoch = SortitionDB::get_stacks_epoch(self.sortdb().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + while burn_block_height < target_height { + if burn_block_height < epoch_30_height - 1 { + let current_reward_cycle = self.get_reward_cycle(); + // Before we can mine pox 4 lockup, make sure we mine at least one block. + // If we have mined the lockup already, just mine a regular tenure + // Note, we cannot mine a pox 4 lockup, if it isn't activated yet + // And must mine it in the reward cycle directly prior to the Nakamoto + // activated reward cycle + if !mined_pox_4_lockup + && burn_block_height > self.config.current_block + && burn_block_height + 1 >= epoch_25_height + && current_reward_cycle + 1 == epoch_30_reward_cycle + { + debug!("Mining pox-4 lockup"); + self.mine_pox_4_lockup(private_key); + mined_pox_4_lockup = true; + } else { + debug!("Mining pre-nakamoto tenure"); + let stacks_block = self.tenure_with_txs(&[]); + let (stacks_tip_ch, stacks_tip_bh) = + SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()) + .expect("Failed to get canonical chain tip"); + let stacks_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); + assert_eq!(stacks_block, stacks_tip); + } + } else { + debug!("Mining post-nakamoto tenure"); + self.mine_nakamoto_tenure(); + } + burn_block_height = self.get_burn_block_height(); } + } - debug!("\n\n======================"); - debug!("Make PoX-4 lockups"); - debug!("========================\n\n"); + /// This must be called after pox 4 activation and at or past the Epoch 2.5 boundary + pub fn mine_pox_4_lockup(&mut self, private_key: &StacksPrivateKey) { + let sortition_height = self.get_burn_block_height(); + let epoch_25_height = self + .config + .epochs + .as_ref() + .unwrap() + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch25) + .unwrap() + .start_height; + assert!( + sortition_height + 1 >= epoch_25_height, + "Cannot mine pox-4 lockups if not at or past Epoch 2.5 boundary" + ); + + let addr = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(private_key)); + let default_pox_addr = + PoxAddress::from_legacy(AddressHashMode::SerializeP2PKH, addr.bytes().clone()); let reward_cycle = self .config @@ -460,49 +566,65 @@ impl<'a> TestChainstate<'a> { }) .collect(); - let stacks_block = self.tenure_with_txs(&stack_txs, nonce); + let stacks_block = self.tenure_with_txs(&stack_txs); let (stacks_tip_ch, stacks_tip_bh) = SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); let stacks_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); assert_eq!(stacks_block, stacks_tip); + } - debug!("\n\n======================"); - debug!("Advance to the Prepare Phase"); - debug!("========================\n\n"); - - // Advance to the prepare phase - while !self.config.burnchain.is_in_prepare_phase(sortition_height) { - let (stacks_tip_ch, stacks_tip_bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); - let old_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); - let stacks_block = self.tenure_with_txs(&[], nonce); - let (stacks_tip_ch, stacks_tip_bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); - let stacks_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); - assert_ne!(old_tip, stacks_tip); - sortition_height = self.get_burn_block_height(); - } + pub fn mine_nakamoto_tenure(&mut self) { + let burn_block_height = self.get_burn_block_height(); + let (burn_ops, mut tenure_change, miner_key) = + self.begin_nakamoto_tenure(TenureChangeCause::BlockFound); + let (_, header_hash, consensus_hash) = self.next_burnchain_block(burn_ops); + let vrf_proof = self.make_nakamoto_vrf_proof(miner_key); - debug!("\n\n======================"); - debug!("Advance to Epoch 3.0"); - debug!("========================\n\n"); - - // Advance to Epoch 3.0 - while sortition_height < epoch_30_height - 1 { - let (stacks_tip_ch, stacks_tip_bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); - let old_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); - self.tenure_with_txs(&[], nonce); - let (stacks_tip_ch, stacks_tip_bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); - let stacks_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); - assert_ne!(old_tip, stacks_tip); - sortition_height = self.get_burn_block_height(); - } + tenure_change.tenure_consensus_hash = consensus_hash.clone(); + tenure_change.burn_view_consensus_hash = consensus_hash.clone(); + let tenure_change_tx = self.miner.make_nakamoto_tenure_change(tenure_change); + let coinbase_tx = self.miner.make_nakamoto_coinbase(None, vrf_proof); - debug!("\n\n======================"); - debug!("Welcome to Nakamoto!"); - debug!("========================\n\n"); + let blocks_and_sizes = self + .make_nakamoto_tenure(tenure_change_tx, coinbase_tx, Some(0)) + .unwrap(); + assert_eq!( + blocks_and_sizes.len(), + 1, + "Mined more than one Nakamoto block" + ); + } + + /// Advance a TestChainstate into the provided epoch. + /// Does nothing if chainstate is already in the target epoch. Panics if it is past the epoch. + pub fn advance_into_epoch( + &mut self, + private_key: &StacksPrivateKey, + target_epoch: StacksEpochId, + ) { + let burn_block_height = self.get_burn_block_height(); + let current_epoch = + SortitionDB::get_stacks_epoch(self.sortdb_ref().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + assert!( + current_epoch <= target_epoch, + "Already advanced past target epoch ({target_epoch}). Currently in epoch {current_epoch} at burn block height: {burn_block_height}." + ); + // Don't bother advancing to the boundary if we are already in it. + if current_epoch < target_epoch { + self.advance_to_epoch_boundary(private_key, target_epoch); + if target_epoch < StacksEpochId::Epoch30 { + self.tenure_with_txs(&[]); + } else { + self.mine_nakamoto_tenure(); + } + } + let burn_block_height = self.get_burn_block_height(); + debug!( + "Advanced into epoch {target_epoch}. Current burn block height: {burn_block_height}" + ); } pub fn get_burnchain_db(&self, readwrite: bool) -> BurnchainDB { @@ -1044,21 +1166,33 @@ impl<'a> TestChainstate<'a> { self.stacks_node.as_ref().unwrap() } - /// Make a tenure with the given transactions. Creates a coinbase tx with the given nonce, and then increments - /// the provided reference. - pub fn tenure_with_txs( + /// Make a tenure with the given transactions. Creates a coinbase tx with the given nonce. Processes + /// the tenure and then increments the provided nonce reference. + pub fn tenure_with_txs(&mut self, txs: &[StacksTransaction]) -> StacksBlockId { + let (burn_ops, stacks_block, microblocks) = self.make_tenure_with_txs(txs); + + let (_, _, consensus_hash) = self.next_burnchain_block(burn_ops); + self.process_stacks_epoch_at_tip(&stacks_block, µblocks); + + StacksBlockId::new(&consensus_hash, &stacks_block.block_hash()) + } + + /// Make a pre-naka tenure with the given transactions + pub fn make_tenure_with_txs( &mut self, txs: &[StacksTransaction], - coinbase_nonce: &mut usize, - ) -> StacksBlockId { + ) -> ( + Vec, + StacksBlock, + Vec, + ) { let microblock_privkey = self.miner.next_microblock_privkey(); let microblock_pubkeyhash = Hash160::from_node_public_key(&StacksPublicKey::from_private(µblock_privkey)); let tip = SortitionDB::get_canonical_burn_chain_tip(self.sortdb.as_ref().unwrap().conn()) .unwrap(); let burnchain = self.config.burnchain.clone(); - - let (burn_ops, stacks_block, microblocks) = self.make_tenure( + self.make_tenure( |ref mut miner, ref mut sortdb, ref mut chainstate, @@ -1066,7 +1200,7 @@ impl<'a> TestChainstate<'a> { ref parent_opt, ref parent_microblock_header_opt| { let parent_tip = get_parent_tip(parent_opt, chainstate, sortdb); - let coinbase_tx = make_coinbase(miner, *coinbase_nonce); + let coinbase_tx = make_coinbase(miner, tip.block_height.try_into().unwrap()); let mut block_txs = vec![coinbase_tx]; block_txs.extend_from_slice(txs); @@ -1089,14 +1223,7 @@ impl<'a> TestChainstate<'a> { .unwrap(); (anchored_block, vec![]) }, - ); - - let (_, _, consensus_hash) = self.next_burnchain_block(burn_ops); - self.process_stacks_epoch_at_tip(&stacks_block, µblocks); - - *coinbase_nonce += 1; - - StacksBlockId::new(&consensus_hash, &stacks_block.block_hash()) + ) } /// Make a tenure, using `tenure_builder` to generate a Stacks block and a list of @@ -1518,4 +1645,310 @@ impl<'a> TestChainstate<'a> { self.stacks_node = Some(stacks_node); Ok(block_data) } + + /// Create an epoch list for testing Epoch 2.5 onwards + pub fn epoch_2_5_onwards(first_burnchain_height: u64) -> EpochList { + info!( + "StacksEpoch 2.5 onwards unit test first_burnchain_height = {first_burnchain_height}" + ); + EpochList::new(&[ + StacksEpoch { + epoch_id: StacksEpochId::Epoch10, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_1_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch20, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch2_05, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_05, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch21, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch22, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_2, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_3, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch24, + start_height: 0, + end_height: 0, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_4, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch25, + start_height: 0, + end_height: first_burnchain_height, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_2_5, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch30, + start_height: first_burnchain_height, + end_height: first_burnchain_height + 1, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch31, + start_height: first_burnchain_height + 1, + end_height: first_burnchain_height + 2, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: first_burnchain_height + 2, + end_height: first_burnchain_height + 3, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_2, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch33, + start_height: first_burnchain_height + 3, + end_height: STACKS_EPOCH_MAX, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_3, + }, + ]) + } + + pub fn all_epochs(first_burnchain_height: u64) -> EpochList { + info!("StacksEpoch all_epochs first_burn_height = {first_burnchain_height}"); + + EpochList::new(&[ + StacksEpoch { + epoch_id: StacksEpochId::Epoch10, + start_height: 0, + end_height: first_burnchain_height, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_1_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch20, + start_height: first_burnchain_height, + end_height: first_burnchain_height + 1, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch2_05, + start_height: first_burnchain_height + 1, + end_height: first_burnchain_height + 2, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_05, + }, + StacksEpoch { + // Give a few extra blocks for pre naka blocks + // Since we may want to create multiple stacks blocks + // per epoch (especially for clarity version testing) + epoch_id: StacksEpochId::Epoch21, + start_height: first_burnchain_height + 2, + end_height: first_burnchain_height + 6, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch22, + start_height: first_burnchain_height + 6, + end_height: first_burnchain_height + 10, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_2, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: first_burnchain_height + 10, + end_height: first_burnchain_height + 14, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_3, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch24, + start_height: first_burnchain_height + 14, + end_height: first_burnchain_height + 18, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_4, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch25, + // Give an extra couple burn blocks for epoch 25 to activate pox-4 + start_height: first_burnchain_height + 18, + end_height: first_burnchain_height + 24, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_5, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch30, + start_height: first_burnchain_height + 24, + end_height: first_burnchain_height + 25, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_3_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch31, + start_height: first_burnchain_height + 25, + end_height: first_burnchain_height + 26, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_3_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: first_burnchain_height + 26, + end_height: first_burnchain_height + 27, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_3_2, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch33, + start_height: first_burnchain_height + 27, + end_height: STACKS_EPOCH_MAX, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_3_2, + }, + ]) + } +} + +#[test] +/// Tests that we can instantiate a chainstate from nothing and advance sequentially through every epoch +fn advance_through_all_epochs() { + let privk = StacksPrivateKey::random(); + let mut boot_plan = NakamotoBootPlan::new(function_name!()) + .with_pox_constants(7, 1) + .with_private_key(privk.clone()); + boot_plan.pox_constants.reward_cycle_length = 5; + boot_plan.pox_constants.prepare_length = 2; + let first_burnchain_height = (boot_plan.pox_constants.pox_4_activation_height + + boot_plan.pox_constants.reward_cycle_length + + 1) as u64; + + let epochs = TestChainstate::all_epochs(first_burnchain_height); + boot_plan = boot_plan.with_epochs(epochs); + let mut chainstate = boot_plan.to_chainstate(None, Some(first_burnchain_height)); + let burn_block_height = chainstate.get_burn_block_height(); + let current_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(current_epoch, StacksEpochId::Epoch20); + + // Make sure we can advance through every single epoch. + for target_epoch in [ + StacksEpochId::Epoch2_05, + StacksEpochId::Epoch21, + StacksEpochId::Epoch22, + StacksEpochId::Epoch23, + StacksEpochId::Epoch24, + StacksEpochId::Epoch25, + StacksEpochId::Epoch30, + StacksEpochId::Epoch31, + StacksEpochId::Epoch32, + StacksEpochId::Epoch33, + ] { + chainstate.advance_to_epoch_boundary(&privk, target_epoch); + let burn_block_height = chainstate.get_burn_block_height(); + let current_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + assert!(current_epoch < target_epoch); + let next_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height + 1) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(next_epoch, target_epoch); + } +} + +#[test] +/// Tests that we can instantiate a chainstate from nothing and +/// bootstrap to nakamoto +fn advance_to_nakamoto_bootstrapped() { + let privk = StacksPrivateKey::random(); + let mut boot_plan = NakamotoBootPlan::new(function_name!()) + .with_pox_constants(7, 1) + .with_private_key(privk.clone()); + let epochs = TestChainstate::epoch_2_5_onwards( + (boot_plan.pox_constants.pox_4_activation_height + + boot_plan.pox_constants.reward_cycle_length + + 1) as u64, + ); + boot_plan = boot_plan.with_epochs(epochs); + let mut chainstate = boot_plan.to_chainstate(None, None); + chainstate.advance_to_epoch_boundary(&privk, StacksEpochId::Epoch30); + let burn_block_height = chainstate.get_burn_block_height(); + let current_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(current_epoch, StacksEpochId::Epoch25); + let next_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height + 1) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(next_epoch, StacksEpochId::Epoch30); +} + +#[test] +/// Tests that we can instantiate a chainstate from nothing and +/// bootstrap directly from nakamoto and across it +fn advance_through_nakamoto_bootstrapped() { + let privk = StacksPrivateKey::random(); + let mut boot_plan = NakamotoBootPlan::new(function_name!()) + .with_pox_constants(7, 1) + .with_private_key(privk.clone()); + let epochs = TestChainstate::epoch_2_5_onwards( + (boot_plan.pox_constants.pox_4_activation_height + + boot_plan.pox_constants.reward_cycle_length + + 1) as u64, + ); + let activation_height = boot_plan.pox_constants.pox_4_activation_height; + boot_plan = boot_plan.with_epochs(epochs); + let mut chainstate = boot_plan.to_chainstate(None, Some(activation_height.into())); + // Make sure we can advance through every single epoch. + chainstate.advance_to_epoch_boundary(&privk, StacksEpochId::Epoch33); + let burn_block_height = chainstate.get_burn_block_height(); + let current_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(current_epoch, StacksEpochId::Epoch32); + let next_epoch = + SortitionDB::get_stacks_epoch(chainstate.sortdb().conn(), burn_block_height + 1) + .unwrap() + .unwrap() + .epoch_id; + assert_eq!(next_epoch, StacksEpochId::Epoch33); } diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap index 65ae5f95fd2..fa812378e17 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "95999ab12ae2162f2dd25c4a7f7807017b3b5d20f28a53248e37c9864f923718", + marf_hash: "167a40e9a29bf010186406caa75d269ab8cc43910ae5145870034da049bb585c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -62,7 +62,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7e9b86e5b0ff545908784c1c674d1354228ce2884395ced13f7c9e4eaa7ecfd0", + marf_hash: "42fd84cd736e41fb999c9ea077a59213301256329ef6159381a0aa5fa501090e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap index e1f44a75010..0d3d46bb2cc 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap @@ -3,11 +3,11 @@ source: stackslib/src/chainstate/tests/consensus.rs expression: result --- [ - Failure("Invalid Stacks block 7e31cbb71c79284d65fcf9ed65e7263e6320f267f8b29eb31aca3a8fcb65302b: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 1204d27abf8c5274ca561a1f9d14c7d64c3f802ff2b78a164d7d29eb8808d547: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 435d0c567d8f970c7fde521b4b22e270e78ffbb8c8b09e33bbc6d5b4e7d0257f: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 76cc5894a268fa256bb03bfd5e378de73db2a9ab1c88b9a0c0dca80c41782137: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block e9b506ca1ce75c6ee393200d7ab89742e0dd74f64b1b3363fd98411d163251e7: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block c1dfc3fad9244f636197bd062b88a8aee581a0df0f2632f7f4639ada153e88d6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 072ea79f6c9908c904a6c90881324c52ba67f7a27887e932571ac26e2ad03d19: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 04fba94ef9639692880cdd683d0f89b3eb8ae3f66abe6d4b47818f243c6b1685: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 5f159c8302430ff284324505b78c16b524c71917ceaefab0db3e6aacaff50cfc: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 0ab2d9dab6c26de5dfc3c6ac6d745a555e9acdcd48367896190100d7a96dbf84: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 83f47b75df0d5fe7bc727b797ab6ea72f0c21c182e6964c0b9aaa3355e31ae71: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 6db9f369ebf5b4112666041bdd75d608172f7ecd5fbc5fc1e83f40e3697de239: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block ed364591529d14d6108f22df86e060c333c9e1afe455698c0d963d16d4b752d7: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block e2d6554bd289a828a6076dd2bd3fe63d73804554b31b763b3561e76ab46fd419: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), ] diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index 8d9eddf5f4c..1e2434e87d6 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "7dc0aaa26bad4b0300a451df39c8ce76beff0b49da2db3fce47d63b87509c32e", + marf_hash: "d69ae3c88a96363d40459f86d341998fc2afc39b23ec74e9e80fc41bc2bb84bb", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -32,7 +32,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4e611b18dc48b4ad2825a92431834e3bab0c69393e99a0fd6e5c18c4023a7705", + marf_hash: "1179565d4864b9b0e0c6b2bc39ae31f8dd3d8b2c2dc0a025f0b5d05466b5a0db", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -60,7 +60,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "46da815cdf0c9816d304feb9771763a4ff424b8362e559bbe0981305e6efd889", + marf_hash: "6181bdb5e506cdbad54bd5d741c7edcc01acf2df5378a51484bde110db55ae30", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -88,7 +88,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5fa759b7495ff89f5274d907b105f9afdfaa7f78942952b60a53b2c921e19607", + marf_hash: "698c03661f443d02eaea7405a17e39719cdc6239a6b27278e4f989d0005eafd5", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -116,7 +116,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "159b15f8004355f25117d9a35d11a55f38c80c7886b9cba5ed63a454fc9a0112", + marf_hash: "1b683de91b139b242f9533248a3637a89e793d3eb56124c9215dc42c19ee476c", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -144,7 +144,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ed275e6d1c8a6cf8afa1c0836aa00fb02e63b3a3cd263137ed4627e8fa23d50c", + marf_hash: "38b3c34015a222f0f5188f0ade1e3fbefb59c0f9792951249c6b9ecdd40dd126", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -172,7 +172,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "68b0d2bb6af72ff3d906da9fb1647f82b689fb7eefb88bff0c3d7324811bc7ff", + marf_hash: "55a966c58704f4fda3d36c19fbb2bc54d12ccbd0ae8057363d0c97768f815921", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -200,7 +200,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2df07ee1b39c63c331bd882265dd335326f179cb46ac59229238f70a55af7926", + marf_hash: "819dd16988980eb2ccbf3e67f1ca10ea37ccf6567ef31b216b1d8ad59f9c4cc8", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -228,7 +228,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8b4b06c870fd5b66e33fe4f1496509cc182827c2e175851d31b85ef00e161625", + marf_hash: "f843cc680839ef0f442c1a34eeea431f4be57872b21fad40cd37f4e7473e1872", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -256,7 +256,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ed3ac24606bfab532195a58a924461dadda0c0e5e52c58d50004293f10a9ffa8", + marf_hash: "b4b27ee7f4b3db96e33cb1976c0282d3e42f2e646751541eb89d6b2f0bf044da", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -284,7 +284,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "af5b3fcf2446019e5179753ca3d5a9c87bfd4992db44f23ff265d713051684fc", + marf_hash: "dfc45534d2d1d9bab6c11017bde1baa1b0cc711ba8faf18b322608c04633b7fd", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -312,7 +312,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ab1b2f5cc50cbc4a44efacfa273bebc6a8613324e8c41fd092cd99813f7a5641", + marf_hash: "02f12b59c658b5347941787a4ccabc16a6701a4fd23a80cfa7a011d864893882", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -340,7 +340,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "38b0d2fe231731c95ab452ed2e048abfafaf163416a6304af8845d5963fb4347", + marf_hash: "e9d5639b17bc8b64ccff15577a17b483c2c0401011d5c1bd8d445837e111382e", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -368,7 +368,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "34f0b0577b92091983d86b293c3c78ce83a129fa94bc125bec5c8ebf08f91308", + marf_hash: "71ba9a91ca920e7fa71e7a54c68ff3ca33ff1a0789d2231b45368546b319ceda", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -396,7 +396,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a9cf5345902cba7d1529cc72f23a1ceed8d0addcbc2d608163513233fcf76f1d", + marf_hash: "08cf6388bd4f3a352805a4123cf95ff751128c4a7a3137536412e5250178fb28", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -424,7 +424,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c290afd1e6546e3efa21a1403dd0cd2845a9b5dffd288c907ef2ff40b5328e5f", + marf_hash: "8e80a4543d025f81973117505539efe33e3dc04a1980bc635fcc6982cd0c43fc", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -452,7 +452,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d616f061a5fb3377b12d60a6af69d280bfc3df72f5d00bf4dff0063003a39500", + marf_hash: "6daf9c2e6789daceae7b353c170a20f94a50f20519249d02d74a7ba793afe6f6", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -480,7 +480,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "677f80720b7d3ab147d595942e54ab6d1c16bf91628ca1d9b9fbf18a4456ca3f", + marf_hash: "fb4187e30248b8f86d1cb529a4f84950e0613e9e84eee44799ab70e9c075d98c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -508,7 +508,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c0d6892c54926e704f2b79f5bfcea311b6ecbfd1709de05d8ed817f54f392e1c", + marf_hash: "a274b82ed064f2eddd71a76df42de6f7b1f34de99d0e2856da0a737bd7611e24", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -536,7 +536,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "bc08a038f2c9819baeb15f306063c351b2ad4d2b6297de98e5019c0655ac29d1", + marf_hash: "5d2372dacb473ced6ed4c86d8ed044f69ff1c38b374740ad3e844d1d38fbfd36", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -564,7 +564,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "deda548b30bd858b6b42b638258ea0a45919972b339604544090af42f7c4330b", + marf_hash: "63e85c6d9a25e545d1f72fc40207ed56b2383e61736f583cf895d0b1e67baa99", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -592,7 +592,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1f8603c7d580008f986774bc4a624d429d5b45b4a4385c14656a5398bf8a56ed", + marf_hash: "e4452fe90645ad0e5db5b6da0fd29f7c00cec47c0260dcfe981b03a645ad7247", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -620,7 +620,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "59df1ff6ea26ae936061b8565b962dfbd0d9420efedbf9c9aefb43365e4da789", + marf_hash: "ce5300f6ae0a486d9997da9917a7b4208543a3005143b2092fd5536357f514e6", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -648,7 +648,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "fb5d51ffb295367d9dcd567775618bd54b21c56a9503c2678fac49f53d5e23fa", + marf_hash: "f469a5fda879d19429e9b91bcd9da02d40ce723478ac7c9dca0918a4b0dbe144", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -676,7 +676,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "505471f1004e5ab38ef24a05b22422b8d66c9313a85c1d9c3470204d7e38f422", + marf_hash: "be699e3c1c296165721f09f2903c9bc18e5860b3014ea3b91b380fdac73e50dd", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -704,7 +704,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5260eec1120a1caeaceb27842cb4e4a235cbb0791fb67682499bb3796d07c31a", + marf_hash: "ce7042cb681cb7c8536177a5cf1a7af643c459bf163d017ce0b22fb435c80677", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -732,7 +732,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "105587537589f0bdad491c5cbcdfcffea475176a064c01ffd7f61a1eedd15af5", + marf_hash: "9c2bbf9df60ffea14bab25fd14090a7ad4cd4f58b08956674238d7229f871ec0", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -760,7 +760,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "861d1186952ddb0e242bfbe997ea9860cc6739880b47a0c268d7211d54942740", + marf_hash: "8895872a3b36259eeacb3f604cd8425909c41fa5e685c362bb1b809e84103d9e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -788,7 +788,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "235447452a8921fe9bbf8999194cd001e0ded05bfcbf220b5408be28a4b505f8", + marf_hash: "b22542cbc238a78f3a46efda88bdbd4aec0496bfccfb207411870fdc8ef98c72", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -816,7 +816,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "74ea86662425f6fe76a58bb677e85aaab12ca27b2ec9a2e0c69bbb401275a859", + marf_hash: "8a1b16f8c6b83efbd46091978e8e13fe6aab5ba562325c4c4351ba71bcbd8ac0", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -844,7 +844,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "795d447db68ddb26b9fd4d091f9e0643c48d44cf34b4c584b916ffe4281c5041", + marf_hash: "bb69b44d18abb9ac51ff47d2d640e5b011eab31865068637164b8fb296bb70a0", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -872,7 +872,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "228ad67aa6b7422fc7c8dbbc31c3e8e3042fe8c6bbd6d6a7407788b6d6902388", + marf_hash: "b702c03be183db4c5493777caf231a1964be71ef7df7848f7d958a400c71f935", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -900,7 +900,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "31234b0b5cbda935741846fc6aee0138858ee589a8610587485fe2ac6ccbde3b", + marf_hash: "a94fcac5f0fabfc01e980eadd2a3b074af9f7fc4bb6a110d7962efabf89cf603", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -928,7 +928,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5254245eb17bc1de6da56501a52b9a5f5fb8fd19c40eac285778a46eddc864c8", + marf_hash: "d296e22e136cdefb026c9479084e4439117059ea2612ea406c786018831a3735", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -956,7 +956,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "9a7ed0c58f9c0611f61c78a71741c7f0256402e5493843e6b1e327a8e7f2157c", + marf_hash: "e21bbdd0c941d7aa06ea15513c04ad815e60cebb5c8b2b5c7177349efc1ccd36", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( diff --git a/stackslib/src/net/tests/mod.rs b/stackslib/src/net/tests/mod.rs index 60f31432ceb..858ffd69e19 100644 --- a/stackslib/src/net/tests/mod.rs +++ b/stackslib/src/net/tests/mod.rs @@ -25,7 +25,7 @@ pub mod relay; use std::collections::{HashMap, HashSet}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use clarity::types::EpochList; +use clarity::types::{EpochList, StacksEpochId}; use clarity::vm::costs::ExecutionCost; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use libstackerdb::StackerDBChunkData; @@ -400,17 +400,19 @@ impl NakamotoBootPlan { } } - /// Make a chainstate and transition it into the Nakamoto epoch. + /// Make a chainstate capable of transitioning into the Nakamoto epoch. /// The node needs to be stacking; otherwise, Nakamoto won't activate. - pub fn boot_nakamoto_chainstate( + pub fn to_chainstate( self, observer: Option<&TestEventObserver>, + current_block: Option, ) -> TestChainstate<'_> { - let chainstate_config = self.build_nakamoto_chainstate_config(); + let mut chainstate_config = self.build_nakamoto_chainstate_config(); + if let Some(current_block) = current_block { + chainstate_config.current_block = current_block; + } let mut chain = TestChainstate::new_with_observer(chainstate_config, observer); chain.mine_malleablized_blocks = self.malleablized_blocks; - let mut chain_nonce = 0; - chain.advance_to_nakamoto_epoch(&self.private_key, &mut chain_nonce); chain } @@ -449,18 +451,13 @@ impl NakamotoBootPlan { other_peers.push(other_peer); } - let mut peer_nonce = 0; - let mut other_peer_nonces = vec![0; other_peers.len()]; - // Advance primary peer and other peers to Nakamoto epoch peer.chain - .advance_to_nakamoto_epoch(&self.private_key, &mut peer_nonce); - for (other_peer, other_peer_nonce) in - other_peers.iter_mut().zip(other_peer_nonces.iter_mut()) - { + .advance_to_epoch_boundary(&self.private_key, StacksEpochId::Epoch30); + for other_peer in &mut other_peers { other_peer .chain - .advance_to_nakamoto_epoch(&self.private_key, other_peer_nonce); + .advance_to_epoch_boundary(&self.private_key, StacksEpochId::Epoch30); } (peer, other_peers) From 0d2e5fa17a0b26539f0fffd446142b1e2230c9a5 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 27 Oct 2025 12:22:04 -0700 Subject: [PATCH 02/22] Set lower block limits Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 2 - stackslib/src/chainstate/tests/mod.rs | 56 +++++++-------- ...nsensus__append_stx_transfers_success.snap | 4 +- ...error_expression_stack_depth_too_deep.snap | 14 ++-- ...nsensus__successfully_deploy_and_call.snap | 70 +++++++++---------- 5 files changed, 71 insertions(+), 75 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index bd2bf71a888..7d19efea0c8 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -699,8 +699,6 @@ impl ConsensusTest<'_> { .with_pox_constants(7, 1) .with_initial_balances(initial_balances) .with_private_key(FAUCET_PRIV_KEY.clone()); - boot_plan.pox_constants.reward_cycle_length = 5; - boot_plan.pox_constants.prepare_length = 2; let first_burnchain_height = (boot_plan.pox_constants.pox_4_activation_height + boot_plan.pox_constants.reward_cycle_length + 1) as u64; diff --git a/stackslib/src/chainstate/tests/mod.rs b/stackslib/src/chainstate/tests/mod.rs index 440e7c34d25..c4ed26b0fa6 100644 --- a/stackslib/src/chainstate/tests/mod.rs +++ b/stackslib/src/chainstate/tests/mod.rs @@ -1754,14 +1754,14 @@ impl<'a> TestChainstate<'a> { epoch_id: StacksEpochId::Epoch20, start_height: first_burnchain_height, end_height: first_burnchain_height + 1, - block_limit: ExecutionCost::max_value(), + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_0, }, StacksEpoch { epoch_id: StacksEpochId::Epoch2_05, start_height: first_burnchain_height + 1, end_height: first_burnchain_height + 2, - block_limit: ExecutionCost::max_value(), + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_05, }, StacksEpoch { @@ -1770,65 +1770,65 @@ impl<'a> TestChainstate<'a> { // per epoch (especially for clarity version testing) epoch_id: StacksEpochId::Epoch21, start_height: first_burnchain_height + 2, - end_height: first_burnchain_height + 6, - block_limit: ExecutionCost::max_value(), + end_height: first_burnchain_height + 4, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_1, }, StacksEpoch { epoch_id: StacksEpochId::Epoch22, - start_height: first_burnchain_height + 6, - end_height: first_burnchain_height + 10, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 4, + end_height: first_burnchain_height + 8, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_2, }, StacksEpoch { epoch_id: StacksEpochId::Epoch23, - start_height: first_burnchain_height + 10, - end_height: first_burnchain_height + 14, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 8, + end_height: first_burnchain_height + 12, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_3, }, StacksEpoch { epoch_id: StacksEpochId::Epoch24, - start_height: first_burnchain_height + 14, - end_height: first_burnchain_height + 18, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 12, + end_height: first_burnchain_height + 16, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_4, }, StacksEpoch { epoch_id: StacksEpochId::Epoch25, // Give an extra couple burn blocks for epoch 25 to activate pox-4 - start_height: first_burnchain_height + 18, - end_height: first_burnchain_height + 24, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 16, + end_height: first_burnchain_height + 22, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_5, }, StacksEpoch { epoch_id: StacksEpochId::Epoch30, - start_height: first_burnchain_height + 24, - end_height: first_burnchain_height + 25, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 22, + end_height: first_burnchain_height + 23, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_0, }, StacksEpoch { epoch_id: StacksEpochId::Epoch31, - start_height: first_burnchain_height + 25, - end_height: first_burnchain_height + 26, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 23, + end_height: first_burnchain_height + 24, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_1, }, StacksEpoch { epoch_id: StacksEpochId::Epoch32, - start_height: first_burnchain_height + 26, - end_height: first_burnchain_height + 27, - block_limit: ExecutionCost::max_value(), + start_height: first_burnchain_height + 24, + end_height: first_burnchain_height + 25, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_2, }, StacksEpoch { epoch_id: StacksEpochId::Epoch33, - start_height: first_burnchain_height + 27, + start_height: first_burnchain_height + 25, end_height: STACKS_EPOCH_MAX, - block_limit: ExecutionCost::max_value(), + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_2, }, ]) @@ -1842,8 +1842,6 @@ fn advance_through_all_epochs() { let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_pox_constants(7, 1) .with_private_key(privk.clone()); - boot_plan.pox_constants.reward_cycle_length = 5; - boot_plan.pox_constants.prepare_length = 2; let first_burnchain_height = (boot_plan.pox_constants.pox_4_activation_height + boot_plan.pox_constants.reward_cycle_length + 1) as u64; diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap index 16c61699f39..ddba6a25014 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "167a40e9a29bf010186406caa75d269ab8cc43910ae5145870034da049bb585c", + marf_hash: "aab6f5e58bbfc8d6e9fedb753ad0f8b2b5e97d387e33c90f9f40bd7b9ebd857d", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -62,7 +62,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "cec3300861f732af94e019d044b16aa027245c953610740dd193069737c4b7c7", + marf_hash: "d3775594dbe9784d8fb72165dc1bdefb207328c656a2a008d0e054719a1afcd8", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap index c4ae3fb8473..ca631fa0f3f 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap @@ -3,11 +3,11 @@ source: stackslib/src/chainstate/tests/consensus.rs expression: result --- [ - Failure("Invalid Stacks block 04fba94ef9639692880cdd683d0f89b3eb8ae3f66abe6d4b47818f243c6b1685: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 5f159c8302430ff284324505b78c16b524c71917ceaefab0db3e6aacaff50cfc: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 0ab2d9dab6c26de5dfc3c6ac6d745a555e9acdcd48367896190100d7a96dbf84: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block b59b31b299b35264f5d147c0183d0a121de9aa4c7ee0c3b4130ae5dd23847b30: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block f9a9d0c897a8831d55e3dc1409bd99e48947017a8ee5909d3f4649bdad593906: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 9b2baaf9918fa2b8341afd32e4d7dcedacf4f37914813f837a2ea9dd54aa4106: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 7dbd4edc9b92fa326721d653a0ef07b5b83691464707a4d105939b5eed4c07b6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 9db818437bc1f2586809ebe565bdabf60b20d3573acb2a7a1912d70ce1c9fb23: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 9a9a496555aa10ab066791b17ded8aa87ac9a230f213544207c50270e02f9038: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 85e42a68bbb5c0f95c47aa8c5d5aa4c4edb48deafe7e00733d962234ec519ef2: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 54353178d06fffbf54e5ccfb9a1b08c1d216432ec6e15135dd31d4fa3a8c4141: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 84429852bb2d94c8f8fd290a07bdfd046ef605a245295bc5bf0f0ddef4998102: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 9d33887daee79aebcbf21ef2cef9f6dc41aabc58589c3099929c92ab5eb042f3: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block b12297188a90214bea2afdd171268fda75ab3ad7463e75fc46dfb22d3b1b64aa: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), ] diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index 95cf9021204..708fde119ab 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "d69ae3c88a96363d40459f86d341998fc2afc39b23ec74e9e80fc41bc2bb84bb", + marf_hash: "7554402ce42be546db8d3c6ad05b541a1d6fc13bf23e22cd2a27a24f5492f9ef", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -32,7 +32,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1179565d4864b9b0e0c6b2bc39ae31f8dd3d8b2c2dc0a025f0b5d05466b5a0db", + marf_hash: "a99d1095268efd0b0db2c3d9dad1aa8bf41471afee5aa18f7f8f790a93a5097e", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -60,7 +60,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6181bdb5e506cdbad54bd5d741c7edcc01acf2df5378a51484bde110db55ae30", + marf_hash: "108d805ea6ec544e9dbc5d9f6b5a6af5a060f0b51352f1860b067cdf66507d86", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -88,7 +88,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "698c03661f443d02eaea7405a17e39719cdc6239a6b27278e4f989d0005eafd5", + marf_hash: "91d6b154f0479b3969e25f6efefbd600f8160a1f4f2cfa90b4c7d60fb363f7f8", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -116,7 +116,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1b683de91b139b242f9533248a3637a89e793d3eb56124c9215dc42c19ee476c", + marf_hash: "1be772eba1d55f3518b7e2a88b33056dd47e353903eb54eb2734e61b0a17ebb7", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -144,7 +144,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "38b3c34015a222f0f5188f0ade1e3fbefb59c0f9792951249c6b9ecdd40dd126", + marf_hash: "26744c864f3e37e32c335a21942ff9331a0db52cf8ac4bfad1b2628121361041", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -172,7 +172,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "55a966c58704f4fda3d36c19fbb2bc54d12ccbd0ae8057363d0c97768f815921", + marf_hash: "e22aaea43328f9cd07908731732e7970d97d2e28b71f04179c2c0a860dfcb8a6", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -200,7 +200,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "819dd16988980eb2ccbf3e67f1ca10ea37ccf6567ef31b216b1d8ad59f9c4cc8", + marf_hash: "59038ace75e40aa602fb7e71cf7d4d8c2eb07414ae32a926553482e953ca9e42", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -228,7 +228,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f843cc680839ef0f442c1a34eeea431f4be57872b21fad40cd37f4e7473e1872", + marf_hash: "419fd50a9b8d265473484f7d15c688fd2a5769105ab58f32a7a2784195a5418c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -256,7 +256,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "b4b27ee7f4b3db96e33cb1976c0282d3e42f2e646751541eb89d6b2f0bf044da", + marf_hash: "0f66e22a68dbf8f5d5ec79aeba0907442f7c4f167900290fe48d2f618ef4c43c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -284,7 +284,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "dfc45534d2d1d9bab6c11017bde1baa1b0cc711ba8faf18b322608c04633b7fd", + marf_hash: "66397374594fcee6206ac0dd9c517598ff20708d3e8def0878fb25964394fbe8", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -312,7 +312,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "02f12b59c658b5347941787a4ccabc16a6701a4fd23a80cfa7a011d864893882", + marf_hash: "30ff392402ffb9e727148793918d30d4c70a3886bfcbee684e0190b6b89a2fff", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -340,7 +340,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e9d5639b17bc8b64ccff15577a17b483c2c0401011d5c1bd8d445837e111382e", + marf_hash: "ea65ce3a606eeb95616f59b6f1d841e83807f6a9185ca207e0ed95980957d959", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -368,7 +368,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "71ba9a91ca920e7fa71e7a54c68ff3ca33ff1a0789d2231b45368546b319ceda", + marf_hash: "f494c150b9df95b1588e45b1ed920d322cf095aa2448668d24ffb58bcaae118c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -396,7 +396,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "08cf6388bd4f3a352805a4123cf95ff751128c4a7a3137536412e5250178fb28", + marf_hash: "6ce8988004708e4181cecec6c0ab87165283267588ebd7a10ead967f919df479", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -424,7 +424,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8e80a4543d025f81973117505539efe33e3dc04a1980bc635fcc6982cd0c43fc", + marf_hash: "5b53a9938be2537d748c0a67378c0fc95523dd40ae00a72401c00966119e5fdb", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -452,7 +452,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6daf9c2e6789daceae7b353c170a20f94a50f20519249d02d74a7ba793afe6f6", + marf_hash: "107660a02bd5b9d8a06ebeaf650cdf713e7b7b48c912ca5f164d7e470c449de9", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -480,7 +480,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "fb4187e30248b8f86d1cb529a4f84950e0613e9e84eee44799ab70e9c075d98c", + marf_hash: "823c64dab3a89bf318661983bd53866413cf42f49af9167a472e3c31fb2d2463", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -508,7 +508,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "21b2fddef392498cc31ee5612c69538f796de771e454f36c054b13b0e05915bb", + marf_hash: "3cb4b7e4902802e74efb0160b41ba09355221a76547c6ca7ad6b70950ba98d44", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -536,7 +536,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5e84d1ef2eded318075cf170bb23bf315b47ebd2b7dfc2dbcd111a819efc856a", + marf_hash: "a9ee60eca6ffaaec668faab356414f6116641c51a927866baf63b946e017b823", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -564,7 +564,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "136adcd9de5084b400e57f38f9c67785c7da1822ee23a5a03a2ff49e496340a1", + marf_hash: "89fe0afa9a80096df73d41668e1ce934fda43bfe0b8c51807e5ce264daa42c42", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -592,7 +592,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7c8a419e1d762472657460ec3c54987d59e4976c8b34407b768a328993498199", + marf_hash: "0e7e949cf85b66d13976776129d8598bf46d370711a44394bfa6fc82d89d669e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -620,7 +620,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8db68a6e0a4244658a5b0bfe9c96e36d57b7f733f047d7036b8170c9db5ea0b7", + marf_hash: "33ff455df6722731e6902aa6f3d1f94d1e7369877005b2f62226f89d8b99492a", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -648,7 +648,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "910bcbbee8db3125852c619f2a521cf97104556edd0595faa5a0c65ef319bad2", + marf_hash: "5765bc3a0650f645ef92c02d4fc003bc2182500452231a1d3095e60065a4b230", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -676,7 +676,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "51a5186c0fa2dd1918c5c0ffad56fd8b6eaed88b4e7364c5f5c75a70e36a6d5a", + marf_hash: "e23125e060a16a3d165b2c3a1b13c045926c9890931c620466c002f75aa17329", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -704,7 +704,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6109d481dd79e32c2191904e15d2b70fbba9bdf230b5adf58379df02c6308b61", + marf_hash: "a1794d31f114bce147ceb101fba7ed04bef104e2b94e579e53c729d62fb2d93f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -732,7 +732,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7b683bfa9a6fc1497530b59b603ea7faf6cb817a4274c5bc9ac1f51e0f921b5d", + marf_hash: "a97fc75cccaa58344fb2f1beb6201e3dfd743099ab39d680ae12369e1c00f584", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -760,7 +760,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "555a0208907edb85c57ec670aa65cd20a832f7ded2dd50975f5b2cea07fb0dad", + marf_hash: "c89f80c8891a828f6d81457f298243e581197daca2ce584546e4dda3a8bf910e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -788,7 +788,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "584d7edc84d5d433b571aa861d2e2b643d1e85eecbf4383bc83b0b2d978754c2", + marf_hash: "d35437401175858ec9d01eb7064c4f03fcecf4f8e4f1945471761933a560bfb4", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -816,7 +816,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "fa51e2b4b8e8cb6512ef96573fe546821008115c1f52ed949172fd662bcb28e2", + marf_hash: "ead9b07b8ff488632cdfd72d607e78ca9e964444a8f10f0da720b4a11844c191", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -844,7 +844,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ce4d46e70dae4610ad5b79f26783a9cf0db1a3139e0b9647df690bc099449190", + marf_hash: "df63f7e76808a2addf9b31551bbac9f9ae9f77ef050b6278bf1fe818a03bd843", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -872,7 +872,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6bfab42409068b14413e58c50db313f200f2b2406fdb19ca4f8da770f596869c", + marf_hash: "9008e8ebed0a81316dfb1761f6b1a7f89783a399e036e04e4f18077a2fab0be5", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -900,7 +900,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f882c8688ffe77f8b3507cec27a45bcc5e4a90df5ae97c8ced4f16b71b36d0b7", + marf_hash: "2ca1a99076d882136aa4b7f3fd83d2169c65a2499bad4750cadca720c7cec18e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -928,7 +928,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "63a471a38ec298ad25db574b82bfba62e09eff58be96b0df03879cc133cdcf11", + marf_hash: "8e8278060989842a82970e0e6b787c596c4e33b582eedf6574c1dd28043f6c68", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -956,7 +956,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "208ab98f91e5e8e8c3411c84df9ce610548716d79d6f46eec3f5be496d1e8965", + marf_hash: "a44772dbcf909fc4e9a4c24ab76e17370ed69795c95352bdca593b5475c4d62d", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( From ed30abc2ef4a9786895cf77773ccced5b1e42311 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 28 Oct 2025 10:09:45 -0700 Subject: [PATCH 03/22] Generate Epoch List on the Fly Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 353 +++++++++++++++--- ...nsensus__append_stx_transfers_success.snap | 4 +- ...error_expression_stack_depth_too_deep.snap | 14 +- ...nsensus__successfully_deploy_and_call.snap | 190 ++++++++-- 4 files changed, 468 insertions(+), 93 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 30932f0e2c7..c4ecf9e49a3 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -17,11 +17,16 @@ use std::sync::LazyLock; use clarity::boot_util::boot_code_addr; use clarity::codec::StacksMessageCodec; -use clarity::consts::CHAIN_ID_TESTNET; +use clarity::consts::{ + CHAIN_ID_TESTNET, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, + PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, + PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, + STACKS_EPOCH_MAX, +}; use clarity::types::chainstate::{ StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey, TrieHash, }; -use clarity::types::StacksEpochId; +use clarity::types::{EpochList, StacksEpoch, StacksEpochId}; use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum}; use clarity::util::secp256k1::MessageSignature; use clarity::vm::ast::stack_depth_checker::AST_CALL_STACK_DEPTH_BUFFER; @@ -45,6 +50,7 @@ use crate::chainstate::tests::TestChainstate; use crate::core::test_util::{ make_contract_call, make_contract_publish_versioned, make_stacks_transfer_tx, to_addr, }; +use crate::core::BLOCK_LIMIT_MAINNET_21; use crate::net::tests::NakamotoBootPlan; /// The epochs to test for consensus are the current and upcoming epochs. @@ -98,14 +104,76 @@ const fn clarity_versions_for_epoch(epoch: StacksEpochId) -> &'static [ClarityVe struct ContractConsensusTest<'a> { tx_factory: TestTxFactory, consensus_test: ConsensusTest<'a>, + contract_names: Vec, } impl ContractConsensusTest<'_> { /// Creates a new `ContractConsensusTest`. - pub fn new(test_name: &str) -> Self { + pub fn new( + test_name: &str, + initial_balances: Vec<(PrincipalData, u64)>, + deploy_epochs: &[StacksEpochId], + call_epochs: &[StacksEpochId], + contract_name: &str, + contract_code: &str, + function_name: &str, + function_args: &[ClarityValue], + ) -> Self { + assert!( + !deploy_epochs.is_empty(), + "At least one deploy epoch is required" + ); + let min_deploy_epoch = deploy_epochs.iter().min().unwrap(); + assert!( + call_epochs.iter().all(|e| e >= min_deploy_epoch), + "All call epochs must be >= the minimum deploy epoch" + ); + + // Build epoch_blocks map based on deploy and call epochs + let mut epoch_blocks: HashMap> = HashMap::new(); + let mut contract_names = vec![]; + let sender = &FAUCET_PRIV_KEY; + let contract_addr = to_addr(sender); + + // Precompute contract names and block counts + for epoch in deploy_epochs + .iter() + .chain(call_epochs) + .collect::>() + { + let mut blocks = vec![]; + if deploy_epochs.contains(&epoch) { + let clarity_versions = clarity_versions_for_epoch(*epoch); + let epoch_name = format!("Epoch{}", epoch.to_string().replace(".", "_")); + for version in clarity_versions { + let name = format!( + "{contract_name}-{epoch_name}-{}", + version.to_string().replace(" ", "") + ); + contract_names.push(name); + // Each deployment is a separate TestBlock + blocks.push(TestBlock { + transactions: vec![], // Placeholder, actual txs generated in run + }); + } + } + if call_epochs.contains(&epoch) { + for _ in &contract_names { + // Each call is a separate TestBlock + blocks.push(TestBlock { + transactions: vec![], // Placeholder + }); + } + } + if !blocks.is_empty() { + epoch_blocks.insert(*epoch, blocks); + } + } + Self { tx_factory: TestTxFactory::new(CHAIN_ID_TESTNET), - consensus_test: ConsensusTest::new(test_name, vec![]), + consensus_test: ConsensusTest::new(test_name, initial_balances, epoch_blocks), + contract_names, } } @@ -171,41 +239,31 @@ impl ContractConsensusTest<'_> { deploy_epochs: &[StacksEpochId], call_epochs: &[StacksEpochId], ) -> Vec { - assert!( - !deploy_epochs.is_empty(), - "At least one deploy epoch is required" - ); - let min_deploy_epoch = deploy_epochs.iter().min().unwrap(); - assert!( - call_epochs.iter().all(|e| e >= min_deploy_epoch), - "All call epochs must be >= the minimum deploy epoch" - ); - - let all_epochs: BTreeSet = - deploy_epochs.iter().chain(call_epochs).cloned().collect(); - - let mut contract_names = vec![]; let sender = &FAUCET_PRIV_KEY; let contract_addr = to_addr(sender); - // Create epoch blocks by pairing each epoch with its corresponding transactions let mut results = vec![]; - all_epochs.into_iter().for_each(|epoch| { + + // Process epochs in order + for epoch in deploy_epochs + .iter() + .chain(call_epochs) + .collect::>() + { + let is_naka_block = *epoch >= StacksEpochId::Epoch30; // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); self.consensus_test .chain - .advance_into_epoch(&private_key, epoch); + .advance_into_epoch(&private_key, *epoch); - let is_naka_block = epoch >= StacksEpochId::Epoch30; if deploy_epochs.contains(&epoch) { - let clarity_versions = clarity_versions_for_epoch(epoch); + let clarity_versions = clarity_versions_for_epoch(*epoch); let epoch_name = format!("Epoch{}", epoch.to_string().replace(".", "_")); - clarity_versions.iter().for_each(|version| { + for version in clarity_versions { let name = format!( "{contract_name}-{epoch_name}-{}", version.to_string().replace(" ", "") ); - contract_names.push(name.clone()); let result = self.append_tx_block( &TestTxSpec::ContractDeploy { sender, @@ -216,24 +274,26 @@ impl ContractConsensusTest<'_> { is_naka_block, ); results.push(result); - }); + } } + if call_epochs.contains(&epoch) { - contract_names.iter().for_each(|contract_name| { + for contract_name in self.contract_names.clone() { let result = self.append_tx_block( &TestTxSpec::ContractCall { sender, contract_addr: &contract_addr, - contract_name, + contract_name: &contract_name, function_name, args: function_args, }, is_naka_block, ); results.push(result); - }); + } } - }); + } + results } } @@ -295,8 +355,16 @@ macro_rules! contract_call_consensus_test { // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) let call_epochs = EPOCHS_TO_TEST; $(let call_epochs = $call_epochs;)? - - let mut contract_test = ContractConsensusTest::new(function_name!()); + let mut contract_test = ContractConsensusTest::new( + function_name!(), + vec![], + deploy_epochs, + call_epochs, + contract_name, + $contract_code, + $function_name, + $function_args, + ); let result = contract_test.run( contract_name, $contract_code, @@ -305,7 +373,6 @@ macro_rules! contract_call_consensus_test { deploy_epochs, call_epochs, ); - insta::assert_ron_snapshot!(result); } }; @@ -689,11 +756,16 @@ pub struct TestBlock { /// Represents a consensus test with chainstate. pub struct ConsensusTest<'a> { pub chain: TestChainstate<'a>, + epoch_blocks: HashMap>, } impl ConsensusTest<'_> { /// Creates a new `ConsensusTest` with the given test name and initial balances. - pub fn new(test_name: &str, initial_balances: Vec<(PrincipalData, u64)>) -> Self { + pub fn new( + test_name: &str, + initial_balances: Vec<(PrincipalData, u64)>, + epoch_blocks: HashMap>, + ) -> Self { // Set up chainstate to support Naka. let mut boot_plan = NakamotoBootPlan::new(test_name) // These are the minimum values found for the fastest test execution. @@ -710,13 +782,200 @@ impl ConsensusTest<'_> { .with_pox_constants(7, 1) .with_initial_balances(initial_balances) .with_private_key(FAUCET_PRIV_KEY.clone()); - let first_burnchain_height = (boot_plan.pox_constants.pox_4_activation_height - + boot_plan.pox_constants.reward_cycle_length - + 1) as u64; - let epochs = TestChainstate::all_epochs(first_burnchain_height); + let (epochs, first_burnchain_height) = + Self::calculate_epochs(&boot_plan.pox_constants, &epoch_blocks); boot_plan = boot_plan.with_epochs(epochs); let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); - Self { chain } + Self { + chain, + epoch_blocks, + } + } + + /// Calculate an Epoch list that is capable of supporting the specified Epoch blocks + fn calculate_epochs( + pox_constants: &PoxConstants, + epoch_blocks: &HashMap>, + ) -> (EpochList, u64) { + // Helper function to check if a height is at a reward cycle boundary + let is_reward_cycle_boundary = |height: u64, reward_cycle_length: u64| -> bool { + height % reward_cycle_length <= 1 // Covers both 0 (end of cycle) and 1 (start of cycle) + }; + + let first_burnchain_height = + (pox_constants.pox_4_activation_height + pox_constants.reward_cycle_length + 1) as u64; + info!("StacksEpoch calculate_epochs first_burn_height = {first_burnchain_height}"); + let reward_cycle_length = pox_constants.reward_cycle_length as u64; + let prepare_length = pox_constants.prepare_length as u64; + // Define all epochs in order + let epoch_ids = [ + StacksEpochId::Epoch10, + StacksEpochId::Epoch20, + StacksEpochId::Epoch2_05, + StacksEpochId::Epoch21, + StacksEpochId::Epoch22, + StacksEpochId::Epoch23, + StacksEpochId::Epoch24, + StacksEpochId::Epoch25, + StacksEpochId::Epoch30, + StacksEpochId::Epoch31, + StacksEpochId::Epoch32, + StacksEpochId::Epoch33, + ]; + // Initialize heights + let mut epochs = vec![]; + let mut current_height = 0; + for epoch_id in epoch_ids.iter() { + let start_height = current_height; + let end_height = match *epoch_id { + StacksEpochId::Epoch10 => first_burnchain_height, + StacksEpochId::Epoch20 + | StacksEpochId::Epoch2_05 + | StacksEpochId::Epoch21 + | StacksEpochId::Epoch22 + | StacksEpochId::Epoch23 + | StacksEpochId::Epoch24 => { + // Use test vector block count, default to 1 if not specified + let num_blocks = epoch_blocks + .get(epoch_id) + .map(|blocks| blocks.len() as u64) + .unwrap_or(0); + let mut end_height = start_height + num_blocks; + while end_height != start_height + && is_reward_cycle_boundary(end_height, reward_cycle_length) + { + end_height += 1; + } + end_height + } + StacksEpochId::Epoch25 => { + // Calculate Epoch 2.5 end height and Epoch 3.0 start height. + // Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0's activation. + // Epoch 2.5 end must equal Epoch 3.0 start + // Epoch 3.0 must not start at a cycle boundary + // Epoch 2.5 and 3.0 cannot be in the same reward cycle. + let epoch_25_start = current_height; + // Calculate number of blocks + let num_blocks = epoch_blocks + .get(epoch_id) + .map(|blocks| blocks.len() as u64) + .unwrap_or(1); + let epoch_25_end = epoch_25_start + num_blocks; + let epoch_30_start = epoch_25_end; // Epoch 2.5 end equals Epoch 3.0 start + let epoch_25_reward_cycle = epoch_25_start / reward_cycle_length; + let mut epoch_30_start = epoch_30_start; + let mut epoch_25_end = epoch_30_start; + let mut epoch_30_reward_cycle = epoch_30_start / reward_cycle_length; + // Ensure different reward cycles and Epoch 2.5 starts before prior cycle's prepare phase + let mut prior_cycle = epoch_30_reward_cycle.saturating_sub(1); + let mut prior_prepare_phase_start = + prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); + while epoch_25_start >= prior_prepare_phase_start + || epoch_25_reward_cycle >= epoch_30_reward_cycle + || is_reward_cycle_boundary(epoch_30_start, reward_cycle_length) + { + // Advance the epoch 30 start so it is not in a reward cycle boundary and to ensure + // epoch_25 starts prior to the prepare phase of epoch 30 reward cycle activation + epoch_30_start += 1; + epoch_25_end = epoch_30_start; // Maintain equality + epoch_30_reward_cycle = epoch_30_start / reward_cycle_length; + prior_cycle = epoch_30_reward_cycle.saturating_sub(1); + prior_prepare_phase_start = prior_cycle * reward_cycle_length + + (reward_cycle_length - prepare_length); + } + current_height = epoch_30_start; + epoch_25_end + } + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { + // Only need 1 block per Epoch + start_height + 1 + } + StacksEpochId::Epoch33 => { + // The last epoch extends to max + STACKS_EPOCH_MAX + } + }; + // Create epoch + let block_limit = if *epoch_id == StacksEpochId::Epoch10 { + ExecutionCost::max_value() + } else { + BLOCK_LIMIT_MAINNET_21.clone() + }; + let network_epoch = match *epoch_id { + StacksEpochId::Epoch10 => PEER_VERSION_EPOCH_1_0, + StacksEpochId::Epoch20 => PEER_VERSION_EPOCH_2_0, + StacksEpochId::Epoch2_05 => PEER_VERSION_EPOCH_2_05, + StacksEpochId::Epoch21 => PEER_VERSION_EPOCH_2_1, + StacksEpochId::Epoch22 => PEER_VERSION_EPOCH_2_2, + StacksEpochId::Epoch23 => PEER_VERSION_EPOCH_2_3, + StacksEpochId::Epoch24 => PEER_VERSION_EPOCH_2_4, + StacksEpochId::Epoch25 => PEER_VERSION_EPOCH_2_5, + StacksEpochId::Epoch30 => PEER_VERSION_EPOCH_3_0, + StacksEpochId::Epoch31 => PEER_VERSION_EPOCH_3_1, + StacksEpochId::Epoch32 | StacksEpochId::Epoch33 => PEER_VERSION_EPOCH_3_2, + }; + epochs.push(StacksEpoch { + epoch_id: *epoch_id, + start_height, + end_height, + block_limit, + network_epoch, + }); + current_height = end_height; + } + // Validate Epoch 2.5 and 3.0 constraints + if let Some(epoch_3_0) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { + let epoch_2_5 = epochs + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch25) + .expect("Epoch 2.5 not found"); + let epoch_2_5_start = epoch_2_5.start_height; + let epoch_3_0_start = epoch_3_0.start_height; + let epoch_2_5_end = epoch_2_5.end_height; + + let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; + let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; + let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); + let epoch_3_0_prepare_phase = + prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); + assert!( + epoch_2_5_start < epoch_3_0_prepare_phase, + "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" + ); + assert_eq!( + epoch_2_5_end, epoch_3_0.start_height, + "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" + ); + assert_ne!( + epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, + "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" + ); + assert!( + !is_reward_cycle_boundary(epoch_3_0_start, reward_cycle_length), + "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" + ); + } + // Validate test vector block counts + for (epoch_id, blocks) in epoch_blocks { + let epoch = epochs + .iter() + .find(|e| e.epoch_id == *epoch_id) + .expect("Epoch not found"); + let epoch_length = epoch.end_height - epoch.start_height; + if *epoch_id > StacksEpochId::Epoch25 { + assert!( + epoch_length > 0, + "{epoch_id:?} must have at least 1 burn block." + ); + } else { + let num_blocks = blocks.len() as u64; + assert!( + epoch_length >= num_blocks, + "{epoch_id:?} must have at least {num_blocks} burn blocks, got {epoch_length}" + ); + } + } + (EpochList::new(&epochs), first_burnchain_height) } /// Appends a single block to the chain as a Nakamoto block and returns the result. @@ -733,14 +992,13 @@ impl ConsensusTest<'_> { /// A [`ExpectedResult`] with the outcome of the block processing. fn append_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { debug!("--------- Running block {block:?} ---------"); - let (nakamoto_block, block_size) = self.construct_nakamoto_block(block); + let (nakamoto_block, _block_size) = self.construct_nakamoto_block(block); let mut sortdb = self.chain.sortdb.take().unwrap(); let mut stacks_node = self.chain.stacks_node.take().unwrap(); let chain_tip = NakamotoChainState::get_canonical_block_header(stacks_node.chainstate.db(), &sortdb) .unwrap() .unwrap(); - let pox_constants = PoxConstants::test_default(); let sig_hash = nakamoto_block.header.signer_signature_hash(); debug!( "--------- Processing block {sig_hash} ---------"; @@ -852,12 +1110,9 @@ impl ConsensusTest<'_> { /// # Returns /// /// A `Vec` with the outcome of each block for snapshot testing. - pub fn run( - mut self, - epoch_blocks: HashMap>, - ) -> Vec { + pub fn run(mut self) -> Vec { // Validate blocks - for (epoch_id, blocks) in &epoch_blocks { + for (epoch_id, blocks) in &self.epoch_blocks { assert_ne!( *epoch_id, StacksEpochId::Epoch10, @@ -869,7 +1124,7 @@ impl ConsensusTest<'_> { ); } - let mut sorted_epochs: Vec<_> = epoch_blocks.into_iter().collect(); + let mut sorted_epochs: Vec<_> = self.epoch_blocks.clone().into_iter().collect(); sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); let mut results = vec![]; @@ -1032,7 +1287,7 @@ fn test_append_empty_blocks() { epoch_blocks.insert(*epoch, empty_test_blocks.clone()); } - let result = ConsensusTest::new(function_name!(), vec![]).run(epoch_blocks); + let result = ConsensusTest::new(function_name!(), vec![], epoch_blocks).run(); insta::assert_ron_snapshot!(result); } @@ -1080,7 +1335,7 @@ fn test_append_stx_transfers_success() { epoch_blocks.insert(*epoch, vec![TestBlock { transactions }]); } - let result = ConsensusTest::new(function_name!(), initial_balances).run(epoch_blocks); + let result = ConsensusTest::new(function_name!(), initial_balances, epoch_blocks).run(); insta::assert_ron_snapshot!(result); } diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap index ddba6a25014..f4a2ccbdb68 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "aab6f5e58bbfc8d6e9fedb753ad0f8b2b5e97d387e33c90f9f40bd7b9ebd857d", + marf_hash: "64483c8d3db8e18ae57cc7139f2342bb3b2c5b7150e5258f24e29b78ab79f572", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -62,7 +62,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d3775594dbe9784d8fb72165dc1bdefb207328c656a2a008d0e054719a1afcd8", + marf_hash: "08b75a114cace7e997a25a9d7373e8aa7b2d5ab06cb456466acc91c9485d899f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap index ca631fa0f3f..ffe7a23b5d6 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap @@ -3,11 +3,11 @@ source: stackslib/src/chainstate/tests/consensus.rs expression: result --- [ - Failure("Invalid Stacks block 9db818437bc1f2586809ebe565bdabf60b20d3573acb2a7a1912d70ce1c9fb23: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 9a9a496555aa10ab066791b17ded8aa87ac9a230f213544207c50270e02f9038: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 85e42a68bbb5c0f95c47aa8c5d5aa4c4edb48deafe7e00733d962234ec519ef2: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 54353178d06fffbf54e5ccfb9a1b08c1d216432ec6e15135dd31d4fa3a8c4141: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 84429852bb2d94c8f8fd290a07bdfd046ef605a245295bc5bf0f0ddef4998102: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 9d33887daee79aebcbf21ef2cef9f6dc41aabc58589c3099929c92ab5eb042f3: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block b12297188a90214bea2afdd171268fda75ab3ad7463e75fc46dfb22d3b1b64aa: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 0a5c31ffdbf6dfda87072ad1f9dcd6453b5ff8ad860ecc45a7f7b5e19a72a08a: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block bbbc45b25a7134397ed2168e6a745430285742a21c2e6b7b5c2b7cc845940b7a: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 4b82fb932b2d144e4854302adea1001488d3cee0612500875d5b19165fa8b6c6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 7a87cee5c0ab74e23218852dc427390e7a4ce86caa04d934cf988c605dec96ed: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block addd2c5dafa867c91a76c05f39150a21797941d7741a98a378c2dfaaf040b4a6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block c367dc191cfb48f1521fdce59bd5b6849d401f9b27bf489a5a93d3716f9c432e: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block a5a342aeb5041bd83a042fba39816c25ee0d534ee02f9868734ba78838387e11: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), ] diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index 708fde119ab..acf1aa59d4a 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "7554402ce42be546db8d3c6ad05b541a1d6fc13bf23e22cd2a27a24f5492f9ef", + marf_hash: "42c982671273563377e8a431afa0871d61e1e5727bf84896e9c9f5bcf86bb222", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -32,7 +32,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a99d1095268efd0b0db2c3d9dad1aa8bf41471afee5aa18f7f8f790a93a5097e", + marf_hash: "44722bd3973ed790599cb6f7a21cc87eccae97666e90144923929e5fee6ffe21", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -60,7 +60,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "108d805ea6ec544e9dbc5d9f6b5a6af5a060f0b51352f1860b067cdf66507d86", + marf_hash: "e1ddc745c36674e792e2fcca9b8673b63855f767002ab34407566d2546db2bdd", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -88,7 +88,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "91d6b154f0479b3969e25f6efefbd600f8160a1f4f2cfa90b4c7d60fb363f7f8", + marf_hash: "3a33c1ada606795398c9e1b9b71d9923f4fe32eddf6d4b1fddd6018248e73d35", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -116,7 +116,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1be772eba1d55f3518b7e2a88b33056dd47e353903eb54eb2734e61b0a17ebb7", + marf_hash: "26cc9d4f3f8dae7bcb2d39955f6df2b1fe9c6ddbfeebb1affb8e87ce4f9e92ff", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -144,7 +144,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "26744c864f3e37e32c335a21942ff9331a0db52cf8ac4bfad1b2628121361041", + marf_hash: "5fbf9b595ab130b2732e18be85a79958d1ee2be12e292fe07b103309c68e1d34", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -172,7 +172,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e22aaea43328f9cd07908731732e7970d97d2e28b71f04179c2c0a860dfcb8a6", + marf_hash: "da26830050107703565cd560dde93d23f2d86e4a31c4d6df6cf2c987d82d324f", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -200,7 +200,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "59038ace75e40aa602fb7e71cf7d4d8c2eb07414ae32a926553482e953ca9e42", + marf_hash: "ff7ebfc33069247d300c23ede0ade81560289fbbe9192d852a5bfb39c7272abf", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -228,7 +228,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "419fd50a9b8d265473484f7d15c688fd2a5769105ab58f32a7a2784195a5418c", + marf_hash: "66c2e9a5df2068fddfa8320795030fe992d3e48b737879ca0e982340a9aadb87", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -256,7 +256,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0f66e22a68dbf8f5d5ec79aeba0907442f7c4f167900290fe48d2f618ef4c43c", + marf_hash: "75fd936236697fae8ae60afa68090d69b55733c2fd1074d1e0f26c001e6a1541", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -284,7 +284,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "66397374594fcee6206ac0dd9c517598ff20708d3e8def0878fb25964394fbe8", + marf_hash: "2b411e3a0c4d4544ab26ca39f06d044d7d7b5099ae759c54834f24a0fa6ebe94", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -312,7 +312,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "30ff392402ffb9e727148793918d30d4c70a3886bfcbee684e0190b6b89a2fff", + marf_hash: "b4db59dad45d307e0ec62224cfc227176225a86fff9717ce6c42db56921a6453", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -340,7 +340,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ea65ce3a606eeb95616f59b6f1d841e83807f6a9185ca207e0ed95980957d959", + marf_hash: "bac221861205fb31fa0c1ea832b756c189743a57b0be3118144e1d2fc8d10b27", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -368,7 +368,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f494c150b9df95b1588e45b1ed920d322cf095aa2448668d24ffb58bcaae118c", + marf_hash: "756ca87c5089c476ea135c6c738942743b87433536d135b5349a7c8554c5af4c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -396,7 +396,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6ce8988004708e4181cecec6c0ab87165283267588ebd7a10ead967f919df479", + marf_hash: "96c9f18a4955d066d5d8b7acb83c78d572c0137eb7ad3c5b7cd1a04a7ca2375a", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -424,7 +424,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5b53a9938be2537d748c0a67378c0fc95523dd40ae00a72401c00966119e5fdb", + marf_hash: "3979a8004a58cf1fc5b565178b6409bb24f5b94b3039cc9dedf499f0cefa7f1c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -452,7 +452,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "107660a02bd5b9d8a06ebeaf650cdf713e7b7b48c912ca5f164d7e470c449de9", + marf_hash: "d0fe93d798dd8d15094d53039dd883d288f6533132295a4e8238f96e1b748aeb", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -480,7 +480,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "823c64dab3a89bf318661983bd53866413cf42f49af9167a472e3c31fb2d2463", + marf_hash: "20d42bd924755ae2508ccc42e76b10f8853c710561080bf09754bddbda97780a", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -508,7 +508,127 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3cb4b7e4902802e74efb0160b41ba09355221a76547c6ca7ad6b70950ba98d44", + marf_hash: "783fe32668e84d8a4fc9a18dc8bb800334ffa0b7c8789a2442a7ee897ba73c39", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity1\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "060c51c5713973c338b6ae9d0ff12f2b40ce38c65679aa2eec3a767a0c3ca491", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity2\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "dff43f71420fc31e0323c9859acefd095085f6741c05801c6f995fbcd37e14e0", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity3\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "ebf3fa7865cfdb8f64ca8476aed5e77f4b4624eedb4f8c7e66c7ddd7036a9f7e", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity4\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "865729ee60984b891c3dadefa4251ecf616bdb76e9dbcf0e4c532e8ba55e809e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -536,7 +656,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a9ee60eca6ffaaec668faab356414f6116641c51a927866baf63b946e017b823", + marf_hash: "2b42a9954144b76c5a77eab040908d9a160a7b6eb4174f070bfeee8b1c0e7f28", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -564,7 +684,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "89fe0afa9a80096df73d41668e1ce934fda43bfe0b8c51807e5ce264daa42c42", + marf_hash: "c4ebcf45811a541a94bfd83823b20647fccdcef245effb1eff72dc1a66846998", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -592,7 +712,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0e7e949cf85b66d13976776129d8598bf46d370711a44394bfa6fc82d89d669e", + marf_hash: "f2915dd59f38bdd3497b6cdf6188638da517dda3fbf9545a6efd80a7926dd512", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -620,7 +740,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "33ff455df6722731e6902aa6f3d1f94d1e7369877005b2f62226f89d8b99492a", + marf_hash: "de05599803ccb3dd8aa8cc5e8d514510f688ed4228f55f6a4f7befa9626b02f8", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -648,7 +768,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5765bc3a0650f645ef92c02d4fc003bc2182500452231a1d3095e60065a4b230", + marf_hash: "2371fc157ae4ce4f36a726d61a154a00c5a05bb89266abe0f819c260a92a7acb", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -676,7 +796,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e23125e060a16a3d165b2c3a1b13c045926c9890931c620466c002f75aa17329", + marf_hash: "79626a488bd08fb706ccd7e03f0504d40e212974e73d50b7bf911c2e7d53786b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -704,7 +824,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a1794d31f114bce147ceb101fba7ed04bef104e2b94e579e53c729d62fb2d93f", + marf_hash: "7c6a90349a6ff3c982af2bd6b599552cb2a724b9caf157e761838759922e1429", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -732,7 +852,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a97fc75cccaa58344fb2f1beb6201e3dfd743099ab39d680ae12369e1c00f584", + marf_hash: "5eb68c13c22e4b151270e8f7b87551fdf40398b5601e15f8d28c261962484531", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -760,7 +880,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c89f80c8891a828f6d81457f298243e581197daca2ce584546e4dda3a8bf910e", + marf_hash: "cc74a307cb642bbbc9b8ae22f07af21f1f58c74fc1b7a026538223b87bbcc71c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -788,7 +908,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d35437401175858ec9d01eb7064c4f03fcecf4f8e4f1945471761933a560bfb4", + marf_hash: "d02b68bf5b76ac8d44980e462278746c32bc18b0e3cd8560cbee17bfdca9460a", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -816,7 +936,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ead9b07b8ff488632cdfd72d607e78ca9e964444a8f10f0da720b4a11844c191", + marf_hash: "99882189d9a201923d2cc7892191e4fd2c045bc2fab29ca6814bbf84177d781c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -844,7 +964,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "df63f7e76808a2addf9b31551bbac9f9ae9f77ef050b6278bf1fe818a03bd843", + marf_hash: "5c3f7f1830c7118d6fab693685f740f2b5410e402f436dcb1a3ae6f710f270e7", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -872,7 +992,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "9008e8ebed0a81316dfb1761f6b1a7f89783a399e036e04e4f18077a2fab0be5", + marf_hash: "82a58693c5b1512cd132a16a619468f64f9aca1bf388e0e83eb69e15ca4bf34f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -900,7 +1020,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2ca1a99076d882136aa4b7f3fd83d2169c65a2499bad4750cadca720c7cec18e", + marf_hash: "5c4a700da1fb15d9eee54506035dec2098351af28da39e26bc527e95653507bf", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -928,7 +1048,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8e8278060989842a82970e0e6b787c596c4e33b582eedf6574c1dd28043f6c68", + marf_hash: "1d7b7bfb83573c6769d140e96167342c0d0d8e7fc054db4ad955aec53c4cc741", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -956,7 +1076,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a44772dbcf909fc4e9a4c24ab76e17370ed69795c95352bdca593b5475c4d62d", + marf_hash: "79db6c4bfba3f42b1011f4f95b84e11d27c2569d84c1fc97c2fca476a4c559be", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( From eafad79a8d322c782a4f6b9616db2afcde77d2bc Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 28 Oct 2025 14:28:22 -0700 Subject: [PATCH 04/22] Fix Clarity versions per Epoch and update contract_call_consensus_test to use all epochs GTE epoch 2.0 Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 9 +- stackslib/src/chainstate/tests/consensus.rs | 320 +- stackslib/src/chainstate/tests/mod.rs | 27 +- ...nsensus__append_stx_transfers_success.snap | 4 +- ...error_expression_stack_depth_too_deep.snap | 14 +- ...nsensus__successfully_deploy_and_call.snap | 2858 +++++++++++++++-- 6 files changed, 2895 insertions(+), 337 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 3116d552e55..ccde96a07a5 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -441,7 +441,14 @@ impl StacksEpochId { StacksEpochId::Epoch32 } - pub const ALL_GTE_30: &'static [StacksEpochId] = &[ + pub const ALL_GTE_20: &'static [StacksEpochId] = &[ + StacksEpochId::Epoch20, + StacksEpochId::Epoch2_05, + StacksEpochId::Epoch21, + StacksEpochId::Epoch22, + StacksEpochId::Epoch23, + StacksEpochId::Epoch24, + StacksEpochId::Epoch25, StacksEpochId::Epoch30, StacksEpochId::Epoch31, StacksEpochId::Epoch32, diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index c4ecf9e49a3..07e79937870 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -74,8 +74,7 @@ const FOO_CONTRACT: &str = "(define-public (foo) (ok 1)) /// Returns the list of Clarity versions that can be used to deploy contracts in the given epoch. const fn clarity_versions_for_epoch(epoch: StacksEpochId) -> &'static [ClarityVersion] { match epoch { - StacksEpochId::Epoch10 => &[], - StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => &[ClarityVersion::Clarity1], + StacksEpochId::Epoch10 | StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => &[], StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 @@ -109,6 +108,11 @@ struct ContractConsensusTest<'a> { impl ContractConsensusTest<'_> { /// Creates a new `ContractConsensusTest`. + /// + /// # Panics + /// - If `deploy_epochs` is empty. + /// - If any `call_epoch` is less than the minimum `deploy_epoch`. + // Creates a new ContractConsensusTest pub fn new( test_name: &str, initial_balances: Vec<(PrincipalData, u64)>, @@ -132,32 +136,39 @@ impl ContractConsensusTest<'_> { // Build epoch_blocks map based on deploy and call epochs let mut epoch_blocks: HashMap> = HashMap::new(); let mut contract_names = vec![]; - let sender = &FAUCET_PRIV_KEY; - let contract_addr = to_addr(sender); + + // Combine and sort unique epochs + let all_epochs: BTreeSet<_> = deploy_epochs.iter().chain(call_epochs).collect(); // Precompute contract names and block counts - for epoch in deploy_epochs - .iter() - .chain(call_epochs) - .collect::>() - { + for epoch in all_epochs { let mut blocks = vec![]; - if deploy_epochs.contains(&epoch) { + + if deploy_epochs.contains(epoch) { let clarity_versions = clarity_versions_for_epoch(*epoch); - let epoch_name = format!("Epoch{}", epoch.to_string().replace(".", "_")); - for version in clarity_versions { - let name = format!( - "{contract_name}-{epoch_name}-{}", - version.to_string().replace(" ", "") - ); - contract_names.push(name); - // Each deployment is a separate TestBlock + let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); + + // Each deployment is a seperate TestBlock + if clarity_versions.is_empty() { + let name = format!("{contract_name}-{epoch_name}-None"); + contract_names.push(name.clone()); blocks.push(TestBlock { - transactions: vec![], // Placeholder, actual txs generated in run + transactions: vec![], }); + } else { + for &version in clarity_versions { + let version_tag = version.to_string().replace(' ', ""); + let name = format!("{contract_name}-{epoch_name}-{version_tag}"); + contract_names.push(name.clone()); + blocks.push(TestBlock { + transactions: vec![], + }); + } } } - if call_epochs.contains(&epoch) { + + if call_epochs.contains(epoch) { + // Each call is a separate TestBlock for _ in &contract_names { // Each call is a separate TestBlock blocks.push(TestBlock { @@ -194,6 +205,77 @@ impl ContractConsensusTest<'_> { result } + /// Helper to deploy contracts a contract in a given epoch. + fn deploy_contracts( + &mut self, + epoch: StacksEpochId, + contract_name: &str, + contract_code: &str, + is_naka_block: bool, + ) -> Vec { + let mut results = Vec::new(); + let clarity_versions = clarity_versions_for_epoch(epoch); + let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); + + if clarity_versions.is_empty() { + let name = format!("{contract_name}-{epoch_name}-None"); + results.push(self.append_tx_block( + &TestTxSpec::ContractDeploy { + sender: &FAUCET_PRIV_KEY, + name: &name, + code: contract_code, + clarity_version: None, + }, + is_naka_block, + )); + self.contract_names.push(name); + } else { + for &version in clarity_versions { + let version_tag = version.to_string().replace(' ', ""); + let name = format!("{contract_name}-{epoch_name}-{version_tag}"); + results.push(self.append_tx_block( + &TestTxSpec::ContractDeploy { + sender: &FAUCET_PRIV_KEY, + name: &name, + code: contract_code, + clarity_version: Some(version), + }, + is_naka_block, + )); + self.contract_names.push(name); + } + } + + results + } + + /// Helper to call all deployed contracts in a given epoch. + fn call_contracts( + &mut self, + epoch: StacksEpochId, + contract_addr: &StacksAddress, + function_name: &str, + function_args: &[ClarityValue], + is_naka_block: bool, + ) -> Vec { + self.contract_names + .clone() + .iter() + .map(|contract_name| { + self.append_tx_block( + &TestTxSpec::ContractCall { + sender: &FAUCET_PRIV_KEY, + contract_addr, + contract_name, + function_name, + args: function_args, + }, + is_naka_block, + ) + }) + .collect() + } + /// Executes a consensus test for a contract function across multiple Stacks epochs. /// /// This helper automates deploying a contract and invoking one of its public functions @@ -239,58 +321,46 @@ impl ContractConsensusTest<'_> { deploy_epochs: &[StacksEpochId], call_epochs: &[StacksEpochId], ) -> Vec { - let sender = &FAUCET_PRIV_KEY; - let contract_addr = to_addr(sender); - let mut results = vec![]; + let contract_addr = to_addr(&FAUCET_PRIV_KEY); + let mut results = Vec::new(); - // Process epochs in order - for epoch in deploy_epochs + // Combine all epochs + let all_epochs = deploy_epochs .iter() .chain(call_epochs) - .collect::>() - { + .collect::>(); + + // Process epochs in order + for epoch in all_epochs { let is_naka_block = *epoch >= StacksEpochId::Epoch30; + let is_deploy_epoch = deploy_epochs.contains(epoch); + let is_call_epoch = call_epochs.contains(epoch); + // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); + + // Advance the chain into the target epoch self.consensus_test .chain .advance_into_epoch(&private_key, *epoch); - if deploy_epochs.contains(&epoch) { - let clarity_versions = clarity_versions_for_epoch(*epoch); - let epoch_name = format!("Epoch{}", epoch.to_string().replace(".", "_")); - for version in clarity_versions { - let name = format!( - "{contract_name}-{epoch_name}-{}", - version.to_string().replace(" ", "") - ); - let result = self.append_tx_block( - &TestTxSpec::ContractDeploy { - sender, - name: &name, - code: contract_code, - clarity_version: Some(*version), - }, - is_naka_block, - ); - results.push(result); - } + if is_deploy_epoch { + results.extend(self.deploy_contracts( + *epoch, + contract_name, + contract_code, + is_naka_block, + )); } - if call_epochs.contains(&epoch) { - for contract_name in self.contract_names.clone() { - let result = self.append_tx_block( - &TestTxSpec::ContractCall { - sender, - contract_addr: &contract_addr, - contract_name: &contract_name, - function_name, - args: function_args, - }, - is_naka_block, - ); - results.push(result); - } + if is_call_epoch { + results.extend(self.call_contracts( + *epoch, + &contract_addr, + function_name, + function_args, + is_naka_block, + )); } } @@ -320,7 +390,7 @@ impl ContractConsensusTest<'_> { /// * `contract_code` — The Clarity source code for the contract. /// * `function_name` — The public function to call. /// * `function_args` — Function arguments, provided as a slice of [`ClarityValue`]. -/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 3.0. +/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 2.0. /// * `call_epochs` — *(optional)* Epochs in which to call the function. Defaults to [`EPOCHS_TO_TEST`]. /// /// # Example @@ -346,10 +416,8 @@ macro_rules! contract_call_consensus_test { ) => { #[test] fn $name() { - let contract_name = $contract_name; - - // Handle deploy_epochs parameter (default to all epochs >= 3.0 if not provided) - let deploy_epochs = StacksEpochId::ALL_GTE_30; + // Handle deploy_epochs parameter (default to all epochs >= 2.0 if not provided) + let deploy_epochs = StacksEpochId::ALL_GTE_20; $(let deploy_epochs = $deploy_epochs;)? // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) @@ -360,13 +428,13 @@ macro_rules! contract_call_consensus_test { vec![], deploy_epochs, call_epochs, - contract_name, + $contract_name, $contract_code, $function_name, $function_args, ); let result = contract_test.run( - contract_name, + $contract_name, $contract_code, $function_name, $function_args, @@ -760,25 +828,26 @@ pub struct ConsensusTest<'a> { } impl ConsensusTest<'_> { - /// Creates a new `ConsensusTest` with the given test name and initial balances. + /// Creates a new `ConsensusTest` with the given test name, initial balances, and epoch blocks. pub fn new( test_name: &str, initial_balances: Vec<(PrincipalData, u64)>, epoch_blocks: HashMap>, ) -> Self { + // Validate blocks + for (epoch_id, blocks) in &epoch_blocks { + assert_ne!( + *epoch_id, + StacksEpochId::Epoch10, + "Epoch10 is not supported" + ); + assert!( + !blocks.is_empty(), + "Each epoch must have at least one block" + ); + } // Set up chainstate to support Naka. let mut boot_plan = NakamotoBootPlan::new(test_name) - // These are the minimum values found for the fastest test execution. - // - // If changing these values, ensure the following conditions are met: - // 1. Min 6 reward blocks (test framework limitation). - // 2. Epoch 3.0 starts in the reward phase. - // 3. Tests bypass mainnet's prepare_length >= 3 (allowing 1). - // - Current boot sequence: - // - Cycle 3: Signers at height 27 register for 12 reward cycles - // - Cycle 4: Epoch 3.0 starts at height 30 - // Tests generate 1 bitcoin block per epoch transition after 3.0 - // staying within the registration window .with_pox_constants(7, 1) .with_initial_balances(initial_balances) .with_private_key(FAUCET_PRIV_KEY.clone()); @@ -792,7 +861,28 @@ impl ConsensusTest<'_> { } } - /// Calculate an Epoch list that is capable of supporting the specified Epoch blocks + /// Calculates a valid [`EpochList`] and starting burnchain height for the test harness. + /// + /// The resulting EpochList satisfies the following: + /// - Each epoch has enough burnchain blocks to accommodate all test blocks. + /// - Epoch 2.5 → 3.0 transition satisfies the following constraints: + /// - 2.5 and 3.0 are in **different reward cycles**. + /// - 2.5 starts **before** the prepare phase of the cycle prior to 3.0 activation. + /// - 3.0 does not start on a reward cycle boundary. + /// - All epoch heights are contiguous and correctly ordered. + /// + /// The resulting [`EpochList`] is used to initialize the test chainstate with correct + /// epoch boundaries, enabling accurate simulation of epoch transitions and consensus rules. + /// + /// # Arguments + /// + /// * `pox_constants` - PoX configuration (reward cycle length, prepare phase, etc.). + /// * `epoch_blocks` - Map of epoch IDs to the test blocks to run in each. + /// + /// # Returns + /// + /// `(EpochList, first_burnchain_height)` — the epoch list and the burnchain + /// height at which the first Stacks block is mined. fn calculate_epochs( pox_constants: &PoxConstants, epoch_blocks: &HashMap>, @@ -835,18 +925,13 @@ impl ConsensusTest<'_> { | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 => { - // Use test vector block count, default to 1 if not specified + // Use test vector block count + // Always add 1 so we can ensure we are fully in the epoch before we then execute + // the corresponding test blocks in their own blocks let num_blocks = epoch_blocks .get(epoch_id) - .map(|blocks| blocks.len() as u64) - .unwrap_or(0); - let mut end_height = start_height + num_blocks; - while end_height != start_height - && is_reward_cycle_boundary(end_height, reward_cycle_length) - { - end_height += 1; - } - end_height + .map(|blocks| blocks.len() as u64 + 1).unwrap_or(0); + start_height + num_blocks } StacksEpochId::Epoch25 => { // Calculate Epoch 2.5 end height and Epoch 3.0 start height. @@ -854,41 +939,46 @@ impl ConsensusTest<'_> { // Epoch 2.5 end must equal Epoch 3.0 start // Epoch 3.0 must not start at a cycle boundary // Epoch 2.5 and 3.0 cannot be in the same reward cycle. - let epoch_25_start = current_height; - // Calculate number of blocks let num_blocks = epoch_blocks .get(epoch_id) .map(|blocks| blocks.len() as u64) - .unwrap_or(1); - let epoch_25_end = epoch_25_start + num_blocks; - let epoch_30_start = epoch_25_end; // Epoch 2.5 end equals Epoch 3.0 start + .unwrap_or(0) + .saturating_add(1); // Add one block for pox lockups. + + let epoch_25_start = current_height; + let epoch_30_start = epoch_25_start + num_blocks; + let epoch_25_reward_cycle = epoch_25_start / reward_cycle_length; let mut epoch_30_start = epoch_30_start; - let mut epoch_25_end = epoch_30_start; let mut epoch_30_reward_cycle = epoch_30_start / reward_cycle_length; // Ensure different reward cycles and Epoch 2.5 starts before prior cycle's prepare phase let mut prior_cycle = epoch_30_reward_cycle.saturating_sub(1); let mut prior_prepare_phase_start = prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); - while epoch_25_start >= prior_prepare_phase_start + while epoch_25_start + num_blocks >= prior_prepare_phase_start || epoch_25_reward_cycle >= epoch_30_reward_cycle || is_reward_cycle_boundary(epoch_30_start, reward_cycle_length) { - // Advance the epoch 30 start so it is not in a reward cycle boundary and to ensure - // epoch_25 starts prior to the prepare phase of epoch 30 reward cycle activation + // Advance to 3.0 start so it is not in a reward cycle boundary and to ensure + // 2.5 starts prior to the prepare phase of epoch 30 reward cycle activation epoch_30_start += 1; - epoch_25_end = epoch_30_start; // Maintain equality epoch_30_reward_cycle = epoch_30_start / reward_cycle_length; prior_cycle = epoch_30_reward_cycle.saturating_sub(1); prior_prepare_phase_start = prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); } current_height = epoch_30_start; - epoch_25_end + epoch_30_start // Epoch 2.5 ends where Epoch 3.0 starts } StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { // Only need 1 block per Epoch - start_height + 1 + if epoch_blocks.contains_key(epoch_id) { + start_height + 1 + } else { + // If we don't care to have any blocks in this epoch + // don't bother giving it an epoch height + start_height + } } StacksEpochId::Epoch33 => { // The last epoch extends to max @@ -975,7 +1065,12 @@ impl ConsensusTest<'_> { ); } } - (EpochList::new(&epochs), first_burnchain_height) + let epoch_list = EpochList::new(&epochs); + info!("Calculated EpochList from pox constants with first burnchain height of {first_burnchain_height}."; + "epochs" => ?epoch_list, + "first_burnchain_height" => first_burnchain_height + ); + (epoch_list, first_burnchain_height) } /// Appends a single block to the chain as a Nakamoto block and returns the result. @@ -1038,6 +1133,7 @@ impl ConsensusTest<'_> { /// /// A [`ExpectedResult`] with the outcome of the block processing. fn append_pre_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { + debug!("--------- Running Pre-Nakamoto block {block:?} ---------"); let (ch, bh) = SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) .unwrap(); @@ -1102,28 +1198,10 @@ impl ConsensusTest<'_> { /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s /// associated with that epoch and collects their results. /// - /// # Arguments - /// - /// * `epoch_blocks` - A map where keys are [`StacksEpochId`]s and values are the - /// sequence of blocks to be executed during that epoch. - /// /// # Returns /// /// A `Vec` with the outcome of each block for snapshot testing. pub fn run(mut self) -> Vec { - // Validate blocks - for (epoch_id, blocks) in &self.epoch_blocks { - assert_ne!( - *epoch_id, - StacksEpochId::Epoch10, - "Epoch10 is not supported" - ); - assert!( - !blocks.is_empty(), - "Each epoch must have at least one block" - ); - } - let mut sorted_epochs: Vec<_> = self.epoch_blocks.clone().into_iter().collect(); sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); diff --git a/stackslib/src/chainstate/tests/mod.rs b/stackslib/src/chainstate/tests/mod.rs index c4ed26b0fa6..6256938df91 100644 --- a/stackslib/src/chainstate/tests/mod.rs +++ b/stackslib/src/chainstate/tests/mod.rs @@ -461,10 +461,6 @@ impl<'a> TestChainstate<'a> { .unwrap_or(u64::MAX); let mut mined_pox_4_lockup = false; - let current_epoch = SortitionDB::get_stacks_epoch(self.sortdb().conn(), burn_block_height) - .unwrap() - .unwrap() - .epoch_id; while burn_block_height < target_height { if burn_block_height < epoch_30_height - 1 { let current_reward_cycle = self.get_reward_cycle(); @@ -603,17 +599,18 @@ impl<'a> TestChainstate<'a> { target_epoch: StacksEpochId, ) { let burn_block_height = self.get_burn_block_height(); - let current_epoch = - SortitionDB::get_stacks_epoch(self.sortdb_ref().conn(), burn_block_height) - .unwrap() - .unwrap() - .epoch_id; - assert!( - current_epoch <= target_epoch, - "Already advanced past target epoch ({target_epoch}). Currently in epoch {current_epoch} at burn block height: {burn_block_height}." - ); - // Don't bother advancing to the boundary if we are already in it. - if current_epoch < target_epoch { + let target_height = self + .config + .epochs + .as_ref() + .expect("Epoch configuration missing") + .iter() + .find(|e| e.epoch_id == target_epoch) + .expect("Target epoch not found") + .start_height; + assert!(burn_block_height <= target_height, "We cannot advance backwards. Examine your bootstrap setup. Current burn block height: {burn_block_height}. Target height: {target_height}"); + // Don't bother advancing to the boundary if we are already at it. + if burn_block_height < target_height { self.advance_to_epoch_boundary(private_key, target_epoch); if target_epoch < StacksEpochId::Epoch30 { self.tenure_with_txs(&[]); diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap index f4a2ccbdb68..cf61d1a356d 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "64483c8d3db8e18ae57cc7139f2342bb3b2c5b7150e5258f24e29b78ab79f572", + marf_hash: "82d8f3918bafbdc48320936b9c949749539ecc30eb233ab25eefd56541c1b4c6", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -62,7 +62,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "08b75a114cace7e997a25a9d7373e8aa7b2d5ab06cb456466acc91c9485d899f", + marf_hash: "b8efac93fe58f8bdb875802253f9ccf9f94e5dd80f2b1b163a3d07a29789f56b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap index ffe7a23b5d6..351e186df80 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__chainstate_error_expression_stack_depth_too_deep.snap @@ -3,11 +3,11 @@ source: stackslib/src/chainstate/tests/consensus.rs expression: result --- [ - Failure("Invalid Stacks block 0a5c31ffdbf6dfda87072ad1f9dcd6453b5ff8ad860ecc45a7f7b5e19a72a08a: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block bbbc45b25a7134397ed2168e6a745430285742a21c2e6b7b5c2b7cc845940b7a: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 4b82fb932b2d144e4854302adea1001488d3cee0612500875d5b19165fa8b6c6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block 7a87cee5c0ab74e23218852dc427390e7a4ce86caa04d934cf988c605dec96ed: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block addd2c5dafa867c91a76c05f39150a21797941d7741a98a378c2dfaaf040b4a6: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block c367dc191cfb48f1521fdce59bd5b6849d401f9b27bf489a5a93d3716f9c432e: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), - Failure("Invalid Stacks block a5a342aeb5041bd83a042fba39816c25ee0d534ee02f9868734ba78838387e11: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 89ffe2098a1819614dbe39713ee71a253ddb084a3726ace97687a5d2a98e5d3f: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 11fd9c1a6704fe0308238004527fbf085b6dd5cb19911042d1f2355085fe26d7: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 5922d7841de6eb9e4ec86c9f7b2dbaddbb557251557fc002d706f01e3f289685: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 37a56686cba726faab939b0ce646373da245ce0dc792cf1f078318bd6de5e959: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 97a068569738a013fddbe91f90cea1d99b983fcec440a956a99863a9e16f02d5: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block 1793548f9c0314036c80456536005a81de4115f9febcbf286e547a5db91185a9: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), + Failure("Invalid Stacks block b7c753eef1c646d3ef266a99270a287e4c2407e6ac1c64a9485d85e36abb1ab8: ClarityError(Parse(ParseError { err: ExpressionStackDepthTooDeep, pre_expressions: None, diagnostic: Diagnostic { level: Error, message: \"AST has too deep of an expression nesting. The maximum stack depth is 64\", spans: [], suggestion: None } }))"), ] diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index acf1aa59d4a..4c52b082c0f 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -4,11 +4,112 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "42c982671273563377e8a431afa0871d61e1e5727bf84896e9c9f5bcf86bb222", - evaluated_epoch: Epoch30, + marf_hash: "f82c87d26f3301c77aebe382d6b68cdd9a5d6d593aba41a6f098fd3faa377fae", + evaluated_epoch: Epoch20, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + tx: "Coinbase(1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_0-None, code_body: [..], clarity_version: None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 1175000, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 1175000, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "3aa7ec364b0895b29b20d64bee38a0fe1bac20945e4899b31ea39fd28688ca7b", + evaluated_epoch: Epoch2_05, + transactions: [ + ExpectedTransactionOutput( + tx: "Coinbase(2020202020202020202020202020202020202020202020202020202020202020, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_05-None, code_body: [..], clarity_version: None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 315491, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 315491, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "c6f82628f37b2c76fdb5404e1dc0b3b6a7beb50bd1a8b8931c65c6c207861fa6", + evaluated_epoch: Epoch21, + transactions: [ + ExpectedTransactionOutput( + tx: "Coinbase(2222222222222222222222222222222222222222222222222222222222222222, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_1-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -32,11 +133,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "44722bd3973ed790599cb6f7a21cc87eccae97666e90144923929e5fee6ffe21", - evaluated_epoch: Epoch30, + marf_hash: "0a058e669a64920c6026f4fea10c14c3b7bf1a8df9fce55e2ba140e147d1813f", + evaluated_epoch: Epoch21, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + tx: "Coinbase(2323232323232323232323232323232323232323232323232323232323232323, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_1-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -60,11 +176,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e1ddc745c36674e792e2fcca9b8673b63855f767002ab34407566d2546db2bdd", - evaluated_epoch: Epoch30, + marf_hash: "1a9844dff80d1c9325b8d73c34aa28e85bd15742c01e2623b9c2e79746e42118", + evaluated_epoch: Epoch22, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + tx: "Coinbase(2525252525252525252525252525252525252525252525252525252525252525, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_2-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -88,11 +219,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3a33c1ada606795398c9e1b9b71d9923f4fe32eddf6d4b1fddd6018248e73d35", - evaluated_epoch: Epoch31, + marf_hash: "e8e86ba457695cdb0e18426d6f6f8a7de0fc09e9cbd79e3d9dc01597678b338c", + evaluated_epoch: Epoch22, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + tx: "Coinbase(2626262626262626262626262626262626262626262626262626262626262626, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_2-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -116,11 +262,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "26cc9d4f3f8dae7bcb2d39955f6df2b1fe9c6ddbfeebb1affb8e87ce4f9e92ff", - evaluated_epoch: Epoch31, + marf_hash: "78901bb99cbba1cf2df781c0e034682eb6a5bd79de914383d407780cc089fc05", + evaluated_epoch: Epoch23, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + tx: "Coinbase(2828282828282828282828282828282828282828282828282828282828282828, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -144,11 +305,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5fbf9b595ab130b2732e18be85a79958d1ee2be12e292fe07b103309c68e1d34", - evaluated_epoch: Epoch31, + marf_hash: "27521c9c53503d8103f769cec353561e1aebe1297e28672467651728c8a7f335", + evaluated_epoch: Epoch23, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + tx: "Coinbase(2929292929292929292929292929292929292929292929292929292929292929, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -172,11 +348,26 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "da26830050107703565cd560dde93d23f2d86e4a31c4d6df6cf2c987d82d324f", - evaluated_epoch: Epoch32, + marf_hash: "24fd9ffb4aef7be8948002cf5490c4961abec89ee1d15c38634ba0d4f4b52bcc", + evaluated_epoch: Epoch24, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + tx: "Coinbase(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_4-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -200,11 +391,69 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ff7ebfc33069247d300c23ede0ade81560289fbbe9192d852a5bfb39c7272abf", - evaluated_epoch: Epoch32, + marf_hash: "55a54057d46f262251364ea8e349ede9b082f0efd745c712d3d9d6699633a6f3", + evaluated_epoch: Epoch24, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + tx: "Coinbase(2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_4-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "04362001f26cb019410d4ae83d9217be8eec797d662c427e84fb3222eb296d54", + evaluated_epoch: Epoch25, + transactions: [ + ExpectedTransactionOutput( + tx: "Coinbase(2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_5-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -220,47 +469,2282 @@ expression: result ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "c2a897fbcc709ca5219d92fe0f9e02d20a676084e5e9f842399cea0268c415a1", + evaluated_epoch: Epoch25, + transactions: [ + ExpectedTransactionOutput( + tx: "Coinbase(2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f, None, None)", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch2_5-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "89590343420f317b4ddb5637007298f58bc433df0644f5738cd8793fed56302d", + evaluated_epoch: Epoch30, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "4cc2b4653d17798c544c1b7a1f5fe597b2def190f98c99c5ea71a407ab8abc1a", + evaluated_epoch: Epoch30, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "61c9faba574baee6be481215e932a24498c46d7198b80f558a84b2c7410b1a2f", + evaluated_epoch: Epoch30, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_0-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "8506aa2d472a5eb8d75319a542fdf12a6019293274ab24b4a5beedd6080e3dbc", + evaluated_epoch: Epoch31, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "62cc4b26f2d9f300a73b7dbb956b97b2c4d0636e48ac1d76a8e79d8920fb33f2", + evaluated_epoch: Epoch31, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "badbbe82d38b006719f7bd73d5055c9ee3d95d1a617894e2f532d98ac6c82203", + evaluated_epoch: Epoch31, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_1-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "f9f9cc3a8d7aa235575329752da19805518822df8811bb9e2158cb6e138c3785", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "82fe510e0f301cf710abeadd87d9902ffa07a1997abce3a80a7ac5ad52abd1b5", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "7abc788e3f5780d0b02c643be775835b03efc3ad8ff3ea84ab7ab748b104995b", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "4df955c663f6e20db655c1ff9633c2307a98fb5480a3e75c1631b5ab38e26b73", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "e8d8658d47d643625e8fb8142c7636a44e79a8d45674305e46dcaefa4e22207b", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "6ddb28682278039d8487bec2d126371c467ba21dfffd5323807c47644b1aa7fa", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "c24b9fcaa392437dd6551d21c556b08c365f66e8e07255e595b9763b17996e8c", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "8cd9e3b45a8696c22c3d1ba83d0a6f3efdc25d1c8223b8eb0f92aaaf571bf8e3", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "cbb819dc8e4cae4c84c698ecb9292f7f94c81fb03239685c028ccb374b7ad08b", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "37c5a386c3ebeff2fd06f9117a1bc34ff4e25d33adc200968bc5f0e73a985f2f", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "e8f55d55ab23a26033f4f7a12b103a00568af84b12775efc7bf30010187d086f", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "557c5e7e1bc7d1947d562655ba5c1bd5f0821c2def98597840d926562c2100b7", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "ccfa6f6b41f00afdfc64dd823b9332d72c251fba3e56a362d7f9daf8da7b1848", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "e04cfcb6ad4dae4ee5e0d8f91c56ea14587e7826712ed16e1617ce9109e0ee5c", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "be112cd7abff17d56ae5fa4d60504f726726c785aec795d75b44e90a0bf9407a", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "715246c1fb5b44d571b1b50aa80a807da513a42d794e0d2e561384587b523c12", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "8d6c3c16a29c7ca94f7259c5916e7596307589a4d39763cbeab7ca7e6dc3cd35", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "409bc4791b11470c9a94d8cf7855899967c8bf929c5603a1abf553dfc6b3b359", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "e3fce9db9e23f5f9c88112ca12c771e0e9b4e6beb0653203da85f7792b27062e", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "03a5d3773996381e627b52ea36ed9f2f515d3a8a386e4ccbee8b35319863a4ff", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "bcd2d15593ecae5d378bd4751bbc919eef0a1a6841202481ccbd65833e3b6f48", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "7aec6c8c7e4bec2ceb41c4dfba509656b11a2947c23423db9be22ad271dd993e", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "df7150586c6c2bfc7186b0746b5356004ca42b0eea98b9d6e198c7cb777d05d6", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "bd80268e6f93f1786823493d8248cb494d9cdfd19a49ade778828af008e552f3", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "d36c9f12b0ce36c1f28ce8ed30e7a25b5e51356df6d422e132d79b74ca25cfad", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity1\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "085bac65133b764d92a13d212d271f29b5c50cbb61da4e42e1e7e38893b04bea", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity2\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "46b61ab0d552330aaef855a2322ab27324463a3159b8217e2755f6d353d8ed07", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity3\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "60315f9603b0e09458d9cdc42347b067303dbcf568eca8629cf653d135051776", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity4\")) [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: false, + data: Optional(OptionalData( + data: None, + )), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 0, + read_count: 0, + runtime: 0, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "6c234fcd99f1bbb16287e9e7387b0df980fdbc95f62cd9713182d9736581f251", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "d400b19fd1c878d49c0dfbc74d949355558cb673e90e0a3d6f2387eef9f19123", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "33b565732b4c0c3536cbf1252eeab4d012e6e09e07d0f8d24ac0337f756b835a", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "c4f0d0eba7cbbf1d979f72d0ba3f53e479c01ba8ca293554ae4ba378c03665e4", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "93b7a177a91eaa5a9284ce4d4a3d0ebb721e2a0fa08a53a5e39b3076f34a5697", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "d7e801dc03c4ec4c1273065827fc709c3c632a9aaadbf160cda98dd7c47109ce", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "954af669ddd86fa197e6de0c989dc79d504dfdedf19b912914fed1ea56c45ace", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "38cb3ccb9ccdffc8df8bef8b558671cc1850d97d769076e1b3ae2b6dc1e8166e", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "2295bb17ca7e98354645ee7de732be4ba15e599f7e80de9627cc323125ac03f7", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "69364681827daad2a4ed363b3e395d4c197940df42cc4c85144ee84249240d3b", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "ced059bd55b76b28bfd0e5f7b2f395d67742bdcdc3307a88f01f4432148f0980", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "bac3df42254659c11df1443862fb12b0a8aaeea1dc0a700ebe43976b386b39b3", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "aa1fe044db70b678206852d3bfa40f74750cf36203a2f061efaeb6889a951654", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "a0d17f72f8bc4106ccbf1f847182d5b16f7c0f7129bc608e3d08d2dd196b3f74", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "89f5e60ed452b5c7cba3fb71424ae7f8339ad70ddfa97abae0c103b1479c9b80", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "a466baee5b53a2f0423d0079e3e54eca5c6b860677d90814029bf86979f33eca", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "8234d7670e6a1fdff002672897e3a4df7b75e4f0a8021267307b186d6fe730f9", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "55a3058e6e3f499744b52c6fa3855f9ddb9899b0b86107a5f5bf57011e141114", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "f8540d4f90b6a939a73777adaa5db3d8b7d24f42a79967693a4a40b0892f5f8a", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "a4f672d23f05347dd8d9fff7f1864b65395639d006da3486fde81805935ddb58", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "7b512de10525a1373eed7cc0c48b3cc8841d5ae82e4cf4fd980957ca21d07d48", + evaluated_epoch: Epoch32, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "18d46a85ce457454a85d14acad40baa73a9bdc34794af4f6730ec38917b64380", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "318a0462c77d4c06e869f88e7559d7af196979e0082e677cad68fa6ff8be3499", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "9b6237bbbc0444f5d862c2abac329fbdf0c82010a5b131bcf7f9d21c0f0393a9", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "ab90f1c2a3675cbe9ca20d8dd437cc5164e372566d03a12a20c343b469315f2f", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity4, code_body: [..], clarity_version: Some(Clarity4))", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: Bool(true), + )), + cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "92030cb19982977375a5bfa21f2bb3c1c0268bbe1c1f16e5eb530fa5aed6b366", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "78052d005b3bcec55e2ab5ca5da6049fcdd4127d85bfc95fcbe92c43451c9dda", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "d25ec4ea5b18ada9dd180adc0e57ecb5a8cd5e18d87db5672dec6639dc7d8a31", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "aa81d077516293dec53f178ac6770954cd0166250dcd28671e2f50b29201f8eb", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "462c64b33f61a9670b8ef226d3e3e17f81209981801e12f4e34db13a0ecbdf93", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "020c61044e83f646e88f72d64ec11263c94345023b230529793331c9ba300540", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "ee0d52afede711c64339df5a3e02b54e2e04d53000c803b9695010363fc68048", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "0c8114e169daa1dc6b0acda5b0a6d52a5b481a1ff408dfbc97469c747273499d", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "fc1c8d43575e9a173d0cb470292fe27fd70fd52f529029e0e5b7e51d650740e5", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "27ae5a4eaf462bac33840f30308f3dacbd3d289f51d176baed52acef5b33d86b", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "6817c54b137aa887eab823cc4a82fa8bf2d6b099192106df20fd132a98ded0af", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "5d40c47aac9565b301128b89b82d33370000225221bc95107dc1fcf071b813fc", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "2bcfd267847bd0b8584798f7f9ab9cecb5b3d3b70d3243226307a9c9abb866ed", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "1e7d2a70e496749b72e9b559aa0a16f61e622e0f6e965a5798dc769f1954fede", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "57504e44fe6c7cbebb600a0100ee0903ab183b2c1a985bc683ad5bcf22548a69", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "1d855824e7892f673331babbdb83e0952995c7f7970128b3d931d72e5fe508f1", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "e0703284bb46b60907d2347e5b1112a4b4c17071e4229791f1c335aada950b1d", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "c67bb294ff7935a5653ca30786a61c27085b4a5c463d3abf05fb75159a048d88", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + )), + Success(ExpectedBlockOutput( + marf_hash: "16926561a00620cec4d0d8af59d3ae0d882127fa88b27d9dd36093f2b5b29efd", + evaluated_epoch: Epoch33, + transactions: [ + ExpectedTransactionOutput( + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", + return_type: Response(ResponseData( + committed: true, + data: UInt(1), + )), + cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, + ), + ), + ], + total_block_cost: ExecutionCost( + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "66c2e9a5df2068fddfa8320795030fe992d3e48b737879ca0e982340a9aadb87", - evaluated_epoch: Epoch32, + marf_hash: "881c934c0f2135eac455a38a2a76aad67af916e9b65423de698782e368d9aea5", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_2-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: Bool(true), + data: UInt(1), )), cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "75fd936236697fae8ae60afa68090d69b55733c2fd1074d1e0f26c001e6a1541", - evaluated_epoch: Epoch32, + marf_hash: "36efb10edfa5882a73ee492f393b95dc09df2d25c233e6fb915aecb8dcd5e4a6", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -284,11 +2768,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2b411e3a0c4d4544ab26ca39f06d044d7d7b5099ae759c54834f24a0fa6ebe94", - evaluated_epoch: Epoch32, + marf_hash: "a69e058eeb651c20767e7c09c9e8493d5d47e52c6f3f356d238bd9bcc0ad2cc3", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -312,11 +2796,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "b4db59dad45d307e0ec62224cfc227176225a86fff9717ce6c42db56921a6453", - evaluated_epoch: Epoch32, + marf_hash: "6275ccccd9af6c8c8ba245504f65c4f1bbdf0a19d2d045d72765371bd7195cb2", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -340,11 +2824,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "bac221861205fb31fa0c1ea832b756c189743a57b0be3118144e1d2fc8d10b27", - evaluated_epoch: Epoch32, + marf_hash: "ab1a1a2a99edc2a148a732d52d614ef84dbfcfa02fbc998ec4532a0dfc153be5", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -368,11 +2852,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "756ca87c5089c476ea135c6c738942743b87433536d135b5349a7c8554c5af4c", - evaluated_epoch: Epoch32, + marf_hash: "d987ad3c4873982b28bb49a10a24698681b9c64f6a732351448af7e18814297a", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -396,11 +2880,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "96c9f18a4955d066d5d8b7acb83c78d572c0137eb7ad3c5b7cd1a04a7ca2375a", - evaluated_epoch: Epoch32, + marf_hash: "a578e42926338a36b8837f6b041b7e0b852ec23c0edc4541f0bf482cb2d8eba6", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -424,11 +2908,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3979a8004a58cf1fc5b565178b6409bb24f5b94b3039cc9dedf499f0cefa7f1c", - evaluated_epoch: Epoch32, + marf_hash: "6ad42d96791709eec7da348aa399aba9a724f922bb37d7e1ca6fce1a43be5868", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -452,11 +2936,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d0fe93d798dd8d15094d53039dd883d288f6533132295a4e8238f96e1b748aeb", - evaluated_epoch: Epoch32, + marf_hash: "75f553e3b4c8b8a3a818d6b87d0f1e45bf1a176526f300365dda2e517052c3c2", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -480,11 +2964,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "20d42bd924755ae2508ccc42e76b10f8853c710561080bf09754bddbda97780a", - evaluated_epoch: Epoch32, + marf_hash: "fee65b1f4183d416cc1185a31032ce961600ce1454cf17340b457586c67057da", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -508,239 +2992,231 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "783fe32668e84d8a4fc9a18dc8bb800334ffa0b7c8789a2442a7ee897ba73c39", - evaluated_epoch: Epoch32, + marf_hash: "80ce49aa03d64d1b77615e07edb3808efcbc525ee619fad16e503899e33ab09e", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity1\")) [NON-CONSENSUS BREAKING]", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), + committed: true, + data: UInt(1), )), cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "060c51c5713973c338b6ae9d0ff12f2b40ce38c65679aa2eec3a767a0c3ca491", - evaluated_epoch: Epoch32, + marf_hash: "70171b5f7cb4a4a8d2ed26530188e9c8cb0183acad7e54fcb4abc8e900941845", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity2\")) [NON-CONSENSUS BREAKING]", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), + committed: true, + data: UInt(1), )), cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "dff43f71420fc31e0323c9859acefd095085f6741c05801c6f995fbcd37e14e0", - evaluated_epoch: Epoch32, + marf_hash: "6ae535336ff1072383d93a5ce0847ed9df6a8a5b853e1a389ed82f1ef1717257", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity3\")) [NON-CONSENSUS BREAKING]", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), + committed: true, + data: UInt(1), )), cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "ebf3fa7865cfdb8f64ca8476aed5e77f4b4624eedb4f8c7e66c7ddd7036a9f7e", - evaluated_epoch: Epoch32, + marf_hash: "3f122d9aecea22662ca4c3de0e23d6af1302b71324c7de5d859b1cb6dbc3acc9", + evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity4\")) [NON-CONSENSUS BREAKING]", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), + committed: true, + data: UInt(1), )), cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( write_length: 0, write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "865729ee60984b891c3dadefa4251ecf616bdb76e9dbcf0e4c532e8ba55e809e", + marf_hash: "59a76978d792df3bc862cd7e374960dd2135bacfe7745b17de61f2f54e5226a1", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: Bool(true), + data: UInt(1), )), cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "2b42a9954144b76c5a77eab040908d9a160a7b6eb4174f070bfeee8b1c0e7f28", + marf_hash: "b04b86f0992bab2e60c9e7ee31c6294660c3f1a60f4a3236705327fe7d589c2f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: Bool(true), + data: UInt(1), )), cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "c4ebcf45811a541a94bfd83823b20647fccdcef245effb1eff72dc1a66846998", + marf_hash: "4e76a5fad3c3bd1e7c05956009966aeaaf74b70381a59c78b74c0019fe2a2e31", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: Bool(true), + data: UInt(1), )), cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "f2915dd59f38bdd3497b6cdf6188638da517dda3fbf9545a6efd80a7926dd512", + marf_hash: "ea019f02fa8ec64e7c5850fa3557fdc8bbf476a74307ebad89d4c673d343a554", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity4, code_body: [..], clarity_version: Some(Clarity4))", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: Bool(true), + data: UInt(1), )), cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), ), ], total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, + write_length: 0, + write_count: 0, + read_length: 103, + read_count: 3, + runtime: 499, ), )), Success(ExpectedBlockOutput( - marf_hash: "de05599803ccb3dd8aa8cc5e8d514510f688ed4228f55f6a4f7befa9626b02f8", + marf_hash: "b4355e3b7489f89703d5c81c11fe5064b73784df83a196f873d6778e3a540879", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -768,7 +3244,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2371fc157ae4ce4f36a726d61a154a00c5a05bb89266abe0f819c260a92a7acb", + marf_hash: "c170b9d31c6dcfd0f2c2afd4d78a1942b12407d8472dcc85d2e8561e13a33bf9", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -796,7 +3272,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "79626a488bd08fb706ccd7e03f0504d40e212974e73d50b7bf911c2e7d53786b", + marf_hash: "e17f581cef69de7b09906e6e6d8f96da18212168b477ed72116424c4b40d7449", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -824,7 +3300,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7c6a90349a6ff3c982af2bd6b599552cb2a724b9caf157e761838759922e1429", + marf_hash: "ea19305aec4e4197a82fbfd91599df19cc4ad05aaae6edab422e4f9753c36227", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -852,7 +3328,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5eb68c13c22e4b151270e8f7b87551fdf40398b5601e15f8d28c261962484531", + marf_hash: "dcc80441ea26b9ffa671dba1a18355df95d4acfd1c08cf02e0da2f29e4aacd0b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -880,7 +3356,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "cc74a307cb642bbbc9b8ae22f07af21f1f58c74fc1b7a026538223b87bbcc71c", + marf_hash: "f7f4471a513f5e0c8829bd39298ae84b285365bd3974dd57c80c0c15b60d9c72", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -908,7 +3384,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d02b68bf5b76ac8d44980e462278746c32bc18b0e3cd8560cbee17bfdca9460a", + marf_hash: "109ac891c815db3cbec699a262aebea6e26a0baa2f7fcf5ab4b403f337237241", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -936,7 +3412,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "99882189d9a201923d2cc7892191e4fd2c045bc2fab29ca6814bbf84177d781c", + marf_hash: "dae7b5a14f5098383a56bbd1c40338bf4e7e8e9250d877c8ac1cb5453132314d", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -964,7 +3440,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5c3f7f1830c7118d6fab693685f740f2b5410e402f436dcb1a3ae6f710f270e7", + marf_hash: "31eec47c394ec26b6567b6ed9023b075faeb7458fdf28ead3911c55318bd80be", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -992,7 +3468,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "82a58693c5b1512cd132a16a619468f64f9aca1bf388e0e83eb69e15ca4bf34f", + marf_hash: "71284277f0c5b6b0bc0bd4be09d542a30928be8cada77dd023eb56d4d4000a84", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -1020,7 +3496,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5c4a700da1fb15d9eee54506035dec2098351af28da39e26bc527e95653507bf", + marf_hash: "d73104dec11b2431ccd1c8731c41f144c66834910f7d22fb0d6f337797a6dadf", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -1048,7 +3524,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1d7b7bfb83573c6769d140e96167342c0d0d8e7fc054db4ad955aec53c4cc741", + marf_hash: "395992f9c6275d1e123a7e4f3fae3bc80e51a9fda1347af6a6441b77821abc04", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -1076,7 +3552,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "79db6c4bfba3f42b1011f4f95b84e11d27c2569d84c1fc97c2fca476a4c559be", + marf_hash: "28050813ea4c9cd26f8fa330846282120d577a1666ff8637e2ff2b4560e63880", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( From 13659d17c2521cccc88ebf2f6535a36c3ae74678 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 30 Oct 2025 09:11:17 -0700 Subject: [PATCH 05/22] CRC: check for epoch at deployment time to enable better clarity version handling pre epoch 2.0 Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 46 ++- ...nsensus__successfully_deploy_and_call.snap | 262 +++++++++--------- 2 files changed, 147 insertions(+), 161 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index e130b580e1c..85206e40041 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -74,7 +74,8 @@ const FOO_CONTRACT: &str = "(define-public (foo) (ok 1)) /// Returns the list of Clarity versions that can be used to deploy contracts in the given epoch. const fn clarity_versions_for_epoch(epoch: StacksEpochId) -> &'static [ClarityVersion] { match epoch { - StacksEpochId::Epoch10 | StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => &[], + StacksEpochId::Epoch10 => &[], + StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => &[ClarityVersion::Clarity1], StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 @@ -149,21 +150,13 @@ impl ContractConsensusTest<'_> { let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); // Each deployment is a seperate TestBlock - if clarity_versions.is_empty() { - let name = format!("{contract_name}-{epoch_name}-None"); + for &version in clarity_versions { + let version_tag = version.to_string().replace(' ', ""); + let name = format!("{contract_name}-{epoch_name}-{version_tag}"); contract_names.push(name.clone()); blocks.push(TestBlock { transactions: vec![], }); - } else { - for &version in clarity_versions { - let version_tag = version.to_string().replace(' ', ""); - let name = format!("{contract_name}-{epoch_name}-{version_tag}"); - contract_names.push(name.clone()); - blocks.push(TestBlock { - transactions: vec![], - }); - } } } @@ -217,33 +210,26 @@ impl ContractConsensusTest<'_> { let clarity_versions = clarity_versions_for_epoch(epoch); let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); - if clarity_versions.is_empty() { - let name = format!("{contract_name}-{epoch_name}-None"); + for &version in clarity_versions { + let version_tag = version.to_string().replace(' ', ""); + let name = format!("{contract_name}-{epoch_name}-{version_tag}"); + let clarity_version = if epoch < StacksEpochId::Epoch21 { + // Old epochs have no concept of clarity version. It defaults to + // clarity version 1 behaviour. + None + } else { + Some(version) + }; results.push(self.append_tx_block( &TestTxSpec::ContractDeploy { sender: &FAUCET_PRIV_KEY, name: &name, code: contract_code, - clarity_version: None, + clarity_version, }, is_naka_block, )); self.contract_names.push(name); - } else { - for &version in clarity_versions { - let version_tag = version.to_string().replace(' ', ""); - let name = format!("{contract_name}-{epoch_name}-{version_tag}"); - results.push(self.append_tx_block( - &TestTxSpec::ContractDeploy { - sender: &FAUCET_PRIV_KEY, - name: &name, - code: contract_code, - clarity_version: Some(version), - }, - is_naka_block, - )); - self.contract_names.push(name); - } } results diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index 85a803415c5..e2d227015bc 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -4,7 +4,7 @@ expression: result --- [ Success(ExpectedBlockOutput( - marf_hash: "f82c87d26f3301c77aebe382d6b68cdd9a5d6d593aba41a6f098fd3faa377fae", + marf_hash: "c740de32d7b9273518899f798a6c66ea543dc67c4df2c97f428e37cf86f36857", evaluated_epoch: Epoch20, transactions: [ ExpectedTransactionOutput( @@ -23,7 +23,7 @@ expression: result ), ), ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch2_0-None, code_body: [..], clarity_version: None)", + tx: "SmartContract(name: foo_contract-Epoch2_0-Clarity1, code_body: [..], clarity_version: None)", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -47,7 +47,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3aa7ec364b0895b29b20d64bee38a0fe1bac20945e4899b31ea39fd28688ca7b", + marf_hash: "c197b006221151c65e298beaf88adcc9532f8b494b7f7564b8ef60fb217a4eb5", evaluated_epoch: Epoch2_05, transactions: [ ExpectedTransactionOutput( @@ -66,7 +66,7 @@ expression: result ), ), ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch2_05-None, code_body: [..], clarity_version: None)", + tx: "SmartContract(name: foo_contract-Epoch2_05-Clarity1, code_body: [..], clarity_version: None)", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -90,7 +90,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c6f82628f37b2c76fdb5404e1dc0b3b6a7beb50bd1a8b8931c65c6c207861fa6", + marf_hash: "e568aa031e6c9a4c39dc23202fbc80a267eba760160ce288f3315dd30d9bb4a4", evaluated_epoch: Epoch21, transactions: [ ExpectedTransactionOutput( @@ -133,7 +133,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0a058e669a64920c6026f4fea10c14c3b7bf1a8df9fce55e2ba140e147d1813f", + marf_hash: "0d25118932d001ca3b324614c28b2de4353557df43ec0c8868904a87b1ee9f9a", evaluated_epoch: Epoch21, transactions: [ ExpectedTransactionOutput( @@ -176,7 +176,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1a9844dff80d1c9325b8d73c34aa28e85bd15742c01e2623b9c2e79746e42118", + marf_hash: "211b8b332246d41b5927b4a19ba3d6af7c527dd4f619d92fde6dfcd049503b41", evaluated_epoch: Epoch22, transactions: [ ExpectedTransactionOutput( @@ -219,7 +219,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e8e86ba457695cdb0e18426d6f6f8a7de0fc09e9cbd79e3d9dc01597678b338c", + marf_hash: "4dccf47885edf1f17736a78f772717bea62ea5136e99a981114eacf4ee1c292b", evaluated_epoch: Epoch22, transactions: [ ExpectedTransactionOutput( @@ -262,7 +262,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "78901bb99cbba1cf2df781c0e034682eb6a5bd79de914383d407780cc089fc05", + marf_hash: "f06090bf6277fba5c9e95d3ccea4fdf8d2efe978e435684c38d1216f0f378ff9", evaluated_epoch: Epoch23, transactions: [ ExpectedTransactionOutput( @@ -305,7 +305,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "27521c9c53503d8103f769cec353561e1aebe1297e28672467651728c8a7f335", + marf_hash: "af69aa70ce09a12e0bac066b84f24f103bb3cd4cd49b45b2711fa25e90b70c25", evaluated_epoch: Epoch23, transactions: [ ExpectedTransactionOutput( @@ -348,7 +348,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "24fd9ffb4aef7be8948002cf5490c4961abec89ee1d15c38634ba0d4f4b52bcc", + marf_hash: "bf8243ccf29da7518f4e4f1cb894c17814463806f7ccd9d693e41175329bf017", evaluated_epoch: Epoch24, transactions: [ ExpectedTransactionOutput( @@ -391,7 +391,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "55a54057d46f262251364ea8e349ede9b082f0efd745c712d3d9d6699633a6f3", + marf_hash: "7a009ae7dfd79b021f74d17d1b1ae9015b9f1d8984734099b9d7c1388d254316", evaluated_epoch: Epoch24, transactions: [ ExpectedTransactionOutput( @@ -434,7 +434,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "04362001f26cb019410d4ae83d9217be8eec797d662c427e84fb3222eb296d54", + marf_hash: "46990434b77ba670bf7bc908be33b4083bbd7147cd86dffa52a821fc88abbf2f", evaluated_epoch: Epoch25, transactions: [ ExpectedTransactionOutput( @@ -477,7 +477,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c2a897fbcc709ca5219d92fe0f9e02d20a676084e5e9f842399cea0268c415a1", + marf_hash: "ca156312aaf28bd8d37bf879d936a7590fe8b09723f6f5bf65da38c9d8982929", evaluated_epoch: Epoch25, transactions: [ ExpectedTransactionOutput( @@ -520,7 +520,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "89590343420f317b4ddb5637007298f58bc433df0644f5738cd8793fed56302d", + marf_hash: "85ac414f14ae115109c11a1e96dca20daf469108ba8a4cb50cd41adc88f5df23", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -548,7 +548,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4cc2b4653d17798c544c1b7a1f5fe597b2def190f98c99c5ea71a407ab8abc1a", + marf_hash: "f881675119756d835bdeb52563df013cba96400650ca46ad4a6b17e3523421a6", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -576,7 +576,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "61c9faba574baee6be481215e932a24498c46d7198b80f558a84b2c7410b1a2f", + marf_hash: "96c02c20a470487d4c0840cd0907e431a62025705050517aec6273388aaa15b5", evaluated_epoch: Epoch30, transactions: [ ExpectedTransactionOutput( @@ -604,7 +604,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8506aa2d472a5eb8d75319a542fdf12a6019293274ab24b4a5beedd6080e3dbc", + marf_hash: "eda6ac6c341148306e527afd259f67f1960eb6b0e3675457b0ebdd39f6331875", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -632,7 +632,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "62cc4b26f2d9f300a73b7dbb956b97b2c4d0636e48ac1d76a8e79d8920fb33f2", + marf_hash: "4246ce7c78c780a20fe493c0907efe97b4a5dfc09e13bd12904678e74784781e", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -660,7 +660,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "badbbe82d38b006719f7bd73d5055c9ee3d95d1a617894e2f532d98ac6c82203", + marf_hash: "263bdc37c726aa7045bfbacfdb2c28c9b224a3b38455251d474a5e6e80485e68", evaluated_epoch: Epoch31, transactions: [ ExpectedTransactionOutput( @@ -688,7 +688,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f9f9cc3a8d7aa235575329752da19805518822df8811bb9e2158cb6e138c3785", + marf_hash: "8f128dbc3ea384d85bea79d9933e5153874215ef37dc4f53f1385b220d64644f", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -716,7 +716,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "82fe510e0f301cf710abeadd87d9902ffa07a1997abce3a80a7ac5ad52abd1b5", + marf_hash: "b03cb4e4df40f04e9709c57b8fe0016b6e2079b5b52797d227f52c8e8a41cccb", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -744,7 +744,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7abc788e3f5780d0b02c643be775835b03efc3ad8ff3ea84ab7ab748b104995b", + marf_hash: "8334f3de48ac9c8994bc89a9b141fd7d3a6553fb3dec2ccf1730dbbd06ccf481", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -772,11 +772,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4df955c663f6e20db655c1ff9633c2307a98fb5480a3e75c1631b5ab38e26b73", + marf_hash: "dfd7987ed3594de16a5a049afddcc6b1554d1c872ff62505193d651f39358ba1", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -800,11 +800,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e8d8658d47d643625e8fb8142c7636a44e79a8d45674305e46dcaefa4e22207b", + marf_hash: "eb2610cbcb9bcb2b9ef4ca096ea86ba5c650c0827948e2ea3d7217134f78abbb", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -828,7 +828,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6ddb28682278039d8487bec2d126371c467ba21dfffd5323807c47644b1aa7fa", + marf_hash: "aacde9ab4bb9e268861e798a60cebe203ad63eb620557a807c18be6ca4778b36", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -856,7 +856,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c24b9fcaa392437dd6551d21c556b08c365f66e8e07255e595b9763b17996e8c", + marf_hash: "1dadd6143cdd8858b1be818ec305284d5d7b5ea5c74798a55bbaa65fd4960da4", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -884,7 +884,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8cd9e3b45a8696c22c3d1ba83d0a6f3efdc25d1c8223b8eb0f92aaaf571bf8e3", + marf_hash: "33f654fae321df1a7a8c999ff5bd9f7157838686cc66052713350fbabfec6475", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -912,7 +912,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "cbb819dc8e4cae4c84c698ecb9292f7f94c81fb03239685c028ccb374b7ad08b", + marf_hash: "a62d1626724274fde8a5ff7896df804e8d9d9b360fd15e3f5c4163c1b1883fc4", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -940,7 +940,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "37c5a386c3ebeff2fd06f9117a1bc34ff4e25d33adc200968bc5f0e73a985f2f", + marf_hash: "03113d14b71423632965e65c09ae3c25e02fcadb50643597fbb422b63fb40046", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -968,7 +968,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e8f55d55ab23a26033f4f7a12b103a00568af84b12775efc7bf30010187d086f", + marf_hash: "a98500c481c27c5a9a145be38e97899bff6594094792c2e096554dc1d673ac6c", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -996,7 +996,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "557c5e7e1bc7d1947d562655ba5c1bd5f0821c2def98597840d926562c2100b7", + marf_hash: "662d3b6c0b7626514a6b6db933085131eabbed7e511a652821e04b391293f562", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1024,7 +1024,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ccfa6f6b41f00afdfc64dd823b9332d72c251fba3e56a362d7f9daf8da7b1848", + marf_hash: "d1542be1ac31dcad1cd400f4da054ff9483e8288f4eb62c81ad0c738565c1748", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1052,7 +1052,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e04cfcb6ad4dae4ee5e0d8f91c56ea14587e7826712ed16e1617ce9109e0ee5c", + marf_hash: "de0cc119f7d0fb0c6bee1dbc197f8e00b50a818b7cb71b197271d0d52547ee18", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1080,7 +1080,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "be112cd7abff17d56ae5fa4d60504f726726c785aec795d75b44e90a0bf9407a", + marf_hash: "6d65110f96a096b36de9e731d2c05d1e489a60964db8a68192794c3ab188d698", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1108,7 +1108,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "715246c1fb5b44d571b1b50aa80a807da513a42d794e0d2e561384587b523c12", + marf_hash: "4c1bfbe42283cc5e98bedaf29f389b9dbddbcae1fc6a9f3dd58a159f8acaa0d2", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1136,7 +1136,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8d6c3c16a29c7ca94f7259c5916e7596307589a4d39763cbeab7ca7e6dc3cd35", + marf_hash: "7e6068e2124476b0bc68b5ea1b92af144b69b2e5cc99070dafdf7ecbf6ffa3ab", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1164,7 +1164,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "409bc4791b11470c9a94d8cf7855899967c8bf929c5603a1abf553dfc6b3b359", + marf_hash: "5f3f0b515bdf85a87ddf95d0630c988c96e2e8f4072435755d4572a2a19ee13f", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1192,7 +1192,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e3fce9db9e23f5f9c88112ca12c771e0e9b4e6beb0653203da85f7792b27062e", + marf_hash: "38c1fe84e0a3a5b865dc3c04c2834f8fe6ef0ac0ac5bc0976496af7b3d99e9e1", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1220,7 +1220,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "03a5d3773996381e627b52ea36ed9f2f515d3a8a386e4ccbee8b35319863a4ff", + marf_hash: "83cb893a388306c229959558bc8840911099641abf724707687cb36d1a49ea8f", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1248,7 +1248,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "bcd2d15593ecae5d378bd4751bbc919eef0a1a6841202481ccbd65833e3b6f48", + marf_hash: "54e129237da03ab5215ec50928b16950a3f1a11a25a7d509e5ca488e30904a70", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1276,7 +1276,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7aec6c8c7e4bec2ceb41c4dfba509656b11a2947c23423db9be22ad271dd993e", + marf_hash: "7abd4e113f31e7d9431617303f68757d880cf98badbba442269c79f1397fece0", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1304,7 +1304,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "df7150586c6c2bfc7186b0746b5356004ca42b0eea98b9d6e198c7cb777d05d6", + marf_hash: "0ef2659b0f4a3e3cd79b983c42308a8791d73d9c54f98ce1babad6bce4f450af", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1332,7 +1332,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "bd80268e6f93f1786823493d8248cb494d9cdfd19a49ade778828af008e552f3", + marf_hash: "6fb786959add35c7f138469509e89a151410346410432724577e1dafe1209fb1", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1360,7 +1360,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d36c9f12b0ce36c1f28ce8ed30e7a25b5e51356df6d422e132d79b74ca25cfad", + marf_hash: "5c85f79f442d8a76d9c42b09f5ca469efe75297691d04be5b8b1ac8c6dddb712", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1390,7 +1390,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "085bac65133b764d92a13d212d271f29b5c50cbb61da4e42e1e7e38893b04bea", + marf_hash: "529fc08636f1dc4997ab9166e33e2a864c500388a8ca2e2d81d1c9edf9a299ba", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1420,7 +1420,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "46b61ab0d552330aaef855a2322ab27324463a3159b8217e2755f6d353d8ed07", + marf_hash: "4a60d7ad17d1934d2946920340fd65ed869f9616c11a929dafce7930c860529e", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1450,7 +1450,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "60315f9603b0e09458d9cdc42347b067303dbcf568eca8629cf653d135051776", + marf_hash: "f46c919ce39bafea42b70d956fa76a0f63c80ea81c3e8008a482e1f8977cb089", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1480,11 +1480,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "6c234fcd99f1bbb16287e9e7387b0df980fdbc95f62cd9713182d9736581f251", + marf_hash: "4375b36e05ed4ae090da7cdd26a46621e437b0327effee1e2a70b68eaaa3f620", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -1508,11 +1508,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d400b19fd1c878d49c0dfbc74d949355558cb673e90e0a3d6f2387eef9f19123", + marf_hash: "061a403ba2dac23b855a07f9c8bcfdc76aa32802910b3781e71ba4627ccd811d", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -1536,7 +1536,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "33b565732b4c0c3536cbf1252eeab4d012e6e09e07d0f8d24ac0337f756b835a", + marf_hash: "acb2b8a774450154b86e15be4adbae94350841f6865dec8f9b35a79da62d3fab", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1564,7 +1564,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c4f0d0eba7cbbf1d979f72d0ba3f53e479c01ba8ca293554ae4ba378c03665e4", + marf_hash: "3e3d96be0eefa48f39fa24e8cfff545d5a2870a49d5e1153361418cc350a0251", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1592,7 +1592,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "93b7a177a91eaa5a9284ce4d4a3d0ebb721e2a0fa08a53a5e39b3076f34a5697", + marf_hash: "3fc5cc358dea0639d7d3d83a8104aebe7de509d696c45199d38a7f477bafa3e0", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1620,7 +1620,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d7e801dc03c4ec4c1273065827fc709c3c632a9aaadbf160cda98dd7c47109ce", + marf_hash: "c0a89ebec0fe92431ae8e7fa7bb4e7891872767cc604e5dfe686bab1ce8198c5", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1648,7 +1648,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "954af669ddd86fa197e6de0c989dc79d504dfdedf19b912914fed1ea56c45ace", + marf_hash: "3011760394ccc2247a937a44de1e997936b36b394eca22928bc9e597284204d3", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1676,7 +1676,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "38cb3ccb9ccdffc8df8bef8b558671cc1850d97d769076e1b3ae2b6dc1e8166e", + marf_hash: "695f92457e64c9e7f2fd5b2104413067747309cfd91f055988115356a67fa3dd", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1704,7 +1704,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2295bb17ca7e98354645ee7de732be4ba15e599f7e80de9627cc323125ac03f7", + marf_hash: "c6ff999ebcff7aa9acf19426de1d9fd453ccece45f6597739c9588c02f6f6feb", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1732,7 +1732,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "69364681827daad2a4ed363b3e395d4c197940df42cc4c85144ee84249240d3b", + marf_hash: "2888b44ac5d1cc4f498ba5c3fbb988154546f8ffae20d39d7efa0f5d2f0cceef", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1760,7 +1760,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ced059bd55b76b28bfd0e5f7b2f395d67742bdcdc3307a88f01f4432148f0980", + marf_hash: "eee2216ac8c4bd5c48f2f975b3f91581dbf718628f321936032086bc6dcd8968", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1788,7 +1788,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "bac3df42254659c11df1443862fb12b0a8aaeea1dc0a700ebe43976b386b39b3", + marf_hash: "9846692add234371fbb114f440a3de2fc5b2f6c6ff71816b97c9de9a12c91722", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1816,7 +1816,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "aa1fe044db70b678206852d3bfa40f74750cf36203a2f061efaeb6889a951654", + marf_hash: "bdded23abbc45d91bb038e57dc692ee8a693c48cf40d1d28a78fa95aa46da1a5", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1844,7 +1844,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a0d17f72f8bc4106ccbf1f847182d5b16f7c0f7129bc608e3d08d2dd196b3f74", + marf_hash: "645f3d75513ea0d0659518952bd66f11aed541d9afd376b1b1a4b65d6ced4d34", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1872,7 +1872,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "89f5e60ed452b5c7cba3fb71424ae7f8339ad70ddfa97abae0c103b1479c9b80", + marf_hash: "0a86087643cf2f5d14bd80dbce432919af0bf00c7cf6c74b615eabc6d5c76c0a", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1900,7 +1900,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a466baee5b53a2f0423d0079e3e54eca5c6b860677d90814029bf86979f33eca", + marf_hash: "dd0475657336ba13c24eb042e30e750e26a53affd6421861c6725056088be3e0", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1928,7 +1928,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8234d7670e6a1fdff002672897e3a4df7b75e4f0a8021267307b186d6fe730f9", + marf_hash: "b1fdc2ecd0bbc423f0aaff2349ee69a9221ee0a8857a5f5c9cf9924f1ffa81f2", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1956,7 +1956,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "55a3058e6e3f499744b52c6fa3855f9ddb9899b0b86107a5f5bf57011e141114", + marf_hash: "5828fc8a9cb1e4543920b73f845c666860b3f86711e92780b8f40aa489eec9bc", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -1984,7 +1984,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f8540d4f90b6a939a73777adaa5db3d8b7d24f42a79967693a4a40b0892f5f8a", + marf_hash: "9fc1b695621e584e28228f37419734a26f161e18bc8cb9d23850b37eccec6e6d", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -2012,7 +2012,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a4f672d23f05347dd8d9fff7f1864b65395639d006da3486fde81805935ddb58", + marf_hash: "a056bfec29b84376e184a8afed73d6665110169a36c20fda7b515a8cded3926e", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -2040,7 +2040,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "7b512de10525a1373eed7cc0c48b3cc8841d5ae82e4cf4fd980957ca21d07d48", + marf_hash: "a78b8227dc7e082208c8219fe4f7a4e39dae0188bfdc8da040b93037a33df128", evaluated_epoch: Epoch32, transactions: [ ExpectedTransactionOutput( @@ -2068,7 +2068,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5f520c30e3d0b82080917fd8f618db6fbf30e24d9a83cff191b4fec27f9b3da2", + marf_hash: "a9d8cd8ed5f44af877943552e71a105d05beb93537b9d343d02b7f04d9e34490", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2096,7 +2096,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e471d94683f589984e818a1f1307361596d2aaba03ce18b9380e5c8910015225", + marf_hash: "e718b64af4bd4ed2f80a14f3414121faf7a68fd14123f6b6ea9ba78372153afc", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2124,7 +2124,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "56d4f17b00260e60f1823914c2b61ad0bac5262c8d95b28db257a669ae7dfd98", + marf_hash: "5f27a364c5456339f26f734c832ef2796390f122b2aba0bf0bbbaf7333fef865", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2152,7 +2152,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e5dfeaa0bfe3f667437823e1a2877deab8b4ee9eb052c6d0fb0c0c2aa42a279f", + marf_hash: "fef84d80507dd69b3f3eaeba70fc4c23bd0be57283aec9f4d8f80a512851cbd5", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2180,11 +2180,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "da00cd9e83ddf05801b8b6ba403ced4fb49626fa22c2b2fe5f4dc9e1a233e9aa", + marf_hash: "fce86bd21fba70f054755e194a8a3aa8421fca0ad260e2105f475bb57322dd5f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -2208,11 +2208,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "9cf0a60a3d2caf5961b5799612abde203a23cee6f71ab7a83c0833c3f77bc3ae", + marf_hash: "09349179bb7cf35ac025413196f2404d65fea3bdf3c7062716f192528398199d", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -2236,7 +2236,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "b4912d016b164d30c17f43b6b146f866b448fd4a671790a84a2260bfd6d7e4ba", + marf_hash: "11f03c7ba858deb1f7d3bfe36ad35ea0b2702f21b6c2cfee338505d8f4f242db", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2264,7 +2264,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "41185207dca0e8ca52a522832b7d962a48405f7e09556a19c224a85124e7a0cf", + marf_hash: "c25f81701ae72442cb327502c3c656fe2f44230a0a6a548a378b4e2a8a576b1e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2292,7 +2292,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1fb76c5d3fc226ac8c21d2a1ce9c6ed28de2d81de125619e624d1685a1712f03", + marf_hash: "bbdb12a0d08b091fc4cf457e935b9f23b971a666b5a9ea77506e6cb02ae6d425", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2320,7 +2320,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f108d381794d048685fd1e03fe5cdee6e4413e06f91b6072825a12dd671ce664", + marf_hash: "ae87d7c858a33b47107aa1ae1610f82ad3ddfca123c2845cdfb75ef4d0f2a697", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2348,7 +2348,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "560a414e35525ef15767b8b393e6eb522ee174990b34fde8c0470e830bbc9360", + marf_hash: "3318fb8beb6709a295a494cc8c5c91296d7411a35e3df64e7f2935a870315223", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2376,7 +2376,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "29e7441ee5bc3f8f7e1d2e78af280b6ba2b63ebc1a0d016c495992f04f36066d", + marf_hash: "05323d59547eb058ae9df3808b0093ddb1eac2727baa04884dfe5b31819882c3", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2404,7 +2404,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "730934be4a8328522986fc4d93f841fc3763e3701336119e0e06153972b85fd6", + marf_hash: "adfe63fb5f34fd7393d53ca7975465cf4a599e3fb3a91a271f3d3d71fe22375c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2432,7 +2432,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8938fed48fcbcc78f5bdccd3e209e08f2730629c47c9c446f700d3b737597832", + marf_hash: "e2c4e1c7d5c0cda8e1a2cf78709f90d9b7a32e640bdbc066a8a43bcefc5feb9b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2460,7 +2460,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "22a20b370577af015d0fd917062f3565856e3bc57c58ca13d48359726b9d4234", + marf_hash: "af5f4418ff6a436c6c0b46072a7d55bd99cc1957d7a869a75d5743f810792e67", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2488,7 +2488,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3771e05840c6119d0ffbfdab16f2dbffc2c4dc57ede7c63c971fbb00750de0f2", + marf_hash: "ad4c07c6dd58f89faea5ee28d85292941f56c16a47df879526763c61f61e7a3f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2516,7 +2516,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "26ef0f265de24fb42f7631e5e9f4c0418e0d07e3a7b2f83fd02460dede4ecdb2", + marf_hash: "706be0a2530ac3ed3afed27f80c5f9dfa93413575b42d9778366116bdb88c0a3", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2544,7 +2544,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c5802bff377e6d518f1f474da80fbb78e66c02e24274d853d6f24912e3bd9060", + marf_hash: "678b509ea7598c3ade749077713a94e05f852353b5295c983eb818a4ec1544e9", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2572,7 +2572,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "99e9c5e1e80288660cb5a22390f026036454d996bf647d5f6be3a102d5e3f2be", + marf_hash: "451575b5dd4de490987a4bc8e7ddc0be7f092bcfbf19d17cb1e9dbe9e0556a4f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2600,7 +2600,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "2babe5af8029f21339c8c76ee1ec7a5c2ac7d5f4239d4c234b4fa4a2920ac134", + marf_hash: "6b469931e4ac9cc4f8b7566f17fbd52ea5035f627e4de12212a8817670275eba", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2628,7 +2628,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "ac585d0a448df09d5dc45db8b5a6ff83168f24fe9d7ff39f18a79ab2cda6d0cb", + marf_hash: "76057e23ffbaada3cbb34471b00a64c962b0f6e155e4d8a7674262f4122500ea", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2656,7 +2656,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "51538ad4cad7ceafbb5911846213e24bbe842588295922a23e31940dbaa77606", + marf_hash: "480385127d9376b38a4dc83040ff5660e13c3b4b942ff7a34b508cc5eaae00ad", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2684,7 +2684,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "dc16348f126f7b15b6d42a60438f938966d92ec60a87e731b3158e80d91f29a3", + marf_hash: "9d5ea2ca966bac1ae5c29ad461a80a2957b143e4fd69b92e9f5586753781c8fa", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2712,7 +2712,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "afcca5c78f28d9b4f20919ed2da609da3c19663daef5e4fa1c54cf35a6d6891f", + marf_hash: "2af8cde232a1677485cba8ae0825fa1f18ef5fbbce30ebd97535582b070ba670", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2740,7 +2740,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0c2ed3bb5b71bb761dabdf0ab8ec6af5c5f55b33ec8bc037dca221080f7b6051", + marf_hash: "c0a2a9c6383b7437a5ef856ae896ba0f49729fa45f8d9add8a0d1b7e19d5b0be", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2768,7 +2768,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0803467023003916485c392e4ad80cd18c99714740d7d4e0b68e881f5fc50cb9", + marf_hash: "7120a9aeb9991ad1c9d89efdc6c342e8c4699e5cf20122207cf3ce70427815a1", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2796,7 +2796,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "abb85ae52fda7b70413feb1db92eefe1f7fc1fd0670d919bd453ca099d991515", + marf_hash: "4f99f7e933ab4d439067701766264055c1c4457343f0f3a6cdbbd4eee44c62de", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2824,7 +2824,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "946058c28b2afb5185d797ff3428b348aaebda39b92c1e4902b9c5681d7a87b2", + marf_hash: "defc58d5be3e5400cd551608394670f9fe823b77cf1e1e37eec540d90354852b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2852,7 +2852,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "82210d1bff6d6a5598444b15d166010d8cf39ec63f4c12a48d294dac75a4310d", + marf_hash: "a7a14328b2325dc5426aba9ff9e954043ed3dace6efa4ef12e2388b157f1be2b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2880,11 +2880,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "77d2a17dbae059ec1371e76de18f4cf54c30f70b5585662eccb437c48cdfee3e", + marf_hash: "37bca0446b3ac19b573d448668e1a4628968bdd50d3737a6625901a1ead4fcd5", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -2908,11 +2908,11 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a75dba51fc1e03230bf1bbd965bb35a32ee10666d08ec94197ba8d55ea555a22", + marf_hash: "0bea4e6e656680788ee9c978eda4038768dda8ed65c2bafd2b0b8d1233b8b5fc", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-None, function_name: bar, function_args: [[UInt(1)]])", + tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, @@ -2936,7 +2936,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "46ed1e62ba9d4bcc65935b4a6a3c7facc86873a108f8fc68c7eac8fd39b43f2e", + marf_hash: "079f52daef6eb6c650bf7eae1549bf432e4942969bb19d29285029779e03ad28", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2964,7 +2964,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8ef3f7ec50d1d5d92ebf9e8f5d45aa033cce43cefe2d6f41b8b2c2c8d7e78a06", + marf_hash: "aaddba35cac01b42d5ad94dc80142f163a2e4fe0f68cd2a459536610d6ac8663", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2992,7 +2992,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "af816182d140bb4c258f4388beff629f2590062404705571c71947ad16aa2f70", + marf_hash: "0c02834881fd78966ff56800ca27028cb6ad00715ec65c14fe91c507a3434bdc", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3020,7 +3020,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "733657793e15c1d48906170edb571a0cc5ee70f2d5a70f2c736ac032f43c2182", + marf_hash: "db7ee3014f222b85b8d2b7f3efb5e6ba6c15dc6be8e5e9889830692b40320e2d", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3048,7 +3048,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d2bc2c7596b718f70ba5ea6b05e057684c16828204933339c368b5b30f715634", + marf_hash: "fb902c58b140acf8244b4ef1a2613d332e8d55b3f564276c29748fee7451cf83", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3076,7 +3076,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0711f71088f9d4083f7f4b5750f0a476bb649e406f8f9bca3095dff78fe04d81", + marf_hash: "22ca8a2c5da213ceb07bdc8466ddced12c684708196f9bbe767f058e68501457", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3104,7 +3104,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "446e4a5298b15046fd047b5be184775b01fc87db58faf312b89bdbf5021a712d", + marf_hash: "d7d89f8e75e84f35e8d236972f17512c1d2ee0de1bbd581475466bf65c36fe00", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3132,7 +3132,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f6abbf0c4b8e8197670815ff3531484f754d1ea0613e094caa4bde47dd9390a4", + marf_hash: "9a4d8b7bc0f81ef6a265cff294ab98a11a5f8993060530455bece0e52390fc76", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3160,7 +3160,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f6cb37a61910f79a57c8208c447a5b2f79718e21bb23f79c18b530d312ba9be5", + marf_hash: "aa9132f4ee67332367cf14cbe4e28854d5e5e5d20d17c9223479082080304c73", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3188,7 +3188,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3058c97eb0f8862430b7d0eac1eeff006cbcd72f3ddb2915be656cefd355a795", + marf_hash: "4b20d700321ecfb548dffa85334e8599cbca5a12016c78ae0d92366f80eac231", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3216,7 +3216,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "fbad0e1af2f946c7beb4497761104b76eb729beb987dcc3f5313d605c5450fa6", + marf_hash: "3a1c45d42877ba3eaa7ae0dfa500562a7f3507e328823833bf48487df8e4cb90", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3244,7 +3244,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "524db3ddf0a60a232dc0eee8da6c0c003f26fece044c46ee59df32ef78d62b3d", + marf_hash: "5f005bc8f53d8178dbd381323ca8269b5a0ce1f754a279ccd0fb98f25c0475ed", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3272,7 +3272,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "89477a4e5efa53504e2b46e8c41858c58beadd808e27280db05886c643000991", + marf_hash: "e33d426f90df4d7a63474905f94e8b3f24ec12c89bed5db72534b5a3da815c1c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3300,7 +3300,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0f85cb61d0d5d1f8f66afd516cd4a1677ce7cb67ec78fcce6fbf81f9cb3b7902", + marf_hash: "a00da2af43b76c025c04e2164d523d65acec487f116ac41ed0ebdd66523b9a7b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3328,7 +3328,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4842d810a1c568a63b33a21398f8f5282c340900403d3fa7d5e50f0623a51404", + marf_hash: "1d555792f123ffd679f9a0d4cfaa0d04f9d432b5619ff65052f2858f019a79c0", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3356,7 +3356,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "60c506297abad437574f1eaa2a203c074778c23fee13ba8fbc8b47313114e0a0", + marf_hash: "07b5f4d3937b9774ca5409e1ab4384dabc3bad9207782e09692e811b468770c7", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3384,7 +3384,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "14fb7f5dafd404d3a97ca6d759a414fc31513f155693df558990c82e024e40a3", + marf_hash: "b6cf81f520fab267c7fe5d03cc061ee8b29bcd7e242404eabc00f1d7ccbe2fbd", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3412,7 +3412,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8cb95e7e962bd83da36bbe1b9132cb0b7f1919c1e34001a990bffada35b8d36c", + marf_hash: "72193df4d1e062df216d871f44166b4fe3dc0041c48cb6b52584b5e77af0096a", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3440,7 +3440,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4dd41b51e91eaab6f6e0af78d657ebdb7c11cb6a9d9d040272a5b1075730d8fb", + marf_hash: "8fab3bd321b98c6ddc1c9b84fdfc4c161ff18625f1147ed0c26148f30e8a33f4", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3468,7 +3468,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "dfaf31283bb890a396240df60a239e87ef92190844e1f80c4de3891443815657", + marf_hash: "43642752b9ca9ab7a13a75f489adf79acb92bea899dafcd32bb2f03b2744b3df", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3496,7 +3496,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1d56d1ce8b74502ffadbb0762df7f501bde8eb214039de7c47e6c3b78a47c7c9", + marf_hash: "f3972a9b04c63876ce486f14797e0099dc71bd75cdf7f5cc5c0be44a82fec2c3", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3524,7 +3524,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c75385104eb5ee8ae9b27340d31c3a8b6c27a7706140254900b5b115e16be98d", + marf_hash: "c6ce77294ac9ea065ec33e3343aefcc112387093d40ed940210fc74275c54d3e", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3552,7 +3552,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "60c34c6cb90b8ecd947249e4af2c762508d9dc87175f1af71018a46d30b23ec2", + marf_hash: "67483c8a1d0a048bc61cbed380e80bb694c3770c85fa4c6c100a6f2a037a6e66", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( From d7c728a36f844250541587e47af513eb91b5cd18 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 30 Oct 2025 10:48:48 -0700 Subject: [PATCH 06/22] CRC: rename some functions and improve their scope Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 6 ++++-- stackslib/src/chainstate/tests/mod.rs | 23 ++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 85206e40041..9cc69c8fbbd 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -114,6 +114,7 @@ impl ContractConsensusTest<'_> { /// - If `deploy_epochs` is empty. /// - If any `call_epoch` is less than the minimum `deploy_epoch`. // Creates a new ContractConsensusTest + #[allow(clippy::too_many_arguments)] pub fn new( test_name: &str, initial_balances: Vec<(PrincipalData, u64)>, @@ -1125,8 +1126,9 @@ impl ConsensusTest<'_> { SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) .unwrap(); let tip_id = StacksBlockId::new(&ch, &bh); - let (burn_ops, stacks_block, microblocks) = - self.chain.make_tenure_with_txs(&block.transactions); + let (burn_ops, stacks_block, microblocks) = self + .chain + .make_pre_nakamoto_tenure_with_txs(&block.transactions); let (_, _, consensus_hash) = self.chain.next_burnchain_block(burn_ops); debug!( diff --git a/stackslib/src/chainstate/tests/mod.rs b/stackslib/src/chainstate/tests/mod.rs index 6256938df91..702644481f1 100644 --- a/stackslib/src/chainstate/tests/mod.rs +++ b/stackslib/src/chainstate/tests/mod.rs @@ -479,7 +479,7 @@ impl<'a> TestChainstate<'a> { mined_pox_4_lockup = true; } else { debug!("Mining pre-nakamoto tenure"); - let stacks_block = self.tenure_with_txs(&[]); + let stacks_block = self.mine_pre_nakamoto_tenure_with_txs(&[]); let (stacks_tip_ch, stacks_tip_bh) = SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()) .expect("Failed to get canonical chain tip"); @@ -495,7 +495,7 @@ impl<'a> TestChainstate<'a> { } /// This must be called after pox 4 activation and at or past the Epoch 2.5 boundary - pub fn mine_pox_4_lockup(&mut self, private_key: &StacksPrivateKey) { + fn mine_pox_4_lockup(&mut self, private_key: &StacksPrivateKey) { let sortition_height = self.get_burn_block_height(); let epoch_25_height = self .config @@ -562,14 +562,17 @@ impl<'a> TestChainstate<'a> { }) .collect(); - let stacks_block = self.tenure_with_txs(&stack_txs); + let stacks_block = self.mine_pre_nakamoto_tenure_with_txs(&stack_txs); let (stacks_tip_ch, stacks_tip_bh) = SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb().conn()).unwrap(); let stacks_tip = StacksBlockId::new(&stacks_tip_ch, &stacks_tip_bh); assert_eq!(stacks_block, stacks_tip); } - pub fn mine_nakamoto_tenure(&mut self) { + /// Mines a new bitcoin block with a new tenure block-commit, using it to mine the start of a new Stacks Nakmoto tenure, + /// It will mine subsequently mine the coinbase and tenure change Stacks txs. + /// NOTE: mines a total of one Bitcoin block and one Stacks block. + fn mine_nakamoto_tenure(&mut self) { let burn_block_height = self.get_burn_block_height(); let (burn_ops, mut tenure_change, miner_key) = self.begin_nakamoto_tenure(TenureChangeCause::BlockFound); @@ -613,7 +616,7 @@ impl<'a> TestChainstate<'a> { if burn_block_height < target_height { self.advance_to_epoch_boundary(private_key, target_epoch); if target_epoch < StacksEpochId::Epoch30 { - self.tenure_with_txs(&[]); + self.mine_pre_nakamoto_tenure_with_txs(&[]); } else { self.mine_nakamoto_tenure(); } @@ -1163,10 +1166,10 @@ impl<'a> TestChainstate<'a> { self.stacks_node.as_ref().unwrap() } - /// Make a tenure with the given transactions. Creates a coinbase tx with the given nonce. Processes - /// the tenure and then increments the provided nonce reference. - pub fn tenure_with_txs(&mut self, txs: &[StacksTransaction]) -> StacksBlockId { - let (burn_ops, stacks_block, microblocks) = self.make_tenure_with_txs(txs); + /// Mines a pre-naka tenure with the given transactions. Creates a coinbase tx. Processes the tenure + /// NOTE: mines one burnchain block and one Stacks block. + fn mine_pre_nakamoto_tenure_with_txs(&mut self, txs: &[StacksTransaction]) -> StacksBlockId { + let (burn_ops, stacks_block, microblocks) = self.make_pre_nakamoto_tenure_with_txs(txs); let (_, _, consensus_hash) = self.next_burnchain_block(burn_ops); self.process_stacks_epoch_at_tip(&stacks_block, µblocks); @@ -1175,7 +1178,7 @@ impl<'a> TestChainstate<'a> { } /// Make a pre-naka tenure with the given transactions - pub fn make_tenure_with_txs( + pub fn make_pre_nakamoto_tenure_with_txs( &mut self, txs: &[StacksTransaction], ) -> ( From 84306cd36bd8ca4d90e9160c13ee3b8427b3a9ce Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 30 Oct 2025 10:57:06 -0700 Subject: [PATCH 07/22] CRC: remove coinbase txs from epoch receipt for pre nakamoto tenures Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 14 +- ...nsensus__successfully_deploy_and_call.snap | 180 ------------------ 2 files changed, 13 insertions(+), 181 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 9cc69c8fbbd..3b6a386ab82 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -1152,7 +1152,19 @@ impl ConsensusTest<'_> { "--------- Processed Pre-Nakamoto block ---------"; "block" => ?stacks_block ); - let remapped_result = res.map(|receipt| receipt.unwrap()); + let remapped_result = res.map(|receipt| { + let mut receipt = receipt.unwrap(); + let mut sanitized_receipts = vec![]; + for tx_receipt in &receipt.tx_receipts { + // Remove any coinbase transactions from the output + if tx_receipt.is_coinbase_tx() { + continue; + } + sanitized_receipts.push(tx_receipt.clone()); + } + receipt.tx_receipts = sanitized_receipts; + receipt + }); // Restore chainstate for the next block self.chain.sortdb = Some(sortdb); self.chain.stacks_node = Some(stacks_node); diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index e2d227015bc..fb8ebecbd32 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -7,21 +7,6 @@ expression: result marf_hash: "c740de32d7b9273518899f798a6c66ea543dc67c4df2c97f428e37cf86f36857", evaluated_epoch: Epoch20, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e1e, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_0-Clarity1, code_body: [..], clarity_version: None)", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -50,21 +35,6 @@ expression: result marf_hash: "c197b006221151c65e298beaf88adcc9532f8b494b7f7564b8ef60fb217a4eb5", evaluated_epoch: Epoch2_05, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2020202020202020202020202020202020202020202020202020202020202020, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_05-Clarity1, code_body: [..], clarity_version: None)", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -93,21 +63,6 @@ expression: result marf_hash: "e568aa031e6c9a4c39dc23202fbc80a267eba760160ce288f3315dd30d9bb4a4", evaluated_epoch: Epoch21, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2222222222222222222222222222222222222222222222222222222222222222, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_1-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -136,21 +91,6 @@ expression: result marf_hash: "0d25118932d001ca3b324614c28b2de4353557df43ec0c8868904a87b1ee9f9a", evaluated_epoch: Epoch21, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2323232323232323232323232323232323232323232323232323232323232323, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_1-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -179,21 +119,6 @@ expression: result marf_hash: "211b8b332246d41b5927b4a19ba3d6af7c527dd4f619d92fde6dfcd049503b41", evaluated_epoch: Epoch22, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2525252525252525252525252525252525252525252525252525252525252525, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_2-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -222,21 +147,6 @@ expression: result marf_hash: "4dccf47885edf1f17736a78f772717bea62ea5136e99a981114eacf4ee1c292b", evaluated_epoch: Epoch22, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2626262626262626262626262626262626262626262626262626262626262626, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_2-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -265,21 +175,6 @@ expression: result marf_hash: "f06090bf6277fba5c9e95d3ccea4fdf8d2efe978e435684c38d1216f0f378ff9", evaluated_epoch: Epoch23, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2828282828282828282828282828282828282828282828282828282828282828, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -308,21 +203,6 @@ expression: result marf_hash: "af69aa70ce09a12e0bac066b84f24f103bb3cd4cd49b45b2711fa25e90b70c25", evaluated_epoch: Epoch23, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2929292929292929292929292929292929292929292929292929292929292929, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -351,21 +231,6 @@ expression: result marf_hash: "bf8243ccf29da7518f4e4f1cb894c17814463806f7ccd9d693e41175329bf017", evaluated_epoch: Epoch24, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_4-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -394,21 +259,6 @@ expression: result marf_hash: "7a009ae7dfd79b021f74d17d1b1ae9015b9f1d8984734099b9d7c1388d254316", evaluated_epoch: Epoch24, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_4-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -437,21 +287,6 @@ expression: result marf_hash: "46990434b77ba670bf7bc908be33b4083bbd7147cd86dffa52a821fc88abbf2f", evaluated_epoch: Epoch25, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_5-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", @@ -480,21 +315,6 @@ expression: result marf_hash: "ca156312aaf28bd8d37bf879d936a7590fe8b09723f6f5bf65da38c9d8982929", evaluated_epoch: Epoch25, transactions: [ - ExpectedTransactionOutput( - tx: "Coinbase(2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f, None, None)", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), ExpectedTransactionOutput( tx: "SmartContract(name: foo_contract-Epoch2_5-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", From c37946e1e6baf767421df19c5f51ef24a45e1c1a Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Fri, 31 Oct 2025 17:20:59 -0700 Subject: [PATCH 08/22] CRC: fix duplicate calls and cleanup ContractConsensusTest struct Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 389 ++-- ...nsensus__successfully_deploy_and_call.snap | 1562 +---------------- 2 files changed, 292 insertions(+), 1659 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 3b6a386ab82..bf0ce20790b 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -97,23 +97,70 @@ const fn clarity_versions_for_epoch(epoch: StacksEpochId) -> &'static [ClarityVe /// A high-level test harness for running consensus-critical smart contract tests. /// -/// This struct combines a [`ConsensusTest`] instance for chainstate management and a -/// [`TestTxFactory`] for transaction generation. It provides convenience methods to -/// automate test scenarios involving contract deployments and calls across multiple -/// epochs and Clarity versions. +/// This struct enables end-to-end testing of Clarity smart contracts under varying epoch conditions, +/// including different Clarity language versions and block rule sets. It automates: +/// +/// - Contract deployment in specified epochs (with epoch-appropriate Clarity versions) +/// - Function execution in subsequent or same epochs +/// - Block-by-block execution with precise control over transaction ordering and nonces +/// - Snapshot testing of execution outcomes via [`ExpectedResult`] +/// +/// It integrates: +/// - [`ConsensusTest`] for chain simulation and block production +/// - [`TestTxFactory`] for deterministic transaction generation +/// +/// NOTE: The **majority of logic and state computation occurs during construction to enable a deterministic TestChainstate** (`new()`): +/// - All contract names are generated and versioned +/// - Block counts per epoch are precomputed +/// - Epoch order is finalized +/// - Transaction sequencing is fully planned struct ContractConsensusTest<'a> { + /// Factory for generating signed, nonce-managed transactions. tx_factory: TestTxFactory, + /// Underlying chainstate used for block execution and consensus checks. consensus_test: ConsensusTest<'a>, - contract_names: Vec, + /// Address of the contract deployer (the test faucet). + contract_addr: StacksAddress, + /// Mapping of epoch → list of `(contract_name, ClarityVersion)` deployed in that epoch. + /// Multiple versions may exist per epoch (e.g., Clarity 1, 2, 3 in Epoch 3.0). + contract_deploys_per_epoch: HashMap>, + /// Mapping of epoch → list of `contract_names` that should be called in that epoch. + contract_calls_per_epoch: HashMap>, + /// Source code of the Clarity contract being deployed and called. + contract_code: String, + /// Name of the public function to invoke during the call phase. + function_name: String, + /// Arguments to pass to `function_name` on every call. + function_args: Vec, + /// Sorted, deduplicated set of all epochs involved. + /// Used to iterate through test phases in chronological order. + all_epochs: BTreeSet, } impl ContractConsensusTest<'_> { - /// Creates a new `ContractConsensusTest`. + /// Creates a new [`ContractConsensusTest`] instance. + /// + /// Initializes the test environment to: + /// - Deploy `contract_code` under `contract_name` in each `deploy_epochs` + /// - Call `function_name` with `function_args` in each `call_epochs` + /// - Track all contract instances per epoch and Clarity version + /// - Precompute block counts per epoch for stable chain simulation + /// + /// # Arguments + /// + /// * `test_name` - Unique identifier for the test run (used in logging and snapshots) + /// * `initial_balances` - Initial STX balances for principals (e.g., faucet, users) + /// * `deploy_epochs` - List of epochs where contract deployment should occur + /// * `call_epochs` - List of epochs where function calls should be executed + /// * `contract_name` - Base name for deployed contracts (versioned suffixes added automatically) + /// * `contract_code` - Clarity source code of the contract + /// * `function_name` - Contract function to test + /// * `function_args` - Arguments passed to `function_name` on every call /// /// # Panics + /// /// - If `deploy_epochs` is empty. /// - If any `call_epoch` is less than the minimum `deploy_epoch`. - // Creates a new ContractConsensusTest #[allow(clippy::too_many_arguments)] pub fn new( test_name: &str, @@ -136,15 +183,19 @@ impl ContractConsensusTest<'_> { ); // Build epoch_blocks map based on deploy and call epochs - let mut epoch_blocks: HashMap> = HashMap::new(); + let mut num_blocks_per_epoch: HashMap = HashMap::new(); + let mut contract_deploys_per_epoch: HashMap> = + HashMap::new(); + let mut contract_calls_per_epoch: HashMap> = HashMap::new(); let mut contract_names = vec![]; // Combine and sort unique epochs - let all_epochs: BTreeSet<_> = deploy_epochs.iter().chain(call_epochs).collect(); + let all_epochs: BTreeSet = + deploy_epochs.iter().chain(call_epochs).cloned().collect(); // Precompute contract names and block counts - for epoch in all_epochs { - let mut blocks = vec![]; + for epoch in &all_epochs { + let mut num_blocks = 0; if deploy_epochs.contains(epoch) { let clarity_versions = clarity_versions_for_epoch(*epoch); @@ -154,36 +205,57 @@ impl ContractConsensusTest<'_> { for &version in clarity_versions { let version_tag = version.to_string().replace(' ', ""); let name = format!("{contract_name}-{epoch_name}-{version_tag}"); + contract_deploys_per_epoch + .entry(*epoch) + .or_default() + .push((name.clone(), version)); contract_names.push(name.clone()); - blocks.push(TestBlock { - transactions: vec![], - }); + num_blocks += 1; } } if call_epochs.contains(epoch) { // Each call is a separate TestBlock - for _ in &contract_names { + for name in &contract_names { // Each call is a separate TestBlock - blocks.push(TestBlock { - transactions: vec![], // Placeholder - }); + contract_calls_per_epoch + .entry(*epoch) + .or_default() + .push(name.clone()); + num_blocks += 1; } } - if !blocks.is_empty() { - epoch_blocks.insert(*epoch, blocks); + if num_blocks > 0 { + num_blocks_per_epoch.insert(*epoch, num_blocks); } } Self { tx_factory: TestTxFactory::new(CHAIN_ID_TESTNET), - consensus_test: ConsensusTest::new(test_name, initial_balances, epoch_blocks), - contract_names, + consensus_test: ConsensusTest::new(test_name, initial_balances, num_blocks_per_epoch), + contract_addr: to_addr(&FAUCET_PRIV_KEY), + contract_deploys_per_epoch, + contract_calls_per_epoch, + contract_code: contract_code.to_string(), + function_name: function_name.to_string(), + function_args: function_args.to_vec(), + all_epochs, } } - /// Generates and executes the given transaction in a new block. - /// Increases the nonce if the transaction succeeds. + /// Generates a transaction, appends it to a new test block, and executes the block. + /// + /// If the transaction succeeds, this function automatically increments the sender's + /// nonce for subsequent transactions. + /// + /// # Arguments + /// + /// - `tx_spec`: The transaction specification to generate and execute. + /// - `is_naka_block`: Whether this block is mined under Nakamoto consensus rules. + /// + /// # Returns + /// + /// The [`ExpectedResult`] of block execution (success/failure with VM output) fn append_tx_block(&mut self, tx_spec: &TestTxSpec, is_naka_block: bool) -> ExpectedResult { let tx = self.tx_factory.generate_tx(tx_spec); let block = TestBlock { @@ -199,63 +271,77 @@ impl ContractConsensusTest<'_> { result } - /// Helper to deploy contracts a contract in a given epoch. - fn deploy_contracts( - &mut self, - epoch: StacksEpochId, - contract_name: &str, - contract_code: &str, - is_naka_block: bool, - ) -> Vec { - let mut results = Vec::new(); - let clarity_versions = clarity_versions_for_epoch(epoch); - let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); - - for &version in clarity_versions { - let version_tag = version.to_string().replace(' ', ""); - let name = format!("{contract_name}-{epoch_name}-{version_tag}"); - let clarity_version = if epoch < StacksEpochId::Epoch21 { - // Old epochs have no concept of clarity version. It defaults to - // clarity version 1 behaviour. - None - } else { - Some(version) - }; - results.push(self.append_tx_block( - &TestTxSpec::ContractDeploy { - sender: &FAUCET_PRIV_KEY, - name: &name, - code: contract_code, - clarity_version, - }, - is_naka_block, - )); - self.contract_names.push(name); - } + /// Deploys all contract versions scheduled for the given epoch. + /// + /// For each Clarity version supported in the epoch: + /// - Generates a unique contract name (e.g., `my-contract-Epoch30-Clarity3`) + /// - Deploys in a **separate block** + /// - Uses `None` for Clarity version in pre-2.1 epochs (behaviour defaults to Clarity 1) + /// + /// # Returns + /// A vector of [`ExpectedResult`] values, one per deployment block. + fn deploy_contracts(&mut self, epoch: StacksEpochId) -> Vec { + let Some(contract_names) = self.contract_deploys_per_epoch.get(&epoch) else { + warn!("No contract deployments found for {epoch}."); + return vec![]; + }; - results + let is_naka_block = epoch.uses_nakamoto_blocks(); + contract_names + .clone() + .iter() + .map(|(name, version)| { + let clarity_version = if epoch < StacksEpochId::Epoch21 { + // Old epochs have no concept of clarity version. It defaults to + // clarity version 1 behaviour. + None + } else { + Some(*version) + }; + self.append_tx_block( + &TestTxSpec::ContractDeploy { + sender: &FAUCET_PRIV_KEY, + name, + code: &self.contract_code.clone(), + clarity_version, + }, + is_naka_block, + ) + }) + .collect() } - /// Helper to call all deployed contracts in a given epoch. - fn call_contracts( - &mut self, - epoch: StacksEpochId, - contract_addr: &StacksAddress, - function_name: &str, - function_args: &[ClarityValue], - is_naka_block: bool, - ) -> Vec { - self.contract_names + /// Executes the test function on **all** contracts deployed in the given epoch. + /// + /// Each call occurs in a **separate block** to isolate side effects and enable + /// fine-grained snapshot assertions. All prior deployments (even from earlier epochs) + /// are callable if they exist in the chain state. + /// + /// # Arguments + /// + /// - `epoch`: The epoch in which to perform contract calls. + /// + /// # Returns + /// + /// A [`Vec`] with one entry per function call + fn call_contracts(&mut self, epoch: StacksEpochId) -> Vec { + let Some(contract_names) = self.contract_calls_per_epoch.get(&epoch) else { + warn!("No contract calls found for {epoch}."); + return vec![]; + }; + + let is_naka_block = epoch.uses_nakamoto_blocks(); + contract_names .clone() .iter() .map(|contract_name| { self.append_tx_block( &TestTxSpec::ContractCall { sender: &FAUCET_PRIV_KEY, - contract_addr, + contract_addr: &self.contract_addr.clone(), contract_name, - function_name, - args: function_args, + function_name: &self.function_name.clone(), + args: &self.function_args.clone(), }, is_naka_block, ) @@ -263,92 +349,45 @@ impl ContractConsensusTest<'_> { .collect() } - /// Executes a consensus test for a contract function across multiple Stacks epochs. - /// - /// This helper automates deploying a contract and invoking one of its public functions - /// across different epochs and Clarity versions, ensuring consistent consensus behavior. + /// Executes the full consensus test: deploy in [`Self::contract_deploys_per_epoch`], call in [`Self::contract_calls_per_epoch`]. /// - /// # Behavior + /// Processes epochs in **sorted order** using [`Self::all_epochs`]. For each epoch: + /// - Advances the chain into the target epoch + /// - Deploys contracts (if scheduled) + /// - Executes function calls (if scheduled) /// - /// The function performs two main phases: - /// 1. **Deployment:** Deploys `contract_code` in each epoch listed in `deploy_epochs` for all - /// applicable Clarity versions. - /// 2. **Execution:** Calls `function_name` in each epoch listed in `call_epochs` on every - /// previously deployed contract. + /// # Execution Order Example /// - /// ## Example - /// If `deploy_epochs` = `[2.0, 3.0]` and `call_epochs` = `[3.1]`, the following sequence occurs: - /// - Deploy contract in epoch 2.0 with Clarity 1. - /// - Deploy contract in epoch 3.0 with Clarity 1, 2, and 3. - /// - Call the function in epoch 3.1 on all four deployed contracts. + /// Given at test instantiation: + /// ```rust + /// deploy_epochs = [Epoch20, Epoch30] + /// call_epochs = [Epoch30, Epoch31] + /// ``` /// - /// # Arguments - /// - /// * `contract_name` - Base name for the contract. - /// * `contract_code` - Clarity source code of the contract. - /// * `function_name` - Public function to invoke. - /// * `function_args` - Arguments to pass to the function call. - /// * `deploy_epochs` - Epochs during which the contract should be deployed. - /// * `call_epochs` - Epochs during which the function should be executed. + /// The sequence is: + /// 1. Enter Epoch 2.0 → Deploy `contract-v1` + /// 2. Enter Epoch 3.0 → Deploy `contract-v1`, `contract-v2`, `contract-v3` + /// 3. Enter Epoch 3.0 → Call function on all 4 deployed contracts + /// 4. Enter Epoch 3.1 → Call function on all 4 deployed contracts /// /// # Returns /// /// A `Vec` with the outcome of each block for snapshot testing. - /// - /// # Panics - /// - /// * If `deploy_epochs` is empty. - /// * If any `call_epoch` precedes the earliest `deploy_epoch`. - pub fn run( - &mut self, - contract_name: &str, - contract_code: &str, - function_name: &str, - function_args: &[ClarityValue], - deploy_epochs: &[StacksEpochId], - call_epochs: &[StacksEpochId], - ) -> Vec { - let contract_addr = to_addr(&FAUCET_PRIV_KEY); + pub fn run(mut self) -> Vec { let mut results = Vec::new(); - // Combine all epochs - let all_epochs = deploy_epochs - .iter() - .chain(call_epochs) - .collect::>(); - // Process epochs in order - for epoch in all_epochs { - let is_naka_block = *epoch >= StacksEpochId::Epoch30; - let is_deploy_epoch = deploy_epochs.contains(epoch); - let is_call_epoch = call_epochs.contains(epoch); - + for epoch in self.all_epochs.clone() { // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); // Advance the chain into the target epoch self.consensus_test .chain - .advance_into_epoch(&private_key, *epoch); - - if is_deploy_epoch { - results.extend(self.deploy_contracts( - *epoch, - contract_name, - contract_code, - is_naka_block, - )); - } + .advance_into_epoch(&private_key, epoch); - if is_call_epoch { - results.extend(self.call_contracts( - *epoch, - &contract_addr, - function_name, - function_args, - is_naka_block, - )); - } + results.extend(self.deploy_contracts(epoch)); + results.extend(self.call_contracts(epoch)); } results @@ -410,7 +449,7 @@ macro_rules! contract_call_consensus_test { // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) let call_epochs = EPOCHS_TO_TEST; $(let call_epochs = $call_epochs;)? - let mut contract_test = ContractConsensusTest::new( + let contract_test = ContractConsensusTest::new( function_name!(), vec![], deploy_epochs, @@ -420,14 +459,7 @@ macro_rules! contract_call_consensus_test { $function_name, $function_args, ); - let result = contract_test.run( - $contract_name, - $contract_code, - $function_name, - $function_args, - deploy_epochs, - call_epochs, - ); + let result = contract_test.run(); insta::assert_ron_snapshot!(result); } }; @@ -811,7 +843,6 @@ pub struct TestBlock { /// Represents a consensus test with chainstate. pub struct ConsensusTest<'a> { pub chain: TestChainstate<'a>, - epoch_blocks: HashMap>, } impl ConsensusTest<'_> { @@ -819,18 +850,18 @@ impl ConsensusTest<'_> { pub fn new( test_name: &str, initial_balances: Vec<(PrincipalData, u64)>, - epoch_blocks: HashMap>, + num_blocks_per_epoch: HashMap, ) -> Self { // Validate blocks - for (epoch_id, blocks) in &epoch_blocks { + for (epoch_id, num_blocks) in &num_blocks_per_epoch { assert_ne!( *epoch_id, StacksEpochId::Epoch10, "Epoch10 is not supported" ); assert!( - !blocks.is_empty(), - "Each epoch must have at least one block" + *num_blocks > 0, + "Each epoch must have at least one block. {epoch_id} is empty" ); } // Set up chainstate to support Naka. @@ -839,13 +870,10 @@ impl ConsensusTest<'_> { .with_initial_balances(initial_balances) .with_private_key(FAUCET_PRIV_KEY.clone()); let (epochs, first_burnchain_height) = - Self::calculate_epochs(&boot_plan.pox_constants, &epoch_blocks); + Self::calculate_epochs(&boot_plan.pox_constants, num_blocks_per_epoch); boot_plan = boot_plan.with_epochs(epochs); let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); - Self { - chain, - epoch_blocks, - } + Self { chain } } /// Calculates a valid [`EpochList`] and starting burnchain height for the test harness. @@ -864,7 +892,7 @@ impl ConsensusTest<'_> { /// # Arguments /// /// * `pox_constants` - PoX configuration (reward cycle length, prepare phase, etc.). - /// * `epoch_blocks` - Map of epoch IDs to the test blocks to run in each. + /// * `num_blocks_per_epoch` - Map of epoch IDs to the number of test blocks to run in each. /// /// # Returns /// @@ -872,7 +900,7 @@ impl ConsensusTest<'_> { /// height at which the first Stacks block is mined. fn calculate_epochs( pox_constants: &PoxConstants, - epoch_blocks: &HashMap>, + num_blocks_per_epoch: HashMap, ) -> (EpochList, u64) { // Helper function to check if a height is at a reward cycle boundary let is_reward_cycle_boundary = |height: u64, reward_cycle_length: u64| -> bool { @@ -915,9 +943,9 @@ impl ConsensusTest<'_> { // Use test vector block count // Always add 1 so we can ensure we are fully in the epoch before we then execute // the corresponding test blocks in their own blocks - let num_blocks = epoch_blocks + let num_blocks = num_blocks_per_epoch .get(epoch_id) - .map(|blocks| blocks.len() as u64 + 1) + .map(|num_blocks| *num_blocks + 1) .unwrap_or(0); start_height + num_blocks } @@ -927,9 +955,9 @@ impl ConsensusTest<'_> { // Epoch 2.5 end must equal Epoch 3.0 start // Epoch 3.0 must not start at a cycle boundary // Epoch 2.5 and 3.0 cannot be in the same reward cycle. - let num_blocks = epoch_blocks + let num_blocks = num_blocks_per_epoch .get(epoch_id) - .map(|blocks| blocks.len() as u64) + .copied() .unwrap_or(0) .saturating_add(1); // Add one block for pox lockups. @@ -960,7 +988,7 @@ impl ConsensusTest<'_> { } StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { // Only need 1 block per Epoch - if epoch_blocks.contains_key(epoch_id) { + if num_blocks_per_epoch.contains_key(epoch_id) { start_height + 1 } else { // If we don't care to have any blocks in this epoch @@ -1034,19 +1062,18 @@ impl ConsensusTest<'_> { ); } // Validate test vector block counts - for (epoch_id, blocks) in epoch_blocks { + for (epoch_id, num_blocks) in num_blocks_per_epoch { let epoch = epochs .iter() - .find(|e| e.epoch_id == *epoch_id) + .find(|e| e.epoch_id == epoch_id) .expect("Epoch not found"); let epoch_length = epoch.end_height - epoch.start_height; - if *epoch_id > StacksEpochId::Epoch25 { + if epoch_id > StacksEpochId::Epoch25 { assert!( epoch_length > 0, "{epoch_id:?} must have at least 1 burn block." ); } else { - let num_blocks = blocks.len() as u64; assert!( epoch_length >= num_blocks, "{epoch_id:?} must have at least {num_blocks} burn blocks, got {epoch_length}" @@ -1199,11 +1226,19 @@ impl ConsensusTest<'_> { /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s /// associated with that epoch and collects their results. /// + /// # Arguments + /// + /// * `epoch_blocks` - A map where keys are [`StacksEpochId`]s and values are the + /// sequence of blocks to be executed during that epoch. + /// /// # Returns /// /// A `Vec` with the outcome of each block for snapshot testing. - pub fn run(mut self) -> Vec { - let mut sorted_epochs: Vec<_> = self.epoch_blocks.clone().into_iter().collect(); + pub fn run( + mut self, + epoch_blocks: HashMap>, + ) -> Vec { + let mut sorted_epochs: Vec<_> = epoch_blocks.clone().into_iter().collect(); sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); let mut results = vec![]; @@ -1218,7 +1253,7 @@ impl ConsensusTest<'_> { self.chain.advance_into_epoch(&miner_key, epoch); for block in blocks { - results.push(self.append_block(block, epoch >= StacksEpochId::Epoch30)); + results.push(self.append_block(block, epoch.uses_nakamoto_blocks())); } } results @@ -1362,11 +1397,14 @@ fn test_append_empty_blocks() { transactions: vec![], }]; let mut epoch_blocks = HashMap::new(); + let mut num_blocks_per_epoch = HashMap::new(); for epoch in EPOCHS_TO_TEST { epoch_blocks.insert(*epoch, empty_test_blocks.clone()); + num_blocks_per_epoch.insert(*epoch, 1); } - let result = ConsensusTest::new(function_name!(), vec![], epoch_blocks).run(); + let result = + ConsensusTest::new(function_name!(), vec![], num_blocks_per_epoch).run(epoch_blocks); insta::assert_ron_snapshot!(result); } @@ -1391,6 +1429,7 @@ fn test_append_stx_transfers_success() { // build transactions per epoch, incrementing nonce per sender let mut epoch_blocks = HashMap::new(); + let mut num_blocks_per_epoch = HashMap::new(); let mut nonces = vec![0u64; sender_privks.len()]; // track nonce per sender for epoch in EPOCHS_TO_TEST { @@ -1411,10 +1450,12 @@ fn test_append_stx_transfers_success() { }) .collect(); + num_blocks_per_epoch.insert(*epoch, 1); epoch_blocks.insert(*epoch, vec![TestBlock { transactions }]); } - let result = ConsensusTest::new(function_name!(), initial_balances, epoch_blocks).run(); + let result = ConsensusTest::new(function_name!(), initial_balances, num_blocks_per_epoch) + .run(epoch_blocks); insta::assert_ron_snapshot!(result); } diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap index fb8ebecbd32..055bf8f7443 100644 --- a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap +++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__successfully_deploy_and_call.snap @@ -1180,1527 +1180,119 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5c85f79f442d8a76d9c42b09f5ca469efe75297691d04be5b8b1ac8c6dddb712", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity1\")) [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "529fc08636f1dc4997ab9166e33e2a864c500388a8ca2e2d81d1c9edf9a299ba", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity2\")) [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "4a60d7ad17d1934d2946920340fd65ed869f9616c11a929dafce7930c860529e", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity3\")) [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "f46c919ce39bafea42b70d956fa76a0f63c80ea81c3e8008a482e1f8977cb089", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "Some(NoSuchContract(\"ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP.foo_contract-Epoch3_3-Clarity4\")) [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: false, - data: Optional(OptionalData( - data: None, - )), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 0, - read_count: 0, - runtime: 0, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "4375b36e05ed4ae090da7cdd26a46621e437b0327effee1e2a70b68eaaa3f620", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "061a403ba2dac23b855a07f9c8bcfdc76aa32802910b3781e71ba4627ccd811d", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "acb2b8a774450154b86e15be4adbae94350841f6865dec8f9b35a79da62d3fab", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "3e3d96be0eefa48f39fa24e8cfff545d5a2870a49d5e1153361418cc350a0251", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "3fc5cc358dea0639d7d3d83a8104aebe7de509d696c45199d38a7f477bafa3e0", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "c0a89ebec0fe92431ae8e7fa7bb4e7891872767cc604e5dfe686bab1ce8198c5", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "3011760394ccc2247a937a44de1e997936b36b394eca22928bc9e597284204d3", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "695f92457e64c9e7f2fd5b2104413067747309cfd91f055988115356a67fa3dd", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "c6ff999ebcff7aa9acf19426de1d9fd453ccece45f6597739c9588c02f6f6feb", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "2888b44ac5d1cc4f498ba5c3fbb988154546f8ffae20d39d7efa0f5d2f0cceef", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "eee2216ac8c4bd5c48f2f975b3f91581dbf718628f321936032086bc6dcd8968", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "9846692add234371fbb114f440a3de2fc5b2f6c6ff71816b97c9de9a12c91722", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "bdded23abbc45d91bb038e57dc692ee8a693c48cf40d1d28a78fa95aa46da1a5", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "645f3d75513ea0d0659518952bd66f11aed541d9afd376b1b1a4b65d6ced4d34", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "0a86087643cf2f5d14bd80dbce432919af0bf00c7cf6c74b615eabc6d5c76c0a", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "dd0475657336ba13c24eb042e30e750e26a53affd6421861c6725056088be3e0", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "b1fdc2ecd0bbc423f0aaff2349ee69a9221ee0a8857a5f5c9cf9924f1ffa81f2", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "5828fc8a9cb1e4543920b73f845c666860b3f86711e92780b8f40aa489eec9bc", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "9fc1b695621e584e28228f37419734a26f161e18bc8cb9d23850b37eccec6e6d", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "a056bfec29b84376e184a8afed73d6665110169a36c20fda7b515a8cded3926e", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "a78b8227dc7e082208c8219fe4f7a4e39dae0188bfdc8da040b93037a33df128", - evaluated_epoch: Epoch32, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "a9d8cd8ed5f44af877943552e71a105d05beb93537b9d343d02b7f04d9e34490", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "e718b64af4bd4ed2f80a14f3414121faf7a68fd14123f6b6ea9ba78372153afc", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "5f27a364c5456339f26f734c832ef2796390f122b2aba0bf0bbbaf7333fef865", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "fef84d80507dd69b3f3eaeba70fc4c23bd0be57283aec9f4d8f80a512851cbd5", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity4, code_body: [..], clarity_version: Some(Clarity4))", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: Bool(true), - )), - cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 121, - write_count: 2, - read_length: 1, - read_count: 1, - runtime: 11968, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "fce86bd21fba70f054755e194a8a3aa8421fca0ad260e2105f475bb57322dd5f", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "09349179bb7cf35ac025413196f2404d65fea3bdf3c7062716f192528398199d", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_05-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "11f03c7ba858deb1f7d3bfe36ad35ea0b2702f21b6c2cfee338505d8f4f242db", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "c25f81701ae72442cb327502c3c656fe2f44230a0a6a548a378b4e2a8a576b1e", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "bbdb12a0d08b091fc4cf457e935b9f23b971a666b5a9ea77506e6cb02ae6d425", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "ae87d7c858a33b47107aa1ae1610f82ad3ddfca123c2845cdfb75ef4d0f2a697", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "3318fb8beb6709a295a494cc8c5c91296d7411a35e3df64e7f2935a870315223", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "05323d59547eb058ae9df3808b0093ddb1eac2727baa04884dfe5b31819882c3", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "adfe63fb5f34fd7393d53ca7975465cf4a599e3fb3a91a271f3d3d71fe22375c", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "e2c4e1c7d5c0cda8e1a2cf78709f90d9b7a32e640bdbc066a8a43bcefc5feb9b", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_4-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "af5f4418ff6a436c6c0b46072a7d55bd99cc1957d7a869a75d5743f810792e67", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "ad4c07c6dd58f89faea5ee28d85292941f56c16a47df879526763c61f61e7a3f", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch2_5-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "706be0a2530ac3ed3afed27f80c5f9dfa93413575b42d9778366116bdb88c0a3", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "678b509ea7598c3ade749077713a94e05f852353b5295c983eb818a4ec1544e9", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "451575b5dd4de490987a4bc8e7ddc0be7f092bcfbf19d17cb1e9dbe9e0556a4f", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_0-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "6b469931e4ac9cc4f8b7566f17fbd52ea5035f627e4de12212a8817670275eba", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "76057e23ffbaada3cbb34471b00a64c962b0f6e155e4d8a7674262f4122500ea", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "480385127d9376b38a4dc83040ff5660e13c3b4b942ff7a34b508cc5eaae00ad", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_1-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "9d5ea2ca966bac1ae5c29ad461a80a2957b143e4fd69b92e9f5586753781c8fa", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity1, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "2af8cde232a1677485cba8ae0825fa1f18ef5fbbce30ebd97535582b070ba670", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity2, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "c0a2a9c6383b7437a5ef856ae896ba0f49729fa45f8d9add8a0d1b7e19d5b0be", - evaluated_epoch: Epoch33, - transactions: [ - ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_2-Clarity3, function_name: bar, function_args: [[UInt(1)]])", - vm_error: "None [NON-CONSENSUS BREAKING]", - return_type: Response(ResponseData( - committed: true, - data: UInt(1), - )), - cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - ), - ], - total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, - ), - )), - Success(ExpectedBlockOutput( - marf_hash: "7120a9aeb9991ad1c9d89efdc6c342e8c4699e5cf20122207cf3ce70427815a1", + marf_hash: "7c49b5e1ef621f812e098505662ec22466e35dba63790f0207ca975ba140e1bf", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity1, function_name: bar, function_args: [[UInt(1)]])", + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity1, code_body: [..], clarity_version: Some(Clarity1))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: UInt(1), + data: Bool(true), )), cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), ), ], total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), )), Success(ExpectedBlockOutput( - marf_hash: "4f99f7e933ab4d439067701766264055c1c4457343f0f3a6cdbbd4eee44c62de", + marf_hash: "1ce3dbfc54ed002976f2ac1b9481002cc5693c6986a0a276aa493204ce5c50cd", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity2, function_name: bar, function_args: [[UInt(1)]])", + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity2, code_body: [..], clarity_version: Some(Clarity2))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: UInt(1), + data: Bool(true), )), cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), ), ], total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), )), Success(ExpectedBlockOutput( - marf_hash: "defc58d5be3e5400cd551608394670f9fe823b77cf1e1e37eec540d90354852b", + marf_hash: "bc53a4af2876d50e4b1f2fc617662a2c7fe407f399a5e9ed70f1a5d6ef632c96", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity3, function_name: bar, function_args: [[UInt(1)]])", + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity3, code_body: [..], clarity_version: Some(Clarity3))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: UInt(1), + data: Bool(true), )), cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), ), ], total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), )), Success(ExpectedBlockOutput( - marf_hash: "a7a14328b2325dc5426aba9ff9e954043ed3dace6efa4ef12e2388b157f1be2b", + marf_hash: "1a5f4dcc0061e5f372a7fcea574f14942ac7366f16ef7254adbdcd08b1559a0a", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( - tx: "ContractCall(address: ST1AW6EKPGT61SQ9FNVDS17RKNWT8ZP582VF9HSCP, contract_name: foo_contract-Epoch3_3-Clarity4, function_name: bar, function_args: [[UInt(1)]])", + tx: "SmartContract(name: foo_contract-Epoch3_3-Clarity4, code_body: [..], clarity_version: Some(Clarity4))", vm_error: "None [NON-CONSENSUS BREAKING]", return_type: Response(ResponseData( committed: true, - data: UInt(1), + data: Bool(true), )), cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), ), ], total_block_cost: ExecutionCost( - write_length: 0, - write_count: 0, - read_length: 103, - read_count: 3, - runtime: 499, + write_length: 121, + write_count: 2, + read_length: 1, + read_count: 1, + runtime: 11968, ), )), Success(ExpectedBlockOutput( - marf_hash: "37bca0446b3ac19b573d448668e1a4628968bdd50d3737a6625901a1ead4fcd5", + marf_hash: "611f8299c20e38188d76c1d8981a8ea5d000c73ede60c854010598d23c75f7f4", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2728,7 +1320,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0bea4e6e656680788ee9c978eda4038768dda8ed65c2bafd2b0b8d1233b8b5fc", + marf_hash: "fad2c2400a9ed905609b276a3e0ac12e82666495a187cc9d96c6f15c2ce0f9f2", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2756,7 +1348,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "079f52daef6eb6c650bf7eae1549bf432e4942969bb19d29285029779e03ad28", + marf_hash: "ff78af2148d178968917f4dcc3bf0e84e99e94e7e03cce0481e7bb2ed722e64f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2784,7 +1376,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "aaddba35cac01b42d5ad94dc80142f163a2e4fe0f68cd2a459536610d6ac8663", + marf_hash: "c24417065f0ae7793e7b7048483d88d6d847d06f5dc141a2e82f5d9cfc708036", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2812,7 +1404,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "0c02834881fd78966ff56800ca27028cb6ad00715ec65c14fe91c507a3434bdc", + marf_hash: "0cd887293455e8a6d347eae14d5b9790dcc7cb015294c2a929477ca3e7afd51f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2840,7 +1432,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "db7ee3014f222b85b8d2b7f3efb5e6ba6c15dc6be8e5e9889830692b40320e2d", + marf_hash: "130b8d2b572b04c64fd7d9b34cd63ec0400a792f5a3bb1eb905a80074f10a68b", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2868,7 +1460,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "fb902c58b140acf8244b4ef1a2613d332e8d55b3f564276c29748fee7451cf83", + marf_hash: "ed142964c3dafbe32c916591ef45f871fbf31330d770884ec466aa13fdc57790", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2896,7 +1488,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "22ca8a2c5da213ceb07bdc8466ddced12c684708196f9bbe767f058e68501457", + marf_hash: "7b61d1f32b9ee3c3672aee6fe1c31298c2b5a92f04304df3b2e1c16e51501dfe", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2924,7 +1516,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "d7d89f8e75e84f35e8d236972f17512c1d2ee0de1bbd581475466bf65c36fe00", + marf_hash: "3f01fae225c8345bd4d00385351a0e06c368975409fdb5ed550413a8c2752bde", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2952,7 +1544,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "9a4d8b7bc0f81ef6a265cff294ab98a11a5f8993060530455bece0e52390fc76", + marf_hash: "c19b2af6f7214f293596ed8ee49f4a719d117699655fcf0dfdae6a94c993f318", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -2980,7 +1572,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "aa9132f4ee67332367cf14cbe4e28854d5e5e5d20d17c9223479082080304c73", + marf_hash: "6742b63ec373563e0f67b06e09e48dcaeff96501d8b2971af9db4309d353d120", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3008,7 +1600,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "4b20d700321ecfb548dffa85334e8599cbca5a12016c78ae0d92366f80eac231", + marf_hash: "d8649197d538722552412dce20ce4c197035bf92b5c247b2668361d9a4755942", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3036,7 +1628,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "3a1c45d42877ba3eaa7ae0dfa500562a7f3507e328823833bf48487df8e4cb90", + marf_hash: "6cde15ab15ad480cd95a0f4beb707e5db334c55cff6027f37bc0b1b8cb44ea41", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3064,7 +1656,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "5f005bc8f53d8178dbd381323ca8269b5a0ce1f754a279ccd0fb98f25c0475ed", + marf_hash: "d6c085c796f234102b6de6259e0e014aa88e789923d00fb8f7a25857cc9f1a15", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3092,7 +1684,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "e33d426f90df4d7a63474905f94e8b3f24ec12c89bed5db72534b5a3da815c1c", + marf_hash: "645db2f08fb74c7cd7830cb90d8db8dccc3c4e37b89be42d3c7b2bd214f58556", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3120,7 +1712,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "a00da2af43b76c025c04e2164d523d65acec487f116ac41ed0ebdd66523b9a7b", + marf_hash: "43e8fb9110329a7a8892e227509f5833b3a0cfe54f4c0c1d1ae58d17622d6e01", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3148,7 +1740,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "1d555792f123ffd679f9a0d4cfaa0d04f9d432b5619ff65052f2858f019a79c0", + marf_hash: "6564f3eccdf42fb951cc61b687b493b339c68f3a185328eede8f3bbc2d60435d", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3176,7 +1768,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "07b5f4d3937b9774ca5409e1ab4384dabc3bad9207782e09692e811b468770c7", + marf_hash: "1c926315fee79fd91f30ece3612ea499866ff5d8e26ea76947d3147675d2463c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3204,7 +1796,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "b6cf81f520fab267c7fe5d03cc061ee8b29bcd7e242404eabc00f1d7ccbe2fbd", + marf_hash: "6fd0a270a21d6defe7796db517f01351dbf4690a3087b2ad045530f0f10f0d9c", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3232,7 +1824,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "72193df4d1e062df216d871f44166b4fe3dc0041c48cb6b52584b5e77af0096a", + marf_hash: "12dcc58b837c4780423c76953847dcfa787adfc3a12745319e245cd944aa0c17", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3260,7 +1852,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "8fab3bd321b98c6ddc1c9b84fdfc4c161ff18625f1147ed0c26148f30e8a33f4", + marf_hash: "9bcf59d22afcbbdd3ea4660e823b385ebc9b0098310c893b0810378041d63d06", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3288,7 +1880,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "43642752b9ca9ab7a13a75f489adf79acb92bea899dafcd32bb2f03b2744b3df", + marf_hash: "157c33212448710e558bf87edcb73a65f7c7d64ea25f11c0d1197e9c3ebe7b85", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3316,7 +1908,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "f3972a9b04c63876ce486f14797e0099dc71bd75cdf7f5cc5c0be44a82fec2c3", + marf_hash: "d36b236e0c9430cf3c6782b8d1d56b777c83328861d428bcc72a6fe497d4c5ee", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3344,7 +1936,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "c6ce77294ac9ea065ec33e3343aefcc112387093d40ed940210fc74275c54d3e", + marf_hash: "29c9591ed858e7f921eb0af320f8823ca5c80255a4003d86d444bc5cbb9128ad", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( @@ -3372,7 +1964,7 @@ expression: result ), )), Success(ExpectedBlockOutput( - marf_hash: "67483c8a1d0a048bc61cbed380e80bb694c3770c85fa4c6c100a6f2a037a6e66", + marf_hash: "76de1f6c346b550f60eca65ae89861c1fd7a73f463ea928791bd2e63ff15b18f", evaluated_epoch: Epoch33, transactions: [ ExpectedTransactionOutput( From 14e13e22d332b696b3b2c4ee8f45e3ea00978e08 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 10:09:28 -0800 Subject: [PATCH 09/22] Automatically create StacksEpochId::ALL from varients to prevent manual update Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 34 ++++++++++----------- stackslib/src/chainstate/tests/consensus.rs | 19 ++---------- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 6ad3ba10a4a..4831c95a4bb 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -95,9 +95,23 @@ pub const MINING_COMMITMENT_WINDOW: u8 = 6; // Only relevant for Nakamoto (epoch 3.x) pub const MINING_COMMITMENT_FREQUENCY_NAKAMOTO: u8 = 3; -#[repr(u32)] -#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy, Serialize, Deserialize)] -pub enum StacksEpochId { +macro_rules! define_stacks_epochs { + ($($variant:ident = $value:expr),* $(,)?) => { + #[repr(u32)] + #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] + pub enum StacksEpochId { + $($variant = $value),* + } + + impl StacksEpochId { + pub const ALL: &'static [StacksEpochId] = &[ + $(StacksEpochId::$variant),* + ]; + } + }; +} + +define_stacks_epochs! { Epoch10 = 0x01000, Epoch20 = 0x02000, Epoch2_05 = 0x02005, @@ -447,20 +461,6 @@ impl StacksEpochId { StacksEpochId::Epoch33 } - pub const ALL_GTE_20: &'static [StacksEpochId] = &[ - StacksEpochId::Epoch20, - StacksEpochId::Epoch2_05, - StacksEpochId::Epoch21, - StacksEpochId::Epoch22, - StacksEpochId::Epoch23, - StacksEpochId::Epoch24, - StacksEpochId::Epoch25, - StacksEpochId::Epoch30, - StacksEpochId::Epoch31, - StacksEpochId::Epoch32, - StacksEpochId::Epoch33, - ]; - /// In this epoch, how should the mempool perform garbage collection? pub fn mempool_garbage_behavior(&self) -> MempoolCollectionBehavior { match self { diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index bf0ce20790b..7a4b23195cb 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -443,7 +443,7 @@ macro_rules! contract_call_consensus_test { #[test] fn $name() { // Handle deploy_epochs parameter (default to all epochs >= 2.0 if not provided) - let deploy_epochs = StacksEpochId::ALL_GTE_20; + let deploy_epochs = &StacksEpochId::ALL[1..]; $(let deploy_epochs = $deploy_epochs;)? // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) @@ -912,25 +912,10 @@ impl ConsensusTest<'_> { info!("StacksEpoch calculate_epochs first_burn_height = {first_burnchain_height}"); let reward_cycle_length = pox_constants.reward_cycle_length as u64; let prepare_length = pox_constants.prepare_length as u64; - // Define all epochs in order - let epoch_ids = [ - StacksEpochId::Epoch10, - StacksEpochId::Epoch20, - StacksEpochId::Epoch2_05, - StacksEpochId::Epoch21, - StacksEpochId::Epoch22, - StacksEpochId::Epoch23, - StacksEpochId::Epoch24, - StacksEpochId::Epoch25, - StacksEpochId::Epoch30, - StacksEpochId::Epoch31, - StacksEpochId::Epoch32, - StacksEpochId::Epoch33, - ]; // Initialize heights let mut epochs = vec![]; let mut current_height = 0; - for epoch_id in epoch_ids.iter() { + for epoch_id in StacksEpochId::ALL.iter() { let start_height = current_height; let end_height = match *epoch_id { StacksEpochId::Epoch10 => first_burnchain_height, From 0e71860aff97a75bd9a1d4f3031bf2bc5cf79f67 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 10:34:59 -0800 Subject: [PATCH 10/22] Move functions around for easier understanding Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 2244 +++++++++---------- 1 file changed, 1122 insertions(+), 1122 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 7a4b23195cb..1256481732a 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -95,859 +95,244 @@ const fn clarity_versions_for_epoch(epoch: StacksEpochId) -> &'static [ClarityVe } } -/// A high-level test harness for running consensus-critical smart contract tests. -/// -/// This struct enables end-to-end testing of Clarity smart contracts under varying epoch conditions, -/// including different Clarity language versions and block rule sets. It automates: -/// -/// - Contract deployment in specified epochs (with epoch-appropriate Clarity versions) -/// - Function execution in subsequent or same epochs -/// - Block-by-block execution with precise control over transaction ordering and nonces -/// - Snapshot testing of execution outcomes via [`ExpectedResult`] -/// -/// It integrates: -/// - [`ConsensusTest`] for chain simulation and block production -/// - [`TestTxFactory`] for deterministic transaction generation -/// -/// NOTE: The **majority of logic and state computation occurs during construction to enable a deterministic TestChainstate** (`new()`): -/// - All contract names are generated and versioned -/// - Block counts per epoch are precomputed -/// - Epoch order is finalized -/// - Transaction sequencing is fully planned -struct ContractConsensusTest<'a> { - /// Factory for generating signed, nonce-managed transactions. - tx_factory: TestTxFactory, - /// Underlying chainstate used for block execution and consensus checks. - consensus_test: ConsensusTest<'a>, - /// Address of the contract deployer (the test faucet). - contract_addr: StacksAddress, - /// Mapping of epoch → list of `(contract_name, ClarityVersion)` deployed in that epoch. - /// Multiple versions may exist per epoch (e.g., Clarity 1, 2, 3 in Epoch 3.0). - contract_deploys_per_epoch: HashMap>, - /// Mapping of epoch → list of `contract_names` that should be called in that epoch. - contract_calls_per_epoch: HashMap>, - /// Source code of the Clarity contract being deployed and called. - contract_code: String, - /// Name of the public function to invoke during the call phase. - function_name: String, - /// Arguments to pass to `function_name` on every call. - function_args: Vec, - /// Sorted, deduplicated set of all epochs involved. - /// Used to iterate through test phases in chronological order. - all_epochs: BTreeSet, +/// Custom serializer for `Option` to improve snapshot readability. +/// This avoids large diffs in snapshots due to code body changes and focuses on key fields. +fn serialize_opt_tx_payload( + value: &Option, + serializer: S, +) -> Result +where + S: Serializer, +{ + let changed = match value { + None => "BitcoinTx".to_string(), + Some(TransactionPayload::TokenTransfer(sender, amount, memo)) => { + format!("TokenTransfer(from: {sender}, amount: {amount}, memo: {memo})") + } + Some(TransactionPayload::SmartContract( + TransactionSmartContract { name, code_body }, + clarity_version, + )) => { + format!("SmartContract(name: {name}, code_body: [..], clarity_version: {clarity_version:?})") + } + Some(TransactionPayload::ContractCall(TransactionContractCall { + address, + contract_name, + function_name, + function_args, + })) => { + format!("ContractCall(address: {address}, contract_name: {contract_name}, function_name: {function_name}, function_args: [{function_args:?}])") + } + Some(payload) => { + format!("{payload:?}") + } + }; + serializer.serialize_str(&changed) } -impl ContractConsensusTest<'_> { - /// Creates a new [`ContractConsensusTest`] instance. - /// - /// Initializes the test environment to: - /// - Deploy `contract_code` under `contract_name` in each `deploy_epochs` - /// - Call `function_name` with `function_args` in each `call_epochs` - /// - Track all contract instances per epoch and Clarity version - /// - Precompute block counts per epoch for stable chain simulation - /// - /// # Arguments - /// - /// * `test_name` - Unique identifier for the test run (used in logging and snapshots) - /// * `initial_balances` - Initial STX balances for principals (e.g., faucet, users) - /// * `deploy_epochs` - List of epochs where contract deployment should occur - /// * `call_epochs` - List of epochs where function calls should be executed - /// * `contract_name` - Base name for deployed contracts (versioned suffixes added automatically) - /// * `contract_code` - Clarity source code of the contract - /// * `function_name` - Contract function to test - /// * `function_args` - Arguments passed to `function_name` on every call - /// - /// # Panics - /// - /// - If `deploy_epochs` is empty. - /// - If any `call_epoch` is less than the minimum `deploy_epoch`. - #[allow(clippy::too_many_arguments)] - pub fn new( - test_name: &str, - initial_balances: Vec<(PrincipalData, u64)>, - deploy_epochs: &[StacksEpochId], - call_epochs: &[StacksEpochId], - contract_name: &str, - contract_code: &str, - function_name: &str, - function_args: &[ClarityValue], - ) -> Self { - assert!( - !deploy_epochs.is_empty(), - "At least one deploy epoch is required" - ); - let min_deploy_epoch = deploy_epochs.iter().min().unwrap(); - assert!( - call_epochs.iter().all(|e| e >= min_deploy_epoch), - "All call epochs must be >= the minimum deploy epoch" - ); - - // Build epoch_blocks map based on deploy and call epochs - let mut num_blocks_per_epoch: HashMap = HashMap::new(); - let mut contract_deploys_per_epoch: HashMap> = - HashMap::new(); - let mut contract_calls_per_epoch: HashMap> = HashMap::new(); - let mut contract_names = vec![]; - - // Combine and sort unique epochs - let all_epochs: BTreeSet = - deploy_epochs.iter().chain(call_epochs).cloned().collect(); +/// Serialize an optional string field appending a non-consensus breaking info message. +fn serialize_opt_string_ncb(value: &Option, serializer: S) -> Result +where + S: Serializer, +{ + let original = match value.as_deref() { + Some(str) => format!("Some({str})"), + None => "None".to_string(), + }; + let changed = format!("{original} [NON-CONSENSUS BREAKING]"); + serializer.serialize_str(&changed) +} - // Precompute contract names and block counts - for epoch in &all_epochs { - let mut num_blocks = 0; +/// Represents the expected output of a transaction in a test. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ExpectedTransactionOutput { + /// The transaction that was executed. + /// `None` for bitcoin transactions. + #[serde(serialize_with = "serialize_opt_tx_payload")] + pub tx: Option, + /// The possible Clarity VM error message associated to the transaction (non-consensus breaking) + #[serde(serialize_with = "serialize_opt_string_ncb")] + pub vm_error: Option, + /// The expected return value of the transaction. + pub return_type: ClarityValue, + /// The expected execution cost of the transaction. + pub cost: ExecutionCost, +} - if deploy_epochs.contains(epoch) { - let clarity_versions = clarity_versions_for_epoch(*epoch); - let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); +/// Represents the expected outputs for a block's execution. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ExpectedBlockOutput { + /// The expected block marf + pub marf_hash: TrieHash, + /// The epoch in which the test block was expected to be evaluated + pub evaluated_epoch: StacksEpochId, + /// The expected outputs for each transaction, in input order. + pub transactions: Vec, + /// The total execution cost of the block. + pub total_block_cost: ExecutionCost, +} - // Each deployment is a seperate TestBlock - for &version in clarity_versions { - let version_tag = version.to_string().replace(' ', ""); - let name = format!("{contract_name}-{epoch_name}-{version_tag}"); - contract_deploys_per_epoch - .entry(*epoch) - .or_default() - .push((name.clone(), version)); - contract_names.push(name.clone()); - num_blocks += 1; - } - } +/// Represents the expected result of a consensus test. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum ExpectedResult { + /// The test should succeed with the specified outputs. + Success(ExpectedBlockOutput), + /// The test should fail with an error matching the specified string + /// Cannot match on the exact Error directly as they do not implement + /// Serialize/Deserialize or PartialEq + Failure(String), +} - if call_epochs.contains(epoch) { - // Each call is a separate TestBlock - for name in &contract_names { - // Each call is a separate TestBlock - contract_calls_per_epoch - .entry(*epoch) - .or_default() - .push(name.clone()); - num_blocks += 1; - } - } - if num_blocks > 0 { - num_blocks_per_epoch.insert(*epoch, num_blocks); +impl ExpectedResult { + fn create_from( + result: Result, + marf_hash: TrieHash, + ) -> Self { + match result { + Ok(epoch_receipt) => { + let transactions: Vec = epoch_receipt + .tx_receipts + .into_iter() + .map(|r| { + let tx = match r.transaction { + TransactionOrigin::Stacks(tx) => Some(tx.payload), + TransactionOrigin::Burn(..) => None, + }; + ExpectedTransactionOutput { + tx, + return_type: r.result, + cost: r.execution_cost, + vm_error: r.vm_error, + } + }) + .collect(); + ExpectedResult::Success(ExpectedBlockOutput { + marf_hash, + evaluated_epoch: epoch_receipt.evaluated_epoch, + transactions, + total_block_cost: epoch_receipt.anchored_block_cost, + }) } - } - - Self { - tx_factory: TestTxFactory::new(CHAIN_ID_TESTNET), - consensus_test: ConsensusTest::new(test_name, initial_balances, num_blocks_per_epoch), - contract_addr: to_addr(&FAUCET_PRIV_KEY), - contract_deploys_per_epoch, - contract_calls_per_epoch, - contract_code: contract_code.to_string(), - function_name: function_name.to_string(), - function_args: function_args.to_vec(), - all_epochs, + Err(e) => ExpectedResult::Failure(e.to_string()), } } +} - /// Generates a transaction, appends it to a new test block, and executes the block. - /// - /// If the transaction succeeds, this function automatically increments the sender's - /// nonce for subsequent transactions. - /// - /// # Arguments - /// - /// - `tx_spec`: The transaction specification to generate and execute. - /// - `is_naka_block`: Whether this block is mined under Nakamoto consensus rules. - /// - /// # Returns - /// - /// The [`ExpectedResult`] of block execution (success/failure with VM output) - fn append_tx_block(&mut self, tx_spec: &TestTxSpec, is_naka_block: bool) -> ExpectedResult { - let tx = self.tx_factory.generate_tx(tx_spec); - let block = TestBlock { - transactions: vec![tx], - }; +/// Represents a block to be appended in a test and its expected result. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct TestBlock { + /// Transactions to include in the block + pub transactions: Vec, +} - let result = self.consensus_test.append_block(block, is_naka_block); +/// Represents a consensus test with chainstate. +pub struct ConsensusTest<'a> { + pub chain: TestChainstate<'a>, +} - if let ExpectedResult::Success(_) = result { - self.tx_factory.increase_nonce_for_tx(tx_spec); +impl ConsensusTest<'_> { + /// Creates a new `ConsensusTest` with the given test name, initial balances, and epoch blocks. + pub fn new( + test_name: &str, + initial_balances: Vec<(PrincipalData, u64)>, + num_blocks_per_epoch: HashMap, + ) -> Self { + // Validate blocks + for (epoch_id, num_blocks) in &num_blocks_per_epoch { + assert_ne!( + *epoch_id, + StacksEpochId::Epoch10, + "Epoch10 is not supported" + ); + assert!( + *num_blocks > 0, + "Each epoch must have at least one block. {epoch_id} is empty" + ); } - - result + // Set up chainstate to support Naka. + let mut boot_plan = NakamotoBootPlan::new(test_name) + .with_pox_constants(7, 1) + .with_initial_balances(initial_balances) + .with_private_key(FAUCET_PRIV_KEY.clone()); + let (epochs, first_burnchain_height) = + Self::calculate_epochs(&boot_plan.pox_constants, num_blocks_per_epoch); + boot_plan = boot_plan.with_epochs(epochs); + let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); + Self { chain } } - /// Deploys all contract versions scheduled for the given epoch. - /// - /// For each Clarity version supported in the epoch: - /// - Generates a unique contract name (e.g., `my-contract-Epoch30-Clarity3`) - /// - Deploys in a **separate block** - /// - Uses `None` for Clarity version in pre-2.1 epochs (behaviour defaults to Clarity 1) + /// Calculates a valid [`EpochList`] and starting burnchain height for the test harness. /// - /// # Returns - /// A vector of [`ExpectedResult`] values, one per deployment block. - fn deploy_contracts(&mut self, epoch: StacksEpochId) -> Vec { - let Some(contract_names) = self.contract_deploys_per_epoch.get(&epoch) else { - warn!("No contract deployments found for {epoch}."); - return vec![]; - }; - - let is_naka_block = epoch.uses_nakamoto_blocks(); - contract_names - .clone() - .iter() - .map(|(name, version)| { - let clarity_version = if epoch < StacksEpochId::Epoch21 { - // Old epochs have no concept of clarity version. It defaults to - // clarity version 1 behaviour. - None - } else { - Some(*version) - }; - self.append_tx_block( - &TestTxSpec::ContractDeploy { - sender: &FAUCET_PRIV_KEY, - name, - code: &self.contract_code.clone(), - clarity_version, - }, - is_naka_block, - ) - }) - .collect() - } - - /// Executes the test function on **all** contracts deployed in the given epoch. + /// The resulting EpochList satisfies the following: + /// - Each epoch has enough burnchain blocks to accommodate all test blocks. + /// - Epoch 2.5 → 3.0 transition satisfies the following constraints: + /// - 2.5 and 3.0 are in **different reward cycles**. + /// - 2.5 starts **before** the prepare phase of the cycle prior to 3.0 activation. + /// - 3.0 does not start on a reward cycle boundary. + /// - All epoch heights are contiguous and correctly ordered. /// - /// Each call occurs in a **separate block** to isolate side effects and enable - /// fine-grained snapshot assertions. All prior deployments (even from earlier epochs) - /// are callable if they exist in the chain state. + /// The resulting [`EpochList`] is used to initialize the test chainstate with correct + /// epoch boundaries, enabling accurate simulation of epoch transitions and consensus rules. /// /// # Arguments /// - /// - `epoch`: The epoch in which to perform contract calls. + /// * `pox_constants` - PoX configuration (reward cycle length, prepare phase, etc.). + /// * `num_blocks_per_epoch` - Map of epoch IDs to the number of test blocks to run in each. /// /// # Returns /// - /// A [`Vec`] with one entry per function call - fn call_contracts(&mut self, epoch: StacksEpochId) -> Vec { - let Some(contract_names) = self.contract_calls_per_epoch.get(&epoch) else { - warn!("No contract calls found for {epoch}."); - return vec![]; + /// `(EpochList, first_burnchain_height)` — the epoch list and the burnchain + /// height at which the first Stacks block is mined. + fn calculate_epochs( + pox_constants: &PoxConstants, + num_blocks_per_epoch: HashMap, + ) -> (EpochList, u64) { + // Helper function to check if a height is at a reward cycle boundary + let is_reward_cycle_boundary = |height: u64, reward_cycle_length: u64| -> bool { + height % reward_cycle_length <= 1 // Covers both 0 (end of cycle) and 1 (start of cycle) }; - let is_naka_block = epoch.uses_nakamoto_blocks(); - contract_names - .clone() - .iter() - .map(|contract_name| { - self.append_tx_block( - &TestTxSpec::ContractCall { - sender: &FAUCET_PRIV_KEY, - contract_addr: &self.contract_addr.clone(), - contract_name, - function_name: &self.function_name.clone(), - args: &self.function_args.clone(), - }, - is_naka_block, - ) - }) - .collect() - } - - /// Executes the full consensus test: deploy in [`Self::contract_deploys_per_epoch`], call in [`Self::contract_calls_per_epoch`]. - /// - /// Processes epochs in **sorted order** using [`Self::all_epochs`]. For each epoch: - /// - Advances the chain into the target epoch - /// - Deploys contracts (if scheduled) - /// - Executes function calls (if scheduled) - /// - /// # Execution Order Example - /// - /// Given at test instantiation: - /// ```rust - /// deploy_epochs = [Epoch20, Epoch30] - /// call_epochs = [Epoch30, Epoch31] - /// ``` - /// - /// The sequence is: - /// 1. Enter Epoch 2.0 → Deploy `contract-v1` - /// 2. Enter Epoch 3.0 → Deploy `contract-v1`, `contract-v2`, `contract-v3` - /// 3. Enter Epoch 3.0 → Call function on all 4 deployed contracts - /// 4. Enter Epoch 3.1 → Call function on all 4 deployed contracts - /// - /// # Returns - /// - /// A `Vec` with the outcome of each block for snapshot testing. - pub fn run(mut self) -> Vec { - let mut results = Vec::new(); - - // Process epochs in order - for epoch in self.all_epochs.clone() { - // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers - let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); - - // Advance the chain into the target epoch - self.consensus_test - .chain - .advance_into_epoch(&private_key, epoch); - - results.extend(self.deploy_contracts(epoch)); - results.extend(self.call_contracts(epoch)); - } + let first_burnchain_height = + (pox_constants.pox_4_activation_height + pox_constants.reward_cycle_length + 1) as u64; + info!("StacksEpoch calculate_epochs first_burn_height = {first_burnchain_height}"); + let reward_cycle_length = pox_constants.reward_cycle_length as u64; + let prepare_length = pox_constants.prepare_length as u64; + // Initialize heights + let mut epochs = vec![]; + let mut current_height = 0; + for epoch_id in StacksEpochId::ALL.iter() { + let start_height = current_height; + let end_height = match *epoch_id { + StacksEpochId::Epoch10 => first_burnchain_height, + StacksEpochId::Epoch20 + | StacksEpochId::Epoch2_05 + | StacksEpochId::Epoch21 + | StacksEpochId::Epoch22 + | StacksEpochId::Epoch23 + | StacksEpochId::Epoch24 => { + // Use test vector block count + // Always add 1 so we can ensure we are fully in the epoch before we then execute + // the corresponding test blocks in their own blocks + let num_blocks = num_blocks_per_epoch + .get(epoch_id) + .map(|num_blocks| *num_blocks + 1) + .unwrap_or(0); + start_height + num_blocks + } + StacksEpochId::Epoch25 => { + // Calculate Epoch 2.5 end height and Epoch 3.0 start height. + // Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0's activation. + // Epoch 2.5 end must equal Epoch 3.0 start + // Epoch 3.0 must not start at a cycle boundary + // Epoch 2.5 and 3.0 cannot be in the same reward cycle. + let num_blocks = num_blocks_per_epoch + .get(epoch_id) + .copied() + .unwrap_or(0) + .saturating_add(1); // Add one block for pox lockups. - results - } -} - -/// Generates a consensus test for executing a contract function across multiple Stacks epochs. -/// -/// This macro automates both contract deployment and function invocation across different -/// epochs and Clarity versions. -/// It simplifies the setup of consensus-critical tests involving versioned smart contracts. -/// -/// # Behavior -/// -/// - **Deployment:** Deploys `contract_code` in each epoch specified in `deploy_epochs` -/// for every applicable [`ClarityVersion`]. -/// - **Execution:** Calls `function_name` in each epoch from `call_epochs` on all previously -/// deployed contract instances. -/// - **Structure:** Each deployment and function call is executed in its own block, ensuring -/// clear separation between transactions. -/// -/// # Arguments -/// -/// * `$name` — Name of the generated test function. -/// * `contract_name` — The name of the contract. -/// * `contract_code` — The Clarity source code for the contract. -/// * `function_name` — The public function to call. -/// * `function_args` — Function arguments, provided as a slice of [`ClarityValue`]. -/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 2.0. -/// * `call_epochs` — *(optional)* Epochs in which to call the function. Defaults to [`EPOCHS_TO_TEST`]. -/// -/// # Example -/// -/// ```rust,ignore -/// contract_call_consensus_test!( -/// my_test, -/// contract_name: "my-contract", -/// contract_code: "(define-public (get-message) (ok \"hello\"))", -/// function_name: "get-message", -/// function_args: &[], -/// ); -/// ``` -macro_rules! contract_call_consensus_test { - ( - $name:ident, - contract_name: $contract_name:expr, - contract_code: $contract_code:expr, - function_name: $function_name:expr, - function_args: $function_args:expr, - $(deploy_epochs: $deploy_epochs:expr,)? - $(call_epochs: $call_epochs:expr,)? - ) => { - #[test] - fn $name() { - // Handle deploy_epochs parameter (default to all epochs >= 2.0 if not provided) - let deploy_epochs = &StacksEpochId::ALL[1..]; - $(let deploy_epochs = $deploy_epochs;)? - - // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) - let call_epochs = EPOCHS_TO_TEST; - $(let call_epochs = $call_epochs;)? - let contract_test = ContractConsensusTest::new( - function_name!(), - vec![], - deploy_epochs, - call_epochs, - $contract_name, - $contract_code, - $function_name, - $function_args, - ); - let result = contract_test.run(); - insta::assert_ron_snapshot!(result); - } - }; -} - -/// Generates a consensus test for contract deployment across multiple Stacks epochs. -/// -/// This macro automates deploying a contract across different Stacks epochs and -/// Clarity versions. It is primarily used for consensus-critical testing of contract -/// deployment behavior. -/// -/// # Behavior -/// -/// - **Deployment:** Deploys `contract_code` in each epoch specified by `deploy_epochs` -/// for all applicable [`ClarityVersion`]s. -/// - **Structure:** Each deployment is executed in its own block, ensuring clear -/// separation between transactions. -/// -/// # Arguments -/// -/// * `$name` — Name of the generated test function. -/// * `contract_name` — Name of the contract being tested. -/// * `contract_code` — The Clarity source code of the contract. -/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to [`EPOCHS_TO_TEST`]. -/// -/// # Example -/// -/// ```rust,ignore -/// contract_deploy_consensus_test!( -/// deploy_test, -/// contract_name: "my-contract", -/// contract_code: "(define-public (init) (ok true))", -/// ); -/// ``` -macro_rules! contract_deploy_consensus_test { - // Handle the case where deploy_epochs is not provided - ( - $name:ident, - contract_name: $contract_name:expr, - contract_code: $contract_code:expr, - ) => { - contract_deploy_consensus_test!( - $name, - contract_name: $contract_name, - contract_code: $contract_code, - deploy_epochs: EPOCHS_TO_TEST, - ); - }; - ( - $name:ident, - contract_name: $contract_name:expr, - contract_code: $contract_code:expr, - deploy_epochs: $deploy_epochs:expr, - ) => { - contract_call_consensus_test!( - $name, - contract_name: $contract_name, - contract_code: $contract_code, - function_name: "", // No function calls, just deploys - function_args: &[], // No function calls, just deploys - deploy_epochs: $deploy_epochs, - call_epochs: &[], // No function calls, just deploys - ); - }; -} - -/// The type of transaction to create. -pub enum TestTxSpec<'a> { - Transfer { - from: &'a StacksPrivateKey, - to: &'a PrincipalData, - amount: u64, - }, - ContractDeploy { - sender: &'a StacksPrivateKey, - name: &'a str, - code: &'a str, - clarity_version: Option, - }, - ContractCall { - sender: &'a StacksPrivateKey, - contract_addr: &'a StacksAddress, - contract_name: &'a str, - function_name: &'a str, - args: &'a [ClarityValue], - }, -} - -/// A helper to create transactions with incrementing nonces for each account. -pub struct TestTxFactory { - /// Map of address to next nonce - nonce_counter: HashMap, - /// The default chain ID to use for transactions - default_chain_id: u32, -} - -impl TestTxFactory { - /// Creates a new [`TransactionFactory`] with the specified default chain ID. - pub fn new(default_chain_id: u32) -> Self { - Self { - nonce_counter: HashMap::new(), - default_chain_id, - } - } - - /// Manually increments the nonce for the sender of the specified transaction. - /// - /// This method should be called *after* a transaction has been successfully - /// processed to ensure the factory uses the correct next nonce for subsequent - /// transactions from the same sender. - /// - /// # Arguments - /// - /// * `tx_spec` - The original specification of the transaction whose sender's - /// nonce should be incremented. - /// - /// # Panics - /// - /// Panics if the sender's address is not found in the nonce counter map. - pub fn increase_nonce_for_tx(&mut self, tx_spec: &TestTxSpec) { - let sender_privk = match tx_spec { - TestTxSpec::Transfer { from, .. } => from, - TestTxSpec::ContractDeploy { sender, .. } => sender, - TestTxSpec::ContractCall { sender, .. } => sender, - }; - let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender_privk)); - let nonce = self - .nonce_counter - .get_mut(&address) - .unwrap_or_else(|| panic!("Nonce not found for address {address}")); - *nonce += 1; - } - - /// Generates a new transaction of the specified type. - /// - /// Arguments: - /// - `tx_type`: The type of transaction to create. - /// - /// Returns: - /// A [`StacksTransaction`] representing the created transaction. - pub fn generate_tx(&mut self, tx_spec: &TestTxSpec) -> StacksTransaction { - match tx_spec { - TestTxSpec::Transfer { from, to, amount } => self.transfer(from, to, *amount), - TestTxSpec::ContractDeploy { - sender, - name, - code, - clarity_version, - } => self.contract_deploy(sender, name, code, *clarity_version), - TestTxSpec::ContractCall { - sender, - contract_addr, - contract_name, - function_name, - args, - } => self.contract_call(sender, contract_addr, contract_name, function_name, args), - } - } - - /// Create a STX transfer transaction. - /// - /// Arguments: - /// - `from`: The sender's private key. - /// - `to`: The recipient's principal data. - /// - `amount`: The amount of STX to transfer. - /// - /// Returns: - /// A [`StacksTransaction`] representing the transfer. - /// - /// Note: The transaction fee is set to 180 micro-STX. - pub fn transfer( - &mut self, - from: &StacksPrivateKey, - to: &PrincipalData, - amount: u64, - ) -> StacksTransaction { - let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(from)); - let nonce = self.nonce_counter.entry(address).or_insert(0); - make_stacks_transfer_tx(from, *nonce, 180, self.default_chain_id, to, amount) - } - - /// Create a contract deployment transaction. - /// - /// Arguments: - /// `sender`: The sender's private key. - /// `name`: The name of the contract. - /// `code`: The contract code as a string. - /// - /// Returns: - /// A [`StacksTransaction`] representing the contract deployment. - /// - /// Note: The transaction fee is set based on the contract code length. - pub fn contract_deploy( - &mut self, - sender: &StacksPrivateKey, - name: &str, - code: &str, - clarity_version: Option, - ) -> StacksTransaction { - let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender)); - let nonce = self.nonce_counter.entry(address).or_insert(0); - let tx_bytes = make_contract_publish_versioned( - sender, - *nonce, - (code.len() * 100) as u64, - self.default_chain_id, - name, - code, - clarity_version, - ); - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap() - } - - /// Create a contract call transaction. - /// - /// Arguments: - /// `sender`: The sender's private key. - /// `contract_addr`: The address of the contract. - /// `contract_name`: The name of the contract. - /// `function_name`: The name of the function to call. - /// `args`: The arguments to pass to the function. - /// - /// Returns: - /// A [`StacksTransaction`] representing the contract call. - /// - /// Note: The transaction fee is set to 200 micro-STX. - pub fn contract_call( - &mut self, - sender: &StacksPrivateKey, - contract_addr: &StacksAddress, - contract_name: &str, - function_name: &str, - args: &[ClarityValue], - ) -> StacksTransaction { - let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender)); - let nonce = self.nonce_counter.entry(address).or_insert(0); - let tx_bytes = make_contract_call( - sender, - *nonce, - 200, - self.default_chain_id, - contract_addr, - contract_name, - function_name, - args, - ); - StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap() - } -} - -/// Custom serializer for `Option` to improve snapshot readability. -/// This avoids large diffs in snapshots due to code body changes and focuses on key fields. -fn serialize_opt_tx_payload( - value: &Option, - serializer: S, -) -> Result -where - S: Serializer, -{ - let changed = match value { - None => "BitcoinTx".to_string(), - Some(TransactionPayload::TokenTransfer(sender, amount, memo)) => { - format!("TokenTransfer(from: {sender}, amount: {amount}, memo: {memo})") - } - Some(TransactionPayload::SmartContract( - TransactionSmartContract { name, code_body }, - clarity_version, - )) => { - format!("SmartContract(name: {name}, code_body: [..], clarity_version: {clarity_version:?})") - } - Some(TransactionPayload::ContractCall(TransactionContractCall { - address, - contract_name, - function_name, - function_args, - })) => { - format!("ContractCall(address: {address}, contract_name: {contract_name}, function_name: {function_name}, function_args: [{function_args:?}])") - } - Some(payload) => { - format!("{payload:?}") - } - }; - serializer.serialize_str(&changed) -} - -/// Serialize an optional string field appending a non-consensus breaking info message. -fn serialize_opt_string_ncb(value: &Option, serializer: S) -> Result -where - S: Serializer, -{ - let original = match value.as_deref() { - Some(str) => format!("Some({str})"), - None => "None".to_string(), - }; - let changed = format!("{original} [NON-CONSENSUS BREAKING]"); - serializer.serialize_str(&changed) -} - -/// Represents the expected output of a transaction in a test. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct ExpectedTransactionOutput { - /// The transaction that was executed. - /// `None` for bitcoin transactions. - #[serde(serialize_with = "serialize_opt_tx_payload")] - pub tx: Option, - /// The possible Clarity VM error message associated to the transaction (non-consensus breaking) - #[serde(serialize_with = "serialize_opt_string_ncb")] - pub vm_error: Option, - /// The expected return value of the transaction. - pub return_type: ClarityValue, - /// The expected execution cost of the transaction. - pub cost: ExecutionCost, -} - -/// Represents the expected outputs for a block's execution. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct ExpectedBlockOutput { - /// The expected block marf - pub marf_hash: TrieHash, - /// The epoch in which the test block was expected to be evaluated - pub evaluated_epoch: StacksEpochId, - /// The expected outputs for each transaction, in input order. - pub transactions: Vec, - /// The total execution cost of the block. - pub total_block_cost: ExecutionCost, -} - -/// Represents the expected result of a consensus test. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub enum ExpectedResult { - /// The test should succeed with the specified outputs. - Success(ExpectedBlockOutput), - /// The test should fail with an error matching the specified string - /// Cannot match on the exact Error directly as they do not implement - /// Serialize/Deserialize or PartialEq - Failure(String), -} - -impl ExpectedResult { - fn create_from( - result: Result, - marf_hash: TrieHash, - ) -> Self { - match result { - Ok(epoch_receipt) => { - let transactions: Vec = epoch_receipt - .tx_receipts - .into_iter() - .map(|r| { - let tx = match r.transaction { - TransactionOrigin::Stacks(tx) => Some(tx.payload), - TransactionOrigin::Burn(..) => None, - }; - ExpectedTransactionOutput { - tx, - return_type: r.result, - cost: r.execution_cost, - vm_error: r.vm_error, - } - }) - .collect(); - ExpectedResult::Success(ExpectedBlockOutput { - marf_hash, - evaluated_epoch: epoch_receipt.evaluated_epoch, - transactions, - total_block_cost: epoch_receipt.anchored_block_cost, - }) - } - Err(e) => ExpectedResult::Failure(e.to_string()), - } - } -} - -/// Represents a block to be appended in a test and its expected result. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct TestBlock { - /// Transactions to include in the block - pub transactions: Vec, -} - -/// Represents a consensus test with chainstate. -pub struct ConsensusTest<'a> { - pub chain: TestChainstate<'a>, -} - -impl ConsensusTest<'_> { - /// Creates a new `ConsensusTest` with the given test name, initial balances, and epoch blocks. - pub fn new( - test_name: &str, - initial_balances: Vec<(PrincipalData, u64)>, - num_blocks_per_epoch: HashMap, - ) -> Self { - // Validate blocks - for (epoch_id, num_blocks) in &num_blocks_per_epoch { - assert_ne!( - *epoch_id, - StacksEpochId::Epoch10, - "Epoch10 is not supported" - ); - assert!( - *num_blocks > 0, - "Each epoch must have at least one block. {epoch_id} is empty" - ); - } - // Set up chainstate to support Naka. - let mut boot_plan = NakamotoBootPlan::new(test_name) - .with_pox_constants(7, 1) - .with_initial_balances(initial_balances) - .with_private_key(FAUCET_PRIV_KEY.clone()); - let (epochs, first_burnchain_height) = - Self::calculate_epochs(&boot_plan.pox_constants, num_blocks_per_epoch); - boot_plan = boot_plan.with_epochs(epochs); - let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); - Self { chain } - } - - /// Calculates a valid [`EpochList`] and starting burnchain height for the test harness. - /// - /// The resulting EpochList satisfies the following: - /// - Each epoch has enough burnchain blocks to accommodate all test blocks. - /// - Epoch 2.5 → 3.0 transition satisfies the following constraints: - /// - 2.5 and 3.0 are in **different reward cycles**. - /// - 2.5 starts **before** the prepare phase of the cycle prior to 3.0 activation. - /// - 3.0 does not start on a reward cycle boundary. - /// - All epoch heights are contiguous and correctly ordered. - /// - /// The resulting [`EpochList`] is used to initialize the test chainstate with correct - /// epoch boundaries, enabling accurate simulation of epoch transitions and consensus rules. - /// - /// # Arguments - /// - /// * `pox_constants` - PoX configuration (reward cycle length, prepare phase, etc.). - /// * `num_blocks_per_epoch` - Map of epoch IDs to the number of test blocks to run in each. - /// - /// # Returns - /// - /// `(EpochList, first_burnchain_height)` — the epoch list and the burnchain - /// height at which the first Stacks block is mined. - fn calculate_epochs( - pox_constants: &PoxConstants, - num_blocks_per_epoch: HashMap, - ) -> (EpochList, u64) { - // Helper function to check if a height is at a reward cycle boundary - let is_reward_cycle_boundary = |height: u64, reward_cycle_length: u64| -> bool { - height % reward_cycle_length <= 1 // Covers both 0 (end of cycle) and 1 (start of cycle) - }; - - let first_burnchain_height = - (pox_constants.pox_4_activation_height + pox_constants.reward_cycle_length + 1) as u64; - info!("StacksEpoch calculate_epochs first_burn_height = {first_burnchain_height}"); - let reward_cycle_length = pox_constants.reward_cycle_length as u64; - let prepare_length = pox_constants.prepare_length as u64; - // Initialize heights - let mut epochs = vec![]; - let mut current_height = 0; - for epoch_id in StacksEpochId::ALL.iter() { - let start_height = current_height; - let end_height = match *epoch_id { - StacksEpochId::Epoch10 => first_burnchain_height, - StacksEpochId::Epoch20 - | StacksEpochId::Epoch2_05 - | StacksEpochId::Epoch21 - | StacksEpochId::Epoch22 - | StacksEpochId::Epoch23 - | StacksEpochId::Epoch24 => { - // Use test vector block count - // Always add 1 so we can ensure we are fully in the epoch before we then execute - // the corresponding test blocks in their own blocks - let num_blocks = num_blocks_per_epoch - .get(epoch_id) - .map(|num_blocks| *num_blocks + 1) - .unwrap_or(0); - start_height + num_blocks - } - StacksEpochId::Epoch25 => { - // Calculate Epoch 2.5 end height and Epoch 3.0 start height. - // Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0's activation. - // Epoch 2.5 end must equal Epoch 3.0 start - // Epoch 3.0 must not start at a cycle boundary - // Epoch 2.5 and 3.0 cannot be in the same reward cycle. - let num_blocks = num_blocks_per_epoch - .get(epoch_id) - .copied() - .unwrap_or(0) - .saturating_add(1); // Add one block for pox lockups. - - let epoch_25_start = current_height; - let epoch_30_start = epoch_25_start + num_blocks; + let epoch_25_start = current_height; + let epoch_30_start = epoch_25_start + num_blocks; let epoch_25_reward_cycle = epoch_25_start / reward_cycle_length; let mut epoch_30_start = epoch_30_start; @@ -1014,366 +399,981 @@ impl ConsensusTest<'_> { }); current_height = end_height; } - // Validate Epoch 2.5 and 3.0 constraints - if let Some(epoch_3_0) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { - let epoch_2_5 = epochs - .iter() - .find(|e| e.epoch_id == StacksEpochId::Epoch25) - .expect("Epoch 2.5 not found"); - let epoch_2_5_start = epoch_2_5.start_height; - let epoch_3_0_start = epoch_3_0.start_height; - let epoch_2_5_end = epoch_2_5.end_height; + // Validate Epoch 2.5 and 3.0 constraints + if let Some(epoch_3_0) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { + let epoch_2_5 = epochs + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch25) + .expect("Epoch 2.5 not found"); + let epoch_2_5_start = epoch_2_5.start_height; + let epoch_3_0_start = epoch_3_0.start_height; + let epoch_2_5_end = epoch_2_5.end_height; + + let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; + let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; + let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); + let epoch_3_0_prepare_phase = + prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); + assert!( + epoch_2_5_start < epoch_3_0_prepare_phase, + "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" + ); + assert_eq!( + epoch_2_5_end, epoch_3_0.start_height, + "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" + ); + assert_ne!( + epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, + "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" + ); + assert!( + !is_reward_cycle_boundary(epoch_3_0_start, reward_cycle_length), + "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" + ); + } + // Validate test vector block counts + for (epoch_id, num_blocks) in num_blocks_per_epoch { + let epoch = epochs + .iter() + .find(|e| e.epoch_id == epoch_id) + .expect("Epoch not found"); + let epoch_length = epoch.end_height - epoch.start_height; + if epoch_id > StacksEpochId::Epoch25 { + assert!( + epoch_length > 0, + "{epoch_id:?} must have at least 1 burn block." + ); + } else { + assert!( + epoch_length >= num_blocks, + "{epoch_id:?} must have at least {num_blocks} burn blocks, got {epoch_length}" + ); + } + } + let epoch_list = EpochList::new(&epochs); + info!("Calculated EpochList from pox constants with first burnchain height of {first_burnchain_height}."; + "epochs" => ?epoch_list, + "first_burnchain_height" => first_burnchain_height + ); + (epoch_list, first_burnchain_height) + } + + /// Appends a single block to the chain as a Nakamoto block and returns the result. + /// + /// This method takes a [`TestBlock`] containing a list of transactions, constructs + /// a fully valid [`NakamotoBlock`], processes it against the current chainstate. + /// + /// # Arguments + /// + /// * `block` - The test block to be processed and appended to the chain. + /// + /// # Returns + /// + /// A [`ExpectedResult`] with the outcome of the block processing. + fn append_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { + debug!("--------- Running block {block:?} ---------"); + let (nakamoto_block, _block_size) = self.construct_nakamoto_block(block); + let mut sortdb = self.chain.sortdb.take().unwrap(); + let mut stacks_node = self.chain.stacks_node.take().unwrap(); + let chain_tip = + NakamotoChainState::get_canonical_block_header(stacks_node.chainstate.db(), &sortdb) + .unwrap() + .unwrap(); + let sig_hash = nakamoto_block.header.signer_signature_hash(); + debug!( + "--------- Processing block {sig_hash} ---------"; + "block" => ?nakamoto_block + ); + let expected_marf = nakamoto_block.header.state_index_root; + let res = TestStacksNode::process_pushed_next_ready_block( + &mut stacks_node, + &mut sortdb, + &mut self.chain.miner, + &chain_tip.consensus_hash, + &mut self.chain.coord, + nakamoto_block.clone(), + ); + debug!( + "--------- Processed block: {sig_hash} ---------"; + "block" => ?nakamoto_block + ); + let remapped_result = res.map(|receipt| receipt.unwrap()); + // Restore chainstate for the next block + self.chain.sortdb = Some(sortdb); + self.chain.stacks_node = Some(stacks_node); + ExpectedResult::create_from(remapped_result, expected_marf) + } + + /// Appends a single block to the chain as a Pre-Nakamoto block and returns the result. + /// + /// This method takes a [`TestBlock`] containing a list of transactions, constructs + /// a fully valid [`StacksBlock`], processes it against the current chainstate. + /// + /// # Arguments + /// + /// * `block` - The test block to be processed and appended to the chain. + /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// + /// # Returns + /// + /// A [`ExpectedResult`] with the outcome of the block processing. + fn append_pre_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { + debug!("--------- Running Pre-Nakamoto block {block:?} ---------"); + let (ch, bh) = + SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) + .unwrap(); + let tip_id = StacksBlockId::new(&ch, &bh); + let (burn_ops, stacks_block, microblocks) = self + .chain + .make_pre_nakamoto_tenure_with_txs(&block.transactions); + let (_, _, consensus_hash) = self.chain.next_burnchain_block(burn_ops); + + debug!( + "--------- Processing Pre-Nakamoto block ---------"; + "block" => ?stacks_block + ); + + let mut stacks_node = self.chain.stacks_node.take().unwrap(); + let mut sortdb = self.chain.sortdb.take().unwrap(); + let expected_marf = stacks_block.header.state_index_root; + let res = TestStacksNode::process_pre_nakamoto_next_ready_block( + &mut stacks_node, + &mut sortdb, + &mut self.chain.miner, + &ch, + &mut self.chain.coord, + &stacks_block, + µblocks, + ); + debug!( + "--------- Processed Pre-Nakamoto block ---------"; + "block" => ?stacks_block + ); + let remapped_result = res.map(|receipt| { + let mut receipt = receipt.unwrap(); + let mut sanitized_receipts = vec![]; + for tx_receipt in &receipt.tx_receipts { + // Remove any coinbase transactions from the output + if tx_receipt.is_coinbase_tx() { + continue; + } + sanitized_receipts.push(tx_receipt.clone()); + } + receipt.tx_receipts = sanitized_receipts; + receipt + }); + // Restore chainstate for the next block + self.chain.sortdb = Some(sortdb); + self.chain.stacks_node = Some(stacks_node); + ExpectedResult::create_from(remapped_result, expected_marf) + } + + /// Appends a single block to the chain and returns the result. + /// + /// This method takes a [`TestBlock`] containing a list of transactions, whether the epoch [`is_naka_epoch`] , + /// constructing a fully valid [`StacksBlock`] or [`NakamotoBlock`] accordingly, processes it against the current chainstate. + /// + /// # Arguments + /// + /// * `block` - The test block to be processed and appended to the chain. + /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// + /// # Returns + /// + /// A [`ExpectedResult`] with the outcome of the block processing. + pub fn append_block(&mut self, block: TestBlock, is_naka_epoch: bool) -> ExpectedResult { + if is_naka_epoch { + self.append_nakamoto_block(block) + } else { + self.append_pre_nakamoto_block(block) + } + } - let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; - let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; - let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); - let epoch_3_0_prepare_phase = - prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); - assert!( - epoch_2_5_start < epoch_3_0_prepare_phase, - "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" - ); - assert_eq!( - epoch_2_5_end, epoch_3_0.start_height, - "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" - ); - assert_ne!( - epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, - "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" - ); - assert!( - !is_reward_cycle_boundary(epoch_3_0_start, reward_cycle_length), - "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" + /// Executes a full test plan by processing blocks across multiple epochs. + /// + /// This function serves as the primary test runner. It iterates through the + /// provided epochs in chronological order, automatically advancing the + /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s + /// associated with that epoch and collects their results. + /// + /// # Arguments + /// + /// * `epoch_blocks` - A map where keys are [`StacksEpochId`]s and values are the + /// sequence of blocks to be executed during that epoch. + /// + /// # Returns + /// + /// A `Vec` with the outcome of each block for snapshot testing. + pub fn run( + mut self, + epoch_blocks: HashMap>, + ) -> Vec { + let mut sorted_epochs: Vec<_> = epoch_blocks.clone().into_iter().collect(); + sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); + + let mut results = vec![]; + + for (epoch, blocks) in sorted_epochs { + debug!( + "--------- Processing epoch {epoch:?} with {} blocks ---------", + blocks.len() ); - } - // Validate test vector block counts - for (epoch_id, num_blocks) in num_blocks_per_epoch { - let epoch = epochs - .iter() - .find(|e| e.epoch_id == epoch_id) - .expect("Epoch not found"); - let epoch_length = epoch.end_height - epoch.start_height; - if epoch_id > StacksEpochId::Epoch25 { - assert!( - epoch_length > 0, - "{epoch_id:?} must have at least 1 burn block." - ); - } else { - assert!( - epoch_length >= num_blocks, - "{epoch_id:?} must have at least {num_blocks} burn blocks, got {epoch_length}" - ); + // Use the miner key to prevent messing with FAUCET nonces. + let miner_key = self.chain.miner.nakamoto_miner_key(); + self.chain.advance_into_epoch(&miner_key, epoch); + + for block in blocks { + results.push(self.append_block(block, epoch.uses_nakamoto_blocks())); } } - let epoch_list = EpochList::new(&epochs); - info!("Calculated EpochList from pox constants with first burnchain height of {first_burnchain_height}."; - "epochs" => ?epoch_list, - "first_burnchain_height" => first_burnchain_height + results + } + + /// Constructs a Nakamoto block with the given [`TestBlock`] configuration. + fn construct_nakamoto_block(&mut self, test_block: TestBlock) -> (NakamotoBlock, usize) { + let chain_tip = NakamotoChainState::get_canonical_block_header( + self.chain.stacks_node.as_ref().unwrap().chainstate.db(), + self.chain.sortdb.as_ref().unwrap(), + ) + .unwrap() + .unwrap(); + let cycle = self.chain.get_reward_cycle(); + let burn_spent = SortitionDB::get_block_snapshot_consensus( + self.chain.sortdb_ref().conn(), + &chain_tip.consensus_hash, + ) + .unwrap() + .map(|sn| sn.total_burn) + .unwrap(); + let mut block = NakamotoBlock { + header: NakamotoBlockHeader { + version: 1, + chain_length: chain_tip.stacks_block_height + 1, + burn_spent, + consensus_hash: chain_tip.consensus_hash.clone(), + parent_block_id: chain_tip.index_block_hash(), + tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), + state_index_root: TrieHash::from_empty_data(), + timestamp: 1, + miner_signature: MessageSignature::empty(), + signer_signature: vec![], + pox_treatment: BitVec::ones(1).unwrap(), + }, + txs: test_block.transactions, + }; + + let tx_merkle_root = { + let txid_vecs: Vec<_> = block + .txs + .iter() + .map(|tx| tx.txid().as_bytes().to_vec()) + .collect(); + MerkleTree::::new(&txid_vecs).root() + }; + block.header.tx_merkle_root = tx_merkle_root; + + // Set the MARF root hash or use an all-zero hash in case of failure. + // NOTE: It is expected to fail when trying computing the marf for invalid block/transactions. + let marf_result = self.compute_block_marf_root_hash(block.header.timestamp, &block.txs); + block.header.state_index_root = match marf_result { + Ok(marf) => marf, + Err(_) => TrieHash::from_bytes(&[0; 32]).unwrap(), + }; + + self.chain.miner.sign_nakamoto_block(&mut block); + let mut signers = self.chain.config.test_signers.clone().unwrap_or_default(); + signers.sign_nakamoto_block(&mut block, cycle); + let block_len = block.serialize_to_vec().len(); + (block, block_len) + } + + /// Computes the MARF root hash for a block. + /// + /// This function is intended for use in success test cases only, where all + /// transactions are valid. In other scenarios, the computation may fail. + /// + /// The implementation is deliberately minimal: it does not cover every + /// possible situation (such as new tenure handling), but it should be + /// sufficient for the scope of our test cases. + fn compute_block_marf_root_hash( + &mut self, + block_time: u64, + block_txs: &[StacksTransaction], + ) -> Result { + let node = self.chain.stacks_node.as_mut().unwrap(); + let sortdb = self.chain.sortdb.as_ref().unwrap(); + let burndb_conn = sortdb.index_handle_at_tip(); + let chainstate = &mut node.chainstate; + + let chain_tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb) + .unwrap() + .unwrap(); + + let (chainstate_tx, clarity_instance) = chainstate.chainstate_tx_begin().unwrap(); + let burndb_conn = sortdb.index_handle_at_tip(); + + let mut clarity_tx = StacksChainState::chainstate_block_begin( + &chainstate_tx, + clarity_instance, + &burndb_conn, + &chain_tip.consensus_hash, + &chain_tip.anchored_header.block_hash(), + &MINER_BLOCK_CONSENSUS_HASH, + &MINER_BLOCK_HEADER_HASH, ); - (epoch_list, first_burnchain_height) + let result = Self::inner_compute_block_marf_root_hash( + &mut clarity_tx, + block_time, + block_txs, + chain_tip.burn_header_height, + ); + clarity_tx.rollback_block(); + result + } + + /// This is where the real MARF computation happens. + /// It is extrapolated into an _inner_ method to simplify rollback handling, + /// ensuring that rollback can be applied consistently on both success and failure + /// in the _outer_ method. + fn inner_compute_block_marf_root_hash( + clarity_tx: &mut ClarityTx, + block_time: u64, + block_txs: &[StacksTransaction], + burn_header_height: u32, + ) -> Result { + clarity_tx + .connection() + .as_free_transaction(|clarity_tx_conn| { + clarity_tx_conn.with_clarity_db(|db| { + db.setup_block_metadata(Some(block_time))?; + Ok(()) + }) + }) + .map_err(|e| e.to_string())?; + + StacksChainState::process_block_transactions(clarity_tx, block_txs, 0) + .map_err(|e| e.to_string())?; + + NakamotoChainState::finish_block(clarity_tx, None, false, burn_header_height) + .map_err(|e| e.to_string())?; + + Ok(clarity_tx.seal()) } +} + +/// A high-level test harness for running consensus-critical smart contract tests. +/// +/// This struct enables end-to-end testing of Clarity smart contracts under varying epoch conditions, +/// including different Clarity language versions and block rule sets. It automates: +/// +/// - Contract deployment in specified epochs (with epoch-appropriate Clarity versions) +/// - Function execution in subsequent or same epochs +/// - Block-by-block execution with precise control over transaction ordering and nonces +/// - Snapshot testing of execution outcomes via [`ExpectedResult`] +/// +/// It integrates: +/// - [`ConsensusTest`] for chain simulation and block production +/// - [`TestTxFactory`] for deterministic transaction generation +/// +/// NOTE: The **majority of logic and state computation occurs during construction to enable a deterministic TestChainstate** (`new()`): +/// - All contract names are generated and versioned +/// - Block counts per epoch are precomputed +/// - Epoch order is finalized +/// - Transaction sequencing is fully planned +struct ContractConsensusTest<'a> { + /// Factory for generating signed, nonce-managed transactions. + tx_factory: TestTxFactory, + /// Underlying chainstate used for block execution and consensus checks. + consensus_test: ConsensusTest<'a>, + /// Address of the contract deployer (the test faucet). + contract_addr: StacksAddress, + /// Mapping of epoch → list of `(contract_name, ClarityVersion)` deployed in that epoch. + /// Multiple versions may exist per epoch (e.g., Clarity 1, 2, 3 in Epoch 3.0). + contract_deploys_per_epoch: HashMap>, + /// Mapping of epoch → list of `contract_names` that should be called in that epoch. + contract_calls_per_epoch: HashMap>, + /// Source code of the Clarity contract being deployed and called. + contract_code: String, + /// Name of the public function to invoke during the call phase. + function_name: String, + /// Arguments to pass to `function_name` on every call. + function_args: Vec, + /// Sorted, deduplicated set of all epochs involved. + /// Used to iterate through test phases in chronological order. + all_epochs: BTreeSet, +} - /// Appends a single block to the chain as a Nakamoto block and returns the result. +impl ContractConsensusTest<'_> { + /// Creates a new [`ContractConsensusTest`] instance. /// - /// This method takes a [`TestBlock`] containing a list of transactions, constructs - /// a fully valid [`NakamotoBlock`], processes it against the current chainstate. + /// Initializes the test environment to: + /// - Deploy `contract_code` under `contract_name` in each `deploy_epochs` + /// - Call `function_name` with `function_args` in each `call_epochs` + /// - Track all contract instances per epoch and Clarity version + /// - Precompute block counts per epoch for stable chain simulation /// /// # Arguments /// - /// * `block` - The test block to be processed and appended to the chain. + /// * `test_name` - Unique identifier for the test run (used in logging and snapshots) + /// * `initial_balances` - Initial STX balances for principals (e.g., faucet, users) + /// * `deploy_epochs` - List of epochs where contract deployment should occur + /// * `call_epochs` - List of epochs where function calls should be executed + /// * `contract_name` - Base name for deployed contracts (versioned suffixes added automatically) + /// * `contract_code` - Clarity source code of the contract + /// * `function_name` - Contract function to test + /// * `function_args` - Arguments passed to `function_name` on every call /// - /// # Returns + /// # Panics /// - /// A [`ExpectedResult`] with the outcome of the block processing. - fn append_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { - debug!("--------- Running block {block:?} ---------"); - let (nakamoto_block, _block_size) = self.construct_nakamoto_block(block); - let mut sortdb = self.chain.sortdb.take().unwrap(); - let mut stacks_node = self.chain.stacks_node.take().unwrap(); - let chain_tip = - NakamotoChainState::get_canonical_block_header(stacks_node.chainstate.db(), &sortdb) - .unwrap() - .unwrap(); - let sig_hash = nakamoto_block.header.signer_signature_hash(); - debug!( - "--------- Processing block {sig_hash} ---------"; - "block" => ?nakamoto_block - ); - let expected_marf = nakamoto_block.header.state_index_root; - let res = TestStacksNode::process_pushed_next_ready_block( - &mut stacks_node, - &mut sortdb, - &mut self.chain.miner, - &chain_tip.consensus_hash, - &mut self.chain.coord, - nakamoto_block.clone(), + /// - If `deploy_epochs` is empty. + /// - If any `call_epoch` is less than the minimum `deploy_epoch`. + #[allow(clippy::too_many_arguments)] + pub fn new( + test_name: &str, + initial_balances: Vec<(PrincipalData, u64)>, + deploy_epochs: &[StacksEpochId], + call_epochs: &[StacksEpochId], + contract_name: &str, + contract_code: &str, + function_name: &str, + function_args: &[ClarityValue], + ) -> Self { + assert!( + !deploy_epochs.is_empty(), + "At least one deploy epoch is required" ); - debug!( - "--------- Processed block: {sig_hash} ---------"; - "block" => ?nakamoto_block + let min_deploy_epoch = deploy_epochs.iter().min().unwrap(); + assert!( + call_epochs.iter().all(|e| e >= min_deploy_epoch), + "All call epochs must be >= the minimum deploy epoch" ); - let remapped_result = res.map(|receipt| receipt.unwrap()); - // Restore chainstate for the next block - self.chain.sortdb = Some(sortdb); - self.chain.stacks_node = Some(stacks_node); - ExpectedResult::create_from(remapped_result, expected_marf) + + // Build epoch_blocks map based on deploy and call epochs + let mut num_blocks_per_epoch: HashMap = HashMap::new(); + let mut contract_deploys_per_epoch: HashMap> = + HashMap::new(); + let mut contract_calls_per_epoch: HashMap> = HashMap::new(); + let mut contract_names = vec![]; + + // Combine and sort unique epochs + let all_epochs: BTreeSet = + deploy_epochs.iter().chain(call_epochs).cloned().collect(); + + // Precompute contract names and block counts + for epoch in &all_epochs { + let mut num_blocks = 0; + + if deploy_epochs.contains(epoch) { + let clarity_versions = clarity_versions_for_epoch(*epoch); + let epoch_name = format!("Epoch{}", epoch.to_string().replace('.', "_")); + + // Each deployment is a seperate TestBlock + for &version in clarity_versions { + let version_tag = version.to_string().replace(' ', ""); + let name = format!("{contract_name}-{epoch_name}-{version_tag}"); + contract_deploys_per_epoch + .entry(*epoch) + .or_default() + .push((name.clone(), version)); + contract_names.push(name.clone()); + num_blocks += 1; + } + } + + if call_epochs.contains(epoch) { + // Each call is a separate TestBlock + for name in &contract_names { + // Each call is a separate TestBlock + contract_calls_per_epoch + .entry(*epoch) + .or_default() + .push(name.clone()); + num_blocks += 1; + } + } + if num_blocks > 0 { + num_blocks_per_epoch.insert(*epoch, num_blocks); + } + } + + Self { + tx_factory: TestTxFactory::new(CHAIN_ID_TESTNET), + consensus_test: ConsensusTest::new(test_name, initial_balances, num_blocks_per_epoch), + contract_addr: to_addr(&FAUCET_PRIV_KEY), + contract_deploys_per_epoch, + contract_calls_per_epoch, + contract_code: contract_code.to_string(), + function_name: function_name.to_string(), + function_args: function_args.to_vec(), + all_epochs, + } } - /// Appends a single block to the chain as a Pre-Nakamoto block and returns the result. + /// Generates a transaction, appends it to a new test block, and executes the block. /// - /// This method takes a [`TestBlock`] containing a list of transactions, constructs - /// a fully valid [`StacksBlock`], processes it against the current chainstate. + /// If the transaction succeeds, this function automatically increments the sender's + /// nonce for subsequent transactions. /// /// # Arguments /// - /// * `block` - The test block to be processed and appended to the chain. - /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// - `tx_spec`: The transaction specification to generate and execute. + /// - `is_naka_block`: Whether this block is mined under Nakamoto consensus rules. /// /// # Returns /// - /// A [`ExpectedResult`] with the outcome of the block processing. - fn append_pre_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { - debug!("--------- Running Pre-Nakamoto block {block:?} ---------"); - let (ch, bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) - .unwrap(); - let tip_id = StacksBlockId::new(&ch, &bh); - let (burn_ops, stacks_block, microblocks) = self - .chain - .make_pre_nakamoto_tenure_with_txs(&block.transactions); - let (_, _, consensus_hash) = self.chain.next_burnchain_block(burn_ops); + /// The [`ExpectedResult`] of block execution (success/failure with VM output) + fn append_tx_block(&mut self, tx_spec: &TestTxSpec, is_naka_block: bool) -> ExpectedResult { + let tx = self.tx_factory.generate_tx(tx_spec); + let block = TestBlock { + transactions: vec![tx], + }; - debug!( - "--------- Processing Pre-Nakamoto block ---------"; - "block" => ?stacks_block - ); + let result = self.consensus_test.append_block(block, is_naka_block); - let mut stacks_node = self.chain.stacks_node.take().unwrap(); - let mut sortdb = self.chain.sortdb.take().unwrap(); - let expected_marf = stacks_block.header.state_index_root; - let res = TestStacksNode::process_pre_nakamoto_next_ready_block( - &mut stacks_node, - &mut sortdb, - &mut self.chain.miner, - &ch, - &mut self.chain.coord, - &stacks_block, - µblocks, - ); - debug!( - "--------- Processed Pre-Nakamoto block ---------"; - "block" => ?stacks_block - ); - let remapped_result = res.map(|receipt| { - let mut receipt = receipt.unwrap(); - let mut sanitized_receipts = vec![]; - for tx_receipt in &receipt.tx_receipts { - // Remove any coinbase transactions from the output - if tx_receipt.is_coinbase_tx() { - continue; - } - sanitized_receipts.push(tx_receipt.clone()); - } - receipt.tx_receipts = sanitized_receipts; - receipt - }); - // Restore chainstate for the next block - self.chain.sortdb = Some(sortdb); - self.chain.stacks_node = Some(stacks_node); - ExpectedResult::create_from(remapped_result, expected_marf) + if let ExpectedResult::Success(_) = result { + self.tx_factory.increase_nonce_for_tx(tx_spec); + } + + result } - /// Appends a single block to the chain and returns the result. + /// Deploys all contract versions scheduled for the given epoch. + /// + /// For each Clarity version supported in the epoch: + /// - Generates a unique contract name (e.g., `my-contract-Epoch30-Clarity3`) + /// - Deploys in a **separate block** + /// - Uses `None` for Clarity version in pre-2.1 epochs (behaviour defaults to Clarity 1) + /// + /// # Returns + /// A vector of [`ExpectedResult`] values, one per deployment block. + fn deploy_contracts(&mut self, epoch: StacksEpochId) -> Vec { + let Some(contract_names) = self.contract_deploys_per_epoch.get(&epoch) else { + warn!("No contract deployments found for {epoch}."); + return vec![]; + }; + + let is_naka_block = epoch.uses_nakamoto_blocks(); + contract_names + .clone() + .iter() + .map(|(name, version)| { + let clarity_version = if epoch < StacksEpochId::Epoch21 { + // Old epochs have no concept of clarity version. It defaults to + // clarity version 1 behaviour. + None + } else { + Some(*version) + }; + self.append_tx_block( + &TestTxSpec::ContractDeploy { + sender: &FAUCET_PRIV_KEY, + name, + code: &self.contract_code.clone(), + clarity_version, + }, + is_naka_block, + ) + }) + .collect() + } + + /// Executes the test function on **all** contracts deployed in the given epoch. /// - /// This method takes a [`TestBlock`] containing a list of transactions, whether the epoch [`is_naka_epoch`] , - /// constructing a fully valid [`StacksBlock`] or [`NakamotoBlock`] accordingly, processes it against the current chainstate. + /// Each call occurs in a **separate block** to isolate side effects and enable + /// fine-grained snapshot assertions. All prior deployments (even from earlier epochs) + /// are callable if they exist in the chain state. /// /// # Arguments /// - /// * `block` - The test block to be processed and appended to the chain. - /// * `coinbase_nonce` - The coinbase nonce to use and increment + /// - `epoch`: The epoch in which to perform contract calls. /// /// # Returns /// - /// A [`ExpectedResult`] with the outcome of the block processing. - pub fn append_block(&mut self, block: TestBlock, is_naka_epoch: bool) -> ExpectedResult { - if is_naka_epoch { - self.append_nakamoto_block(block) - } else { - self.append_pre_nakamoto_block(block) - } + /// A [`Vec`] with one entry per function call + fn call_contracts(&mut self, epoch: StacksEpochId) -> Vec { + let Some(contract_names) = self.contract_calls_per_epoch.get(&epoch) else { + warn!("No contract calls found for {epoch}."); + return vec![]; + }; + + let is_naka_block = epoch.uses_nakamoto_blocks(); + contract_names + .clone() + .iter() + .map(|contract_name| { + self.append_tx_block( + &TestTxSpec::ContractCall { + sender: &FAUCET_PRIV_KEY, + contract_addr: &self.contract_addr.clone(), + contract_name, + function_name: &self.function_name.clone(), + args: &self.function_args.clone(), + }, + is_naka_block, + ) + }) + .collect() } - /// Executes a full test plan by processing blocks across multiple epochs. + /// Executes the full consensus test: deploy in [`Self::contract_deploys_per_epoch`], call in [`Self::contract_calls_per_epoch`]. /// - /// This function serves as the primary test runner. It iterates through the - /// provided epochs in chronological order, automatically advancing the - /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s - /// associated with that epoch and collects their results. + /// Processes epochs in **sorted order** using [`Self::all_epochs`]. For each epoch: + /// - Advances the chain into the target epoch + /// - Deploys contracts (if scheduled) + /// - Executes function calls (if scheduled) /// - /// # Arguments + /// # Execution Order Example /// - /// * `epoch_blocks` - A map where keys are [`StacksEpochId`]s and values are the - /// sequence of blocks to be executed during that epoch. + /// Given at test instantiation: + /// ```rust + /// deploy_epochs = [Epoch20, Epoch30] + /// call_epochs = [Epoch30, Epoch31] + /// ``` /// - /// # Returns + /// The sequence is: + /// 1. Enter Epoch 2.0 → Deploy `contract-v1` + /// 2. Enter Epoch 3.0 → Deploy `contract-v1`, `contract-v2`, `contract-v3` + /// 3. Enter Epoch 3.0 → Call function on all 4 deployed contracts + /// 4. Enter Epoch 3.1 → Call function on all 4 deployed contracts + /// + /// # Returns /// /// A `Vec` with the outcome of each block for snapshot testing. - pub fn run( - mut self, - epoch_blocks: HashMap>, - ) -> Vec { - let mut sorted_epochs: Vec<_> = epoch_blocks.clone().into_iter().collect(); - sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); + pub fn run(mut self) -> Vec { + let mut results = Vec::new(); - let mut results = vec![]; + // Process epochs in order + for epoch in self.all_epochs.clone() { + // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers + let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); - for (epoch, blocks) in sorted_epochs { - debug!( - "--------- Processing epoch {epoch:?} with {} blocks ---------", - blocks.len() - ); - // Use the miner key to prevent messing with FAUCET nonces. - let miner_key = self.chain.miner.nakamoto_miner_key(); - self.chain.advance_into_epoch(&miner_key, epoch); + // Advance the chain into the target epoch + self.consensus_test + .chain + .advance_into_epoch(&private_key, epoch); - for block in blocks { - results.push(self.append_block(block, epoch.uses_nakamoto_blocks())); - } + results.extend(self.deploy_contracts(epoch)); + results.extend(self.call_contracts(epoch)); } + results } +} - /// Constructs a Nakamoto block with the given [`TestBlock`] configuration. - fn construct_nakamoto_block(&mut self, test_block: TestBlock) -> (NakamotoBlock, usize) { - let chain_tip = NakamotoChainState::get_canonical_block_header( - self.chain.stacks_node.as_ref().unwrap().chainstate.db(), - self.chain.sortdb.as_ref().unwrap(), - ) - .unwrap() - .unwrap(); - let cycle = self.chain.get_reward_cycle(); - let burn_spent = SortitionDB::get_block_snapshot_consensus( - self.chain.sortdb_ref().conn(), - &chain_tip.consensus_hash, - ) - .unwrap() - .map(|sn| sn.total_burn) - .unwrap(); - let mut block = NakamotoBlock { - header: NakamotoBlockHeader { - version: 1, - chain_length: chain_tip.stacks_block_height + 1, - burn_spent, - consensus_hash: chain_tip.consensus_hash.clone(), - parent_block_id: chain_tip.index_block_hash(), - tx_merkle_root: Sha512Trunc256Sum::from_data(&[]), - state_index_root: TrieHash::from_empty_data(), - timestamp: 1, - miner_signature: MessageSignature::empty(), - signer_signature: vec![], - pox_treatment: BitVec::ones(1).unwrap(), - }, - txs: test_block.transactions, - }; +/// The type of transaction to create. +pub enum TestTxSpec<'a> { + Transfer { + from: &'a StacksPrivateKey, + to: &'a PrincipalData, + amount: u64, + }, + ContractDeploy { + sender: &'a StacksPrivateKey, + name: &'a str, + code: &'a str, + clarity_version: Option, + }, + ContractCall { + sender: &'a StacksPrivateKey, + contract_addr: &'a StacksAddress, + contract_name: &'a str, + function_name: &'a str, + args: &'a [ClarityValue], + }, +} - let tx_merkle_root = { - let txid_vecs: Vec<_> = block - .txs - .iter() - .map(|tx| tx.txid().as_bytes().to_vec()) - .collect(); - MerkleTree::::new(&txid_vecs).root() - }; - block.header.tx_merkle_root = tx_merkle_root; +/// A helper to create transactions with incrementing nonces for each account. +pub struct TestTxFactory { + /// Map of address to next nonce + nonce_counter: HashMap, + /// The default chain ID to use for transactions + default_chain_id: u32, +} - // Set the MARF root hash or use an all-zero hash in case of failure. - // NOTE: It is expected to fail when trying computing the marf for invalid block/transactions. - let marf_result = self.compute_block_marf_root_hash(block.header.timestamp, &block.txs); - block.header.state_index_root = match marf_result { - Ok(marf) => marf, - Err(_) => TrieHash::from_bytes(&[0; 32]).unwrap(), +impl TestTxFactory { + /// Creates a new [`TransactionFactory`] with the specified default chain ID. + pub fn new(default_chain_id: u32) -> Self { + Self { + nonce_counter: HashMap::new(), + default_chain_id, + } + } + + /// Manually increments the nonce for the sender of the specified transaction. + /// + /// This method should be called *after* a transaction has been successfully + /// processed to ensure the factory uses the correct next nonce for subsequent + /// transactions from the same sender. + /// + /// # Arguments + /// + /// * `tx_spec` - The original specification of the transaction whose sender's + /// nonce should be incremented. + /// + /// # Panics + /// + /// Panics if the sender's address is not found in the nonce counter map. + pub fn increase_nonce_for_tx(&mut self, tx_spec: &TestTxSpec) { + let sender_privk = match tx_spec { + TestTxSpec::Transfer { from, .. } => from, + TestTxSpec::ContractDeploy { sender, .. } => sender, + TestTxSpec::ContractCall { sender, .. } => sender, }; + let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender_privk)); + let nonce = self + .nonce_counter + .get_mut(&address) + .unwrap_or_else(|| panic!("Nonce not found for address {address}")); + *nonce += 1; + } - self.chain.miner.sign_nakamoto_block(&mut block); - let mut signers = self.chain.config.test_signers.clone().unwrap_or_default(); - signers.sign_nakamoto_block(&mut block, cycle); - let block_len = block.serialize_to_vec().len(); - (block, block_len) + /// Generates a new transaction of the specified type. + /// + /// Arguments: + /// - `tx_type`: The type of transaction to create. + /// + /// Returns: + /// A [`StacksTransaction`] representing the created transaction. + pub fn generate_tx(&mut self, tx_spec: &TestTxSpec) -> StacksTransaction { + match tx_spec { + TestTxSpec::Transfer { from, to, amount } => self.transfer(from, to, *amount), + TestTxSpec::ContractDeploy { + sender, + name, + code, + clarity_version, + } => self.contract_deploy(sender, name, code, *clarity_version), + TestTxSpec::ContractCall { + sender, + contract_addr, + contract_name, + function_name, + args, + } => self.contract_call(sender, contract_addr, contract_name, function_name, args), + } + } + + /// Create a STX transfer transaction. + /// + /// Arguments: + /// - `from`: The sender's private key. + /// - `to`: The recipient's principal data. + /// - `amount`: The amount of STX to transfer. + /// + /// Returns: + /// A [`StacksTransaction`] representing the transfer. + /// + /// Note: The transaction fee is set to 180 micro-STX. + pub fn transfer( + &mut self, + from: &StacksPrivateKey, + to: &PrincipalData, + amount: u64, + ) -> StacksTransaction { + let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(from)); + let nonce = self.nonce_counter.entry(address).or_insert(0); + make_stacks_transfer_tx(from, *nonce, 180, self.default_chain_id, to, amount) } - /// Computes the MARF root hash for a block. + /// Create a contract deployment transaction. /// - /// This function is intended for use in success test cases only, where all - /// transactions are valid. In other scenarios, the computation may fail. + /// Arguments: + /// `sender`: The sender's private key. + /// `name`: The name of the contract. + /// `code`: The contract code as a string. /// - /// The implementation is deliberately minimal: it does not cover every - /// possible situation (such as new tenure handling), but it should be - /// sufficient for the scope of our test cases. - fn compute_block_marf_root_hash( + /// Returns: + /// A [`StacksTransaction`] representing the contract deployment. + /// + /// Note: The transaction fee is set based on the contract code length. + pub fn contract_deploy( &mut self, - block_time: u64, - block_txs: &[StacksTransaction], - ) -> Result { - let node = self.chain.stacks_node.as_mut().unwrap(); - let sortdb = self.chain.sortdb.as_ref().unwrap(); - let burndb_conn = sortdb.index_handle_at_tip(); - let chainstate = &mut node.chainstate; - - let chain_tip = NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb) - .unwrap() - .unwrap(); - - let (chainstate_tx, clarity_instance) = chainstate.chainstate_tx_begin().unwrap(); - let burndb_conn = sortdb.index_handle_at_tip(); - - let mut clarity_tx = StacksChainState::chainstate_block_begin( - &chainstate_tx, - clarity_instance, - &burndb_conn, - &chain_tip.consensus_hash, - &chain_tip.anchored_header.block_hash(), - &MINER_BLOCK_CONSENSUS_HASH, - &MINER_BLOCK_HEADER_HASH, - ); - let result = Self::inner_compute_block_marf_root_hash( - &mut clarity_tx, - block_time, - block_txs, - chain_tip.burn_header_height, + sender: &StacksPrivateKey, + name: &str, + code: &str, + clarity_version: Option, + ) -> StacksTransaction { + let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender)); + let nonce = self.nonce_counter.entry(address).or_insert(0); + let tx_bytes = make_contract_publish_versioned( + sender, + *nonce, + (code.len() * 100) as u64, + self.default_chain_id, + name, + code, + clarity_version, ); - clarity_tx.rollback_block(); - result + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap() } - /// This is where the real MARF computation happens. - /// It is extrapolated into an _inner_ method to simplify rollback handling, - /// ensuring that rollback can be applied consistently on both success and failure - /// in the _outer_ method. - fn inner_compute_block_marf_root_hash( - clarity_tx: &mut ClarityTx, - block_time: u64, - block_txs: &[StacksTransaction], - burn_header_height: u32, - ) -> Result { - clarity_tx - .connection() - .as_free_transaction(|clarity_tx_conn| { - clarity_tx_conn.with_clarity_db(|db| { - db.setup_block_metadata(Some(block_time))?; - Ok(()) - }) - }) - .map_err(|e| e.to_string())?; + /// Create a contract call transaction. + /// + /// Arguments: + /// `sender`: The sender's private key. + /// `contract_addr`: The address of the contract. + /// `contract_name`: The name of the contract. + /// `function_name`: The name of the function to call. + /// `args`: The arguments to pass to the function. + /// + /// Returns: + /// A [`StacksTransaction`] representing the contract call. + /// + /// Note: The transaction fee is set to 200 micro-STX. + pub fn contract_call( + &mut self, + sender: &StacksPrivateKey, + contract_addr: &StacksAddress, + contract_name: &str, + function_name: &str, + args: &[ClarityValue], + ) -> StacksTransaction { + let address = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(sender)); + let nonce = self.nonce_counter.entry(address).or_insert(0); + let tx_bytes = make_contract_call( + sender, + *nonce, + 200, + self.default_chain_id, + contract_addr, + contract_name, + function_name, + args, + ); + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap() + } +} - StacksChainState::process_block_transactions(clarity_tx, block_txs, 0) - .map_err(|e| e.to_string())?; +/// Generates a consensus test for executing a contract function across multiple Stacks epochs. +/// +/// This macro automates both contract deployment and function invocation across different +/// epochs and Clarity versions. +/// It simplifies the setup of consensus-critical tests involving versioned smart contracts. +/// +/// # Behavior +/// +/// - **Deployment:** Deploys `contract_code` in each epoch specified in `deploy_epochs` +/// for every applicable [`ClarityVersion`]. +/// - **Execution:** Calls `function_name` in each epoch from `call_epochs` on all previously +/// deployed contract instances. +/// - **Structure:** Each deployment and function call is executed in its own block, ensuring +/// clear separation between transactions. +/// +/// # Arguments +/// +/// * `$name` — Name of the generated test function. +/// * `contract_name` — The name of the contract. +/// * `contract_code` — The Clarity source code for the contract. +/// * `function_name` — The public function to call. +/// * `function_args` — Function arguments, provided as a slice of [`ClarityValue`]. +/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to all epochs ≥ 2.0. +/// * `call_epochs` — *(optional)* Epochs in which to call the function. Defaults to [`EPOCHS_TO_TEST`]. +/// +/// # Example +/// +/// ```rust,ignore +/// contract_call_consensus_test!( +/// my_test, +/// contract_name: "my-contract", +/// contract_code: "(define-public (get-message) (ok \"hello\"))", +/// function_name: "get-message", +/// function_args: &[], +/// ); +/// ``` +macro_rules! contract_call_consensus_test { + ( + $name:ident, + contract_name: $contract_name:expr, + contract_code: $contract_code:expr, + function_name: $function_name:expr, + function_args: $function_args:expr, + $(deploy_epochs: $deploy_epochs:expr,)? + $(call_epochs: $call_epochs:expr,)? + ) => { + #[test] + fn $name() { + // Handle deploy_epochs parameter (default to all epochs >= 2.0 if not provided) + let deploy_epochs = &StacksEpochId::ALL[1..]; + $(let deploy_epochs = $deploy_epochs;)? - NakamotoChainState::finish_block(clarity_tx, None, false, burn_header_height) - .map_err(|e| e.to_string())?; + // Handle call_epochs parameter (default to EPOCHS_TO_TEST if not provided) + let call_epochs = EPOCHS_TO_TEST; + $(let call_epochs = $call_epochs;)? + let contract_test = ContractConsensusTest::new( + function_name!(), + vec![], + deploy_epochs, + call_epochs, + $contract_name, + $contract_code, + $function_name, + $function_args, + ); + let result = contract_test.run(); + insta::assert_ron_snapshot!(result); + } + }; +} - Ok(clarity_tx.seal()) - } +/// Generates a consensus test for contract deployment across multiple Stacks epochs. +/// +/// This macro automates deploying a contract across different Stacks epochs and +/// Clarity versions. It is primarily used for consensus-critical testing of contract +/// deployment behavior. +/// +/// # Behavior +/// +/// - **Deployment:** Deploys `contract_code` in each epoch specified by `deploy_epochs` +/// for all applicable [`ClarityVersion`]s. +/// - **Structure:** Each deployment is executed in its own block, ensuring clear +/// separation between transactions. +/// +/// # Arguments +/// +/// * `$name` — Name of the generated test function. +/// * `contract_name` — Name of the contract being tested. +/// * `contract_code` — The Clarity source code of the contract. +/// * `deploy_epochs` — *(optional)* Epochs in which to deploy the contract. Defaults to [`EPOCHS_TO_TEST`]. +/// +/// # Example +/// +/// ```rust,ignore +/// contract_deploy_consensus_test!( +/// deploy_test, +/// contract_name: "my-contract", +/// contract_code: "(define-public (init) (ok true))", +/// ); +/// ``` +macro_rules! contract_deploy_consensus_test { + // Handle the case where deploy_epochs is not provided + ( + $name:ident, + contract_name: $contract_name:expr, + contract_code: $contract_code:expr, + ) => { + contract_deploy_consensus_test!( + $name, + contract_name: $contract_name, + contract_code: $contract_code, + deploy_epochs: EPOCHS_TO_TEST, + ); + }; + ( + $name:ident, + contract_name: $contract_name:expr, + contract_code: $contract_code:expr, + deploy_epochs: $deploy_epochs:expr, + ) => { + contract_call_consensus_test!( + $name, + contract_name: $contract_name, + contract_code: $contract_code, + function_name: "", // No function calls, just deploys + function_args: &[], // No function calls, just deploys + deploy_epochs: $deploy_epochs, + call_epochs: &[], // No function calls, just deploys + ); + }; } #[test] From 6069c3694f108bee679e1a05cd8ecfa69b29342b Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 11:24:23 -0800 Subject: [PATCH 11/22] Split ConsensusTest's chain logic out to underlying ConsensusChain and use in ContractConsensusTest Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 219 ++++++++++++-------- 1 file changed, 131 insertions(+), 88 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 1256481732a..b98cf0cd211 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -225,13 +225,29 @@ pub struct TestBlock { pub transactions: Vec, } -/// Represents a consensus test with chainstate. -pub struct ConsensusTest<'a> { - pub chain: TestChainstate<'a>, +/// Manages a `TestChainstate` tailored for consensus-rule verification. +/// +/// Initialises the chain with enough burn-chain blocks per epoch to run the requested Stacks blocks. +/// +/// Provides high-level helpers for: +/// - Appending Nakamoto or pre-Nakamoto blocks +pub struct ConsensusChain<'a> { + pub test_chainstate: TestChainstate<'a>, } -impl ConsensusTest<'_> { - /// Creates a new `ConsensusTest` with the given test name, initial balances, and epoch blocks. +impl ConsensusChain<'_> { + /// Creates a new `ConsensusChain`. + /// + /// # Arguments + /// + /// * `test_name` – identifier used for logging / snapshot names / database names + /// * `initial_balances` – `(principal, amount)` pairs that receive an initial STX balance + /// * `num_blocks_per_epoch` – how many **Stacks** blocks must fit into each epoch + /// + /// # Panics + /// + /// * If `Epoch10` is requested (unsupported) + /// * If any requested epoch is given `0` blocks pub fn new( test_name: &str, initial_balances: Vec<(PrincipalData, u64)>, @@ -257,8 +273,8 @@ impl ConsensusTest<'_> { let (epochs, first_burnchain_height) = Self::calculate_epochs(&boot_plan.pox_constants, num_blocks_per_epoch); boot_plan = boot_plan.with_epochs(epochs); - let chain = boot_plan.to_chainstate(None, Some(first_burnchain_height)); - Self { chain } + let test_chainstate = boot_plan.to_chainstate(None, Some(first_burnchain_height)); + Self { test_chainstate } } /// Calculates a valid [`EpochList`] and starting burnchain height for the test harness. @@ -473,8 +489,8 @@ impl ConsensusTest<'_> { fn append_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { debug!("--------- Running block {block:?} ---------"); let (nakamoto_block, _block_size) = self.construct_nakamoto_block(block); - let mut sortdb = self.chain.sortdb.take().unwrap(); - let mut stacks_node = self.chain.stacks_node.take().unwrap(); + let mut sortdb = self.test_chainstate.sortdb.take().unwrap(); + let mut stacks_node = self.test_chainstate.stacks_node.take().unwrap(); let chain_tip = NakamotoChainState::get_canonical_block_header(stacks_node.chainstate.db(), &sortdb) .unwrap() @@ -488,9 +504,9 @@ impl ConsensusTest<'_> { let res = TestStacksNode::process_pushed_next_ready_block( &mut stacks_node, &mut sortdb, - &mut self.chain.miner, + &mut self.test_chainstate.miner, &chain_tip.consensus_hash, - &mut self.chain.coord, + &mut self.test_chainstate.coord, nakamoto_block.clone(), ); debug!( @@ -499,8 +515,8 @@ impl ConsensusTest<'_> { ); let remapped_result = res.map(|receipt| receipt.unwrap()); // Restore chainstate for the next block - self.chain.sortdb = Some(sortdb); - self.chain.stacks_node = Some(stacks_node); + self.test_chainstate.sortdb = Some(sortdb); + self.test_chainstate.stacks_node = Some(stacks_node); ExpectedResult::create_from(remapped_result, expected_marf) } @@ -519,29 +535,30 @@ impl ConsensusTest<'_> { /// A [`ExpectedResult`] with the outcome of the block processing. fn append_pre_nakamoto_block(&mut self, block: TestBlock) -> ExpectedResult { debug!("--------- Running Pre-Nakamoto block {block:?} ---------"); - let (ch, bh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.chain.sortdb_ref().conn()) - .unwrap(); + let (ch, bh) = SortitionDB::get_canonical_stacks_chain_tip_hash( + self.test_chainstate.sortdb_ref().conn(), + ) + .unwrap(); let tip_id = StacksBlockId::new(&ch, &bh); let (burn_ops, stacks_block, microblocks) = self - .chain + .test_chainstate .make_pre_nakamoto_tenure_with_txs(&block.transactions); - let (_, _, consensus_hash) = self.chain.next_burnchain_block(burn_ops); + let (_, _, consensus_hash) = self.test_chainstate.next_burnchain_block(burn_ops); debug!( "--------- Processing Pre-Nakamoto block ---------"; "block" => ?stacks_block ); - let mut stacks_node = self.chain.stacks_node.take().unwrap(); - let mut sortdb = self.chain.sortdb.take().unwrap(); + let mut stacks_node = self.test_chainstate.stacks_node.take().unwrap(); + let mut sortdb = self.test_chainstate.sortdb.take().unwrap(); let expected_marf = stacks_block.header.state_index_root; let res = TestStacksNode::process_pre_nakamoto_next_ready_block( &mut stacks_node, &mut sortdb, - &mut self.chain.miner, + &mut self.test_chainstate.miner, &ch, - &mut self.chain.coord, + &mut self.test_chainstate.coord, &stacks_block, µblocks, ); @@ -563,8 +580,8 @@ impl ConsensusTest<'_> { receipt }); // Restore chainstate for the next block - self.chain.sortdb = Some(sortdb); - self.chain.stacks_node = Some(stacks_node); + self.test_chainstate.sortdb = Some(sortdb); + self.test_chainstate.stacks_node = Some(stacks_node); ExpectedResult::create_from(remapped_result, expected_marf) } @@ -589,57 +606,22 @@ impl ConsensusTest<'_> { } } - /// Executes a full test plan by processing blocks across multiple epochs. - /// - /// This function serves as the primary test runner. It iterates through the - /// provided epochs in chronological order, automatically advancing the - /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s - /// associated with that epoch and collects their results. - /// - /// # Arguments - /// - /// * `epoch_blocks` - A map where keys are [`StacksEpochId`]s and values are the - /// sequence of blocks to be executed during that epoch. - /// - /// # Returns - /// - /// A `Vec` with the outcome of each block for snapshot testing. - pub fn run( - mut self, - epoch_blocks: HashMap>, - ) -> Vec { - let mut sorted_epochs: Vec<_> = epoch_blocks.clone().into_iter().collect(); - sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); - - let mut results = vec![]; - - for (epoch, blocks) in sorted_epochs { - debug!( - "--------- Processing epoch {epoch:?} with {} blocks ---------", - blocks.len() - ); - // Use the miner key to prevent messing with FAUCET nonces. - let miner_key = self.chain.miner.nakamoto_miner_key(); - self.chain.advance_into_epoch(&miner_key, epoch); - - for block in blocks { - results.push(self.append_block(block, epoch.uses_nakamoto_blocks())); - } - } - results - } - /// Constructs a Nakamoto block with the given [`TestBlock`] configuration. fn construct_nakamoto_block(&mut self, test_block: TestBlock) -> (NakamotoBlock, usize) { let chain_tip = NakamotoChainState::get_canonical_block_header( - self.chain.stacks_node.as_ref().unwrap().chainstate.db(), - self.chain.sortdb.as_ref().unwrap(), + self.test_chainstate + .stacks_node + .as_ref() + .unwrap() + .chainstate + .db(), + self.test_chainstate.sortdb.as_ref().unwrap(), ) .unwrap() .unwrap(); - let cycle = self.chain.get_reward_cycle(); + let cycle = self.test_chainstate.get_reward_cycle(); let burn_spent = SortitionDB::get_block_snapshot_consensus( - self.chain.sortdb_ref().conn(), + self.test_chainstate.sortdb_ref().conn(), &chain_tip.consensus_hash, ) .unwrap() @@ -680,8 +662,13 @@ impl ConsensusTest<'_> { Err(_) => TrieHash::from_bytes(&[0; 32]).unwrap(), }; - self.chain.miner.sign_nakamoto_block(&mut block); - let mut signers = self.chain.config.test_signers.clone().unwrap_or_default(); + self.test_chainstate.miner.sign_nakamoto_block(&mut block); + let mut signers = self + .test_chainstate + .config + .test_signers + .clone() + .unwrap_or_default(); signers.sign_nakamoto_block(&mut block, cycle); let block_len = block.serialize_to_vec().len(); (block, block_len) @@ -700,8 +687,8 @@ impl ConsensusTest<'_> { block_time: u64, block_txs: &[StacksTransaction], ) -> Result { - let node = self.chain.stacks_node.as_mut().unwrap(); - let sortdb = self.chain.sortdb.as_ref().unwrap(); + let node = self.test_chainstate.stacks_node.as_mut().unwrap(); + let sortdb = self.test_chainstate.sortdb.as_ref().unwrap(); let burndb_conn = sortdb.index_handle_at_tip(); let chainstate = &mut node.chainstate; @@ -761,6 +748,69 @@ impl ConsensusTest<'_> { } } +/// A complete consensus test that drives a `ConsensusChain` through a series of epochs. +/// +/// It stores the blocks to execute per epoch and runs them in chronological order, +/// producing a vector of `ExpectedResult` suitable for snapshot testing. +pub struct ConsensusTest<'a> { + pub chain: ConsensusChain<'a>, + epoch_blocks: HashMap>, +} + +impl ConsensusTest<'_> { + /// Constructs a `ConsensusTest` from a map of **epoch → blocks**. + /// + /// The map is converted into `num_blocks_per_epoch` for chain initialisation. + pub fn new( + test_name: &str, + initial_balances: Vec<(PrincipalData, u64)>, + epoch_blocks: HashMap>, + ) -> Self { + let mut num_blocks_per_epoch = HashMap::new(); + for (epoch, blocks) in &epoch_blocks { + num_blocks_per_epoch.insert(*epoch, blocks.len() as u64); + } + Self { + chain: ConsensusChain::new(test_name, initial_balances, num_blocks_per_epoch), + epoch_blocks, + } + } + + /// Executes a full test plan by processing blocks across multiple epochs. + /// + /// This function serves as the primary test runner. It iterates through the + /// provided epochs in chronological order, automatically advancing the + /// chainstate to the start of each epoch. It then processes all [`TestBlock`]'s + /// associated with that epoch and collects their results. + /// + /// # Returns + /// + /// A `Vec` with the outcome of each block for snapshot testing. + pub fn run(mut self) -> Vec { + let mut sorted_epochs: Vec<_> = self.epoch_blocks.clone().into_iter().collect(); + sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); + + let mut results = vec![]; + + for (epoch, blocks) in sorted_epochs { + debug!( + "--------- Processing epoch {epoch:?} with {} blocks ---------", + blocks.len() + ); + // Use the miner key to prevent messing with FAUCET nonces. + let miner_key = self.chain.test_chainstate.miner.nakamoto_miner_key(); + self.chain + .test_chainstate + .advance_into_epoch(&miner_key, epoch); + + for block in blocks { + results.push(self.chain.append_block(block, epoch.uses_nakamoto_blocks())); + } + } + results + } +} + /// A high-level test harness for running consensus-critical smart contract tests. /// /// This struct enables end-to-end testing of Clarity smart contracts under varying epoch conditions, @@ -772,7 +822,7 @@ impl ConsensusTest<'_> { /// - Snapshot testing of execution outcomes via [`ExpectedResult`] /// /// It integrates: -/// - [`ConsensusTest`] for chain simulation and block production +/// - [`ConsensusChain`] for chain simulation and block production /// - [`TestTxFactory`] for deterministic transaction generation /// /// NOTE: The **majority of logic and state computation occurs during construction to enable a deterministic TestChainstate** (`new()`): @@ -784,7 +834,7 @@ struct ContractConsensusTest<'a> { /// Factory for generating signed, nonce-managed transactions. tx_factory: TestTxFactory, /// Underlying chainstate used for block execution and consensus checks. - consensus_test: ConsensusTest<'a>, + chain: ConsensusChain<'a>, /// Address of the contract deployer (the test faucet). contract_addr: StacksAddress, /// Mapping of epoch → list of `(contract_name, ClarityVersion)` deployed in that epoch. @@ -898,7 +948,7 @@ impl ContractConsensusTest<'_> { Self { tx_factory: TestTxFactory::new(CHAIN_ID_TESTNET), - consensus_test: ConsensusTest::new(test_name, initial_balances, num_blocks_per_epoch), + chain: ConsensusChain::new(test_name, initial_balances, num_blocks_per_epoch), contract_addr: to_addr(&FAUCET_PRIV_KEY), contract_deploys_per_epoch, contract_calls_per_epoch, @@ -928,7 +978,7 @@ impl ContractConsensusTest<'_> { transactions: vec![tx], }; - let result = self.consensus_test.append_block(block, is_naka_block); + let result = self.chain.append_block(block, is_naka_block); if let ExpectedResult::Success(_) = result { self.tx_factory.increase_nonce_for_tx(tx_spec); @@ -1045,11 +1095,11 @@ impl ContractConsensusTest<'_> { // Process epochs in order for epoch in self.all_epochs.clone() { // Use the miner as the sender to prevent messing with the block transaction nonces of the deployer/callers - let private_key = self.consensus_test.chain.miner.nakamoto_miner_key(); + let private_key = self.chain.test_chainstate.miner.nakamoto_miner_key(); // Advance the chain into the target epoch - self.consensus_test - .chain + self.chain + .test_chainstate .advance_into_epoch(&private_key, epoch); results.extend(self.deploy_contracts(epoch)); @@ -1382,14 +1432,11 @@ fn test_append_empty_blocks() { transactions: vec![], }]; let mut epoch_blocks = HashMap::new(); - let mut num_blocks_per_epoch = HashMap::new(); for epoch in EPOCHS_TO_TEST { epoch_blocks.insert(*epoch, empty_test_blocks.clone()); - num_blocks_per_epoch.insert(*epoch, 1); } - let result = - ConsensusTest::new(function_name!(), vec![], num_blocks_per_epoch).run(epoch_blocks); + let result = ConsensusTest::new(function_name!(), vec![], epoch_blocks).run(); insta::assert_ron_snapshot!(result); } @@ -1414,7 +1461,6 @@ fn test_append_stx_transfers_success() { // build transactions per epoch, incrementing nonce per sender let mut epoch_blocks = HashMap::new(); - let mut num_blocks_per_epoch = HashMap::new(); let mut nonces = vec![0u64; sender_privks.len()]; // track nonce per sender for epoch in EPOCHS_TO_TEST { @@ -1434,13 +1480,10 @@ fn test_append_stx_transfers_success() { tx }) .collect(); - - num_blocks_per_epoch.insert(*epoch, 1); epoch_blocks.insert(*epoch, vec![TestBlock { transactions }]); } - let result = ConsensusTest::new(function_name!(), initial_balances, num_blocks_per_epoch) - .run(epoch_blocks); + let result = ConsensusTest::new(function_name!(), initial_balances, epoch_blocks).run(); insta::assert_ron_snapshot!(result); } From 0a44f6c975bc6c5fe81f7112480875e743a48319 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 11:38:50 -0800 Subject: [PATCH 12/22] Create network_epoch helper function Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 22 ++++++++++++++++++++- stackslib/src/chainstate/tests/consensus.rs | 21 ++++---------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 4831c95a4bb..82dad829d06 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -28,7 +28,7 @@ use crate::address::{ C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; -use crate::consts::MICROSTACKS_PER_STACKS; +use crate::consts::{MICROSTACKS_PER_STACKS, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, PEER_VERSION_EPOCH_3_3}; use crate::types::chainstate::{StacksAddress, StacksPublicKey}; use crate::util::hash::Hash160; use crate::util::secp256k1::{MessageSignature, Secp256k1PublicKey}; @@ -126,6 +126,26 @@ define_stacks_epochs! { Epoch33 = 0x03003, } +impl StacksEpochId { + /// Return the network epoch associated with the StacksEpochId + pub fn network_epoch(epoch: StacksEpochId) -> u8 { + match epoch { + StacksEpochId::Epoch10 => PEER_VERSION_EPOCH_1_0, + StacksEpochId::Epoch20 => PEER_VERSION_EPOCH_2_0, + StacksEpochId::Epoch2_05 => PEER_VERSION_EPOCH_2_05, + StacksEpochId::Epoch21 => PEER_VERSION_EPOCH_2_1, + StacksEpochId::Epoch22 => PEER_VERSION_EPOCH_2_2, + StacksEpochId::Epoch23 => PEER_VERSION_EPOCH_2_3, + StacksEpochId::Epoch24 => PEER_VERSION_EPOCH_2_4, + StacksEpochId::Epoch25 => PEER_VERSION_EPOCH_2_5, + StacksEpochId::Epoch30 => PEER_VERSION_EPOCH_3_0, + StacksEpochId::Epoch31 => PEER_VERSION_EPOCH_3_1, + StacksEpochId::Epoch32 => PEER_VERSION_EPOCH_3_2, + StacksEpochId::Epoch33 => PEER_VERSION_EPOCH_3_3, + } + } +} + #[derive(Debug)] pub enum MempoolCollectionBehavior { ByStacksHeight, diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index b98cf0cd211..cd72908778c 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -18,9 +18,7 @@ use std::sync::LazyLock; use clarity::boot_util::boot_code_addr; use clarity::codec::StacksMessageCodec; use clarity::consts::{ - CHAIN_ID_TESTNET, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, - PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, - PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, + CHAIN_ID_TESTNET, STACKS_EPOCH_MAX, }; use clarity::types::chainstate::{ @@ -227,7 +225,8 @@ pub struct TestBlock { /// Manages a `TestChainstate` tailored for consensus-rule verification. /// -/// Initialises the chain with enough burn-chain blocks per epoch to run the requested Stacks blocks. +/// Initialises the chain with enough burn-chain blocks per epoch to run +/// the requested number of Stacks blocks per epoch. /// /// Provides high-level helpers for: /// - Appending Nakamoto or pre-Nakamoto blocks @@ -393,19 +392,7 @@ impl ConsensusChain<'_> { } else { BLOCK_LIMIT_MAINNET_21.clone() }; - let network_epoch = match *epoch_id { - StacksEpochId::Epoch10 => PEER_VERSION_EPOCH_1_0, - StacksEpochId::Epoch20 => PEER_VERSION_EPOCH_2_0, - StacksEpochId::Epoch2_05 => PEER_VERSION_EPOCH_2_05, - StacksEpochId::Epoch21 => PEER_VERSION_EPOCH_2_1, - StacksEpochId::Epoch22 => PEER_VERSION_EPOCH_2_2, - StacksEpochId::Epoch23 => PEER_VERSION_EPOCH_2_3, - StacksEpochId::Epoch24 => PEER_VERSION_EPOCH_2_4, - StacksEpochId::Epoch25 => PEER_VERSION_EPOCH_2_5, - StacksEpochId::Epoch30 => PEER_VERSION_EPOCH_3_0, - StacksEpochId::Epoch31 => PEER_VERSION_EPOCH_3_1, - StacksEpochId::Epoch32 | StacksEpochId::Epoch33 => PEER_VERSION_EPOCH_3_2, - }; + let network_epoch = StacksEpochId::network_epoch(*epoch_id); epochs.push(StacksEpoch { epoch_id: *epoch_id, start_height, From 779315be39195097625d1004f38d3eef85e4d075 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 11:42:01 -0800 Subject: [PATCH 13/22] Fmt Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 7 ++++++- stackslib/src/chainstate/tests/consensus.rs | 5 +---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 82dad829d06..c50cca29682 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -28,7 +28,12 @@ use crate::address::{ C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; -use crate::consts::{MICROSTACKS_PER_STACKS, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, PEER_VERSION_EPOCH_3_3}; +use crate::consts::{ + MICROSTACKS_PER_STACKS, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, + PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, + PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, + PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, PEER_VERSION_EPOCH_3_3, +}; use crate::types::chainstate::{StacksAddress, StacksPublicKey}; use crate::util::hash::Hash160; use crate::util::secp256k1::{MessageSignature, Secp256k1PublicKey}; diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index cd72908778c..fa2f1928c4d 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -17,10 +17,7 @@ use std::sync::LazyLock; use clarity::boot_util::boot_code_addr; use clarity::codec::StacksMessageCodec; -use clarity::consts::{ - CHAIN_ID_TESTNET, - STACKS_EPOCH_MAX, -}; +use clarity::consts::{CHAIN_ID_TESTNET, STACKS_EPOCH_MAX}; use clarity::types::chainstate::{ StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey, TrieHash, }; From a7c544d440782d1d41056aaede5d8f0ff6027c82 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 12:26:59 -0800 Subject: [PATCH 14/22] CRC: use the prepare phase start to determine if Epoch 2.5 or Epoch 3.0 reward set calculation rules apply Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 25 ---------------- .../chainstate/nakamoto/coordinator/mod.rs | 30 +++++-------------- 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index c50cca29682..177dd6c1965 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -663,31 +663,6 @@ impl StacksEpochId { self >= &StacksEpochId::Epoch30 } - /// Does this epoch use the nakamoto reward set, or the epoch2 reward set? - /// We use the epoch2 reward set in all pre-3.0 epochs. - /// We also use the epoch2 reward set in the first 3.0 reward cycle. - /// After that, we use the nakamoto reward set. - pub fn uses_nakamoto_reward_set( - &self, - cur_reward_cycle: u64, - first_epoch30_reward_cycle: u64, - ) -> bool { - match self { - StacksEpochId::Epoch10 - | StacksEpochId::Epoch20 - | StacksEpochId::Epoch2_05 - | StacksEpochId::Epoch21 - | StacksEpochId::Epoch22 - | StacksEpochId::Epoch23 - | StacksEpochId::Epoch24 - | StacksEpochId::Epoch25 => false, - StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 - | StacksEpochId::Epoch32 - | StacksEpochId::Epoch33 => cur_reward_cycle > first_epoch30_reward_cycle, - } - } - /// What is the coinbase (in uSTX) to award for the given burnchain height? /// Applies prior to SIP-029 fn coinbase_reward_pre_sip029( diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index 9fd2ae262df..cd52bd621ca 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -362,28 +362,14 @@ pub fn load_nakamoto_reward_set( sort_db: &SortitionDB, provider: &U, ) -> Result, Error> { + // If the prepare phase started in pre-Nakamoto, it should be using Epoch 2.5 reward + // set calculation rules. let cycle_start_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); - let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), cycle_start_height)? - .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {cycle_start_height}")); - let is_pre_naka_epoch = if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { - true - } else { - let epoch_30 = - SortitionDB::get_stacks_epoch_by_epoch_id(sort_db.conn(), &StacksEpochId::Epoch30)? - .unwrap_or_else(|| panic!("FATAL: no Nakamoto epoch defined")); - // Find the first Stacks block in this reward cycle's preceding prepare phase. - // This block will have invoked `.signers.stackerdb-set-signer-slots()` with the reward set. - // Note that we may not have processed it yet. But, if we do find it, then it's - // unique (and since Nakamoto Stacks blocks are processed in order, the anchor block - // cannot change later). - let first_epoch30_reward_cycle = burnchain - .block_height_to_reward_cycle(epoch_30.start_height) - .expect("FATAL: no reward cycle for epoch 3.0 start height"); - !epoch_at_height - .epoch_id - .uses_nakamoto_reward_set(reward_cycle, first_epoch30_reward_cycle) - }; - if is_pre_naka_epoch { + // This is safe to case because a u32 always fits in a u64 + let prepare_phase_start = cycle_start_height - u64::from(burnchain.pox_constants.prepare_length); + let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start)? + .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {prepare_phase_start}")); + if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { // in epoch 2.5, and in the first reward cycle of epoch 3.0, the reward set can *only* be found in the sortition DB. // The nakamoto chain-processing rules aren't active yet, so we can't look for the reward // cycle info in the nakamoto chain state. @@ -391,7 +377,7 @@ pub fn load_nakamoto_reward_set( get_ancestor_sort_id(&sort_db.index_conn(), cycle_start_height, sortition_tip)? else { // reward cycle is too far in the future - warn!("Requested reward cycle start ancestor sortition ID for cycle {} prepare-end height {}, but tip is {}", reward_cycle, cycle_start_height, sortition_tip); + warn!("Requested reward cycle start ancestor sortition ID for cycle {reward_cycle} prepare-end height {cycle_start_height}, but tip is {sortition_tip}"); return Ok(None); }; From 3ea9d351e71901140774de2b344a014fb13d7a33 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 12:40:12 -0800 Subject: [PATCH 15/22] CRC: use saturating sub in load_nakamoto_reward_set to ensure 0th cycle is handled correctly Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/nakamoto/coordinator/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index cd52bd621ca..82f85165dee 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -365,8 +365,9 @@ pub fn load_nakamoto_reward_set( // If the prepare phase started in pre-Nakamoto, it should be using Epoch 2.5 reward // set calculation rules. let cycle_start_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); - // This is safe to case because a u32 always fits in a u64 - let prepare_phase_start = cycle_start_height - u64::from(burnchain.pox_constants.prepare_length); + // This is safe to cast because a u32 always fits in a u64 + let prepare_phase_start = + cycle_start_height.saturating_sub(u64::from(burnchain.pox_constants.prepare_length)); let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start)? .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {prepare_phase_start}")); if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { From 381398dae682460373f09ed085d3b7ca5debb66c Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 15:47:37 -0800 Subject: [PATCH 16/22] Move asserts about nakamoto boot state to a helper function that can be called elsewhere Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 32 -------------- stackslib/src/config/mod.rs | 10 +++++ stackslib/src/core/mod.rs | 48 +++++++++++++++++++++ stackslib/src/net/tests/mod.rs | 14 ++++++ 4 files changed, 72 insertions(+), 32 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index fa2f1928c4d..7565790bae4 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -399,38 +399,6 @@ impl ConsensusChain<'_> { }); current_height = end_height; } - // Validate Epoch 2.5 and 3.0 constraints - if let Some(epoch_3_0) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { - let epoch_2_5 = epochs - .iter() - .find(|e| e.epoch_id == StacksEpochId::Epoch25) - .expect("Epoch 2.5 not found"); - let epoch_2_5_start = epoch_2_5.start_height; - let epoch_3_0_start = epoch_3_0.start_height; - let epoch_2_5_end = epoch_2_5.end_height; - - let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; - let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; - let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); - let epoch_3_0_prepare_phase = - prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); - assert!( - epoch_2_5_start < epoch_3_0_prepare_phase, - "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" - ); - assert_eq!( - epoch_2_5_end, epoch_3_0.start_height, - "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" - ); - assert_ne!( - epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, - "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" - ); - assert!( - !is_reward_cycle_boundary(epoch_3_0_start, reward_cycle_length), - "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" - ); - } // Validate test vector block counts for (epoch_id, num_blocks) in num_blocks_per_epoch { let epoch = epochs diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 407744f57db..529091e7788 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -684,6 +684,16 @@ impl Config { "FATAL: v1 unlock height is at a reward cycle boundary\nburnchain: {burnchain:?}" ); } + if epochs + .iter() + .any(|epoch| epoch.epoch_id == StacksEpochId::Epoch30) + { + StacksEpoch::assert_valid_epoch_3_0_activation( + epochs, + burnchain.pox_constants.reward_cycle_length as u64, + burnchain.pox_constants.prepare_length as u64, + ); + } } // TODO: add tests from mutation testing results #4866 diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index 9ad2d1b950a..3196582ac38 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -883,6 +883,12 @@ pub trait StacksEpochExtension { bitcoin_network: BitcoinNetworkType, configured_epochs: Option<&EpochList>, ) -> EpochList; + /// Assert that the epochs list will satisfy the Epoch 3.0 activation requirements given the reward cycle length and prepare phase length + fn assert_valid_epoch_3_0_activation( + epochs: &[StacksEpoch], + reward_cycle_length: u64, + prepare_length: u64, + ); } impl StacksEpochExtension for StacksEpoch { @@ -2347,6 +2353,48 @@ impl StacksEpochExtension for StacksEpoch { } assert_eq!(epoch_end_height, STACKS_EPOCH_MAX); + EpochList::new(&epochs) } + + fn assert_valid_epoch_3_0_activation( + epochs: &[StacksEpoch], + reward_cycle_length: u64, + prepare_length: u64, + ) { + // Validate Epoch 2.5 and 3.0 constraints + let epoch_3_0 = epochs + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch30) + .expect("Cannot activate Epoch 3.0 without specifying its activation height"); + let epoch_2_5 = epochs + .iter() + .find(|e| e.epoch_id == StacksEpochId::Epoch25) + .expect("Epoch 2.5 not found"); + let epoch_2_5_start = epoch_2_5.start_height; + let epoch_3_0_start = epoch_3_0.start_height; + let epoch_2_5_end = epoch_2_5.end_height; + + let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; + let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; + let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); + let epoch_3_0_prepare_phase = + prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); + assert!( + epoch_2_5_start < epoch_3_0_prepare_phase, + "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" + ); + assert_eq!( + epoch_2_5_end, epoch_3_0.start_height, + "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" + ); + assert_ne!( + epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, + "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" + ); + assert!( + epoch_3_0_start % reward_cycle_length > 1, + "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" + ); + } } diff --git a/stackslib/src/net/tests/mod.rs b/stackslib/src/net/tests/mod.rs index 3a4d670d11c..b90758b70a7 100644 --- a/stackslib/src/net/tests/mod.rs +++ b/stackslib/src/net/tests/mod.rs @@ -437,6 +437,13 @@ impl NakamotoBootPlan { if let Some(current_block) = current_block { chainstate_config.current_block = current_block; } + if let Some(epochs) = self.epochs.as_ref() { + StacksEpoch::assert_valid_epoch_3_0_activation( + epochs, + self.pox_constants.reward_cycle_length as u64, + self.pox_constants.prepare_length as u64, + ); + } let mut chain = TestChainstate::new_with_observer(chainstate_config, observer); chain.mine_malleablized_blocks = self.malleablized_blocks; chain @@ -449,6 +456,13 @@ impl NakamotoBootPlan { self, observer: Option<&TestEventObserver>, ) -> (TestPeer<'_>, Vec>) { + if let Some(epochs) = self.epochs.as_ref() { + StacksEpoch::assert_valid_epoch_3_0_activation( + epochs, + self.pox_constants.reward_cycle_length as u64, + self.pox_constants.prepare_length as u64, + ); + } let mut peer_config = TestPeerConfig::new(&self.test_name, 0, 0); peer_config.chain_config = self.build_nakamoto_chainstate_config(); peer_config.private_key = self.private_key.clone(); From e4e5ab63c2d00d5f06fcf752441b36ce124b0c0b Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 3 Nov 2025 15:47:42 -0800 Subject: [PATCH 17/22] Cannot use prepare phase length to determine reward set calculation Signed-off-by: Jacinta Ferrant --- stacks-common/src/types/mod.rs | 25 ++++++++++++++++ .../chainstate/nakamoto/coordinator/mod.rs | 29 ++++++++++++++----- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index 177dd6c1965..c50cca29682 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -663,6 +663,31 @@ impl StacksEpochId { self >= &StacksEpochId::Epoch30 } + /// Does this epoch use the nakamoto reward set, or the epoch2 reward set? + /// We use the epoch2 reward set in all pre-3.0 epochs. + /// We also use the epoch2 reward set in the first 3.0 reward cycle. + /// After that, we use the nakamoto reward set. + pub fn uses_nakamoto_reward_set( + &self, + cur_reward_cycle: u64, + first_epoch30_reward_cycle: u64, + ) -> bool { + match self { + StacksEpochId::Epoch10 + | StacksEpochId::Epoch20 + | StacksEpochId::Epoch2_05 + | StacksEpochId::Epoch21 + | StacksEpochId::Epoch22 + | StacksEpochId::Epoch23 + | StacksEpochId::Epoch24 + | StacksEpochId::Epoch25 => false, + StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 + | StacksEpochId::Epoch33 => cur_reward_cycle > first_epoch30_reward_cycle, + } + } + /// What is the coinbase (in uSTX) to award for the given burnchain height? /// Applies prior to SIP-029 fn coinbase_reward_pre_sip029( diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index 82f85165dee..0b6a4fa6c04 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -362,15 +362,28 @@ pub fn load_nakamoto_reward_set( sort_db: &SortitionDB, provider: &U, ) -> Result, Error> { - // If the prepare phase started in pre-Nakamoto, it should be using Epoch 2.5 reward - // set calculation rules. let cycle_start_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); - // This is safe to cast because a u32 always fits in a u64 - let prepare_phase_start = - cycle_start_height.saturating_sub(u64::from(burnchain.pox_constants.prepare_length)); - let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start)? - .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {prepare_phase_start}")); - if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { + let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), cycle_start_height)? + .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {cycle_start_height}")); + let is_pre_naka_epoch = if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { + true + } else { + let epoch_30 = + SortitionDB::get_stacks_epoch_by_epoch_id(sort_db.conn(), &StacksEpochId::Epoch30)? + .unwrap_or_else(|| panic!("FATAL: no Nakamoto epoch defined")); + // Find the first Stacks block in this reward cycle's preceding prepare phase. + // This block will have invoked `.signers.stackerdb-set-signer-slots()` with the reward set. + // Note that we may not have processed it yet. But, if we do find it, then it's + // unique (and since Nakamoto Stacks blocks are processed in order, the anchor block + // cannot change later). + let first_epoch30_reward_cycle = burnchain + .block_height_to_reward_cycle(epoch_30.start_height) + .expect("FATAL: no reward cycle for epoch 3.0 start height"); + !epoch_at_height + .epoch_id + .uses_nakamoto_reward_set(reward_cycle, first_epoch30_reward_cycle) + }; + if is_pre_naka_epoch { // in epoch 2.5, and in the first reward cycle of epoch 3.0, the reward set can *only* be found in the sortition DB. // The nakamoto chain-processing rules aren't active yet, so we can't look for the reward // cycle info in the nakamoto chain state. From 7fec30886b91141497949a44534af1b629058593 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 4 Nov 2025 10:26:41 -0800 Subject: [PATCH 18/22] CRC: cleanup comments and move epoch assertions into build_nakamoto_chainstate_config Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/consensus.rs | 16 +++++++-------- stackslib/src/net/tests/mod.rs | 22 ++++++++------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs index 7565790bae4..8e71d159813 100644 --- a/stackslib/src/chainstate/tests/consensus.rs +++ b/stackslib/src/chainstate/tests/consensus.rs @@ -232,7 +232,7 @@ pub struct ConsensusChain<'a> { } impl ConsensusChain<'_> { - /// Creates a new `ConsensusChain`. + /// Creates a new [`ConsensusChain`]. /// /// # Arguments /// @@ -700,17 +700,17 @@ impl ConsensusChain<'_> { } } -/// A complete consensus test that drives a `ConsensusChain` through a series of epochs. +/// A complete consensus test that drives a [`ConsensusChain`] through a series of epochs. /// /// It stores the blocks to execute per epoch and runs them in chronological order, -/// producing a vector of `ExpectedResult` suitable for snapshot testing. +/// producing a vector of [`ExpectedResult`] suitable for snapshot testing. pub struct ConsensusTest<'a> { pub chain: ConsensusChain<'a>, epoch_blocks: HashMap>, } impl ConsensusTest<'_> { - /// Constructs a `ConsensusTest` from a map of **epoch → blocks**. + /// Constructs a [`ConsensusTest`] from a map of **epoch → blocks**. /// /// The map is converted into `num_blocks_per_epoch` for chain initialisation. pub fn new( @@ -737,7 +737,7 @@ impl ConsensusTest<'_> { /// /// # Returns /// - /// A `Vec` with the outcome of each block for snapshot testing. + /// A Vec<['ExpectedResult`]> with the outcome of each block for snapshot testing. pub fn run(mut self) -> Vec { let mut sorted_epochs: Vec<_> = self.epoch_blocks.clone().into_iter().collect(); sorted_epochs.sort_by_key(|(epoch_id, _)| *epoch_id); @@ -991,7 +991,7 @@ impl ContractConsensusTest<'_> { /// /// # Returns /// - /// A [`Vec`] with one entry per function call + /// A Vec<['ExpectedResult`]> with one entry per function call fn call_contracts(&mut self, epoch: StacksEpochId) -> Vec { let Some(contract_names) = self.contract_calls_per_epoch.get(&epoch) else { warn!("No contract calls found for {epoch}."); @@ -1027,7 +1027,7 @@ impl ContractConsensusTest<'_> { /// # Execution Order Example /// /// Given at test instantiation: - /// ```rust + /// ```rust,ignore /// deploy_epochs = [Epoch20, Epoch30] /// call_epochs = [Epoch30, Epoch31] /// ``` @@ -1040,7 +1040,7 @@ impl ContractConsensusTest<'_> { /// /// # Returns /// - /// A `Vec` with the outcome of each block for snapshot testing. + /// A Vec<['ExpectedResult`]> with the outcome of each block for snapshot testing. pub fn run(mut self) -> Vec { let mut results = Vec::new(); diff --git a/stackslib/src/net/tests/mod.rs b/stackslib/src/net/tests/mod.rs index b90758b70a7..54e06d5c239 100644 --- a/stackslib/src/net/tests/mod.rs +++ b/stackslib/src/net/tests/mod.rs @@ -186,6 +186,14 @@ impl NakamotoBootPlan { chainstate_config.test_stackers = Some(self.test_stackers.clone()); chainstate_config.burnchain.pox_constants = self.pox_constants.clone(); + if let Some(epochs) = chainstate_config.epochs.as_ref() { + StacksEpoch::assert_valid_epoch_3_0_activation( + epochs, + self.pox_constants.reward_cycle_length as u64, + self.pox_constants.prepare_length as u64, + ); + } + chainstate_config } @@ -437,13 +445,6 @@ impl NakamotoBootPlan { if let Some(current_block) = current_block { chainstate_config.current_block = current_block; } - if let Some(epochs) = self.epochs.as_ref() { - StacksEpoch::assert_valid_epoch_3_0_activation( - epochs, - self.pox_constants.reward_cycle_length as u64, - self.pox_constants.prepare_length as u64, - ); - } let mut chain = TestChainstate::new_with_observer(chainstate_config, observer); chain.mine_malleablized_blocks = self.malleablized_blocks; chain @@ -456,13 +457,6 @@ impl NakamotoBootPlan { self, observer: Option<&TestEventObserver>, ) -> (TestPeer<'_>, Vec>) { - if let Some(epochs) = self.epochs.as_ref() { - StacksEpoch::assert_valid_epoch_3_0_activation( - epochs, - self.pox_constants.reward_cycle_length as u64, - self.pox_constants.prepare_length as u64, - ); - } let mut peer_config = TestPeerConfig::new(&self.test_name, 0, 0); peer_config.chain_config = self.build_nakamoto_chainstate_config(); peer_config.private_key = self.private_key.clone(); From 6da8514f7190e7e533843fd6eca8d08d8d18bec5 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 4 Nov 2025 11:20:07 -0800 Subject: [PATCH 19/22] Cleanup assert_valid_epoch_3_0_activation and rename to validate_nakamoto_transition_schedule Signed-off-by: Jacinta Ferrant --- stackslib/src/config/mod.rs | 37 +++-------- stackslib/src/core/mod.rs | 112 ++++++++++++++++++++++++++------- stackslib/src/net/tests/mod.rs | 5 +- 3 files changed, 99 insertions(+), 55 deletions(-) diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 529091e7788..0abe5aa9e4a 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -582,33 +582,19 @@ impl Config { fn check_nakamoto_config(&self, burnchain: &Burnchain) { let epochs = self.burnchain.get_epoch_list(); - let Some(epoch_30) = epochs.get(StacksEpochId::Epoch30) else { - // no Epoch 3.0, so just return + if epochs + .iter() + .all(|epoch| epoch.epoch_id < StacksEpochId::Epoch30) + { return; - }; + } if burnchain.pox_constants.prepare_length < 3 { panic!( "FATAL: Nakamoto rules require a prepare length >= 3. Prepare length set to {}", burnchain.pox_constants.prepare_length ); } - if burnchain.is_in_prepare_phase(epoch_30.start_height) { - panic!( - "FATAL: Epoch 3.0 must start *during* a reward phase, not a prepare phase. Epoch 3.0 start set to: {}. PoX Parameters: {:?}", - epoch_30.start_height, - &burnchain.pox_constants - ); - } - let activation_reward_cycle = burnchain - .block_height_to_reward_cycle(epoch_30.start_height) - .expect("FATAL: Epoch 3.0 starts before the first burnchain block"); - if activation_reward_cycle < 2 { - panic!( - "FATAL: Epoch 3.0 must start at or after the second reward cycle. Epoch 3.0 start set to: {}. PoX Parameters: {:?}", - epoch_30.start_height, - &burnchain.pox_constants - ); - } + StacksEpoch::validate_nakamoto_transition_schedule(&epochs, burnchain); } /// Connect to the MempoolDB using the configured cost estimation @@ -684,16 +670,7 @@ impl Config { "FATAL: v1 unlock height is at a reward cycle boundary\nburnchain: {burnchain:?}" ); } - if epochs - .iter() - .any(|epoch| epoch.epoch_id == StacksEpochId::Epoch30) - { - StacksEpoch::assert_valid_epoch_3_0_activation( - epochs, - burnchain.pox_constants.reward_cycle_length as u64, - burnchain.pox_constants.prepare_length as u64, - ); - } + StacksEpoch::validate_nakamoto_transition_schedule(epochs, burnchain); } // TODO: add tests from mutation testing results #4866 diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index 3196582ac38..8079457ee49 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -29,6 +29,7 @@ use stacks_common::types::{EpochList as GenericEpochList, StacksEpoch as Generic pub use self::mempool::MemPoolDB; use crate::burnchains::bitcoin::indexer::get_bitcoin_stacks_epochs; use crate::burnchains::bitcoin::BitcoinNetworkType; +use crate::burnchains::Burnchain; use crate::chainstate::burn::ConsensusHash; pub mod mempool; pub mod nonce_cache; @@ -883,12 +884,9 @@ pub trait StacksEpochExtension { bitcoin_network: BitcoinNetworkType, configured_epochs: Option<&EpochList>, ) -> EpochList; - /// Assert that the epochs list will satisfy the Epoch 3.0 activation requirements given the reward cycle length and prepare phase length - fn assert_valid_epoch_3_0_activation( - epochs: &[StacksEpoch], - reward_cycle_length: u64, - prepare_length: u64, - ); + /// Validates that Epoch 3.0 activation (if present) satisfies all required safety + /// invariants for Nakamoto transition, using the provided burnchain configuration. + fn validate_nakamoto_transition_schedule(epochs: &[StacksEpoch], burnchain: &Burnchain); } impl StacksEpochExtension for StacksEpoch { @@ -2357,44 +2355,114 @@ impl StacksEpochExtension for StacksEpoch { EpochList::new(&epochs) } - fn assert_valid_epoch_3_0_activation( - epochs: &[StacksEpoch], - reward_cycle_length: u64, - prepare_length: u64, - ) { - // Validate Epoch 2.5 and 3.0 constraints + /// Validates that Epoch 3.0 activation (if present) satisfies all required safety + /// invariants for Nakamoto transition, using the provided burnchain configuration. + /// + /// This function is only relevant when **Nakamoto epochs** (Epoch 3.0+) exist in the + /// epoch list. If no post Epoch 2.5 is defined, the function returns early with no checks. + /// + /// ### Required Invariants for Safe Epoch 3.0 Activation + /// + /// 1. **Epoch 2.5 must exist** and start **before** the prepare phase of the reward + /// cycle immediately preceding Epoch 3.0. + /// 2. **Epoch 2.5 must end exactly at the start of Epoch 3.0** — they are contiguous. + /// 3. **Epoch 2.5 and Epoch 3.0 must be in different reward cycles** + /// 4. **Epoch 3.0 must start during a reward phase**, not in a prepare phase. + /// 5. **Epoch 3.0 must not start at a reward cycle boundary** (i.e., block height + /// modulo `reward_cycle_length` must not be 0 or 1). + /// 6. **Epoch 3.0 must activate at or after reward cycle 2** (cycle 0 and 1 are + /// reserved for early network bootstrapping). + /// + /// # Parameters + /// + /// - `epochs`: List of defined Stacks epochs. + /// - `burnchain`: Burnchain configuration, providing PoX reward cycle parameters + /// (`reward_cycle_length`, `prepare_length`) and height-to-cycle utilities. + /// + /// # Panics + /// + /// This function panics if any of the invariants fail. + /// These panics are intended to catch **misconfigured networks** at startup + fn validate_nakamoto_transition_schedule(epochs: &[StacksEpoch], burnchain: &Burnchain) { + // Early return if no Nakamoto epochs are defined + if epochs + .iter() + .all(|epoch| epoch.epoch_id < StacksEpochId::Epoch30) + { + return; + } let epoch_3_0 = epochs .iter() .find(|e| e.epoch_id == StacksEpochId::Epoch30) - .expect("Cannot activate Epoch 3.0 without specifying its activation height"); + .expect("FATAL: Cannot activate Epoch 3.0 without specifying its activation height"); let epoch_2_5 = epochs .iter() .find(|e| e.epoch_id == StacksEpochId::Epoch25) - .expect("Epoch 2.5 not found"); - let epoch_2_5_start = epoch_2_5.start_height; + .expect("FATAL: Epoch 2.5 not found"); let epoch_3_0_start = epoch_3_0.start_height; + let epoch_2_5_start = epoch_2_5.start_height; let epoch_2_5_end = epoch_2_5.end_height; + let reward_cycle_length = u64::from(burnchain.pox_constants.reward_cycle_length); + let prepare_length = u64::from(burnchain.pox_constants.prepare_length); + + assert!( + !burnchain.is_in_prepare_phase(epoch_3_0_start), + "FATAL: Epoch 3.0 must start *during* a reward phase, not prepare phase. \ + Activation height: {epoch_3_0_start}, PoX Parameters: {:?}", + burnchain.pox_constants + ); + + let activation_reward_cycle = burnchain + .block_height_to_reward_cycle(epoch_3_0_start) + .expect("FATAL: Epoch 3.0 cannot start before the first burnchain block"); + assert!( + activation_reward_cycle >= 2, + "FATAL: Epoch 3.0 must start at or after reward cycle 2. \ + Activation height: {epoch_3_0_start}, cycle: {activation_reward_cycle}, \ + PoX Parameters: {:?}", + burnchain.pox_constants + ); + let epoch_2_5_reward_cycle = epoch_2_5_start / reward_cycle_length; let epoch_3_0_reward_cycle = epoch_3_0_start / reward_cycle_length; + // Start of prepare phase in the cycle before Epoch 3.0 let prior_cycle = epoch_3_0_reward_cycle.saturating_sub(1); - let epoch_3_0_prepare_phase = + let epoch_3_0_prepare_phase_start = prior_cycle * reward_cycle_length + (reward_cycle_length - prepare_length); assert!( - epoch_2_5_start < epoch_3_0_prepare_phase, - "Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. (Epoch 2.5 activation height: {epoch_2_5_start}. Epoch 3.0 prepare phase start: {epoch_3_0_prepare_phase})" + epoch_2_5_start < epoch_3_0_prepare_phase_start, + "FATAL: Epoch 2.5 must start before the prepare phase of the cycle prior to Epoch 3.0. \ + Epoch 2.5 start: {epoch_2_5_start}, \ + Epoch 3.0 prior cycle prepare phase start: {epoch_3_0_prepare_phase_start}, \ + PoX Parameters: {:?}", + burnchain.pox_constants ); + assert_eq!( - epoch_2_5_end, epoch_3_0.start_height, - "Epoch 2.5 end must equal Epoch 3.0 start (epoch_2_5_end: {epoch_2_5_end}, epoch_3_0_start: {epoch_3_0_start})" + epoch_2_5_end, epoch_3_0_start, + "FATAL: Epoch 2.5 end must equal Epoch 3.0 start. \ + End: {epoch_2_5_end}, Start: {epoch_3_0_start}" ); + assert_ne!( epoch_2_5_reward_cycle, epoch_3_0_reward_cycle, - "Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle (epoch_2_5_reward_cycle: {epoch_2_5_reward_cycle}, epoch_3_0_reward_cycle: {epoch_3_0_reward_cycle})" + "FATAL: Epoch 2.5 and Epoch 3.0 must not be in the same reward cycle. \ + Epoch 2.5 cycle: {epoch_2_5_reward_cycle}, \ + Epoch 3.0 cycle: {epoch_3_0_reward_cycle}, \ + PoX Parameters: {:?}", + burnchain.pox_constants ); + + // Epoch 2.5 has some confusing boundary logic for calculating the reward set hence why + // the boundary is viewed as both 0 and 1. assert!( epoch_3_0_start % reward_cycle_length > 1, - "Epoch 3.0 must not start at a reward cycle boundary (epoch_3_0_start: {epoch_3_0_start})" + "FATAL: Epoch 3.0 must not start at a reward cycle boundary (offset 0 or 1). \ + Activation height: {epoch_3_0_start}, \ + offset: {}, PoX Parameters: {:?}", + epoch_3_0_start % reward_cycle_length, + burnchain.pox_constants ); } } diff --git a/stackslib/src/net/tests/mod.rs b/stackslib/src/net/tests/mod.rs index 54e06d5c239..c6dd5d00cf8 100644 --- a/stackslib/src/net/tests/mod.rs +++ b/stackslib/src/net/tests/mod.rs @@ -187,10 +187,9 @@ impl NakamotoBootPlan { chainstate_config.burnchain.pox_constants = self.pox_constants.clone(); if let Some(epochs) = chainstate_config.epochs.as_ref() { - StacksEpoch::assert_valid_epoch_3_0_activation( + StacksEpoch::validate_nakamoto_transition_schedule( epochs, - self.pox_constants.reward_cycle_length as u64, - self.pox_constants.prepare_length as u64, + &chainstate_config.burnchain, ); } From 70727c6825e5fc6220f1ddf1f3c5a103b49c425a Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 4 Nov 2025 19:18:11 -0800 Subject: [PATCH 20/22] Fix tests that have reward cycle starting on boundary Signed-off-by: Jacinta Ferrant --- .../src/chainstate/nakamoto/coordinator/tests.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/coordinator/tests.rs b/stackslib/src/chainstate/nakamoto/coordinator/tests.rs index 24e813f1e05..e7094c9499b 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/tests.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/tests.rs @@ -942,7 +942,7 @@ fn block_descendant() { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_test_stackers(test_stackers) @@ -1031,7 +1031,7 @@ fn block_info_tests(use_primary_testnet: bool) { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let chain_id = if use_primary_testnet { CHAIN_ID_TESTNET @@ -1466,7 +1466,7 @@ fn pox_treatment() { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_test_stackers(test_stackers.clone()) @@ -1719,7 +1719,7 @@ fn transactions_indexing() { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_test_stackers(test_stackers.clone()) @@ -1784,7 +1784,7 @@ fn transactions_not_indexing() { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_test_stackers(test_stackers.clone()) @@ -3897,7 +3897,7 @@ fn process_next_nakamoto_block_deadlock() { pox_constants.v2_unlock_height = 21; pox_constants.pox_3_activation_height = 26; pox_constants.v3_unlock_height = 27; - pox_constants.pox_4_activation_height = 28; + pox_constants.pox_4_activation_height = 33; let mut boot_plan = NakamotoBootPlan::new(function_name!()) .with_test_stackers(test_stackers) From 52fc4c47920de4a9331bc7d943a3a7836933b6fa Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 4 Nov 2025 19:47:09 -0800 Subject: [PATCH 21/22] CRC: use the prepare phase start to determine if Epoch 2.5 or Epoch 3.0 reward set calculation rules apply Signed-off-by: Jacinta Ferrant --- .../chainstate/nakamoto/coordinator/mod.rs | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index 0b6a4fa6c04..a154c330623 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -363,27 +363,13 @@ pub fn load_nakamoto_reward_set( provider: &U, ) -> Result, Error> { let cycle_start_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); - let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), cycle_start_height)? - .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {cycle_start_height}")); - let is_pre_naka_epoch = if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { - true - } else { - let epoch_30 = - SortitionDB::get_stacks_epoch_by_epoch_id(sort_db.conn(), &StacksEpochId::Epoch30)? - .unwrap_or_else(|| panic!("FATAL: no Nakamoto epoch defined")); - // Find the first Stacks block in this reward cycle's preceding prepare phase. - // This block will have invoked `.signers.stackerdb-set-signer-slots()` with the reward set. - // Note that we may not have processed it yet. But, if we do find it, then it's - // unique (and since Nakamoto Stacks blocks are processed in order, the anchor block - // cannot change later). - let first_epoch30_reward_cycle = burnchain - .block_height_to_reward_cycle(epoch_30.start_height) - .expect("FATAL: no reward cycle for epoch 3.0 start height"); - !epoch_at_height - .epoch_id - .uses_nakamoto_reward_set(reward_cycle, first_epoch30_reward_cycle) - }; - if is_pre_naka_epoch { + let prepare_phase_start_height = + cycle_start_height.saturating_sub(u64::from(burnchain.pox_constants.prepare_length)); + let epoch_at_height = + SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start_height)?.unwrap_or_else( + || panic!("FATAL: no epoch defined for burn height {prepare_phase_start_height}"), + ); + if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { // in epoch 2.5, and in the first reward cycle of epoch 3.0, the reward set can *only* be found in the sortition DB. // The nakamoto chain-processing rules aren't active yet, so we can't look for the reward // cycle info in the nakamoto chain state. From 1ff2cd8975f97d289e148fb225b22722a96c7d25 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 5 Nov 2025 15:51:43 -0800 Subject: [PATCH 22/22] Do not check epochs in new_with_observer Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/tests/mod.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/stackslib/src/chainstate/tests/mod.rs b/stackslib/src/chainstate/tests/mod.rs index 702644481f1..02df4fefc24 100644 --- a/stackslib/src/chainstate/tests/mod.rs +++ b/stackslib/src/chainstate/tests/mod.rs @@ -204,32 +204,6 @@ impl<'a> TestChainstate<'a> { StacksEpoch::unit_test_pre_2_05(config.burnchain.first_block_height) }); - if let Some(epoch_30) = epochs.iter().find(|e| e.epoch_id == StacksEpochId::Epoch30) { - assert!(config.current_block < epoch_30.start_height, "Cannot use a Nakamoto chainstate if bootstrapped to a burn block height ({}) greater than or equal to the Epoch 3.0 activation height ({}).", config.current_block, epoch_30.start_height); - let epoch_25 = config - .epochs - .as_ref() - .expect("Epoch configuration missing") - .iter() - .find(|e| e.epoch_id == StacksEpochId::Epoch25) - .expect("Must specify an Epoch25 start_height to use Nakamoto"); - let epoch_25_reward_cycle = config - .burnchain - .block_height_to_reward_cycle(epoch_25.start_height) - .expect("Failed to determine reward cycle of epoch 2.5"); - let epoch_30_reward_cycle = config - .burnchain - .block_height_to_reward_cycle(epoch_30.start_height) - .expect("Failed to determine reward cycle of Epoch 3.0"); - let epoch_25_in_prepare_phase = - config.burnchain.is_in_prepare_phase(epoch_25.start_height); - - assert_ne!(epoch_25_reward_cycle, epoch_30_reward_cycle, "Cannot activate Epoch 2.5 and Epoch 3.0 in the same reward cycle. Examine your bootstrap setup."); - if epoch_25_reward_cycle.saturating_add(1) == epoch_30_reward_cycle { - assert!(!epoch_25_in_prepare_phase, "Must activate Epoch 2.5 prior to the prepare phase in which Epoch 3.0 is activated. Examine your bootstrap setup."); - } - } - let mut sortdb = SortitionDB::connect( &config.burnchain.get_db_path(), config.burnchain.first_block_height,