@@ -3104,12 +3104,36 @@ impl StacksChainState {
31043104 return Some ( ( end, None ) ) ;
31053105 }
31063106
3107- /// Validate an anchored block against the burn chain state.
3107+ /// Determine whether or not a block executed an epoch transition. That is, did this block
3108+ /// call `initialize_epoch_2_05()` or similar when it was processed.
3109+ pub fn block_crosses_epoch_boundary (
3110+ block_conn : & DBConn ,
3111+ parent_consensus_hash : & ConsensusHash ,
3112+ parent_block_hash : & BlockHeaderHash ,
3113+ ) -> Result < bool , db_error > {
3114+ let sql = "SELECT 1 FROM epoch_transitions WHERE block_id = ?1" ;
3115+ let args: & [ & dyn ToSql ] = & [ & StacksBlockHeader :: make_index_block_hash (
3116+ parent_consensus_hash,
3117+ parent_block_hash,
3118+ ) ] ;
3119+ let res = block_conn
3120+ . query_row ( sql, args, |_r| Ok ( ( ) ) )
3121+ . optional ( )
3122+ . map ( |x| x. is_some ( ) ) ?;
3123+
3124+ Ok ( res)
3125+ }
3126+
3127+ /// Validate an anchored block against the burn chain state. Determines if this given Stacks
3128+ /// block can attach to the chainstate. Called before inserting the block into the staging
3129+ /// DB.
3130+ ///
31083131 /// Returns Some(commit burn, total burn) if valid
31093132 /// Returns None if not valid
31103133 /// * consensus_hash is the PoX history hash of the burnchain block whose sortition
31113134 /// (ostensibly) selected this block for inclusion.
31123135 fn validate_anchored_block_burnchain (
3136+ blocks_conn : & DBConn ,
31133137 db_handle : & SortitionHandleConn ,
31143138 consensus_hash : & ConsensusHash ,
31153139 block : & StacksBlock ,
@@ -3190,6 +3214,27 @@ impl StacksChainState {
31903214 return Ok ( None ) ;
31913215 }
31923216
3217+ // NEW in 2.05
3218+ // if the parent block marks an epoch transition, then its children necessarily run in a
3219+ // different Clarity epoch. Its children therefore are not permitted to confirm any of
3220+ // their parents' microblocks.
3221+ if StacksChainState :: block_crosses_epoch_boundary (
3222+ blocks_conn,
3223+ & stacks_chain_tip. consensus_hash ,
3224+ & stacks_chain_tip. winning_stacks_block_hash ,
3225+ ) ? {
3226+ if block. has_microblock_parent ( ) {
3227+ warn ! (
3228+ "Invalid block {}/{}: its parent {}/{} crossed the epoch boundary but this block confirmed its microblocks" ,
3229+ & consensus_hash,
3230+ & block. block_hash( ) ,
3231+ & stacks_chain_tip. consensus_hash,
3232+ & stacks_chain_tip. winning_stacks_block_hash
3233+ ) ;
3234+ return Ok ( None ) ;
3235+ }
3236+ }
3237+
31933238 let sortition_burns =
31943239 SortitionDB :: get_block_burn_amount ( db_handle, & penultimate_sortition_snapshot)
31953240 . expect ( "FATAL: have block commit but no total burns in its sortition" ) ;
@@ -3204,7 +3249,10 @@ impl StacksChainState {
32043249 /// to the blockchain. The consensus_hash is the hash of the burnchain block whose sortition
32053250 /// elected the given Stacks block.
32063251 ///
3207- /// If we find the same Stacks block in two or more burnchain forks, insert it there too
3252+ /// If we find the same Stacks block in two or more burnchain forks, insert it there too.
3253+ ///
3254+ /// (New in 2.05+) If the anchored block descends from a parent anchored block in a different
3255+ /// system epoch, then it *must not* have a parent microblock stream.
32083256 ///
32093257 /// sort_ic: an indexed connection to a sortition DB
32103258 /// consensus_hash: this is the consensus hash of the sortition that chose this block
@@ -3274,6 +3322,7 @@ impl StacksChainState {
32743322
32753323 // does this block match the burnchain state? skip if not
32763324 let validation_res = StacksChainState :: validate_anchored_block_burnchain (
3325+ & block_tx,
32773326 & sort_handle,
32783327 consensus_hash,
32793328 block,
@@ -3896,10 +3945,11 @@ impl StacksChainState {
38963945
38973946 /// If an epoch transition occurs at this Stacks block,
38983947 /// apply the transition and return any receipts from the transition.
3948+ /// Return (applied?, receipts)
38993949 pub fn process_epoch_transition (
39003950 clarity_tx : & mut ClarityTx ,
39013951 chain_tip_burn_header_height : u32 ,
3902- ) -> Result < Vec < StacksTransactionReceipt > , Error > {
3952+ ) -> Result < ( bool , Vec < StacksTransactionReceipt > ) , Error > {
39033953 // is this stacks block the first of a new epoch?
39043954 let ( stacks_parent_epoch, sortition_epoch) = clarity_tx. with_clarity_db_readonly ( |db| {
39053955 (
@@ -3909,6 +3959,7 @@ impl StacksChainState {
39093959 } ) ;
39103960
39113961 let mut receipts = vec ! [ ] ;
3962+ let mut applied = false ;
39123963
39133964 if let Some ( sortition_epoch) = sortition_epoch {
39143965 // the parent stacks block has a different epoch than what the Sortition DB
@@ -3929,14 +3980,15 @@ impl StacksChainState {
39293980 "Should only transition from Epoch20 to Epoch2_05"
39303981 ) ;
39313982 receipts. push ( clarity_tx. block . initialize_epoch_2_05 ( ) ?) ;
3983+ applied = true ;
39323984 }
39333985 StacksEpochId :: Epoch2_05 => {
39343986 panic ! ( "No defined transition from Epoch2_05 forward" )
39353987 }
39363988 }
39373989 }
39383990 }
3939- Ok ( receipts)
3991+ Ok ( ( applied , receipts) )
39403992 }
39413993
39423994 /// Process any Stacking-related bitcoin operations
@@ -4263,6 +4315,31 @@ impl StacksChainState {
42634315
42644316 let mainnet = chainstate_tx. get_config ( ) . mainnet ;
42654317 let next_block_height = block. header . total_work . work ;
4318+ let applied_epoch_transition;
4319+
4320+ // NEW in 2.05
4321+ // if the parent marked an epoch transition -- i.e. its children necessarily run in
4322+ // different Clarity epochs -- then this block cannot confirm any of its microblocks.
4323+ if StacksChainState :: block_crosses_epoch_boundary (
4324+ chainstate_tx. deref ( ) ,
4325+ & parent_chain_tip. consensus_hash ,
4326+ & parent_chain_tip. anchored_header . block_hash ( ) ,
4327+ ) ? {
4328+ debug ! (
4329+ "Block {}/{} (mblock parent {}) crosses epoch boundary from parent {}/{}" ,
4330+ chain_tip_consensus_hash,
4331+ & block. block_hash( ) ,
4332+ & block. header. parent_microblock,
4333+ & parent_chain_tip. consensus_hash,
4334+ & parent_chain_tip. anchored_header. block_hash( )
4335+ ) ;
4336+ if block. has_microblock_parent ( ) {
4337+ let msg =
4338+ "Invalid block, mined in different epoch than parent but confirms microblocks" ;
4339+ warn ! ( "{}" , & msg) ;
4340+ return Err ( Error :: InvalidStacksBlock ( msg. to_string ( ) ) ) ;
4341+ }
4342+ }
42664343
42674344 // find matured miner rewards, so we can grant them within the Clarity DB tx.
42684345 let latest_matured_miners = StacksChainState :: get_scheduled_block_rewards (
@@ -4494,11 +4571,13 @@ impl StacksChainState {
44944571 "evaluated_epoch" => %evaluated_epoch) ;
44954572
44964573 // is this stacks block the first of a new epoch?
4497- let mut receipts = StacksChainState :: process_epoch_transition (
4574+ let ( epoch_transition , mut receipts) = StacksChainState :: process_epoch_transition (
44984575 & mut clarity_tx,
44994576 chain_tip_burn_header_height,
45004577 ) ?;
45014578
4579+ applied_epoch_transition = epoch_transition;
4580+
45024581 // process stacking operations from bitcoin ops
45034582 receipts. extend ( StacksChainState :: process_stacking_ops (
45044583 & mut clarity_tx,
@@ -4698,6 +4777,7 @@ impl StacksChainState {
46984777 user_burns,
46994778 & block_execution_cost,
47004779 block_size,
4780+ applied_epoch_transition,
47014781 )
47024782 . expect ( "FATAL: failed to advance chain tip" ) ;
47034783
0 commit comments