diff --git a/CHANGELOG.md b/CHANGELOG.md index 927e16f6e..2c643f3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ incompatible, however. transaction. Second, fix the "same chainstate hash" fallback check. - Winning block txid lookups in the SortitionDB have been corrected to use the txid during the lookup. +- The miner will no longer attempt to mine a new Stacks block if it receives a + microblock in a discontinuous microblock stream. ## [2.0.5] - 2021-02-12 diff --git a/src/chainstate/stacks/db/blocks.rs b/src/chainstate/stacks/db/blocks.rs index 4e25e8dc7..91fe7222d 100644 --- a/src/chainstate/stacks/db/blocks.rs +++ b/src/chainstate/stacks/db/blocks.rs @@ -1356,6 +1356,7 @@ impl StacksChainState { let mut ret = vec![]; let mut tip: Option = None; let mut fork_poison = None; + let mut expected_sequence = start_seq; // load associated staging microblock data, but best-effort. // Stop loading once we find a fork juncture. @@ -1384,6 +1385,18 @@ impl StacksChainState { } }; + if mblock.header.sequence > expected_sequence { + warn!( + "Discontinuous microblock stream: expected seq {}, got {}", + expected_sequence, mblock.header.sequence + ); + break; + } + + // expect forks, so expected_sequence may not always increase + expected_sequence = + cmp::min(mblock.header.sequence, expected_sequence).saturating_add(1); + if let Some(tip_mblock) = tip { if mblock.header.sequence == tip_mblock.header.sequence { debug!( @@ -6912,6 +6925,133 @@ pub mod test { ); } + #[test] + fn stacks_db_staging_microblock_stream_load_continuous_streams() { + let mut chainstate = instantiate_chainstate( + false, + 0x80000000, + "stacks_db_staging_microblock_stream_load_continuous_streams", + ); + let privk = StacksPrivateKey::from_hex( + "eb05c83546fdd2c79f10f5ad5434a90dd28f7e3acb7c092157aa1bc3656b012c01", + ) + .unwrap(); + + let block = make_empty_coinbase_block(&privk); + let microblocks = make_sample_microblock_stream(&privk, &block.block_hash()); + let mut child_block = make_empty_coinbase_block(&privk); + + child_block.header.parent_block = block.block_hash(); + child_block.header.parent_microblock = microblocks.first().as_ref().unwrap().block_hash(); + child_block.header.parent_microblock_sequence = + microblocks.first().as_ref().unwrap().header.sequence; + + assert!(StacksChainState::load_staging_microblock( + &chainstate.db(), + &ConsensusHash([2u8; 20]), + &block.block_hash(), + µblocks[0].block_hash() + ) + .unwrap() + .is_none()); + assert!(StacksChainState::load_descendant_staging_microblock_stream( + &chainstate.db(), + &StacksBlockHeader::make_index_block_hash( + &ConsensusHash([2u8; 20]), + &block.block_hash() + ), + 0, + u16::max_value() + ) + .unwrap() + .is_none()); + + store_staging_block( + &mut chainstate, + &ConsensusHash([2u8; 20]), + &block, + &ConsensusHash([1u8; 20]), + 1, + 2, + ); + + // don't store the first microblock, but store the rest + for (i, mb) in microblocks.iter().enumerate() { + if i > 0 { + store_staging_microblock( + &mut chainstate, + &ConsensusHash([2u8; 20]), + &block.block_hash(), + mb, + ); + } + } + store_staging_block( + &mut chainstate, + &ConsensusHash([3u8; 20]), + &child_block, + &ConsensusHash([2u8; 20]), + 1, + 2, + ); + + // block should be stored to staging + assert_block_staging_not_processed(&mut chainstate, &ConsensusHash([2u8; 20]), &block); + assert_block_staging_not_processed( + &mut chainstate, + &ConsensusHash([3u8; 20]), + &child_block, + ); + assert_block_not_stored(&mut chainstate, &ConsensusHash([2u8; 20]), &block); + assert_block_not_stored(&mut chainstate, &ConsensusHash([3u8; 20]), &child_block); + + // missing head + assert!(StacksChainState::load_staging_microblock( + &chainstate.db(), + &ConsensusHash([2u8; 20]), + &block.block_hash(), + µblocks[0].block_hash() + ) + .unwrap() + .is_none()); + + // subsequent microblock stream should be stored to staging + assert!(StacksChainState::load_staging_microblock( + &chainstate.db(), + &ConsensusHash([2u8; 20]), + &block.block_hash(), + µblocks[1].block_hash() + ) + .unwrap() + .is_some()); + assert_eq!( + StacksChainState::load_staging_microblock( + &chainstate.db(), + &ConsensusHash([2u8; 20]), + &block.block_hash(), + µblocks[1].block_hash() + ) + .unwrap() + .unwrap() + .try_into_microblock() + .unwrap(), + microblocks[1] + ); + + // can't load descendent stream because missing head + assert!(StacksChainState::load_descendant_staging_microblock_stream( + &chainstate.db(), + &StacksBlockHeader::make_index_block_hash( + &ConsensusHash([2u8; 20]), + &block.block_hash() + ), + 0, + u16::max_value() + ) + .unwrap() + .is_none()); + } + #[test] fn stacks_db_validate_parent_microblock_stream() { let privk = StacksPrivateKey::from_hex(