diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 66adabc451..212ded7b76 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -101,6 +101,7 @@ jobs: - tests::epoch_22::pox_2_unlock_all - tests::epoch_22::disable_pox - tests::epoch_22::test_pox_reorg_one_flap + - tests::epoch_23::trait_invocation_behavior - tests::neon_integrations::bad_microblock_pubkey steps: - uses: actions/checkout@v2 diff --git a/clarity/src/vm/analysis/mod.rs b/clarity/src/vm/analysis/mod.rs index 443fbc1954..d94e2fd1b3 100644 --- a/clarity/src/vm/analysis/mod.rs +++ b/clarity/src/vm/analysis/mod.rs @@ -137,7 +137,7 @@ pub fn run_analysis( StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => { TypeChecker2_05::run_pass(&epoch, &mut contract_analysis, db) } - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { TypeChecker2_1::run_pass(&epoch, &mut contract_analysis, db) } StacksEpochId::Epoch10 => unreachable!("Epoch 1.0 is not a valid epoch for analysis"), diff --git a/clarity/src/vm/analysis/trait_checker/tests.rs b/clarity/src/vm/analysis/trait_checker/tests.rs index d0bffe2c10..42f42777c0 100644 --- a/clarity/src/vm/analysis/trait_checker/tests.rs +++ b/clarity/src/vm/analysis/trait_checker/tests.rs @@ -34,6 +34,10 @@ use stacks_common::types::StacksEpochId; #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch2_05)] #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch21)] #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch21)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch23)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch23)] fn test_clarity_versions_trait_checker( #[case] version: ClarityVersion, #[case] epoch: StacksEpochId, diff --git a/clarity/src/vm/analysis/type_checker/mod.rs b/clarity/src/vm/analysis/type_checker/mod.rs index 8286cbdc87..bbcd9270cb 100644 --- a/clarity/src/vm/analysis/type_checker/mod.rs +++ b/clarity/src/vm/analysis/type_checker/mod.rs @@ -50,7 +50,7 @@ impl FunctionType { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => { self.check_args_2_05(accounting, args) } - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { self.check_args_2_1(accounting, args, clarity_version) } StacksEpochId::Epoch10 => unreachable!("Epoch10 is not supported"), @@ -68,7 +68,7 @@ impl FunctionType { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => { self.check_args_by_allowing_trait_cast_2_05(db, func_args) } - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { self.check_args_by_allowing_trait_cast_2_1(db, clarity_version, func_args) } StacksEpochId::Epoch10 => unreachable!("Epoch10 is not supported"), diff --git a/clarity/src/vm/analysis/type_checker/v2_1/tests/mod.rs b/clarity/src/vm/analysis/type_checker/v2_1/tests/mod.rs index 8fdfefc6b6..cdbdf8d85f 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/tests/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/tests/mod.rs @@ -58,6 +58,10 @@ pub mod contracts; #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch2_05)] #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch21)] #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch21)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch23)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch23)] fn test_clarity_versions_type_checker( #[case] version: ClarityVersion, #[case] epoch: StacksEpochId, diff --git a/clarity/src/vm/costs/mod.rs b/clarity/src/vm/costs/mod.rs index e8998d0759..dfb7cd81a6 100644 --- a/clarity/src/vm/costs/mod.rs +++ b/clarity/src/vm/costs/mod.rs @@ -699,7 +699,9 @@ impl LimitedCostTracker { } StacksEpochId::Epoch20 => COSTS_1_NAME.to_string(), StacksEpochId::Epoch2_05 => COSTS_2_NAME.to_string(), - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => COSTS_3_NAME.to_string(), + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { + COSTS_3_NAME.to_string() + } } } } diff --git a/clarity/src/vm/functions/mod.rs b/clarity/src/vm/functions/mod.rs index 21e6fcdfb2..e2d0f5c6e4 100644 --- a/clarity/src/vm/functions/mod.rs +++ b/clarity/src/vm/functions/mod.rs @@ -58,6 +58,8 @@ macro_rules! switch_on_global_epoch { StacksEpochId::Epoch21 => $Epoch205Version(args, env, context), // Note: We reuse 2.05 for 2.2. StacksEpochId::Epoch22 => $Epoch205Version(args, env, context), + // Note: We reuse 2.05 for 2.3. + StacksEpochId::Epoch23 => $Epoch205Version(args, env, context), } } }; diff --git a/clarity/src/vm/tests/traits.rs b/clarity/src/vm/tests/traits.rs index 6e1e7fa2d0..0ee695c837 100644 --- a/clarity/src/vm/tests/traits.rs +++ b/clarity/src/vm/tests/traits.rs @@ -37,6 +37,10 @@ use crate::vm::ContractContext; #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch2_05)] #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch21)] #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch21)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch22)] +#[case(ClarityVersion::Clarity1, StacksEpochId::Epoch23)] +#[case(ClarityVersion::Clarity2, StacksEpochId::Epoch23)] fn test_epoch_clarity_versions(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {} #[apply(test_epoch_clarity_versions)] diff --git a/clarity/src/vm/types/signatures.rs b/clarity/src/vm/types/signatures.rs index 446a2cb5f2..b2ec129a76 100644 --- a/clarity/src/vm/types/signatures.rs +++ b/clarity/src/vm/types/signatures.rs @@ -529,7 +529,9 @@ impl TypeSignature { pub fn admits_type(&self, epoch: &StacksEpochId, other: &TypeSignature) -> Result { match epoch { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => self.admits_type_v2_0(&other), - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => self.admits_type_v2_1(other), + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { + self.admits_type_v2_1(other) + } StacksEpochId::Epoch10 => unreachable!("epoch 1.0 not supported"), } } @@ -722,8 +724,13 @@ impl TypeSignature { /// types for the specified epoch. pub fn canonicalize(&self, epoch: &StacksEpochId) -> TypeSignature { match epoch { - StacksEpochId::Epoch21 => self.canonicalize_v2_1(), - _ => self.clone(), + StacksEpochId::Epoch10 + | StacksEpochId::Epoch20 + | StacksEpochId::Epoch2_05 + // Epoch-2.2 had a regression in canonicalization, so it must be preserved here. + | StacksEpochId::Epoch22 => self.clone(), + // Note for future epochs: Epochs >= 2.3 should use the canonicalize_v2_1() routine + StacksEpochId::Epoch21 | StacksEpochId::Epoch23 => self.canonicalize_v2_1(), } } @@ -1045,7 +1052,9 @@ impl TypeSignature { ) -> Result { match epoch { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => Self::least_supertype_v2_0(a, b), - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => Self::least_supertype_v2_1(a, b), + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { + Self::least_supertype_v2_1(a, b) + } StacksEpochId::Epoch10 => unreachable!("Clarity 1.0 is not supported"), } } @@ -1932,6 +1941,10 @@ mod test { #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch2_05)] #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch21)] #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch21)] + #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch22)] + #[case(ClarityVersion::Clarity2, StacksEpochId::Epoch23)] + #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch22)] + #[case(ClarityVersion::Clarity1, StacksEpochId::Epoch23)] fn test_clarity_versions_signatures( #[case] version: ClarityVersion, #[case] epoch: StacksEpochId, diff --git a/clarity/src/vm/version.rs b/clarity/src/vm/version.rs index 3b667fd507..46ad1500aa 100644 --- a/clarity/src/vm/version.rs +++ b/clarity/src/vm/version.rs @@ -32,6 +32,7 @@ impl ClarityVersion { StacksEpochId::Epoch2_05 => ClarityVersion::Clarity1, StacksEpochId::Epoch21 => ClarityVersion::Clarity2, StacksEpochId::Epoch22 => ClarityVersion::Clarity2, + StacksEpochId::Epoch23 => ClarityVersion::Clarity2, } } } diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index c0eae3046e..3355191901 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -501,7 +501,7 @@ impl FromRow for StacksEpoch { } } -pub const SORTITION_DB_VERSION: &'static str = "5"; +pub const SORTITION_DB_VERSION: &'static str = "6"; const SORTITION_DB_INITIAL_SCHEMA: &'static [&'static str] = &[ r#" @@ -707,6 +707,9 @@ const SORTITION_DB_SCHEMA_4: &'static [&'static str] = &[ const SORTITION_DB_SCHEMA_5: &'static [&'static str] = &[r#" DELETE FROM epochs;"#]; +const SORTITION_DB_SCHEMA_6: &'static [&'static str] = &[r#" + DELETE FROM epochs;"#]; + // update this to add new indexes const LAST_SORTITION_DB_INDEX: &'static str = "index_delegate_stx_burn_header_hash"; @@ -2663,6 +2666,7 @@ impl SortitionDB { SortitionDB::apply_schema_3(&db_tx)?; SortitionDB::apply_schema_4(&db_tx)?; SortitionDB::apply_schema_5(&db_tx, epochs_ref)?; + SortitionDB::apply_schema_6(&db_tx, epochs_ref)?; db_tx.instantiate_index()?; @@ -2856,12 +2860,24 @@ impl SortitionDB { || version == "3" || version == "4" || version == "5" + || version == "6" } StacksEpochId::Epoch2_05 => { - version == "2" || version == "3" || version == "4" || version == "5" + version == "2" + || version == "3" + || version == "4" + || version == "5" + || version == "6" + } + StacksEpochId::Epoch21 => { + version == "3" || version == "4" || version == "5" || version == "6" + } + StacksEpochId::Epoch22 => { + version == "3" || version == "4" || version == "5" || version == "6" + } + StacksEpochId::Epoch23 => { + version == "3" || version == "4" || version == "5" || version == "6" } - StacksEpochId::Epoch21 => version == "3" || version == "4" || version == "5", - StacksEpochId::Epoch22 => version == "3" || version == "4" || version == "5", } } @@ -2948,6 +2964,21 @@ impl SortitionDB { Ok(()) } + fn apply_schema_6(tx: &DBTx, epochs: &[StacksEpoch]) -> Result<(), db_error> { + for sql_exec in SORTITION_DB_SCHEMA_6 { + tx.execute_batch(sql_exec)?; + } + + SortitionDB::validate_and_insert_epochs(&tx, epochs)?; + + tx.execute( + "INSERT OR REPLACE INTO db_config (version) VALUES (?1)", + &["6"], + )?; + + Ok(()) + } + fn check_schema_version_or_error(&mut self) -> Result<(), db_error> { match SortitionDB::get_schema_version(self.conn()) { Ok(Some(version)) => { @@ -2990,6 +3021,10 @@ impl SortitionDB { let tx = self.tx_begin()?; SortitionDB::apply_schema_5(&tx.deref(), epochs)?; tx.commit()?; + } else if version == "5" { + let tx = self.tx_begin()?; + SortitionDB::apply_schema_6(&tx.deref(), epochs)?; + tx.commit()?; } else if version == expected_version { return Ok(()); } else { diff --git a/src/chainstate/burn/operations/leader_block_commit.rs b/src/chainstate/burn/operations/leader_block_commit.rs index 2e9ece5cb3..6f00e186b3 100644 --- a/src/chainstate/burn/operations/leader_block_commit.rs +++ b/src/chainstate/burn/operations/leader_block_commit.rs @@ -39,6 +39,7 @@ use crate::chainstate::stacks::index::storage::TrieFileStorage; use crate::chainstate::stacks::{StacksPrivateKey, StacksPublicKey}; use crate::codec::{write_next, Error as codec_error, StacksMessageCodec}; use crate::core::STACKS_EPOCH_2_2_MARKER; +use crate::core::STACKS_EPOCH_2_3_MARKER; use crate::core::{StacksEpoch, StacksEpochId}; use crate::core::{STACKS_EPOCH_2_05_MARKER, STACKS_EPOCH_2_1_MARKER}; use crate::net::Error as net_error; @@ -755,6 +756,7 @@ impl LeaderBlockCommitOp { StacksEpochId::Epoch2_05 => self.check_epoch_commit_marker(STACKS_EPOCH_2_05_MARKER), StacksEpochId::Epoch21 => self.check_epoch_commit_marker(STACKS_EPOCH_2_1_MARKER), StacksEpochId::Epoch22 => self.check_epoch_commit_marker(STACKS_EPOCH_2_2_MARKER), + StacksEpochId::Epoch23 => self.check_epoch_commit_marker(STACKS_EPOCH_2_3_MARKER), } } @@ -769,7 +771,7 @@ impl LeaderBlockCommitOp { ) -> Result { let tx_tip = tx.context.chain_tip.clone(); let intended_sortition = match epoch_id { - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { // correct behavior -- uses *sortition height* to find the intended sortition ID let sortition_height = self .block_height diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index c033254e07..16f8f30c1b 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -2991,8 +2991,10 @@ impl< return Ok(Some(pox_anchor)); } } - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { - // 2.1 behavior: the anchor block must also be the + StacksEpochId::Epoch21 + | StacksEpochId::Epoch22 + | StacksEpochId::Epoch23 => { + // 2.1 and onward behavior: the anchor block must also be the // heaviest-confirmed anchor block by BTC weight, and the highest // such anchor block if there are multiple contenders. if let Some(pox_anchor) = diff --git a/src/chainstate/stacks/boot/contract_tests.rs b/src/chainstate/stacks/boot/contract_tests.rs index 5b565f64ae..55be1ff722 100644 --- a/src/chainstate/stacks/boot/contract_tests.rs +++ b/src/chainstate/stacks/boot/contract_tests.rs @@ -97,7 +97,7 @@ lazy_static! { pub struct ClarityTestSim { marf: MarfedKV, - height: u64, + pub height: u64, fork: u64, /// This vec specifies the transitions for each epoch. /// It is a list of heights at which the simulated chain transitions @@ -379,6 +379,8 @@ impl BurnStateDB for TestSimBurnStateDB { 0 => StacksEpochId::Epoch20, 1 => StacksEpochId::Epoch2_05, 2 => StacksEpochId::Epoch21, + 3 => StacksEpochId::Epoch22, + 4 => StacksEpochId::Epoch23, _ => panic!("Epoch unknown"), }; diff --git a/src/chainstate/stacks/db/blocks.rs b/src/chainstate/stacks/db/blocks.rs index 512a1cf4e0..440029166f 100644 --- a/src/chainstate/stacks/db/blocks.rs +++ b/src/chainstate/stacks/db/blocks.rs @@ -4892,6 +4892,13 @@ impl StacksChainState { receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); applied = true; } + StacksEpochId::Epoch23 => { + receipts.push(clarity_tx.block.initialize_epoch_2_05()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_1()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_3()?); + applied = true; + } _ => { panic!("Bad Stacks epoch transition; parent_epoch = {}, current_epoch = {}", &stacks_parent_epoch, &sortition_epoch.epoch_id); } @@ -4906,21 +4913,41 @@ impl StacksChainState { receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); applied = true; } + StacksEpochId::Epoch23 => { + receipts.append(&mut clarity_tx.block.initialize_epoch_2_1()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_3()?); + applied = true; + } + _ => { + panic!("Bad Stacks epoch transition; parent_epoch = {}, current_epoch = {}", &stacks_parent_epoch, &sortition_epoch.epoch_id); + } + }, + StacksEpochId::Epoch21 => match sortition_epoch.epoch_id { + StacksEpochId::Epoch22 => { + receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); + applied = true; + } + StacksEpochId::Epoch23 => { + receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_3()?); + applied = true; + } _ => { panic!("Bad Stacks epoch transition; parent_epoch = {}, current_epoch = {}", &stacks_parent_epoch, &sortition_epoch.epoch_id); } }, - StacksEpochId::Epoch21 => { + StacksEpochId::Epoch22 => { assert_eq!( sortition_epoch.epoch_id, - StacksEpochId::Epoch22, - "Should only transition from Epoch21 to Epoch22" + StacksEpochId::Epoch23, + "Should only transition from Epoch22 to Epoch23" ); - receipts.append(&mut clarity_tx.block.initialize_epoch_2_2()?); + receipts.append(&mut clarity_tx.block.initialize_epoch_2_3()?); applied = true; } - StacksEpochId::Epoch22 => { - panic!("No defined transition from Epoch22 forward") + StacksEpochId::Epoch23 => { + panic!("No defined transition from Epoch23 forward") } } } @@ -5507,7 +5534,7 @@ impl StacksChainState { // The DelegateStx bitcoin wire format does not exist before Epoch 2.1. Ok((stack_ops, transfer_ops, vec![])) } - StacksEpochId::Epoch21 | StacksEpochId::Epoch22 => { + StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 => { StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( chainstate_tx, parent_index_hash, diff --git a/src/chainstate/stacks/db/mod.rs b/src/chainstate/stacks/db/mod.rs index 00b56892c8..faec4e7da2 100644 --- a/src/chainstate/stacks/db/mod.rs +++ b/src/chainstate/stacks/db/mod.rs @@ -224,6 +224,7 @@ impl DBConfig { } StacksEpochId::Epoch21 => self.version == "3" || self.version == "4", StacksEpochId::Epoch22 => self.version == "3" || self.version == "4", + StacksEpochId::Epoch23 => self.version == "3" || self.version == "4", } } } diff --git a/src/chainstate/stacks/db/transactions.rs b/src/chainstate/stacks/db/transactions.rs index 17868ddaab..e4461722d8 100644 --- a/src/chainstate/stacks/db/transactions.rs +++ b/src/chainstate/stacks/db/transactions.rs @@ -8356,6 +8356,7 @@ pub mod test { StacksEpochId::Epoch2_05 => self.get_stacks_epoch(1), StacksEpochId::Epoch21 => self.get_stacks_epoch(2), StacksEpochId::Epoch22 => self.get_stacks_epoch(3), + StacksEpochId::Epoch23 => self.get_stacks_epoch(4), } } fn get_pox_payout_addrs( diff --git a/src/chainstate/stacks/mod.rs b/src/chainstate/stacks/mod.rs index e10460930e..4ed4169d4a 100644 --- a/src/chainstate/stacks/mod.rs +++ b/src/chainstate/stacks/mod.rs @@ -84,7 +84,7 @@ pub use stacks_common::address::{ C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG, }; -pub const STACKS_BLOCK_VERSION: u8 = 5; +pub const STACKS_BLOCK_VERSION: u8 = 6; pub const STACKS_BLOCK_VERSION_AST_PRECHECK_SIZE: u8 = 1; pub const MAX_BLOCK_LEN: u32 = 2 * 1024 * 1024; diff --git a/src/clarity_vm/clarity.rs b/src/clarity_vm/clarity.rs index 071185650b..ac4f562f72 100644 --- a/src/clarity_vm/clarity.rs +++ b/src/clarity_vm/clarity.rs @@ -1109,6 +1109,33 @@ impl<'a, 'b> ClarityBlockConnection<'a, 'b> { }) } + pub fn initialize_epoch_2_3(&mut self) -> Result, Error> { + // use the `using!` statement to ensure that the old cost_tracker is placed + // back in all branches after initialization + using!(self.cost_track, "cost tracker", |old_cost_tracker| { + // epoch initialization is *free*. + // NOTE: this also means that cost functions won't be evaluated. + self.cost_track.replace(LimitedCostTracker::new_free()); + self.epoch = StacksEpochId::Epoch23; + self.as_transaction(|tx_conn| { + // bump the epoch in the Clarity DB + tx_conn + .with_clarity_db(|db| { + db.set_clarity_epoch_version(StacksEpochId::Epoch23); + Ok(()) + }) + .unwrap(); + + // require 2.2 rules henceforth in this connection as well + tx_conn.epoch = StacksEpochId::Epoch23; + }); + + debug!("Epoch 2.3 initialized"); + + (old_cost_tracker, Ok(vec![])) + }) + } + pub fn start_transaction_processing<'c>(&'c mut self) -> ClarityTransactionConnection<'c, 'a> { let store = &mut self.datastore; let cost_track = &mut self.cost_track; diff --git a/src/clarity_vm/tests/contracts.rs b/src/clarity_vm/tests/contracts.rs index 2f2cd2cf96..1e366346ce 100644 --- a/src/clarity_vm/tests/contracts.rs +++ b/src/clarity_vm/tests/contracts.rs @@ -397,10 +397,11 @@ fn trait_invocation_205_with_stored_principal() { } /// Publish a trait in epoch 2.05 and then invoke it in epoch 2.1. +/// Test the behaviors in 2.2 and 2.3 as well. #[test] fn trait_invocation_cross_epoch() { let mut sim = ClarityTestSim::new(); - sim.epoch_bounds = vec![0, 3, 5]; + sim.epoch_bounds = vec![0, 3, 5, 7, 9]; // Advance two blocks so we get to Stacks 2.05. sim.execute_next_block(|_env| {}); @@ -426,6 +427,7 @@ fn trait_invocation_cross_epoch() { let sender = StacksAddress::burn_address(false).into(); + info!("Sim height = {}", sim.height); sim.execute_next_block_as_conn(|conn| { let epoch = conn.get_epoch(); let clarity_version = ClarityVersion::default_for_epoch(epoch); @@ -434,6 +436,7 @@ fn trait_invocation_cross_epoch() { publish_contract(conn, &use_contract_id, use_contract, clarity_version).unwrap(); }); // Advance another block so we get to Stacks 2.1. This is the last block in 2.05 + info!("Sim height = {}", sim.height); sim.execute_next_block(|_| {}); // now in Stacks 2.1 sim.execute_next_block_as_conn(|conn| { @@ -443,6 +446,72 @@ fn trait_invocation_cross_epoch() { publish_contract(conn, &invoke_contract_id, invoke_contract, clarity_version).unwrap(); }); + info!("Sim height = {}", sim.height); + sim.execute_next_block_as_conn(|conn| { + let epoch = conn.get_epoch(); + conn.as_transaction(|clarity_db| { + clarity_db + .run_contract_call( + &sender, + None, + &invoke_contract_id, + "invocation-1", + &[], + |_, _| false, + ) + .unwrap(); + }); + }); + + info!("Sim height = {}", sim.height); + // now in Stacks 2.2 + sim.execute_next_block_as_conn(|conn| { + let epoch = conn.get_epoch(); + conn.as_transaction(|clarity_db| { + let error = clarity_db + .run_contract_call( + &sender, + None, + &invoke_contract_id, + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + |_, _| false, + ) + .unwrap_err(); + + if let ClarityError::Interpreter(Error::Unchecked(CheckErrors::TypeValueError(TypeSignature::TraitReferenceType(_), value))) = error { + // pass + } else { + panic!("Expected an Interpreter(UncheckedError(TypeValue(TraitReferenceType, Principal))) during Epoch-2.2"); + }; + }); + }); + + info!("Sim height = {}", sim.height); + sim.execute_next_block_as_conn(|conn| { + let epoch = conn.get_epoch(); + conn.as_transaction(|clarity_db| { + let error = clarity_db + .run_contract_call( + &sender, + None, + &invoke_contract_id, + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + |_, _| false, + ) + .unwrap_err(); + + if let ClarityError::Interpreter(Error::Unchecked(CheckErrors::TypeValueError(TypeSignature::TraitReferenceType(_), value))) = error { + // pass + } else { + panic!("Expected an Interpreter(UncheckedError(TypeValue(TraitReferenceType, Principal))) during Epoch-2.2"); + }; + }); + }); + + // should now be in Stacks 2.3, so the invocation should work again! + info!("Sim height = {}", sim.height); sim.execute_next_block_as_conn(|conn| { let epoch = conn.get_epoch(); conn.as_transaction(|clarity_db| { @@ -459,6 +528,7 @@ fn trait_invocation_cross_epoch() { }); }); + info!("Sim height = {}", sim.height); sim.execute_next_block_as_conn(|conn| { let epoch = conn.get_epoch(); conn.as_transaction(|clarity_db| { @@ -468,7 +538,7 @@ fn trait_invocation_cross_epoch() { None, &invoke_contract_id, "invocation-2", - &[Value::Principal(impl_contract_id.into())], + &[Value::Principal(impl_contract_id.clone().into())], |_, _| false, ) .unwrap(); diff --git a/src/core/mod.rs b/src/core/mod.rs index 5bc8d7753c..3e78dee91e 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -58,6 +58,7 @@ pub const PEER_VERSION_EPOCH_2_0: u8 = 0x00; pub const PEER_VERSION_EPOCH_2_05: u8 = 0x05; pub const PEER_VERSION_EPOCH_2_1: u8 = 0x06; pub const PEER_VERSION_EPOCH_2_2: u8 = 0x07; +pub const PEER_VERSION_EPOCH_2_3: u8 = 0x08; // network identifiers pub const NETWORK_ID_MAINNET: u32 = 0x17000000; @@ -108,6 +109,8 @@ pub const BITCOIN_MAINNET_STACKS_2_05_BURN_HEIGHT: u64 = 713_000; pub const BITCOIN_MAINNET_STACKS_21_BURN_HEIGHT: u64 = 781_551; /// This is Epoch-2.2 activation height proposed in SIP-022 pub const BITCOIN_MAINNET_STACKS_22_BURN_HEIGHT: u64 = 787_651; +/// This is Epoch-2.3 activation height proposed in SIP-023 +pub const BITCOIN_MAINNET_STACKS_23_BURN_HEIGHT: u64 = 788_240; pub const BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT: u64 = 2000000; pub const BITCOIN_TESTNET_FIRST_BLOCK_TIMESTAMP: u32 = 1622691840; @@ -116,6 +119,7 @@ pub const BITCOIN_TESTNET_FIRST_BLOCK_HASH: &str = pub const BITCOIN_TESTNET_STACKS_2_05_BURN_HEIGHT: u64 = 2_104_380; pub const BITCOIN_TESTNET_STACKS_21_BURN_HEIGHT: u64 = 2_422_101; pub const BITCOIN_TESTNET_STACKS_22_BURN_HEIGHT: u64 = 2_431_300; +pub const BITCOIN_TESTNET_STACKS_23_BURN_HEIGHT: u64 = 2_431_633; pub const BITCOIN_REGTEST_FIRST_BLOCK_HEIGHT: u64 = 0; pub const BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP: u32 = 0; @@ -229,7 +233,7 @@ pub fn check_fault_injection(fault_name: &str) -> bool { } lazy_static! { - pub static ref STACKS_EPOCHS_MAINNET: [StacksEpoch; 5] = [ + pub static ref STACKS_EPOCHS_MAINNET: [StacksEpoch; 6] = [ StacksEpoch { epoch_id: StacksEpochId::Epoch10, start_height: 0, @@ -261,15 +265,22 @@ lazy_static! { StacksEpoch { epoch_id: StacksEpochId::Epoch22, start_height: BITCOIN_MAINNET_STACKS_22_BURN_HEIGHT, - end_height: STACKS_EPOCH_MAX, + end_height: BITCOIN_MAINNET_STACKS_23_BURN_HEIGHT, block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_2 }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: BITCOIN_MAINNET_STACKS_23_BURN_HEIGHT, + end_height: STACKS_EPOCH_MAX, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_2_3 + }, ]; } lazy_static! { - pub static ref STACKS_EPOCHS_TESTNET: [StacksEpoch; 5] = [ + pub static ref STACKS_EPOCHS_TESTNET: [StacksEpoch; 6] = [ StacksEpoch { epoch_id: StacksEpochId::Epoch10, start_height: 0, @@ -301,10 +312,17 @@ lazy_static! { StacksEpoch { epoch_id: StacksEpochId::Epoch22, start_height: BITCOIN_TESTNET_STACKS_22_BURN_HEIGHT, - end_height: STACKS_EPOCH_MAX, + end_height: BITCOIN_TESTNET_STACKS_23_BURN_HEIGHT, block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_2_2 }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: BITCOIN_TESTNET_STACKS_23_BURN_HEIGHT, + end_height: STACKS_EPOCH_MAX, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_2_3 + }, ]; } @@ -353,6 +371,10 @@ pub static STACKS_EPOCH_2_1_MARKER: u8 = 0x06; /// *or greater*. pub static STACKS_EPOCH_2_2_MARKER: u8 = 0x07; +/// Stacks 2.3 epoch marker. All block-commits in 2.3 must have a memo bitfield with this value +/// *or greater*. +pub static STACKS_EPOCH_2_3_MARKER: u8 = 0x08; + #[test] fn test_ord_for_stacks_epoch() { let epochs = STACKS_EPOCHS_MAINNET.clone(); @@ -429,6 +451,8 @@ pub trait StacksEpochExtension { #[cfg(test)] fn unit_test_2_2(epoch_2_0_block_height: u64) -> Vec; #[cfg(test)] + fn unit_test_2_3(epoch_2_0_block_height: u64) -> Vec; + #[cfg(test)] fn unit_test_2_1_only(epoch_2_0_block_height: u64) -> Vec; fn all( epoch_2_0_block_height: u64, @@ -655,6 +679,83 @@ impl StacksEpochExtension for StacksEpoch { ] } + #[cfg(test)] + fn unit_test_2_3(first_burnchain_height: u64) -> Vec { + info!( + "StacksEpoch unit_test_2_3 first_burn_height = {}", + first_burnchain_height + ); + + vec![ + 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 + 4, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_2_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch2_05, + start_height: first_burnchain_height + 4, + end_height: first_burnchain_height + 8, + block_limit: ExecutionCost { + write_length: 205205, + write_count: 205205, + read_length: 205205, + read_count: 205205, + runtime: 205205, + }, + network_epoch: PEER_VERSION_EPOCH_2_05, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch21, + start_height: first_burnchain_height + 8, + end_height: first_burnchain_height + 12, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch22, + start_height: first_burnchain_height + 12, + end_height: first_burnchain_height + 16, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_2, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: first_burnchain_height + 16, + end_height: STACKS_EPOCH_MAX, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_3, + }, + ] + } + #[cfg(test)] fn unit_test_2_1_only(first_burnchain_height: u64) -> Vec { info!( @@ -715,6 +816,7 @@ impl StacksEpochExtension for StacksEpoch { StacksEpochId::Epoch2_05 => StacksEpoch::unit_test_2_05(first_burnchain_height), StacksEpochId::Epoch21 => StacksEpoch::unit_test_2_1(first_burnchain_height), StacksEpochId::Epoch22 => StacksEpoch::unit_test_2_2(first_burnchain_height), + StacksEpochId::Epoch23 => StacksEpoch::unit_test_2_3(first_burnchain_height), } } diff --git a/src/cost_estimates/pessimistic.rs b/src/cost_estimates/pessimistic.rs index 4264151160..4fdf109792 100644 --- a/src/cost_estimates/pessimistic.rs +++ b/src/cost_estimates/pessimistic.rs @@ -232,6 +232,8 @@ impl PessimisticEstimator { StacksEpochId::Epoch21 => ":2.1", // reuse cost estimates in Epoch22 StacksEpochId::Epoch22 => ":2.1", + // reuse cost estimates in Epoch23 + StacksEpochId::Epoch23 => ":2.1", }; format!( "cc{}:{}:{}.{}", diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index f39691072f..f71cd7a475 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -73,11 +73,12 @@ pub enum StacksEpochId { Epoch2_05 = 0x02005, Epoch21 = 0x0200a, Epoch22 = 0x0200f, + Epoch23 = 0x02014, } impl StacksEpochId { pub fn latest() -> StacksEpochId { - StacksEpochId::Epoch22 + StacksEpochId::Epoch23 } } @@ -89,6 +90,7 @@ impl std::fmt::Display for StacksEpochId { StacksEpochId::Epoch2_05 => write!(f, "2.05"), StacksEpochId::Epoch21 => write!(f, "2.1"), StacksEpochId::Epoch22 => write!(f, "2.2"), + StacksEpochId::Epoch23 => write!(f, "2.3"), } } } @@ -103,6 +105,7 @@ impl TryFrom for StacksEpochId { x if x == StacksEpochId::Epoch2_05 as u32 => Ok(StacksEpochId::Epoch2_05), x if x == StacksEpochId::Epoch21 as u32 => Ok(StacksEpochId::Epoch21), x if x == StacksEpochId::Epoch22 as u32 => Ok(StacksEpochId::Epoch22), + x if x == StacksEpochId::Epoch23 as u32 => Ok(StacksEpochId::Epoch23), _ => Err("Invalid epoch"), } } diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index 42da5ac074..5ad0ecc586 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -177,7 +177,7 @@ use stacks::chainstate::stacks::{ use stacks::codec::StacksMessageCodec; use stacks::core::mempool::MemPoolDB; use stacks::core::FIRST_BURNCHAIN_CONSENSUS_HASH; -use stacks::core::STACKS_EPOCH_2_2_MARKER; +use stacks::core::STACKS_EPOCH_2_3_MARKER; use stacks::cost_estimates::metrics::CostMetric; use stacks::cost_estimates::metrics::UnitMetric; use stacks::cost_estimates::UnitEstimator; @@ -1326,7 +1326,7 @@ impl BlockMinerThread { apparent_sender: sender, key_block_ptr: key.block_height as u32, key_vtxindex: key.op_vtxindex as u16, - memo: vec![STACKS_EPOCH_2_2_MARKER], + memo: vec![STACKS_EPOCH_2_3_MARKER], new_seed: vrf_seed, parent_block_ptr, parent_vtxindex, diff --git a/testnet/stacks-node/src/tests/epoch_23.rs b/testnet/stacks-node/src/tests/epoch_23.rs new file mode 100644 index 0000000000..84072c9a0f --- /dev/null +++ b/testnet/stacks-node/src/tests/epoch_23.rs @@ -0,0 +1,641 @@ +// Copyright (C) 2023 Stacks Open Internet Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::collections::HashMap; +use std::env; +use std::thread; + +use stacks::burnchains::Burnchain; +use stacks::core::PEER_VERSION_EPOCH_2_2; +use stacks::core::PEER_VERSION_EPOCH_2_3; +use stacks::core::STACKS_EPOCH_MAX; +use stacks::vm::types::QualifiedContractIdentifier; + +use crate::config::EventKeyType; +use crate::config::EventObserverConfig; +use crate::config::InitialBalance; +use crate::neon; +use crate::tests::bitcoin_regtest::BitcoinCoreController; +use crate::tests::neon_integrations::*; +use crate::tests::*; +use crate::BitcoinRegtestController; +use crate::BurnchainController; +use stacks::core; + +use stacks::burnchains::PoxConstants; + +use clarity::vm::types::PrincipalData; + +#[test] +#[ignore] +/// Test the trait invocation behavior for contracts instantiated in epoch 2.05 +/// * in epoch 2.1: the trait invocation works +/// * in epoch 2.2: trait invocation is broken, and returns a runtime error, even when wrapped +/// * in epoch 2.3: the trait invocation works +fn trait_invocation_behavior() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let reward_cycle_len = 10; + let prepare_phase_len = 3; + let epoch_2_05 = 215; + let epoch_2_1 = 230; + let v1_unlock_height = 231; + let epoch_2_2 = 235; + let epoch_2_3 = 241; + + let spender_sk = StacksPrivateKey::new(); + let contract_addr = to_addr(&spender_sk); + let spender_addr: PrincipalData = to_addr(&spender_sk).into(); + + let impl_contract_id = + QualifiedContractIdentifier::new(contract_addr.clone().into(), "impl-simple".into()); + + let mut spender_nonce = 0; + let fee_amount = 10_000; + + let mut initial_balances = vec![]; + + initial_balances.push(InitialBalance { + address: spender_addr.clone(), + amount: 1_000_000, + }); + + let trait_contract = "(define-trait simple-method ((foo (uint) (response uint uint)) ))"; + let impl_contract = + "(impl-trait .simple-trait.simple-method) (define-read-only (foo (x uint)) (ok x))"; + let use_contract = "(use-trait simple .simple-trait.simple-method) + (define-public (call-simple (s )) (contract-call? s foo u0))"; + let invoke_contract = " + (use-trait simple .simple-trait.simple-method) + (define-public (invocation-1) + (contract-call? .use-simple call-simple .impl-simple)) + (define-public (invocation-2 (st )) + (contract-call? .use-simple call-simple st)) + "; + + let wrapper_contract = " + (use-trait simple .simple-trait.simple-method) + (define-public (invocation-1) + (contract-call? .invoke-simple invocation-1)) + (define-public (invocation-2 (st )) + (contract-call? .invoke-simple invocation-2 st)) + "; + + let (mut conf, _) = neon_integration_test_conf(); + + conf.node.mine_microblocks = false; + conf.burnchain.max_rbf = 1000000; + conf.node.wait_time_for_microblocks = 0; + conf.node.microblock_frequency = 1_000; + conf.miner.first_attempt_time_ms = 2_000; + conf.miner.subsequent_attempt_time_ms = 5_000; + conf.node.wait_time_for_blocks = 1_000; + conf.miner.wait_for_block_download = false; + + conf.miner.min_tx_fee = 1; + conf.miner.first_attempt_time_ms = i64::max_value() as u64; + conf.miner.subsequent_attempt_time_ms = i64::max_value() as u64; + + test_observer::spawn(); + + conf.events_observers.push(EventObserverConfig { + endpoint: format!("localhost:{}", test_observer::EVENT_OBSERVER_PORT), + events_keys: vec![EventKeyType::AnyEvent], + }); + conf.initial_balances.append(&mut initial_balances); + + let mut epochs = core::STACKS_EPOCHS_REGTEST.to_vec(); + epochs[1].end_height = epoch_2_05; + epochs[2].start_height = epoch_2_05; + epochs[2].end_height = epoch_2_1; + epochs[3].start_height = epoch_2_1; + epochs[3].end_height = epoch_2_2; + epochs.push(StacksEpoch { + epoch_id: StacksEpochId::Epoch22, + start_height: epoch_2_2, + end_height: epoch_2_3, + block_limit: epochs[3].block_limit.clone(), + network_epoch: PEER_VERSION_EPOCH_2_2, + }); + epochs.push(StacksEpoch { + epoch_id: StacksEpochId::Epoch23, + start_height: epoch_2_3, + end_height: STACKS_EPOCH_MAX, + block_limit: epochs[3].block_limit.clone(), + network_epoch: PEER_VERSION_EPOCH_2_3, + }); + conf.burnchain.epochs = Some(epochs); + + let mut burnchain_config = Burnchain::regtest(&conf.get_burn_db_path()); + + let pox_constants = PoxConstants::new( + reward_cycle_len, + prepare_phase_len, + 4 * prepare_phase_len / 5, + 5, + 15, + u64::max_value() - 2, + u64::max_value() - 1, + v1_unlock_height as u32, + epoch_2_2 as u32 + 1, + ); + burnchain_config.pox_constants = pox_constants.clone(); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller + .start_bitcoind() + .map_err(|_e| ()) + .expect("Failed starting bitcoind"); + + let mut btc_regtest_controller = BitcoinRegtestController::with_burnchain( + conf.clone(), + None, + Some(burnchain_config.clone()), + None, + ); + let http_origin = format!("http://{}", &conf.node.rpc_bind); + + btc_regtest_controller.bootstrap_chain(201); + + eprintln!("Chain bootstrapped..."); + + let mut run_loop = neon::RunLoop::new(conf.clone()); + let runloop_burnchain = burnchain_config.clone(); + + let blocks_processed = run_loop.get_blocks_processed_arc(); + + let channel = run_loop.get_coordinator_channel().unwrap(); + + thread::spawn(move || run_loop.start(Some(runloop_burnchain), 0)); + + // give the run loop some time to start up! + wait_for_runloop(&blocks_processed); + + // first block wakes up the run loop + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // first block will hold our VRF registration + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // second block will be the first mined Stacks block + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // push us to block 205 + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // publish contracts right away! + let publish_trait = make_contract_publish( + &spender_sk, + spender_nonce, + fee_amount, + "simple-trait", + trait_contract, + ); + + spender_nonce += 1; + + let publish_impl = make_contract_publish( + &spender_sk, + spender_nonce, + fee_amount, + "impl-simple", + impl_contract, + ); + + spender_nonce += 1; + + let publish_use = make_contract_publish( + &spender_sk, + spender_nonce, + fee_amount, + "use-simple", + use_contract, + ); + + spender_nonce += 1; + + let publish_invoke = make_contract_publish( + &spender_sk, + spender_nonce, + fee_amount, + "invoke-simple", + invoke_contract, + ); + + spender_nonce += 1; + + info!("Submit 2.05 txs"); + submit_tx(&http_origin, &publish_trait); + submit_tx(&http_origin, &publish_impl); + submit_tx(&http_origin, &publish_use); + submit_tx(&http_origin, &publish_invoke); + + info!( + "At height = {}, epoch-2.1 = {}", + get_chain_info(&conf).burn_block_height, + epoch_2_1 + ); + // wait until just before epoch 2.1 + loop { + let tip_info = get_chain_info(&conf); + if tip_info.burn_block_height >= epoch_2_1 - 3 { + break; + } + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + } + + // submit invocation txs. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-1", + &[], + ); + let expected_good_205_1_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_good_205_2_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + // this mines bitcoin block epoch_2_1 - 2, and causes the the + // stacks node to mine the stacks block which will be included in + // epoch_2_1 - 1, so these are the last transactions processed pre-2.1. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // submit invocation txs. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-1", + &[], + ); + let expected_good_21_1_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_good_21_2_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + // this mines those transactions into epoch 2.1 + // mine until just before epoch 2.2 + loop { + let tip_info = get_chain_info(&conf); + if tip_info.burn_block_height >= epoch_2_2 - 3 { + break; + } + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + } + + // submit invocation txs. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-1", + &[], + ); + let expected_good_21_3_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_good_21_4_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + // this mines bitcoin block epoch_2_2 - 2, and causes the the + // stacks node to mine the stacks block which will be included in + // epoch_2_2 - 1, so these are the last transactions processed pre-2.2. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + let publish_invoke = make_contract_publish( + &spender_sk, + spender_nonce, + fee_amount, + "wrap-simple", + wrapper_contract, + ); + + spender_nonce += 1; + submit_tx(&http_origin, &publish_invoke); + + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // submit invocation txs. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-1", + &[], + ); + let expected_bad_22_1_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_bad_22_2_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + // this mines those transactions into epoch 2.2 + // mine until just before epoch 2.3 + loop { + let tip_info = get_chain_info(&conf); + if tip_info.burn_block_height >= epoch_2_3 - 3 { + break; + } + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + } + + // submit invocation txs in epoch 2.2. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-1", + &[], + ); + let expected_bad_22_3_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_bad_22_4_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + // this mines bitcoin block epoch_2_3 - 2, and causes the the + // stacks node to mine the stacks block which will be included in + // epoch_2_3 - 1, so these are the last transactions processed pre-2.3. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + let tx_3 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-1", + &[], + ); + let expected_good_23_3_nonce = spender_nonce; + spender_nonce += 1; + + let tx_4 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "wrap-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_good_23_4_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_3); + submit_tx(&http_origin, &tx_4); + + // advance to epoch_2_3 before submitting the next transactions, + // so that they can pass the mempool. + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // submit invocation txs. + let tx_1 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-1", + &[], + ); + let expected_good_23_1_nonce = spender_nonce; + spender_nonce += 1; + + let tx_2 = make_contract_call( + &spender_sk, + spender_nonce, + fee_amount, + &contract_addr, + "invoke-simple", + "invocation-2", + &[Value::Principal(impl_contract_id.clone().into())], + ); + let expected_good_23_2_nonce = spender_nonce; + spender_nonce += 1; + + submit_tx(&http_origin, &tx_1); + submit_tx(&http_origin, &tx_2); + + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + info!("Total spender txs = {}", spender_nonce); + + let blocks = test_observer::get_blocks(); + + let mut transaction_receipts = Vec::new(); + + for block in blocks { + let transactions = block.get("transactions").unwrap().as_array().unwrap(); + for tx in transactions { + let raw_tx = tx.get("raw_tx").unwrap().as_str().unwrap(); + if raw_tx == "0x00" { + continue; + } + let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap(); + let parsed = + StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap(); + let tx_sender = PrincipalData::from(parsed.auth.origin().address_testnet()); + if &tx_sender == &spender_addr { + let contract_call = match &parsed.payload { + TransactionPayload::ContractCall(cc) => cc, + // only interested in contract calls + _ => continue, + }; + let result = Value::try_deserialize_hex_untyped( + tx.get("raw_result").unwrap().as_str().unwrap(), + ) + .unwrap(); + + transaction_receipts.push(( + parsed.auth.get_origin_nonce(), + (contract_call.clone(), result), + )); + } + } + } + + transaction_receipts.sort_by_key(|x| x.0); + + let transaction_receipts: HashMap<_, _> = transaction_receipts.into_iter().collect(); + + for tx_nonce in [ + expected_good_205_1_nonce, + expected_good_21_1_nonce, + expected_good_21_3_nonce, + expected_good_23_1_nonce, + ] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "invoke-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-1" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(ok u0)"); + } + + for tx_nonce in [ + expected_good_205_2_nonce, + expected_good_21_2_nonce, + expected_good_21_4_nonce, + expected_good_23_2_nonce, + ] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "invoke-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-2" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(ok u0)"); + } + + for tx_nonce in [expected_good_23_3_nonce] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "wrap-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-1" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(ok u0)"); + } + + for tx_nonce in [expected_good_23_4_nonce] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "wrap-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-2" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(ok u0)"); + } + + for tx_nonce in [expected_bad_22_1_nonce, expected_bad_22_3_nonce] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "wrap-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-1" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(err none)"); + } + + for tx_nonce in [expected_bad_22_2_nonce, expected_bad_22_4_nonce] { + assert_eq!( + transaction_receipts[&tx_nonce].0.contract_name.as_str(), + "wrap-simple" + ); + assert_eq!( + transaction_receipts[&tx_nonce].0.function_name.as_str(), + "invocation-2" + ); + assert_eq!(&transaction_receipts[&tx_nonce].1.to_string(), "(err none)"); + } + + for (key, value) in transaction_receipts.iter() { + eprintln!("{} => {} of {}", key, value.0, value.1); + } + + test_observer::clear(); + channel.stop_chains_coordinator(); +} diff --git a/testnet/stacks-node/src/tests/mod.rs b/testnet/stacks-node/src/tests/mod.rs index 766123b6f1..8eca8f21fc 100644 --- a/testnet/stacks-node/src/tests/mod.rs +++ b/testnet/stacks-node/src/tests/mod.rs @@ -43,6 +43,7 @@ mod bitcoin_regtest; mod epoch_205; mod epoch_21; mod epoch_22; +mod epoch_23; mod integrations; mod mempool; pub mod neon_integrations; diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 5456505a89..34f2da930d 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -7711,6 +7711,8 @@ fn atlas_stress_integration_test() { } eprintln!("attachment_indexes = {:?}", &attachment_indexes); + let max_request_time_ms = 100; + for (ibh, attachments) in attachment_indexes.iter() { let l = attachments.len(); for i in 0..(l / MAX_ATTACHMENT_INV_PAGES_PER_REQUEST + 1) { @@ -7754,10 +7756,11 @@ fn atlas_stress_integration_test() { // requests should take no more than 20ms assert!( - total_time < attempts * 50, - "Atlas inventory request is too slow: {} >= {} * 50", + total_time < attempts * max_request_time_ms, + "Atlas inventory request is too slow: {} >= {} * {}", total_time, - attempts + attempts, + max_request_time_ms ); } @@ -7795,10 +7798,11 @@ fn atlas_stress_integration_test() { // requests should take no more than 40ms assert!( - total_time < attempts * 50, - "Atlas chunk request is too slow: {} >= {} * 50", + total_time < attempts * max_request_time_ms, + "Atlas chunk request is too slow: {} >= {} * {}", total_time, - attempts + attempts, + max_request_time_ms ); } }