diff --git a/.github/workflows/stacks-core-tests.yml b/.github/workflows/stacks-core-tests.yml
index 32b2e0ed61..56adefa11c 100644
--- a/.github/workflows/stacks-core-tests.yml
+++ b/.github/workflows/stacks-core-tests.yml
@@ -11,6 +11,7 @@ env:
RUST_BACKTRACE: full
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 3
TEST_TIMEOUT: 30
+ CI: true # Required by insta snapshot tests to run in CI
concurrency:
group: stacks-core-tests-${{ github.head_ref || github.ref || github.run_id }}
diff --git a/Cargo.lock b/Cargo.lock
index 3165220437..500e15aaf3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -665,6 +665,18 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "console"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "const-oid"
version = "0.9.6"
@@ -868,6 +880,12 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
[[package]]
name = "encoding_rs"
version = "0.8.33"
@@ -1542,6 +1560,20 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
+[[package]]
+name = "insta"
+version = "1.41.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8"
+dependencies = [
+ "console",
+ "lazy_static",
+ "linked-hash-map",
+ "ron",
+ "serde",
+ "similar",
+]
+
[[package]]
name = "instant"
version = "0.1.12"
@@ -1797,6 +1829,12 @@ dependencies = [
"stacks-common 0.0.1 (git+https://github.com/stacks-network/stacks-core.git?rev=8a79aaa7df0f13dfc5ab0d0d0bcb8201c90bcba2)",
]
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
@@ -2580,6 +2618,17 @@ dependencies = [
"libc",
]
+[[package]]
+name = "ron"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
+dependencies = [
+ "base64 0.13.1",
+ "bitflags 1.3.2",
+ "serde",
+]
+
[[package]]
name = "rstest"
version = "0.17.0"
@@ -3313,6 +3362,7 @@ dependencies = [
"chrono",
"clarity 0.0.1",
"ed25519-dalek",
+ "insta",
"lazy_static",
"libstackerdb 0.0.1",
"mio 0.6.23",
diff --git a/Cargo.toml b/Cargo.toml
index edbaa79db6..26b166c483 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -55,3 +55,8 @@ lto = "fat"
[profile.release-lite]
inherits = "release"
lto = "thin"
+
+# faster tests for `insta` https://docs.rs/insta/1.43.2/insta/#optional-faster-runs
+[profile.dev.package]
+insta.opt-level = 3
+similar.opt-level = 3
diff --git a/stackslib/Cargo.toml b/stackslib/Cargo.toml
index 7f8209b582..6d2f85dfd0 100644
--- a/stackslib/Cargo.toml
+++ b/stackslib/Cargo.toml
@@ -79,6 +79,7 @@ rlimit = "0.10.2"
chrono = "0.4.19"
tempfile = "3.3"
proptest = { version = "1.6.0", default-features = false, features = ["std"] }
+insta = { version = "1.37.0", features = ["ron"] }
[features]
default = []
diff --git a/stackslib/src/chainstate/tests/consensus.rs b/stackslib/src/chainstate/tests/consensus.rs
index 2c5731e898..585b3f6a07 100644
--- a/stackslib/src/chainstate/tests/consensus.rs
+++ b/stackslib/src/chainstate/tests/consensus.rs
@@ -12,6 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use std::cell::LazyCell;
use std::collections::HashMap;
use clarity::boot_util::boot_code_addr;
@@ -26,11 +27,9 @@ use clarity::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKe
use clarity::types::{StacksEpoch, StacksEpochId};
use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum};
use clarity::util::secp256k1::MessageSignature;
-use clarity::vm::ast::errors::{ParseError, ParseErrors};
use clarity::vm::ast::stack_depth_checker::AST_CALL_STACK_DEPTH_BUFFER;
use clarity::vm::costs::ExecutionCost;
-use clarity::vm::events::StacksTransactionEvent;
-use clarity::vm::types::{PrincipalData, ResponseData};
+use clarity::vm::types::PrincipalData;
use clarity::vm::{Value as ClarityValue, MAX_CALL_STACK_DEPTH};
use serde::{Deserialize, Serialize};
use stacks_common::bitvec::BitVec;
@@ -38,18 +37,29 @@ use stacks_common::bitvec::BitVec;
use crate::burnchains::PoxConstants;
use crate::chainstate::burn::db::sortdb::SortitionDB;
use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader, NakamotoChainState};
-use crate::chainstate::stacks::boot::{RewardSet, RewardSetData};
+use crate::chainstate::stacks::boot::RewardSet;
use crate::chainstate::stacks::db::StacksEpochReceipt;
use crate::chainstate::stacks::{Error as ChainstateError, StacksTransaction, TenureChangeCause};
use crate::chainstate::tests::TestChainstate;
-use crate::clarity_vm::clarity::{Error as ClarityError, PreCommitClarityBlock};
-use crate::core::test_util::{make_contract_publish, make_stacks_transfer_tx};
+use crate::core::test_util::{
+ make_contract_call, make_contract_publish, make_stacks_transfer_tx, to_addr,
+};
use crate::core::{EpochList, BLOCK_LIMIT_MAINNET_21};
use crate::net::tests::NakamotoBootPlan;
+
pub const SK_1: &str = "a1289f6438855da7decf9b61b852c882c398cff1446b2a0f823538aa2ebef92e01";
pub const SK_2: &str = "4ce9a8f7539ea93753a36405b16e8b57e15a552430410709c2b6d65dca5c02e201";
pub const SK_3: &str = "cb95ddd0fe18ec57f4f3533b95ae564b3f1ae063dbf75b46334bd86245aef78501";
+/// The private key for the faucet account.
+pub const FAUCET_PRIV_KEY: LazyCell = LazyCell::new(|| {
+ StacksPrivateKey::from_hex("510f96a8efd0b11e211733c1ac5e3fa6f3d3fcdd62869e376c47decb3e14fea101")
+ .expect("Failed to parse private key")
+});
+
+const FOO_CONTRACT: &str = "(define-public (foo) (ok 1))
+ (define-public (bar (x uint)) (ok x))";
+
fn epoch_3_0_onwards(first_burnchain_height: u64) -> EpochList {
info!("StacksEpoch unit_test first_burn_height = {first_burnchain_height}");
@@ -170,6 +180,29 @@ pub enum ExpectedResult {
Failure(String),
}
+impl From> for ExpectedResult {
+ fn from(result: Result) -> Self {
+ match result {
+ Ok(epoch_receipt) => {
+ let transactions: Vec = epoch_receipt
+ .tx_receipts
+ .iter()
+ .map(|r| ExpectedTransactionOutput {
+ return_type: r.result.clone(),
+ cost: r.execution_cost.clone(),
+ })
+ .collect();
+ let total_block_cost = epoch_receipt.anchored_block_cost.clone();
+ ExpectedResult::Success(ExpectedBlockOutput {
+ transactions,
+ total_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 {
@@ -177,8 +210,6 @@ pub struct TestBlock {
pub marf_hash: String,
/// Transactions to include in the block
pub transactions: Vec,
- /// The expected result after appending the constructed block.
- pub expected_result: ExpectedResult,
}
/// Defines a test vector for a consensus test, including chainstate setup and expected outcomes.
@@ -190,170 +221,6 @@ pub struct ConsensusTestVector {
pub epoch_blocks: HashMap>,
}
-/// Tracks mismatches between actual and expected transaction results.
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
-pub struct TransactionMismatch {
- /// The index of the transaction with mismatches.
- pub index: u32,
- /// Mismatch between actual and expected return types, if any.
- pub return_type: Option<(ClarityValue, ClarityValue)>,
- /// Mismatch between actual and expected execution costs, if any.
- pub cost: Option<(ExecutionCost, ExecutionCost)>,
-}
-
-impl TransactionMismatch {
- /// Creates a new `TransactionMismatch` for the given transaction index.
- fn new(index: u32) -> Self {
- Self {
- index,
- return_type: None,
- cost: None,
- }
- }
-
- /// Adds a return type mismatch to the transaction.
- fn with_return_type_mismatch(mut self, actual: ClarityValue, expected: ClarityValue) -> Self {
- self.return_type = Some((actual, expected));
- self
- }
-
- /// Adds an execution cost mismatch to the transaction.
- fn with_cost_mismatch(mut self, actual: ExecutionCost, expected: ExecutionCost) -> Self {
- self.cost = Some((actual, expected));
- self
- }
-
- /// Returns true if no mismatches are recorded.
- fn is_empty(&self) -> bool {
- self.return_type.is_none() && self.cost.is_none()
- }
-}
-
-/// Aggregates all mismatches between actual and expected test results.
-#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
-pub struct ConsensusMismatch {
- /// Mismatches for individual transactions.
- pub transactions: Vec,
- /// Mismatch between actual and expected total block costs, if any.
- pub total_block_cost: Option<(ExecutionCost, ExecutionCost)>,
- /// Mismatch between actual and expected error messages, if any.
- pub error: Option<(String, String)>,
-}
-
-impl ConsensusMismatch {
- /// Creates a `ConsensusMismatch` from test results, if mismatches exist.
- pub fn from_test_result<'a>(
- append_result: Result<
- (
- StacksEpochReceipt,
- PreCommitClarityBlock<'a>,
- Option,
- Vec,
- ),
- ChainstateError,
- >,
- expected_result: ExpectedResult,
- ) -> Option {
- let mut mismatches = ConsensusMismatch::default();
- match (append_result, expected_result) {
- (Ok((epoch_receipt, clarity_commit, _, _)), ExpectedResult::Success(expected)) => {
- // Convert transaction receipts to `ExpectedTransactionOutput` for comparison.
- let actual_transactions: Vec<_> = epoch_receipt
- .tx_receipts
- .iter()
- .map(|r| {
- (
- r.tx_index,
- ExpectedTransactionOutput {
- return_type: r.result.clone(),
- cost: r.execution_cost.clone(),
- },
- )
- })
- .collect();
-
- // Compare each transaction's actual vs expected outputs.
- for ((tx_index, actual_tx), expected_tx) in
- actual_transactions.iter().zip(expected.transactions.iter())
- {
- let mut tx_mismatch = TransactionMismatch::new(*tx_index);
- let mut has_mismatch = false;
-
- if actual_tx.return_type != expected_tx.return_type {
- tx_mismatch = tx_mismatch.with_return_type_mismatch(
- actual_tx.return_type.clone(),
- expected_tx.return_type.clone(),
- );
- has_mismatch = true;
- }
-
- if actual_tx.cost != expected_tx.cost {
- tx_mismatch = tx_mismatch
- .with_cost_mismatch(actual_tx.cost.clone(), expected_tx.cost.clone());
- has_mismatch = true;
- }
-
- if has_mismatch {
- mismatches.add_transaction_mismatch(tx_mismatch);
- }
- }
-
- // Compare total block execution cost.
- if epoch_receipt.anchored_block_cost != expected.total_block_cost {
- mismatches.add_total_block_cost_mismatch(
- &epoch_receipt.anchored_block_cost,
- &expected.total_block_cost,
- );
- }
- // TODO: add any additional mismatches we might care about?
- clarity_commit.commit();
- }
- (Ok(_), ExpectedResult::Failure(expected_err)) => {
- mismatches.error = Some(("Ok".to_string(), expected_err));
- }
- (Err(actual_err), ExpectedResult::Failure(expected_err)) => {
- let actual_err_str = actual_err.to_string();
- if actual_err_str != expected_err {
- mismatches.error = Some((actual_err_str, expected_err));
- }
- }
- (Err(actual_err), ExpectedResult::Success(_)) => {
- mismatches.error = Some((actual_err.to_string(), "Success".into()));
- }
- }
-
- if mismatches.is_empty() {
- None
- } else {
- Some(mismatches)
- }
- }
-
- /// Adds a transaction mismatch to the collection.
- fn add_transaction_mismatch(&mut self, mismatch: TransactionMismatch) {
- self.transactions.push(mismatch);
- }
-
- /// Records a total block cost mismatch.
- fn add_total_block_cost_mismatch(&mut self, actual: &ExecutionCost, expected: &ExecutionCost) {
- self.total_block_cost = Some((actual.clone(), expected.clone()));
- }
-
- /// Returns true if no mismatches are recorded.
- pub fn is_empty(&self) -> bool {
- self.transactions.is_empty() && self.total_block_cost.is_none() && self.error.is_none()
- }
-
- /// Serializes the given `ConsensusMismatch` as pretty-printed JSON,
- /// or returns an empty string if `None`.
- pub fn to_json_string_pretty(mismatch: &Option) -> String {
- mismatch
- .as_ref()
- .map(|m| serde_json::to_string_pretty(m).unwrap())
- .unwrap_or("".into())
- }
-}
-
/// Represents a consensus test with chainstate and test vector.
pub struct ConsensusTest<'a> {
pub chain: TestChainstate<'a>,
@@ -379,29 +246,19 @@ impl ConsensusTest<'_> {
),
"Pre-Nakamoto Tenures are not Supported"
);
- for block in blocks {
- if let ExpectedResult::Success(output) = &block.expected_result {
- assert_eq!(
- output.transactions.len(),
- block.transactions.len(),
- "Test block is invalid. Must specify an expected output per input transaction"
- );
- }
- }
+ assert!(
+ !blocks.is_empty(),
+ "Each epoch must have at least one block"
+ );
}
- let privk = StacksPrivateKey::from_hex(
- "510f96a8efd0b11e211733c1ac5e3fa6f3d3fcdd62869e376c47decb3e14fea101",
- )
- .unwrap();
-
// 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)
let mut boot_plan = NakamotoBootPlan::new(test_name)
.with_pox_constants(100, 3)
.with_initial_balances(test_vector.initial_balances.clone())
- .with_private_key(privk);
+ .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
@@ -446,13 +303,17 @@ impl ConsensusTest<'_> {
}
}
- /// Runs the consensus test for the test vector, advancing epochs as needed.
- pub fn run(mut self) {
+ /// Runs the consensus test.
+ ///
+ /// This method constructs a block from the test vector, appends it to the
+ /// chain, and returns the result of the block processing.
+ pub fn run(mut self) -> Vec {
// Get sorted epochs
let mut epochs: Vec =
self.test_vector.epoch_blocks.keys().cloned().collect();
epochs.sort();
+ let mut results = vec![];
for epoch in epochs {
debug!(
"--------- Processing epoch {epoch:?} with {} blocks ---------",
@@ -471,7 +332,6 @@ impl ConsensusTest<'_> {
.unwrap()
.unwrap();
let pox_constants = PoxConstants::test_default();
-
debug!(
"--------- Appending block {} ---------",
nakamoto_block.header.signer_signature_hash();
@@ -506,14 +366,13 @@ impl ConsensusTest<'_> {
);
debug!("--------- Appended block: {} ---------", result.is_ok());
-
- // Compare actual vs expected results.
- let mismatches =
- ConsensusMismatch::from_test_result(result, block.expected_result.clone());
- assert!(
- mismatches.is_none(),
- "Mismatches found in block {i} for epoch {epoch:?}: {}",
- ConsensusMismatch::to_json_string_pretty(&mismatches)
+ results.push(
+ result
+ .map(|(receipt, clarity_commit, _, _)| {
+ clarity_commit.commit();
+ receipt
+ })
+ .into(),
);
chainstate_tx.commit().unwrap();
}
@@ -522,6 +381,7 @@ impl ConsensusTest<'_> {
self.chain.sortdb = Some(sortdb);
}
}
+ results
}
/// Constructs a Nakamoto block with the given transactions and state index root.
@@ -583,16 +443,11 @@ impl ConsensusTest<'_> {
#[test]
fn test_append_empty_blocks() {
let mut epoch_blocks = HashMap::new();
- let expected_result = ExpectedResult::Success(ExpectedBlockOutput {
- transactions: vec![],
- total_block_cost: ExecutionCost::ZERO,
- });
epoch_blocks.insert(
StacksEpochId::Epoch30,
vec![TestBlock {
marf_hash: "f1934080b22ef0192cfb39710690e7cb0efa9cff950832b33544bde3aa1484a5".into(),
transactions: vec![],
- expected_result: expected_result.clone(),
}],
);
epoch_blocks.insert(
@@ -600,7 +455,6 @@ fn test_append_empty_blocks() {
vec![TestBlock {
marf_hash: "a05f1383613215f5789eb977e4c62dfbb789d90964e14865d109375f7f6dc3cf".into(),
transactions: vec![],
- expected_result: expected_result.clone(),
}],
);
epoch_blocks.insert(
@@ -608,7 +462,6 @@ fn test_append_empty_blocks() {
vec![TestBlock {
marf_hash: "c17829daff8746329c65ae658f4087519c6a8bd8c7f21e51644ddbc9c010390f".into(),
transactions: vec![],
- expected_result: expected_result.clone(),
}],
);
epoch_blocks.insert(
@@ -616,15 +469,15 @@ fn test_append_empty_blocks() {
vec![TestBlock {
marf_hash: "23ecbcb91cac914ba3994a15f3ea7189bcab4e9762530cd0e6c7d237fcd6dc78".into(),
transactions: vec![],
- expected_result: expected_result.clone(),
}],
);
let test_vector = ConsensusTestVector {
- initial_balances: Vec::new(),
+ initial_balances: vec![],
epoch_blocks,
};
- ConsensusTest::new(function_name!(), test_vector).run();
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ insta::assert_ron_snapshot!(result);
}
#[test]
@@ -635,12 +488,6 @@ fn test_append_state_index_root_mismatches() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(
- "Block ef45bfa44231d9e7aff094b53cfd48df0456067312f169a499354c4273a66fe3 state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got f1934080b22ef0192cfb39710690e7cb0efa9cff950832b33544bde3aa1484a5".into(),
- )
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -648,12 +495,6 @@ fn test_append_state_index_root_mismatches() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(
- "Block a14d0b5c8d3c49554aeb462a8fe019718195789fa1dcd642059b75e41f0ce9cc state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got a05f1383613215f5789eb977e4c62dfbb789d90964e14865d109375f7f6dc3cf".into(),
- )
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -661,12 +502,6 @@ fn test_append_state_index_root_mismatches() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(
- "Block f8120b4a632ee1d49fbbde3e01289588389cd205cab459a4493a7d58d2dc18ed state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got c17829daff8746329c65ae658f4087519c6a8bd8c7f21e51644ddbc9c010390f".into(),
- )
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -674,20 +509,15 @@ fn test_append_state_index_root_mismatches() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(
- "Block 4dcb48b684d105ff0e0ab8becddd4a2d5623cc8b168aacf9c455e20b3e610e63 state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got 23ecbcb91cac914ba3994a15f3ea7189bcab4e9762530cd0e6c7d237fcd6dc78".into(),
- )
- .to_string(),
- ),
}],
);
let test_vector = ConsensusTestVector {
- initial_balances: Vec::new(),
+ initial_balances: vec![],
epoch_blocks,
};
- ConsensusTest::new(function_name!(), test_vector).run();
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ insta::assert_ron_snapshot!(result);
}
#[test]
@@ -718,34 +548,13 @@ fn test_append_stx_transfers_success() {
)
})
.collect();
- let transfer_result = ExpectedTransactionOutput {
- return_type: ClarityValue::Response(ResponseData {
- committed: true,
- data: Box::new(ClarityValue::Bool(true)),
- }),
- cost: ExecutionCost {
- write_length: 0,
- write_count: 0,
- read_length: 0,
- read_count: 0,
- runtime: 0,
- },
- };
- let outputs = ExpectedBlockOutput {
- transactions: vec![
- transfer_result.clone(),
- transfer_result.clone(),
- transfer_result,
- ],
- total_block_cost: ExecutionCost::ZERO,
- };
+
let mut epoch_blocks = HashMap::new();
epoch_blocks.insert(
StacksEpochId::Epoch30,
vec![TestBlock {
marf_hash: "63ea49669d2216ebc7e4f8b5e1cd2c99b8aff9806794adf87dcf709c0a244798".into(),
transactions: transactions.clone(),
- expected_result: ExpectedResult::Success(outputs.clone()),
}],
);
epoch_blocks.insert(
@@ -753,7 +562,6 @@ fn test_append_stx_transfers_success() {
vec![TestBlock {
marf_hash: "7fc538e605a4a353871c4a655ae850fe9a70c3875b65f2bb42ea3bef5effed2c".into(),
transactions: transactions.clone(),
- expected_result: ExpectedResult::Success(outputs.clone()),
}],
);
epoch_blocks.insert(
@@ -761,7 +569,6 @@ fn test_append_stx_transfers_success() {
vec![TestBlock {
marf_hash: "4d5c9a6d07806ac5006137de22b083de66fff7119143dd5cd92e4a457d66e028".into(),
transactions: transactions.clone(),
- expected_result: ExpectedResult::Success(outputs.clone()),
}],
);
epoch_blocks.insert(
@@ -769,7 +576,6 @@ fn test_append_stx_transfers_success() {
vec![TestBlock {
marf_hash: "66eed8c0ab31db111a5adcc83d38a7004c6e464e3b9fb9f52ec589bc6d5f2d32".into(),
transactions: transactions.clone(),
- expected_result: ExpectedResult::Success(outputs.clone()),
}],
);
@@ -777,12 +583,13 @@ fn test_append_stx_transfers_success() {
initial_balances,
epoch_blocks,
};
- ConsensusTest::new(function_name!(), test_vector).run();
+
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ insta::assert_ron_snapshot!(result);
}
#[test]
fn test_append_chainstate_error_expression_stack_depth_too_deep() {
- let sender_privk = StacksPrivateKey::from_hex(SK_1).unwrap();
let exceeds_repeat_factor = AST_CALL_STACK_DEPTH_BUFFER + (MAX_CALL_STACK_DEPTH as u64);
let tx_exceeds_body_start = "{ a : ".repeat(exceeds_repeat_factor as usize);
let tx_exceeds_body_end = "} ".repeat(exceeds_repeat_factor as usize);
@@ -790,7 +597,7 @@ fn test_append_chainstate_error_expression_stack_depth_too_deep() {
let tx_fee = (tx_exceeds_body.len() * 100) as u64;
let tx_bytes = make_contract_publish(
- &sender_privk,
+ &FAUCET_PRIV_KEY,
0,
tx_fee,
CHAIN_ID_TESTNET,
@@ -799,25 +606,13 @@ fn test_append_chainstate_error_expression_stack_depth_too_deep() {
);
let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
- let initial_balances = vec![(
- StacksAddress::p2pkh(false, &StacksPublicKey::from_private(&sender_privk)).into(),
- tx_fee,
- )];
- let e = ChainstateError::ClarityError(ClarityError::Parse(ParseError::new(
- ParseErrors::ExpressionStackDepthTooDeep,
- )));
+
let mut epoch_blocks = HashMap::new();
epoch_blocks.insert(
StacksEpochId::Epoch30,
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![tx.clone()],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(format!(
- "Invalid Stacks block ff0796f9934d45aad71871f317061acb99dd5ef1237a8747a78624a2824f7d32: {e:?}"
- ))
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -825,12 +620,6 @@ fn test_append_chainstate_error_expression_stack_depth_too_deep() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![tx.clone()],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(format!(
- "Invalid Stacks block 9da03cdc774989cea30445f1453073b070430867edcecb180d1cc9a6e9738b46: {e:?}"
- ))
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -838,12 +627,6 @@ fn test_append_chainstate_error_expression_stack_depth_too_deep() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![tx.clone()],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(format!(
- "Invalid Stacks block 76a6d95b3ec273a13f10080b3b18e225cc838044c5e3a3000b7ccdd8b50a5ae1: {e:?}"
- ))
- .to_string(),
- ),
}],
);
epoch_blocks.insert(
@@ -851,18 +634,172 @@ fn test_append_chainstate_error_expression_stack_depth_too_deep() {
vec![TestBlock {
marf_hash: "0000000000000000000000000000000000000000000000000000000000000000".into(),
transactions: vec![tx.clone()],
- expected_result: ExpectedResult::Failure(
- ChainstateError::InvalidStacksBlock(format!(
- "Invalid Stacks block de3c507ab60e717275f97f267ec2608c96aaab42a7e32fc2d8129585dff9e74a: {e:?}"
- ))
- .to_string(),
- ),
}],
);
let test_vector = ConsensusTestVector {
- initial_balances,
+ initial_balances: vec![],
epoch_blocks,
};
- ConsensusTest::new(function_name!(), test_vector).run();
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ insta::assert_ron_snapshot!(result);
+}
+
+#[test]
+fn test_append_block_with_contract_upload_success() {
+ let contract_name = "test-contract";
+ let contract_content = "(/ 1 1)";
+ let tx_fee = (contract_content.len() * 100) as u64;
+
+ let tx_bytes = make_contract_publish(
+ &FAUCET_PRIV_KEY,
+ 0,
+ tx_fee,
+ CHAIN_ID_TESTNET,
+ contract_name,
+ &contract_content,
+ );
+ let tx = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
+
+ let mut epoch_blocks = HashMap::new();
+ epoch_blocks.insert(
+ StacksEpochId::Epoch30,
+ vec![TestBlock {
+ marf_hash: "b45acd35f4c48a834a2f898ca8bb6c48416ac6bec9d8a3f3662b61ab97b1edde".into(),
+ transactions: vec![tx.clone()],
+ }],
+ );
+ epoch_blocks.insert(
+ StacksEpochId::Epoch31,
+ vec![TestBlock {
+ marf_hash: "521d75234ec6c64f68648b6b0f6f385d89b58efb581211a411e0e88aa71f3371".into(),
+ transactions: vec![tx.clone()],
+ }],
+ );
+ epoch_blocks.insert(
+ StacksEpochId::Epoch32,
+ vec![TestBlock {
+ marf_hash: "511e1cc37e83ef3de4ea56962574d6ddd2d8840d24d9238f19eee5a35127df6a".into(),
+ transactions: vec![tx.clone()],
+ }],
+ );
+ epoch_blocks.insert(
+ StacksEpochId::Epoch33,
+ vec![TestBlock {
+ marf_hash: "3520c2dd96f7d91e179c4dcd00f3c49c16d6ec21434fb16921922558282eab26".into(),
+ transactions: vec![tx.clone()],
+ }],
+ );
+ let test_vector = ConsensusTestVector {
+ initial_balances: vec![],
+ epoch_blocks,
+ };
+
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ // Example of expecting the same result across all blocks
+ insta::allow_duplicates! {
+ for res in result {
+ // Example of inline snapshot
+ insta::assert_ron_snapshot!(res, @r"
+ Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ return_type: Response(ResponseData(
+ committed: true,
+ data: Bool(true),
+ )),
+ cost: ExecutionCost(
+ write_length: 13,
+ write_count: 2,
+ read_length: 1,
+ read_count: 1,
+ runtime: 8114,
+ ),
+ ),
+ ],
+ total_block_cost: ExecutionCost(
+ write_length: 13,
+ write_count: 2,
+ read_length: 1,
+ read_count: 1,
+ runtime: 8114,
+ ),
+ ))
+ ");
+ }
+ }
+}
+
+#[test]
+fn test_append_block_with_contract_call_success() {
+ let tx_fee = (FOO_CONTRACT.len() * 100) as u64;
+
+ let tx_bytes = make_contract_publish(
+ &FAUCET_PRIV_KEY,
+ 0,
+ tx_fee,
+ CHAIN_ID_TESTNET,
+ "foo_contract",
+ FOO_CONTRACT,
+ );
+ let tx_contract_deploy =
+ StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap();
+
+ let tx_bytes = make_contract_call(
+ &FAUCET_PRIV_KEY,
+ 1,
+ 200,
+ CHAIN_ID_TESTNET,
+ &to_addr(&FAUCET_PRIV_KEY),
+ "foo_contract",
+ "bar",
+ &[ClarityValue::UInt(1)],
+ );
+ let tx_contract_call =
+ StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap();
+
+ let mut epoch_blocks = HashMap::new();
+ epoch_blocks.insert(
+ StacksEpochId::Epoch30,
+ vec![TestBlock {
+ marf_hash: "186c8e49bcfc59bb67ed22f031f009a44681f296392e0f92bed520918ba463ae".into(),
+ transactions: vec![tx_contract_deploy.clone(), tx_contract_call.clone()],
+ }],
+ );
+
+ epoch_blocks.insert(
+ StacksEpochId::Epoch31,
+ vec![TestBlock {
+ marf_hash: "ad23713f072473cad6a32125ed5fa822bb62bbfae8ed2302209c12d2f1958128".into(),
+ transactions: vec![tx_contract_deploy.clone(), tx_contract_call.clone()],
+ }],
+ );
+
+ epoch_blocks.insert(
+ StacksEpochId::Epoch32,
+ vec![TestBlock {
+ marf_hash: "021bd30b09b5ac6ff34abd11f05244a966af937b584b1752f272cd717bb25f1d".into(),
+ transactions: vec![tx_contract_deploy.clone(), tx_contract_call.clone()],
+ }],
+ );
+
+ epoch_blocks.insert(
+ StacksEpochId::Epoch33,
+ vec![TestBlock {
+ marf_hash: "416e728daeec4de695c89d15eede8ddb7b85fb4af82daffb1e0d8166a3e93451".into(),
+ transactions: vec![tx_contract_deploy, tx_contract_call],
+ }],
+ );
+
+ let test_vector = ConsensusTestVector {
+ initial_balances: vec![],
+ epoch_blocks,
+ };
+
+ let result = ConsensusTest::new(function_name!(), test_vector).run();
+ insta::allow_duplicates! {
+ for res in result {
+ insta::assert_ron_snapshot!(res);
+ }
+ }
}
diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_block_with_contract_call_success.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_block_with_contract_call_success.snap
new file mode 100644
index 0000000000..f50380ee57
--- /dev/null
+++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_block_with_contract_call_success.snap
@@ -0,0 +1,41 @@
+---
+source: stackslib/src/chainstate/tests/consensus.rs
+expression: res
+---
+Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ 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,
+ ),
+ ),
+ ExpectedTransactionOutput(
+ 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: 121,
+ write_count: 2,
+ read_length: 104,
+ read_count: 4,
+ runtime: 12467,
+ ),
+))
diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_chainstate_error_expression_stack_depth_too_deep.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_chainstate_error_expression_stack_depth_too_deep.snap
new file mode 100644
index 0000000000..6089465bfa
--- /dev/null
+++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_chainstate_error_expression_stack_depth_too_deep.snap
@@ -0,0 +1,10 @@
+---
+source: stackslib/src/chainstate/tests/consensus.rs
+expression: result
+---
+[
+ Failure("Invalid Stacks block a60c62267d58f1ea29c64b2f86d62cf210ff5ab14796abfa947ca6d95007d440: 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 238f2ce280580228f19c8122a9bdd0c61299efabe59d8c22c315ee40a865cc7b: 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 b5dd8cdc0f48b30d355a950077f7c9b20bf01062e9c96262c28f17fff55a2b0f: 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 cfbddc874c465753158a065eff61340e933d33671633843dde0fbd2bfaaac7a4: 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__append_empty_blocks.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_empty_blocks.snap
new file mode 100644
index 0000000000..017c5a91da
--- /dev/null
+++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_empty_blocks.snap
@@ -0,0 +1,46 @@
+---
+source: stackslib/src/chainstate/tests/consensus.rs
+expression: result
+---
+[
+ Success(ExpectedBlockOutput(
+ transactions: [],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+]
diff --git a/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_state_index_root_mismatches.snap b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_state_index_root_mismatches.snap
new file mode 100644
index 0000000000..1d73839536
--- /dev/null
+++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_state_index_root_mismatches.snap
@@ -0,0 +1,10 @@
+---
+source: stackslib/src/chainstate/tests/consensus.rs
+expression: result
+---
+[
+ Failure("Block ef45bfa44231d9e7aff094b53cfd48df0456067312f169a499354c4273a66fe3 state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got f1934080b22ef0192cfb39710690e7cb0efa9cff950832b33544bde3aa1484a5"),
+ Failure("Block a14d0b5c8d3c49554aeb462a8fe019718195789fa1dcd642059b75e41f0ce9cc state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got a05f1383613215f5789eb977e4c62dfbb789d90964e14865d109375f7f6dc3cf"),
+ Failure("Block f8120b4a632ee1d49fbbde3e01289588389cd205cab459a4493a7d58d2dc18ed state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got c17829daff8746329c65ae658f4087519c6a8bd8c7f21e51644ddbc9c010390f"),
+ Failure("Block 4dcb48b684d105ff0e0ab8becddd4a2d5623cc8b168aacf9c455e20b3e610e63 state root mismatch: expected 0000000000000000000000000000000000000000000000000000000000000000, got 23ecbcb91cac914ba3994a15f3ea7189bcab4e9762530cd0e6c7d237fcd6dc78"),
+]
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
new file mode 100644
index 0000000000..dd47089fbb
--- /dev/null
+++ b/stackslib/src/chainstate/tests/snapshots/blockstack_lib__chainstate__tests__consensus__append_stx_transfers_success.snap
@@ -0,0 +1,206 @@
+---
+source: stackslib/src/chainstate/tests/consensus.rs
+expression: result
+---
+[
+ Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ 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(
+ 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(
+ 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,
+ ),
+ ),
+ ],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ 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(
+ 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(
+ 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,
+ ),
+ ),
+ ],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ 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(
+ 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(
+ 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,
+ ),
+ ),
+ ],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+ Success(ExpectedBlockOutput(
+ transactions: [
+ ExpectedTransactionOutput(
+ 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(
+ 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(
+ 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,
+ ),
+ ),
+ ],
+ total_block_cost: ExecutionCost(
+ write_length: 0,
+ write_count: 0,
+ read_length: 0,
+ read_count: 0,
+ runtime: 0,
+ ),
+ )),
+]