diff --git a/clarity/src/vm/analysis/mod.rs b/clarity/src/vm/analysis/mod.rs index 19183f5f67..ddbcadb0c3 100644 --- a/clarity/src/vm/analysis/mod.rs +++ b/clarity/src/vm/analysis/mod.rs @@ -152,7 +152,8 @@ pub fn run_analysis( | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => { + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { TypeChecker2_1::run_pass(&epoch, &mut contract_analysis, db, build_type_map) } StacksEpochId::Epoch10 => { diff --git a/clarity/src/vm/analysis/type_checker/mod.rs b/clarity/src/vm/analysis/type_checker/mod.rs index 68bbe1873e..4e6aec42da 100644 --- a/clarity/src/vm/analysis/type_checker/mod.rs +++ b/clarity/src/vm/analysis/type_checker/mod.rs @@ -45,7 +45,8 @@ impl FunctionType { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => self.check_args_2_1(accounting, args, clarity_version), + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => self.check_args_2_1(accounting, args, clarity_version), StacksEpochId::Epoch10 => { Err(CheckErrors::Expects("Epoch10 is not supported".into()).into()) } @@ -69,7 +70,8 @@ impl FunctionType { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => { + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { self.check_args_by_allowing_trait_cast_2_1(db, clarity_version, func_args) } StacksEpochId::Epoch10 => { diff --git a/clarity/src/vm/costs/mod.rs b/clarity/src/vm/costs/mod.rs index 0b1559795f..f680435479 100644 --- a/clarity/src/vm/costs/mod.rs +++ b/clarity/src/vm/costs/mod.rs @@ -857,7 +857,8 @@ impl LimitedCostTracker { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => COSTS_3_NAME.to_string(), + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => COSTS_3_NAME.to_string(), }; Ok(result) } diff --git a/clarity/src/vm/functions/mod.rs b/clarity/src/vm/functions/mod.rs index 3eac4fb19e..7c92d9a929 100644 --- a/clarity/src/vm/functions/mod.rs +++ b/clarity/src/vm/functions/mod.rs @@ -56,6 +56,8 @@ macro_rules! switch_on_global_epoch { StacksEpochId::Epoch30 => $Epoch205Version(args, env, context), // Note: We reuse 2.05 for 3.1. StacksEpochId::Epoch31 => $Epoch205Version(args, env, context), + // Note: We reuse 2.05 for 3.2. + StacksEpochId::Epoch32 => $Epoch205Version(args, env, context), } } }; diff --git a/clarity/src/vm/test_util/mod.rs b/clarity/src/vm/test_util/mod.rs index 37a40182eb..f1b354ebc7 100644 --- a/clarity/src/vm/test_util/mod.rs +++ b/clarity/src/vm/test_util/mod.rs @@ -53,7 +53,8 @@ pub fn generate_test_burn_state_db(epoch_id: StacksEpochId) -> UnitTestBurnState | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => UnitTestBurnStateDB { + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => UnitTestBurnStateDB { epoch_id, ast_rules: ASTRules::PrecheckSize, }, diff --git a/clarity/src/vm/tests/mod.rs b/clarity/src/vm/tests/mod.rs index f261733191..7d58fb515e 100644 --- a/clarity/src/vm/tests/mod.rs +++ b/clarity/src/vm/tests/mod.rs @@ -125,6 +125,7 @@ epochs_template! { Epoch25, Epoch30, Epoch31, + Epoch32, } clarity_template! { @@ -146,6 +147,9 @@ clarity_template! { (Epoch31, Clarity1), (Epoch31, Clarity2), (Epoch31, Clarity3), + (Epoch32, Clarity1), + (Epoch32, Clarity2), + (Epoch32, Clarity3), } #[cfg(test)] diff --git a/clarity/src/vm/types/signatures.rs b/clarity/src/vm/types/signatures.rs index f41b8ed1a3..19da888621 100644 --- a/clarity/src/vm/types/signatures.rs +++ b/clarity/src/vm/types/signatures.rs @@ -585,7 +585,8 @@ impl TypeSignature { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => self.admits_type_v2_1(other), + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => self.admits_type_v2_1(other), StacksEpochId::Epoch10 => Err(CheckErrors::Expects("epoch 1.0 not supported".into())), } } @@ -793,7 +794,8 @@ impl TypeSignature { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => self.canonicalize_v2_1(), + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => self.canonicalize_v2_1(), } } @@ -1152,7 +1154,8 @@ impl TypeSignature { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => Self::least_supertype_v2_1(a, b), + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => Self::least_supertype_v2_1(a, b), StacksEpochId::Epoch10 => Err(CheckErrors::Expects("epoch 1.0 not supported".into())), } } diff --git a/clarity/src/vm/version.rs b/clarity/src/vm/version.rs index 7050d5dbd9..adc801f3e9 100644 --- a/clarity/src/vm/version.rs +++ b/clarity/src/vm/version.rs @@ -41,6 +41,7 @@ impl ClarityVersion { StacksEpochId::Epoch25 => ClarityVersion::Clarity2, StacksEpochId::Epoch30 => ClarityVersion::Clarity3, StacksEpochId::Epoch31 => ClarityVersion::Clarity3, + StacksEpochId::Epoch32 => ClarityVersion::Clarity3, } } } diff --git a/sample/conf/testnet-signer.toml b/sample/conf/testnet-signer.toml index 2fbff7a235..84e2a66d46 100644 --- a/sample/conf/testnet-signer.toml +++ b/sample/conf/testnet-signer.toml @@ -80,3 +80,7 @@ start_height = 1900 [[burnchain.epochs]] epoch_name = "3.1" start_height = 2000 + +[[burnchain.epochs]] +epoch_name = "3.2" +start_height = 2100 \ No newline at end of file diff --git a/stacks-common/src/libcommon.rs b/stacks-common/src/libcommon.rs index 48f8cd5970..7d9136f20b 100644 --- a/stacks-common/src/libcommon.rs +++ b/stacks-common/src/libcommon.rs @@ -80,6 +80,7 @@ pub mod consts { pub const PEER_VERSION_EPOCH_2_5: u8 = 0x0a; pub const PEER_VERSION_EPOCH_3_0: u8 = 0x0b; pub const PEER_VERSION_EPOCH_3_1: u8 = 0x0c; + pub const PEER_VERSION_EPOCH_3_2: u8 = 0x0d; /// this should be updated to the latest network epoch version supported by /// this node. this will be checked by the `validate_epochs()` method. diff --git a/stacks-common/src/types/mod.rs b/stacks-common/src/types/mod.rs index a4cc64aadc..399d5359ad 100644 --- a/stacks-common/src/types/mod.rs +++ b/stacks-common/src/types/mod.rs @@ -102,6 +102,7 @@ pub enum StacksEpochId { Epoch25 = 0x0201a, Epoch30 = 0x03000, Epoch31 = 0x03001, + Epoch32 = 0x03002, } #[derive(Debug)] @@ -269,7 +270,7 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => MempoolCollectionBehavior::ByStacksHeight, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => { + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { MempoolCollectionBehavior::ByReceiveTime } } @@ -286,7 +287,10 @@ impl StacksEpochId { | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 => false, - StacksEpochId::Epoch25 | StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => true, + StacksEpochId::Epoch25 + | StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => true, } } @@ -303,7 +307,8 @@ impl StacksEpochId { StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => true, + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => true, } } @@ -319,7 +324,7 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => false, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => true, + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => true, } } @@ -335,7 +340,7 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => false, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => true, + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => true, } } @@ -350,7 +355,7 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => false, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => true, + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => true, } } @@ -381,7 +386,9 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => 0, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => MINING_COMMITMENT_FREQUENCY_NAKAMOTO, + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { + MINING_COMMITMENT_FREQUENCY_NAKAMOTO + } } } @@ -417,7 +424,7 @@ impl StacksEpochId { | StacksEpochId::Epoch23 | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 => false, - StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => { + StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => { cur_reward_cycle > first_epoch30_reward_cycle } } @@ -535,13 +542,30 @@ impl StacksEpochId { | StacksEpochId::Epoch30 => { self.coinbase_reward_pre_sip029(first_burnchain_height, current_burnchain_height) } - StacksEpochId::Epoch31 => self.coinbase_reward_sip029( + StacksEpochId::Epoch31 | StacksEpochId::Epoch32 => self.coinbase_reward_sip029( mainnet, first_burnchain_height, current_burnchain_height, ), } } + + /// Whether or not this epoch is part of the SIP-031 schedule + pub fn includes_sip_031(&self) -> bool { + match self { + StacksEpochId::Epoch10 + | StacksEpochId::Epoch20 + | StacksEpochId::Epoch2_05 + | StacksEpochId::Epoch21 + | StacksEpochId::Epoch22 + | StacksEpochId::Epoch23 + | StacksEpochId::Epoch24 + | StacksEpochId::Epoch25 + | StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 => false, + StacksEpochId::Epoch32 => true, + } + } } impl std::fmt::Display for StacksEpochId { @@ -557,6 +581,7 @@ impl std::fmt::Display for StacksEpochId { StacksEpochId::Epoch25 => write!(f, "2.5"), StacksEpochId::Epoch30 => write!(f, "3.0"), StacksEpochId::Epoch31 => write!(f, "3.1"), + StacksEpochId::Epoch32 => write!(f, "3.2"), } } } @@ -576,6 +601,7 @@ impl TryFrom for StacksEpochId { x if x == StacksEpochId::Epoch25 as u32 => Ok(StacksEpochId::Epoch25), x if x == StacksEpochId::Epoch30 as u32 => Ok(StacksEpochId::Epoch30), x if x == StacksEpochId::Epoch31 as u32 => Ok(StacksEpochId::Epoch31), + x if x == StacksEpochId::Epoch32 as u32 => Ok(StacksEpochId::Epoch32), _ => Err("Invalid epoch"), } } diff --git a/stacks-common/src/types/tests.rs b/stacks-common/src/types/tests.rs index 20676999e7..cf4668b976 100644 --- a/stacks-common/src/types/tests.rs +++ b/stacks-common/src/types/tests.rs @@ -184,66 +184,26 @@ fn test_get_coinbase_at_effective_height() { #[test] fn test_epoch_coinbase_reward() { // new coinbase schedule - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 666050), - 1_000_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 666051), - 1_000_000_000 - ); + for epoch in [StacksEpochId::Epoch31, StacksEpochId::Epoch32].iter() { + assert_eq!(epoch.coinbase_reward(true, 666050, 666050), 1_000_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 666051), 1_000_000_000); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 944_999), - 1_000_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 945_000), - 500_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 945_001), - 500_000_000 - ); + assert_eq!(epoch.coinbase_reward(true, 666050, 944_999), 1_000_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 945_000), 500_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 945_001), 500_000_000); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_049_999), - 500_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_050_000), - 250_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_050_001), - 250_000_000 - ); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_049_999), 500_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_050_000), 250_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_050_001), 250_000_000); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_259_999), - 250_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_260_000), - 125_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_260_001), - 125_000_000 - ); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_259_999), 250_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_260_000), 125_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_260_001), 125_000_000); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_469_999), - 125_000_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_470_000), - 62_500_000 - ); - assert_eq!( - StacksEpochId::Epoch31.coinbase_reward(true, 666050, 1_470_001), - 62_500_000 - ); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_469_999), 125_000_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_470_000), 62_500_000); + assert_eq!(epoch.coinbase_reward(true, 666050, 1_470_001), 62_500_000); + } // old coinbase schedule for epoch in [ @@ -254,6 +214,7 @@ fn test_epoch_coinbase_reward() { StacksEpochId::Epoch23, StacksEpochId::Epoch24, StacksEpochId::Epoch25, + StacksEpochId::Epoch30, ] .iter() { diff --git a/stackslib/src/chainstate/burn/db/sortdb.rs b/stackslib/src/chainstate/burn/db/sortdb.rs index e5f5bdde96..88fc138a40 100644 --- a/stackslib/src/chainstate/burn/db/sortdb.rs +++ b/stackslib/src/chainstate/burn/db/sortdb.rs @@ -3236,6 +3236,7 @@ impl SortitionDB { StacksEpochId::Epoch25 => version_u32 >= 3, StacksEpochId::Epoch30 => version_u32 >= 3, StacksEpochId::Epoch31 => version_u32 >= 3, + StacksEpochId::Epoch32 => version_u32 >= 3, } } diff --git a/stackslib/src/chainstate/burn/operations/leader_block_commit.rs b/stackslib/src/chainstate/burn/operations/leader_block_commit.rs index 562d409802..abaa31e224 100644 --- a/stackslib/src/chainstate/burn/operations/leader_block_commit.rs +++ b/stackslib/src/chainstate/burn/operations/leader_block_commit.rs @@ -36,7 +36,7 @@ use crate::chainstate::stacks::address::PoxAddress; use crate::core::{ StacksEpochId, STACKS_EPOCH_2_05_MARKER, STACKS_EPOCH_2_1_MARKER, STACKS_EPOCH_2_2_MARKER, STACKS_EPOCH_2_3_MARKER, STACKS_EPOCH_2_4_MARKER, STACKS_EPOCH_2_5_MARKER, - STACKS_EPOCH_3_0_MARKER, STACKS_EPOCH_3_1_MARKER, + STACKS_EPOCH_3_0_MARKER, STACKS_EPOCH_3_1_MARKER, STACKS_EPOCH_3_2_MARKER, }; // return type from parse_data below @@ -877,6 +877,7 @@ impl LeaderBlockCommitOp { StacksEpochId::Epoch25 => self.check_epoch_commit_marker(STACKS_EPOCH_2_5_MARKER), StacksEpochId::Epoch30 => self.check_epoch_commit_marker(STACKS_EPOCH_3_0_MARKER), StacksEpochId::Epoch31 => self.check_epoch_commit_marker(STACKS_EPOCH_3_1_MARKER), + StacksEpochId::Epoch32 => self.check_epoch_commit_marker(STACKS_EPOCH_3_2_MARKER), } } @@ -897,7 +898,8 @@ impl LeaderBlockCommitOp { | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => { + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { // correct behavior -- uses *sortition height* to find the intended sortition ID let sortition_height = self .block_height diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index caf2a90ffb..cc21170543 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -425,8 +425,11 @@ impl OnChainRewardSetProvider<'_, T> { return Ok(RewardSet::empty()); } } - StacksEpochId::Epoch25 | StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => { - // Epoch 2.5, 3.0, and 3.1 compute reward sets, but *only* if PoX-4 is active + StacksEpochId::Epoch25 + | StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { + // Epoch 2.5, 3.0, 3.1 and 3.2 compute reward sets, but *only* if PoX-4 is active if burnchain .pox_constants .active_pox_contract(current_burn_height) diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index 2b6b17c301..0d2319ce12 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -16,11 +16,12 @@ use std::collections::{HashMap, HashSet}; use std::ops::{Deref, DerefMut, Range}; +use std::sync::LazyLock; use clarity::util::secp256k1::Secp256k1PublicKey; use clarity::vm::ast::ASTRules; use clarity::vm::costs::ExecutionCost; -use clarity::vm::events::StacksTransactionEvent; +use clarity::vm::events::{STXEventType, STXMintEventData, StacksTransactionEvent}; use clarity::vm::types::PrincipalData; use clarity::vm::{ClarityVersion, Value}; use lazy_static::lazy_static; @@ -32,7 +33,9 @@ use stacks_common::codec::{ read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN, MAX_PAYLOAD_LEN, }; -use stacks_common::consts::{FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH}; +use stacks_common::consts::{ + FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, MICROSTACKS_PER_STACKS, +}; use stacks_common::types::chainstate::{ BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey, TrieHash, VRFSeed, @@ -74,6 +77,7 @@ use crate::chainstate::nakamoto::tenure::{ NakamotoTenureEventId, NAKAMOTO_TENURES_SCHEMA_1, NAKAMOTO_TENURES_SCHEMA_2, NAKAMOTO_TENURES_SCHEMA_3, }; +use crate::chainstate::stacks::boot::SIP_031_NAME; use crate::chainstate::stacks::db::blocks::DummyEventDispatcher; use crate::chainstate::stacks::db::{ DBConfig as ChainstateConfig, StacksChainState, StacksDBConn, StacksDBTx, @@ -573,6 +577,176 @@ impl MaturedMinerRewards { } } +/// Struct describing the intervals in which SIP-031 emission are applied. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SIP031EmissionInterval { + /// amount of uSTX to emit + pub amount: u128, + /// height of the burn chain in which the interval starts + pub start_height: u64, +} + +// From SIP-031: +// +// | Bitcoin Height | STX Emission | +// |----------------|------------ | +// | 907,740 | 475 | +// | 960,300 | 1,140 | +// | 1,012,860 | 1,705 | +// | 1,065,420 | 1,305 | +// | 1,117,980 | 1,155 | +// | 1,170,540 | 0 | + +/// Mainnet sip-031 emission intervals +pub static SIP031_EMISSION_INTERVALS_MAINNET: LazyLock<[SIP031EmissionInterval; 6]> = + LazyLock::new(|| { + let emissions_schedule = [ + SIP031EmissionInterval { + amount: 0, + start_height: 1_170_540, + }, + SIP031EmissionInterval { + amount: 1_155 * u128::from(MICROSTACKS_PER_STACKS), + start_height: 1_117_980, + }, + SIP031EmissionInterval { + amount: 1_305 * u128::from(MICROSTACKS_PER_STACKS), + start_height: 1_065_420, + }, + SIP031EmissionInterval { + amount: 1_705 * u128::from(MICROSTACKS_PER_STACKS), + start_height: 1_012_860, + }, + SIP031EmissionInterval { + amount: 1_140 * u128::from(MICROSTACKS_PER_STACKS), + start_height: 960_300, + }, + SIP031EmissionInterval { + amount: 475 * u128::from(MICROSTACKS_PER_STACKS), + start_height: 907_740, + }, + ]; + assert!(SIP031EmissionInterval::check_inversed_order( + &emissions_schedule + )); + emissions_schedule + }); + +/// Testnet sip-031 emission intervals (starting from 2100, basically dummy values) +pub static SIP031_EMISSION_INTERVALS_TESTNET: LazyLock<[SIP031EmissionInterval; 6]> = + LazyLock::new(|| { + let emissions_schedule = [ + SIP031EmissionInterval { + amount: 0, + start_height: 2_600, + }, + SIP031EmissionInterval { + amount: 0, + start_height: 2_500, + }, + SIP031EmissionInterval { + amount: 0, + start_height: 2_400, + }, + SIP031EmissionInterval { + amount: 0, + start_height: 2_300, + }, + SIP031EmissionInterval { + amount: 0, + start_height: 2_200, + }, + SIP031EmissionInterval { + amount: 0, + start_height: 2_100, + }, + ]; + assert!(SIP031EmissionInterval::check_inversed_order( + &emissions_schedule + )); + emissions_schedule + }); + +/// Used for testing to substitute a sip-031 emission schedule +#[cfg(test)] +pub static SIP031_EMISSION_INTERVALS_TEST: std::sync::Mutex>> = + std::sync::Mutex::new(None); + +#[cfg(test)] +pub fn set_test_sip_031_emission_schedule(emission_schedule: Option>) { + match SIP031_EMISSION_INTERVALS_TEST.lock() { + Ok(mut schedule_guard) => { + *schedule_guard = emission_schedule; + } + Err(_e) => { + panic!("SIP031_EMISSION_INTERVALS_TEST mutex poisoned"); + } + } +} + +#[cfg(test)] +fn get_sip_031_emission_schedule(_mainnet: bool) -> Vec { + match SIP031_EMISSION_INTERVALS_TEST.lock() { + Ok(schedule_opt) => { + if let Some(schedule) = (*schedule_opt).as_ref() { + info!("Use overridden SIP-031 emission schedule {:?}", &schedule); + return schedule.clone(); + } else { + return vec![]; + } + } + Err(_e) => { + panic!("COINBASE_INTERVALS_TEST mutex poisoned"); + } + } +} + +#[cfg(not(test))] +fn get_sip_031_emission_schedule(mainnet: bool) -> Vec { + if mainnet { + SIP031_EMISSION_INTERVALS_MAINNET.to_vec() + } else { + SIP031_EMISSION_INTERVALS_TESTNET.to_vec() + } +} + +impl SIP031EmissionInterval { + /// Look up the amount of STX to emit at the start of the tenure at the specified height. + /// Precondition: `intervals` must be sorted in descending order by `start_height` + pub fn get_sip_031_emission_at_height(height: u64, mainnet: bool) -> u128 { + let intervals = get_sip_031_emission_schedule(mainnet); + + if intervals.is_empty() { + return 0; + } + + for interval in intervals { + if height >= interval.start_height { + return interval.amount; + } + } + + // default emission (out of SIP-031 ranges) + return 0; + } + + /// Verify that a list of intervals is sorted in descending order by `start_height` + pub fn check_inversed_order(intervals: &[SIP031EmissionInterval]) -> bool { + if intervals.len() < 2 { + return true; + } + + let mut ht = intervals[0].start_height; + for interval in intervals.iter().skip(1) { + if interval.start_height > ht { + return false; + } + ht = interval.start_height; + } + true + } +} + /// Result of preparing to produce or validate a block pub struct SetupBlockResult<'a, 'b> { /// Handle to the ClarityVM @@ -4042,6 +4216,8 @@ impl NakamotoChainState { "parent_header_hash" => %parent_header_hash, ); + let evaluated_epoch = clarity_tx.get_epoch(); + if new_tenure { clarity_tx .connection() @@ -4063,8 +4239,6 @@ impl NakamotoChainState { })?; } - let evaluated_epoch = clarity_tx.get_epoch(); - let auto_unlock_events = if evaluated_epoch >= StacksEpochId::Epoch21 { let unlock_events = StacksChainState::check_and_handle_reward_start( burn_header_height.into(), @@ -4650,6 +4824,49 @@ impl NakamotoChainState { } } + if new_tenure { + if evaluated_epoch.includes_sip_031() { + let mainnet = clarity_tx.config.mainnet; + + let sip_031_mint_and_transfer_amount = + SIP031EmissionInterval::get_sip_031_emission_at_height( + chain_tip_burn_header_height.into(), + mainnet, + ); + + if sip_031_mint_and_transfer_amount > 0 { + let recipient = PrincipalData::Contract(boot_code_id(SIP_031_NAME, mainnet)); + + clarity_tx.connection().as_transaction(|tx_conn| { + tx_conn + .with_clarity_db(|db| { + db.increment_ustx_liquid_supply(sip_031_mint_and_transfer_amount) + .map_err(|e| e.into()) + }) + .expect("FATAL: `SIP-031 mint` overflowed"); + StacksChainState::account_credit( + tx_conn, + &recipient, + u64::try_from(sip_031_mint_and_transfer_amount) + .expect("FATAL: transferred more STX than exist"), + ); + }); + + if let Some(receipt) = tx_receipts.get_mut(0) { + if receipt.is_coinbase_tx() { + let event = STXEventType::STXMintEvent(STXMintEventData { + recipient, + amount: sip_031_mint_and_transfer_amount, + }); + receipt.events.push(StacksTransactionEvent::STXEvent(event)); + } + } else { + warn!("Unable to attach SIP-031 mint events, block's first transaction is not a coinbase transaction") + } + } + } + } + // verify that the resulting chainstate matches the block's state root let root_hash = clarity_tx.seal(); if root_hash != block.header.state_index_root { diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index 27740f75af..6c0bb3c569 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -63,6 +63,7 @@ pub const POX_4_NAME: &str = "pox-4"; pub const SIGNERS_NAME: &str = "signers"; pub const SIGNERS_VOTING_NAME: &str = "signers-voting"; pub const SIGNERS_VOTING_FUNCTION_NAME: &str = "vote-for-aggregate-public-key"; +pub const SIP_031_NAME: &str = "sip-031"; /// This is the name of a variable in the `.signers` contract which tracks the most recently updated /// reward cycle number. pub const SIGNERS_UPDATE_STATE: &str = "last-set-cycle"; @@ -76,6 +77,7 @@ pub const SIGNERS_BODY: &str = std::include_str!("signers.clar"); pub const SIGNERS_DB_0_BODY: &str = std::include_str!("signers-0-xxx.clar"); pub const SIGNERS_DB_1_BODY: &str = std::include_str!("signers-1-xxx.clar"); pub const SIGNERS_VOTING_BODY: &str = std::include_str!("signers-voting.clar"); +pub const SIP_031_BODY: &str = std::include_str!("sip-031.clar"); pub const COSTS_1_NAME: &str = "costs"; pub const COSTS_2_NAME: &str = "costs-2"; diff --git a/stackslib/src/chainstate/stacks/boot/sip-031.clar b/stackslib/src/chainstate/stacks/boot/sip-031.clar new file mode 100644 index 0000000000..d276b8c3f0 --- /dev/null +++ b/stackslib/src/chainstate/stacks/boot/sip-031.clar @@ -0,0 +1,11 @@ +(define-data-var recipient principal 'ST000000000000000000002AMW42H) + +(define-public (update-recipient (new-recipient principal)) (begin + (asserts! (is-eq tx-sender (var-get recipient)) (err u101)) + (var-set recipient new-recipient) + (ok true) +)) ;; Returns (response bool uint) + +(define-read-only (get-recipient) (ok (var-get recipient))) ;; Returns (response principal uint) + +(define-public (claim) (ok true) ) ;; Returns (response uint uint) \ No newline at end of file diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 2c35ac1fe7..7fe642ee71 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -4092,7 +4092,11 @@ impl StacksChainState { current_epoch = StacksEpochId::Epoch31; } StacksEpochId::Epoch31 => { - panic!("No defined transition from Epoch31 forward") + receipts.append(&mut clarity_tx.block.initialize_epoch_3_2()?); + current_epoch = StacksEpochId::Epoch32; + } + StacksEpochId::Epoch32 => { + panic!("No defined transition from Epoch32 forward") } } @@ -4921,7 +4925,10 @@ impl StacksChainState { )?; Ok((stack_ops, transfer_ops, delegate_ops, vec![])) } - StacksEpochId::Epoch25 | StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => { + StacksEpochId::Epoch25 + | StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops_v210( chainstate_tx, parent_index_hash, @@ -5011,13 +5018,14 @@ impl StacksChainState { pox_reward_cycle, pox_start_cycle_info, ), - StacksEpochId::Epoch25 | StacksEpochId::Epoch30 | StacksEpochId::Epoch31 => { - Self::handle_pox_cycle_start_pox_4( - clarity_tx, - pox_reward_cycle, - pox_start_cycle_info, - ) - } + StacksEpochId::Epoch25 + | StacksEpochId::Epoch30 + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => Self::handle_pox_cycle_start_pox_4( + clarity_tx, + pox_reward_cycle, + pox_start_cycle_info, + ), } })?; debug!("check_and_handle_reward_start: handled pox cycle start"); diff --git a/stackslib/src/chainstate/stacks/db/mod.rs b/stackslib/src/chainstate/stacks/db/mod.rs index 09064efae2..51b38fd4b8 100644 --- a/stackslib/src/chainstate/stacks/db/mod.rs +++ b/stackslib/src/chainstate/stacks/db/mod.rs @@ -292,6 +292,7 @@ impl DBConfig { StacksEpochId::Epoch25 => version_u32 >= 3 && version_u32 <= 10, StacksEpochId::Epoch30 => version_u32 >= 3 && version_u32 <= 10, StacksEpochId::Epoch31 => version_u32 >= 3 && version_u32 <= 10, + StacksEpochId::Epoch32 => version_u32 >= 3 && version_u32 <= 10, } } } diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index e432ab902f..2df237e8fa 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -1658,6 +1658,10 @@ pub mod test { epoch_id: StacksEpochId::Epoch31, ast_rules: ASTRules::PrecheckSize, }; + pub const TestBurnStateDB_32: UnitTestBurnStateDB = UnitTestBurnStateDB { + epoch_id: StacksEpochId::Epoch32, + ast_rules: ASTRules::PrecheckSize, + }; pub const ALL_BURN_DBS: &[&dyn BurnStateDB] = &[ &TestBurnStateDB_20 as &dyn BurnStateDB, @@ -8731,6 +8735,7 @@ pub mod test { StacksEpochId::Epoch25 => self.get_stacks_epoch(6), StacksEpochId::Epoch30 => self.get_stacks_epoch(7), StacksEpochId::Epoch31 => self.get_stacks_epoch(8), + StacksEpochId::Epoch32 => self.get_stacks_epoch(9), } } fn get_pox_payout_addrs( diff --git a/stackslib/src/clarity_vm/clarity.rs b/stackslib/src/clarity_vm/clarity.rs index e9abf82f47..b2ee3e1770 100644 --- a/stackslib/src/clarity_vm/clarity.rs +++ b/stackslib/src/clarity_vm/clarity.rs @@ -29,6 +29,7 @@ use clarity::vm::database::{ RollbackWrapperPersistedLog, STXBalance, NULL_BURN_STATE_DB, NULL_HEADER_DB, }; use clarity::vm::errors::Error as InterpreterError; +use clarity::vm::events::{STXEventType, STXMintEventData}; use clarity::vm::representations::SymbolicExpression; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, Value}; use clarity::vm::{ClarityVersion, ContractName}; @@ -42,7 +43,8 @@ use crate::chainstate::stacks::boot::{ BOOT_CODE_COST_VOTING_TESTNET as BOOT_CODE_COST_VOTING, BOOT_CODE_POX_TESTNET, COSTS_2_NAME, COSTS_3_NAME, POX_2_MAINNET_CODE, POX_2_NAME, POX_2_TESTNET_CODE, POX_3_MAINNET_CODE, POX_3_NAME, POX_3_TESTNET_CODE, POX_4_CODE, POX_4_NAME, SIGNERS_BODY, SIGNERS_DB_0_BODY, - SIGNERS_DB_1_BODY, SIGNERS_NAME, SIGNERS_VOTING_BODY, SIGNERS_VOTING_NAME, + SIGNERS_DB_1_BODY, SIGNERS_NAME, SIGNERS_VOTING_BODY, SIGNERS_VOTING_NAME, SIP_031_BODY, + SIP_031_NAME, }; use crate::chainstate::stacks::db::{StacksAccount, StacksChainState}; use crate::chainstate::stacks::events::{StacksTransactionEvent, StacksTransactionReceipt}; @@ -57,6 +59,8 @@ use crate::util_lib::boot::{boot_code_acc, boot_code_addr, boot_code_id, boot_co use crate::util_lib::db::Error as DatabaseError; use crate::util_lib::strings::StacksString; +pub const SIP_031_INITIAL_MINT: u128 = 200_000_000_000_000; + /// /// A high-level interface for interacting with the Clarity VM. /// @@ -1596,6 +1600,113 @@ impl<'a> ClarityBlockConnection<'a, '_> { }) } + pub fn initialize_epoch_3_2(&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::Epoch32; + self.as_transaction(|tx_conn| { + // bump the epoch in the Clarity DB + tx_conn + .with_clarity_db(|db| { + db.set_clarity_epoch_version(StacksEpochId::Epoch32)?; + Ok(()) + }) + .unwrap(); + + // require 3.2 rules henceforth in this connection as well + tx_conn.epoch = StacksEpochId::Epoch32; + }); + + let mut receipts = vec![]; + + let boot_code_account = self + .get_boot_code_account() + .expect("FATAL: did not get boot account"); + + let mainnet = self.mainnet; + let tx_version = if mainnet { + TransactionVersion::Mainnet + } else { + TransactionVersion::Testnet + }; + + let boot_code_address = boot_code_addr(mainnet); + let boot_code_auth = boot_code_tx_auth(boot_code_address.clone()); + + // SIP-031 setup (deploy of the boot contract, minting and transfer to the boot contract) + let sip_031_contract_id = boot_code_id(SIP_031_NAME, mainnet); + let payload = TransactionPayload::SmartContract( + TransactionSmartContract { + name: ContractName::try_from(SIP_031_NAME) + .expect("FATAL: invalid boot-code contract name"), + code_body: StacksString::from_str(SIP_031_BODY) + .expect("FATAL: invalid boot code body"), + }, + Some(ClarityVersion::Clarity3), + ); + + let sip_031_contract_tx = + StacksTransaction::new(tx_version.clone(), boot_code_auth, payload); + + let mut sip_031_initialization_receipt = self.as_transaction(|tx_conn| { + // initialize with a synthetic transaction + debug!("Instantiate {} contract", &sip_031_contract_id); + let receipt = StacksChainState::process_transaction_payload( + tx_conn, + &sip_031_contract_tx, + &boot_code_account, + ASTRules::PrecheckSize, + None, + ) + .expect("FATAL: Failed to process .sip031 contract initialization"); + receipt + }); + + if sip_031_initialization_receipt.result != Value::okay_true() + || sip_031_initialization_receipt.post_condition_aborted + { + panic!( + "FATAL: Failure processing sip031 contract initialization: {:#?}", + &sip_031_initialization_receipt + ); + } + + let recipient = PrincipalData::Contract(boot_code_id(SIP_031_NAME, mainnet)); + + self.as_transaction(|tx_conn| { + tx_conn + .with_clarity_db(|db| { + db.increment_ustx_liquid_supply(SIP_031_INITIAL_MINT) + .map_err(|e| e.into()) + }) + .expect("FATAL: `SIP-031 initial mint` overflowed"); + StacksChainState::account_credit( + tx_conn, + &recipient, + u64::try_from(SIP_031_INITIAL_MINT) + .expect("FATAL: transferred more STX than exist"), + ); + }); + + let event = STXEventType::STXMintEvent(STXMintEventData { + recipient, + amount: SIP_031_INITIAL_MINT, + }); + sip_031_initialization_receipt + .events + .push(StacksTransactionEvent::STXEvent(event)); + + receipts.push(sip_031_initialization_receipt); + + debug!("Epoch 3.2 initialized"); + (old_cost_tracker, Ok(receipts)) + }) + } + pub fn start_transaction_processing(&mut self) -> ClarityTransactionConnection { ClarityTransactionConnection::new( &mut self.datastore, diff --git a/stackslib/src/clarity_vm/tests/large_contract.rs b/stackslib/src/clarity_vm/tests/large_contract.rs index 36e24054d7..1915d3db36 100644 --- a/stackslib/src/clarity_vm/tests/large_contract.rs +++ b/stackslib/src/clarity_vm/tests/large_contract.rs @@ -163,7 +163,8 @@ fn test_simple_token_system(#[case] version: ClarityVersion, #[case] epoch: Stac | StacksEpochId::Epoch24 | StacksEpochId::Epoch25 | StacksEpochId::Epoch30 - | StacksEpochId::Epoch31 => { + | StacksEpochId::Epoch31 + | StacksEpochId::Epoch32 => { let (ast, _analysis) = tx .analyze_smart_contract( &boot_code_id("costs-3", false), diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 4689b73ace..c994d37b23 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -718,6 +718,8 @@ impl Config { Ok(StacksEpochId::Epoch30) } else if epoch_name == EPOCH_CONFIG_3_1_0 { Ok(StacksEpochId::Epoch31) + } else if epoch_name == EPOCH_CONFIG_3_2_0 { + Ok(StacksEpochId::Epoch32) } else { Err(format!("Unknown epoch name specified: {epoch_name}")) }?; @@ -745,6 +747,7 @@ impl Config { StacksEpochId::Epoch25, StacksEpochId::Epoch30, StacksEpochId::Epoch31, + StacksEpochId::Epoch32, ]; for (expected_epoch, configured_epoch) in expected_list .iter() @@ -1726,6 +1729,7 @@ pub const EPOCH_CONFIG_2_4_0: &str = "2.4"; pub const EPOCH_CONFIG_2_5_0: &str = "2.5"; pub const EPOCH_CONFIG_3_0_0: &str = "3.0"; pub const EPOCH_CONFIG_3_1_0: &str = "3.1"; +pub const EPOCH_CONFIG_3_2_0: &str = "3.2"; #[derive(Clone, Deserialize, Default, Debug)] pub struct AffirmationOverride { diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index 76e194aaff..3661c2504a 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -49,8 +49,8 @@ pub use stacks_common::consts::{ NETWORK_ID_TESTNET, PEER_NETWORK_EPOCH, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, - PEER_VERSION_EPOCH_3_1, PEER_VERSION_MAINNET, PEER_VERSION_MAINNET_MAJOR, PEER_VERSION_TESTNET, - PEER_VERSION_TESTNET_MAJOR, STACKS_EPOCH_MAX, + PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, PEER_VERSION_MAINNET, + PEER_VERSION_MAINNET_MAJOR, PEER_VERSION_TESTNET, PEER_VERSION_TESTNET_MAJOR, STACKS_EPOCH_MAX, }; // default port @@ -104,6 +104,8 @@ pub const BITCOIN_MAINNET_STACKS_25_BURN_HEIGHT: u64 = 840_360; pub const BITCOIN_MAINNET_STACKS_30_BURN_HEIGHT: u64 = 867_867; /// This is Epoch-3.1, activation height proposed in SIP-029 pub const BITCOIN_MAINNET_STACKS_31_BURN_HEIGHT: u64 = 875_000; +/// This is Epoch-3.2, activation height proposed in SIP-031 (placeholder for now) +pub const BITCOIN_MAINNET_STACKS_32_BURN_HEIGHT: u64 = u64::MAX; /// Bitcoin mainline testnet3 activation heights. /// TODO: No longer used since testnet3 is dead, so remove. @@ -119,6 +121,7 @@ pub const BITCOIN_TESTNET_STACKS_24_BURN_HEIGHT: u64 = 2_432_545; pub const BITCOIN_TESTNET_STACKS_25_BURN_HEIGHT: u64 = 2_583_893; pub const BITCOIN_TESTNET_STACKS_30_BURN_HEIGHT: u64 = 30_000_000; pub const BITCOIN_TESTNET_STACKS_31_BURN_HEIGHT: u64 = 30_000_001; +pub const BITCOIN_TESTNET_STACKS_32_BURN_HEIGHT: u64 = 30_000_002; /// This constant sets the approximate testnet bitcoin height at which 2.5 Xenon /// was reorged back to 2.5 instantiation. This is only used to calculate the @@ -311,10 +314,17 @@ lazy_static! { StacksEpoch { epoch_id: StacksEpochId::Epoch31, start_height: BITCOIN_MAINNET_STACKS_31_BURN_HEIGHT, - end_height: STACKS_EPOCH_MAX, + end_height: BITCOIN_MAINNET_STACKS_32_BURN_HEIGHT, block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_1 }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: BITCOIN_MAINNET_STACKS_32_BURN_HEIGHT, + end_height: STACKS_EPOCH_MAX, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_2 + }, ]); } @@ -386,10 +396,17 @@ lazy_static! { StacksEpoch { epoch_id: StacksEpochId::Epoch31, start_height: BITCOIN_TESTNET_STACKS_31_BURN_HEIGHT, - end_height: STACKS_EPOCH_MAX, + end_height: BITCOIN_TESTNET_STACKS_32_BURN_HEIGHT, block_limit: BLOCK_LIMIT_MAINNET_21.clone(), network_epoch: PEER_VERSION_EPOCH_3_1 }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: BITCOIN_TESTNET_STACKS_32_BURN_HEIGHT, + end_height: STACKS_EPOCH_MAX, + block_limit: BLOCK_LIMIT_MAINNET_21.clone(), + network_epoch: PEER_VERSION_EPOCH_3_2 + }, ]); } @@ -503,6 +520,10 @@ pub static STACKS_EPOCH_3_0_MARKER: u8 = 0x0b; /// running it prior to 3.1 activation. pub static STACKS_EPOCH_3_1_MARKER: u8 = 0x0d; +/// Stacks 3.0 epoch marker. All block-commits in 3.2 must have a memo bitfield with this value +/// *or greater*. +pub static STACKS_EPOCH_3_2_MARKER: u8 = 0x0e; + #[test] fn test_ord_for_stacks_epoch() { let epochs = &*STACKS_EPOCHS_MAINNET; @@ -783,6 +804,8 @@ pub trait StacksEpochExtension { #[cfg(test)] fn unit_test_3_1(epoch_2_0_block_height: u64) -> EpochList; #[cfg(test)] + fn unit_test_3_2(epoch_2_0_block_height: u64) -> EpochList; + #[cfg(test)] fn unit_test_2_1_only(epoch_2_0_block_height: u64) -> EpochList; #[cfg(test)] fn unit_test_3_0_only(first_burnchain_height: u64) -> EpochList; @@ -1551,6 +1574,148 @@ impl StacksEpochExtension for StacksEpoch { ]) } + #[cfg(test)] + fn unit_test_3_2(first_burnchain_height: u64) -> EpochList { + info!( + "StacksEpoch unit_test_3_2 first_burn_height = {}", + first_burnchain_height + ); + + EpochList::new(&[ + StacksEpoch { + epoch_id: StacksEpochId::Epoch10, + start_height: 0, + end_height: first_burnchain_height, + block_limit: ExecutionCost::max_value(), + network_epoch: PEER_VERSION_EPOCH_1_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch20, + start_height: first_burnchain_height, + end_height: first_burnchain_height + 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: first_burnchain_height + 20, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_3, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch24, + start_height: first_burnchain_height + 20, + end_height: first_burnchain_height + 24, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_4, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch25, + start_height: first_burnchain_height + 24, + end_height: first_burnchain_height + 28, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_2_5, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch30, + start_height: first_burnchain_height + 28, + end_height: first_burnchain_height + 32, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_3_0, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch31, + start_height: first_burnchain_height + 32, + end_height: first_burnchain_height + 36, + block_limit: ExecutionCost { + write_length: 210210, + write_count: 210210, + read_length: 210210, + read_count: 210210, + runtime: 210210, + }, + network_epoch: PEER_VERSION_EPOCH_3_1, + }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: first_burnchain_height + 36, + 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_3_2, + }, + ]) + } + #[cfg(test)] fn unit_test_2_1_only(first_burnchain_height: u64) -> EpochList { info!( @@ -1690,6 +1855,7 @@ impl StacksEpochExtension for StacksEpoch { StacksEpochId::Epoch25 => StacksEpoch::unit_test_2_5(first_burnchain_height), StacksEpochId::Epoch30 => StacksEpoch::unit_test_3_0(first_burnchain_height), StacksEpochId::Epoch31 => StacksEpoch::unit_test_3_1(first_burnchain_height), + StacksEpochId::Epoch32 => StacksEpoch::unit_test_3_2(first_burnchain_height), } } diff --git a/stackslib/src/cost_estimates/pessimistic.rs b/stackslib/src/cost_estimates/pessimistic.rs index 997fe8e8cb..314cbdc6aa 100644 --- a/stackslib/src/cost_estimates/pessimistic.rs +++ b/stackslib/src/cost_estimates/pessimistic.rs @@ -222,6 +222,8 @@ impl PessimisticEstimator { StacksEpochId::Epoch30 => ":2.1", // reuse cost estimates in Epoch31 StacksEpochId::Epoch31 => ":2.1", + // reuse cost estimates in Epoch32 + StacksEpochId::Epoch32 => ":2.1", }; format!( "cc{}:{}:{}.{}", diff --git a/testnet/stacks-node/src/tests/nakamoto_integrations.rs b/testnet/stacks-node/src/tests/nakamoto_integrations.rs index 6adc8255e3..4b3fbc0324 100644 --- a/testnet/stacks-node/src/tests/nakamoto_integrations.rs +++ b/testnet/stacks-node/src/tests/nakamoto_integrations.rs @@ -74,7 +74,8 @@ use stacks::core::{ EpochList, StacksEpoch, StacksEpochId, BLOCK_LIMIT_MAINNET_10, HELIUM_BLOCK_LIMIT_20, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0, PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2, PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, - PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_TESTNET, + PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0, PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, + PEER_VERSION_TESTNET, }; use stacks::libstackerdb::{SlotMetadata, StackerDBChunkData}; use stacks::net::api::callreadonly::CallReadOnlyRequestBody; @@ -127,8 +128,14 @@ use crate::{tests, BitcoinRegtestController, BurnchainController, Config, Config pub static POX_4_DEFAULT_STACKER_BALANCE: u64 = 100_000_000_000_000; pub static POX_4_DEFAULT_STACKER_STX_AMT: u128 = 99_000_000_000_000; +use clarity::vm::database::STXBalance; +use stacks::chainstate::stacks::boot::SIP_031_NAME; +use stacks::clarity_vm::clarity::SIP_031_INITIAL_MINT; + +use crate::clarity::vm::clarity::ClarityConnection; + lazy_static! { - pub static ref NAKAMOTO_INTEGRATION_EPOCHS: [StacksEpoch; 10] = [ + pub static ref NAKAMOTO_INTEGRATION_EPOCHS: [StacksEpoch; 11] = [ StacksEpoch { epoch_id: StacksEpochId::Epoch10, start_height: 0, @@ -195,10 +202,17 @@ lazy_static! { StacksEpoch { epoch_id: StacksEpochId::Epoch31, start_height: 241, - end_height: STACKS_EPOCH_MAX, + end_height: 251, block_limit: HELIUM_BLOCK_LIMIT_20.clone(), network_epoch: PEER_VERSION_EPOCH_3_1 }, + StacksEpoch { + epoch_id: StacksEpochId::Epoch32, + start_height: 251, + end_height: STACKS_EPOCH_MAX, + block_limit: HELIUM_BLOCK_LIMIT_20.clone(), + network_epoch: PEER_VERSION_EPOCH_3_2 + }, ]; } @@ -12667,3 +12681,179 @@ fn write_signer_update( ); } } + +/// Test SIP-031 activation +/// +/// - check epoch 3.2 is active +/// - check sip031 boot contract has a balance of 200_000_000 STX (TODO) +#[test] +#[ignore] +fn test_sip_031_activation() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + // skip the test til we move to epoch 3.2 + if StacksEpochId::latest() != StacksEpochId::Epoch32 { + return; + } + + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + naka_conf.node.pox_sync_sample_secs = 180; + naka_conf.burnchain.max_rbf = 10_000_000; + + let sender_sk = Secp256k1PrivateKey::random(); + let sender_signer_sk = Secp256k1PrivateKey::random(); + let sender_signer_addr = tests::to_addr(&sender_signer_sk); + let mut signers = TestSigners::new(vec![sender_signer_sk]); + let tenure_count = 5; + let inter_blocks_per_tenure = 9; + // setup sender + recipient for some test stx transfers + // these are necessary for the interim blocks to get mined at all + let sender_addr = tests::to_addr(&sender_sk); + let send_amt = 100; + let send_fee = 180; + naka_conf.add_initial_balance( + PrincipalData::from(sender_addr).to_string(), + (send_amt + send_fee) * tenure_count * inter_blocks_per_tenure, + ); + naka_conf.add_initial_balance(PrincipalData::from(sender_signer_addr).to_string(), 100000); + let stacker_sk = setup_stacker(&mut naka_conf); + + test_observer::spawn(); + test_observer::register_any(&mut naka_conf); + + let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone()); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, + naka_submitted_commits: commits_submitted, + .. + } = run_loop.counters(); + let counters = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::Builder::new() + .name("run_loop".into()) + .spawn(move || run_loop.start(None, 0)) + .unwrap(); + wait_for_runloop(&blocks_processed); + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk], + &[sender_signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + let burnchain = naka_conf.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + let (mut chainstate, _) = StacksChainState::open( + naka_conf.is_mainnet(), + naka_conf.burnchain.chain_id, + &naka_conf.get_chainstate_path_str(), + None, + ) + .unwrap(); + + info!("Nakamoto miner started..."); + blind_signer(&naka_conf, &signers, &counters); + + wait_for_first_naka_block_commit(60, &commits_submitted); + + // mine until epooch 3.2 height + loop { + let commits_before = commits_submitted.load(Ordering::SeqCst); + next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel) + .unwrap(); + wait_for(20, || { + Ok(commits_submitted.load(Ordering::SeqCst) > commits_before) + }) + .unwrap(); + + let node_info = get_chain_info_opt(&naka_conf).unwrap(); + if node_info.burn_block_height + >= naka_conf.burnchain.epochs.clone().unwrap()[StacksEpochId::Epoch32].start_height + { + break; + } + } + + info!( + "Nakamoto miner has advanced to bitcoin height {}", + get_chain_info_opt(&naka_conf).unwrap().burn_block_height + ); + + // check for Epoch 3.2 in clarity db + let latest_stacks_block_id = get_latest_block_proposal(&naka_conf, &sortdb) + .unwrap() + .0 + .block_id(); + + let epoch_version = chainstate.with_read_only_clarity_tx( + &sortdb + .index_handle_at_block(&chainstate, &latest_stacks_block_id) + .unwrap(), + &latest_stacks_block_id, + |conn| conn.with_clarity_db_readonly(|db| db.get_clarity_epoch_version().unwrap()), + ); + + assert_eq!(epoch_version, Some(StacksEpochId::Epoch32)); + + // check if sip-031 boot contract has been deployed + let sip_031_boot_contract_exists = chainstate.with_read_only_clarity_tx( + &sortdb + .index_handle_at_block(&chainstate, &latest_stacks_block_id) + .unwrap(), + &latest_stacks_block_id, + |conn| { + conn.with_clarity_db_readonly(|db| { + db.has_contract(&boot_code_id(SIP_031_NAME, naka_conf.is_mainnet())) + }) + }, + ); + + assert_eq!(sip_031_boot_contract_exists, Some(true)); + + // check if sip-031 boot contract has a balance of 200_000_000 STX + let sip_031_boot_contract_balance = chainstate.with_read_only_clarity_tx( + &sortdb + .index_handle_at_block(&chainstate, &latest_stacks_block_id) + .unwrap(), + &latest_stacks_block_id, + |conn| { + conn.with_clarity_db_readonly(|db| { + db.get_account_stx_balance(&PrincipalData::Contract(boot_code_id( + SIP_031_NAME, + naka_conf.is_mainnet(), + ))) + }) + }, + ); + + assert_eq!( + sip_031_boot_contract_balance, + Some(Ok(STXBalance::Unlocked { + amount: SIP_031_INITIAL_MINT + })) + ); + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +}