diff --git a/clarity/src/vm/contexts.rs b/clarity/src/vm/contexts.rs index 6fe59551c..3f9c5bf4f 100644 --- a/clarity/src/vm/contexts.rs +++ b/clarity/src/vm/contexts.rs @@ -74,7 +74,7 @@ pub struct Environment<'a, 'b, 'hooks> { } pub struct OwnedEnvironment<'a, 'hooks> { - context: GlobalContext<'a, 'hooks>, + pub(crate) context: GlobalContext<'a, 'hooks>, call_stack: CallStack, } @@ -1964,18 +1964,6 @@ impl CallStack { } } -#[cfg(any(test, feature = "testing"))] -impl<'a, 'hooks> OwnedEnvironment<'a, 'hooks> { - pub fn set_tenure_height(&mut self, tenure_height: u32) { - self.context.database.begin(); - self.context - .database - .set_tenure_height(tenure_height) - .unwrap(); - self.context.database.commit().unwrap(); - } -} - #[cfg(test)] mod test { use stacks_common::types::chainstate::StacksAddress; diff --git a/clarity/src/vm/database/clarity_db.rs b/clarity/src/vm/database/clarity_db.rs index f5baf6ed8..7a1aa3e3b 100644 --- a/clarity/src/vm/database/clarity_db.rs +++ b/clarity/src/vm/database/clarity_db.rs @@ -858,6 +858,12 @@ impl<'a> ClarityDatabase<'a> { /// Returns the tenure height of the current block. pub fn get_tenure_height(&mut self) -> Result { + if self.get_clarity_epoch_version()? < StacksEpochId::Epoch30 { + // Before epoch 3.0, the tenure height was not stored in the + // Clarity state. Instead, it was the same as the block height. + return Ok(self.get_current_block_height()); + } + self.get_data(TENURE_HEIGHT_KEY)? .ok_or_else(|| { InterpreterError::Expect("No tenure height in stored Clarity state".into()).into() @@ -874,6 +880,11 @@ impl<'a> ClarityDatabase<'a> { /// tenure, this height must be incremented before evaluating any /// transactions in the block. pub fn set_tenure_height(&mut self, height: u32) -> Result<()> { + if self.get_clarity_epoch_version()? < StacksEpochId::Epoch30 { + return Err(Error::Interpreter(InterpreterError::Expect( + "Setting tenure height in Clarity state is not supported before epoch 3.0".into(), + ))); + } self.put_data(TENURE_HEIGHT_KEY, &height) } diff --git a/clarity/src/vm/tests/mod.rs b/clarity/src/vm/tests/mod.rs index e8ab911f9..c60377ba3 100644 --- a/clarity/src/vm/tests/mod.rs +++ b/clarity/src/vm/tests/mod.rs @@ -35,6 +35,18 @@ mod simple_apply_eval; mod traits; mod variables; +#[cfg(any(test, feature = "testing"))] +impl<'a, 'hooks> OwnedEnvironment<'a, 'hooks> { + pub fn set_tenure_height(&mut self, tenure_height: u32) { + self.context.database.begin(); + self.context + .database + .set_tenure_height(tenure_height) + .unwrap(); + self.context.database.commit().unwrap(); + } +} + macro_rules! epochs_template { ($($epoch:ident,)*) => { #[template] @@ -160,7 +172,11 @@ impl MemoryEnvironmentGenerator { pub struct TopLevelMemoryEnvironmentGenerator(MemoryBackingStore); impl TopLevelMemoryEnvironmentGenerator { pub fn get_env(&mut self, epoch: StacksEpochId) -> OwnedEnvironment { - let mut owned_env = OwnedEnvironment::new(self.0.as_clarity_db(), epoch); + let mut db = self.0.as_clarity_db(); + db.begin(); + db.set_clarity_epoch_version(epoch).unwrap(); + db.commit().unwrap(); + let mut owned_env = OwnedEnvironment::new(db, epoch); if epoch >= StacksEpochId::Epoch30 { owned_env.set_tenure_height(1); } diff --git a/stackslib/src/chainstate/stacks/boot/contract_tests.rs b/stackslib/src/chainstate/stacks/boot/contract_tests.rs index d4c212bfb..5d8588836 100644 --- a/stackslib/src/chainstate/stacks/boot/contract_tests.rs +++ b/stackslib/src/chainstate/stacks/boot/contract_tests.rs @@ -164,11 +164,13 @@ impl ClarityTestSim { let cur_epoch = Self::check_and_bump_epoch(&mut store, &headers_db, &burn_db); let mut db = store.as_clarity_db(&headers_db, &burn_db); - db.begin(); - db.set_tenure_height(self.tenure_height as u32 + if new_tenure { 1 } else { 0 }) - .expect("FAIL: unable to set tenure height in Clarity database"); - db.commit() - .expect("FAIL: unable to commit tenure height in Clarity database"); + if cur_epoch >= StacksEpochId::Epoch30 { + db.begin(); + db.set_tenure_height(self.tenure_height as u32 + if new_tenure { 1 } else { 0 }) + .expect("FAIL: unable to set tenure height in Clarity database"); + db.commit() + .expect("FAIL: unable to commit tenure height in Clarity database"); + } let mut block_conn = ClarityBlockConnection::new_test_conn(store, &headers_db, &burn_db, cur_epoch); @@ -215,11 +217,13 @@ impl ClarityTestSim { debug!("Execute block in epoch {}", &cur_epoch); let mut db = store.as_clarity_db(&headers_db, &burn_db); - db.begin(); - db.set_tenure_height(self.tenure_height as u32 + if new_tenure { 1 } else { 0 }) - .expect("FAIL: unable to set tenure height in Clarity database"); - db.commit() - .expect("FAIL: unable to commit tenure height in Clarity database"); + if cur_epoch >= StacksEpochId::Epoch30 { + db.begin(); + db.set_tenure_height(self.tenure_height as u32 + if new_tenure { 1 } else { 0 }) + .expect("FAIL: unable to set tenure height in Clarity database"); + db.commit() + .expect("FAIL: unable to commit tenure height in Clarity database"); + } let mut owned_env = OwnedEnvironment::new_toplevel(db); f(&mut owned_env) }; diff --git a/stackslib/src/clarity_vm/tests/contracts.rs b/stackslib/src/clarity_vm/tests/contracts.rs index 039746379..95d7ff5b2 100644 --- a/stackslib/src/clarity_vm/tests/contracts.rs +++ b/stackslib/src/clarity_vm/tests/contracts.rs @@ -1395,3 +1395,61 @@ fn test_block_heights_across_versions_traits() { assert_eq!(Value::okay(Value::UInt(20)).unwrap(), res2.0); }); } + +#[test] +fn test_block_heights_at_block() { + let mut sim = ClarityTestSim::new(); + sim.epoch_bounds = vec![0, 1, 2, 3, 4, 5, 6, 7]; + + let contract_identifier = QualifiedContractIdentifier::local("test-contract").unwrap(); + + // Advance to epoch 3.0 + while sim.block_height <= 7 { + sim.execute_next_block(|_env| {}); + } + + let block_height = sim.block_height as u128; + sim.execute_next_block_as_conn(|conn| { + let epoch = conn.get_epoch(); + assert_eq!(epoch, StacksEpochId::Epoch30); + + let contract =r#" + (define-private (test-tenure) (at-block (unwrap-panic (get-block-info? id-header-hash u0)) tenure-height)) + (define-private (test-stacks) (at-block (unwrap-panic (get-block-info? id-header-hash u1)) stacks-block-height)) + "#; + + conn.as_transaction(|clarity_db| { + // Analyze the contract + let (ast, analysis) = clarity_db.analyze_smart_contract( + &contract_identifier, + ClarityVersion::Clarity3, + &contract, + ASTRules::PrecheckSize, + ).unwrap(); + + // Publish the contract + clarity_db + .initialize_smart_contract( + &contract_identifier, + ClarityVersion::Clarity3, + &ast, + contract, + None, + |_, _| false, + ).unwrap(); + }); + + // Call the contracts and validate the results + let mut tx = conn.start_transaction_processing(); + assert_eq!( + Value::UInt(0), + tx.eval_read_only(&contract_identifier, "(test-tenure)") + .unwrap() + ); + assert_eq!( + Value::UInt(1), + tx.eval_read_only(&contract_identifier, "(test-stacks)") + .unwrap() + ); + }); +} diff --git a/stackslib/src/clarity_vm/tests/large_contract.rs b/stackslib/src/clarity_vm/tests/large_contract.rs index ddb51d2bd..8db6b3043 100644 --- a/stackslib/src/clarity_vm/tests/large_contract.rs +++ b/stackslib/src/clarity_vm/tests/large_contract.rs @@ -101,8 +101,10 @@ fn new_block<'a, 'b>( block.as_free_transaction(|tx_conn| { tx_conn .with_clarity_db(|db| { - let tenure_height = db.get_tenure_height().unwrap_or(0); - db.set_tenure_height(tenure_height + 1).unwrap(); + if db.get_clarity_epoch_version().unwrap() >= StacksEpochId::Epoch30 { + let tenure_height = db.get_tenure_height().unwrap_or(0); + db.set_tenure_height(tenure_height + 1).unwrap(); + } Ok(()) }) .unwrap();