Skip to content

Commit a750638

Browse files
authored
Merge pull request #2920 from blockstack/fix/2904
2.05: make it so a block whose anchored parent is in a different epoch cannot confirm a microblock stream
2 parents 57c711c + 62d38bc commit a750638

File tree

9 files changed

+861
-62
lines changed

9 files changed

+861
-62
lines changed

src/burnchains/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,13 @@ pub mod test {
10341034
txop.txid =
10351035
Txid::from_test_data(txop.block_height, txop.vtxindex, &txop.burn_header_hash, 0);
10361036

1037+
let epoch = SortitionDB::get_stacks_epoch(ic, txop.block_height)
1038+
.unwrap()
1039+
.expect(&format!("BUG: no epoch for height {}", &txop.block_height));
1040+
if epoch.epoch_id == StacksEpochId::Epoch2_05 {
1041+
txop.memo = vec![STACKS_EPOCH_2_05_MARKER];
1042+
}
1043+
10371044
self.txs
10381045
.push(BlockstackOperationType::LeaderBlockCommit(txop.clone()));
10391046

src/chainstate/burn/db/sortdb.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1604,7 +1604,10 @@ impl<'a> SortitionHandleConn<'a> {
16041604
Ok(winning_user_burns)
16051605
}
16061606

1607-
/// Get the block snapshot of the parent stacks block of the given stacks block
1607+
/// Get the block snapshot of the parent stacks block of the given stacks block.
1608+
/// The returned block-commit is for the given (consensus_hash, block_hash).
1609+
/// The returned BlockSnapshot is for the parent of the block identified by (consensus_hash,
1610+
/// block_hash).
16081611
pub fn get_block_snapshot_of_parent_stacks_block(
16091612
&self,
16101613
consensus_hash: &ConsensusHash,

src/chainstate/stacks/block.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,12 @@ impl StacksBlockHeader {
339339
// * state_index_root (validated on process_block())
340340
Ok(())
341341
}
342+
343+
/// Does this header have a microblock parent?
344+
pub fn has_microblock_parent(&self) -> bool {
345+
self.parent_microblock != EMPTY_MICROBLOCK_PARENT_HASH
346+
|| self.parent_microblock_sequence != 0
347+
}
342348
}
343349

344350
impl StacksMessageCodec for StacksBlock {
@@ -628,6 +634,11 @@ impl StacksBlock {
628634
}
629635
return true;
630636
}
637+
638+
/// Does this block have a microblock parent?
639+
pub fn has_microblock_parent(&self) -> bool {
640+
self.header.has_microblock_parent()
641+
}
631642
}
632643

633644
impl StacksMessageCodec for StacksMicroblockHeader {

src/chainstate/stacks/db/accounts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ mod test {
866866
&user_burns,
867867
&ExecutionCost::zero(),
868868
123,
869+
false,
869870
)
870871
.unwrap();
871872
tx.commit().unwrap();

src/chainstate/stacks/db/blocks.rs

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)