diff --git a/CHANGELOG.md b/CHANGELOG.md index 046ca667a0..b7ba323582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE ### Changed +## [3.0.0.0.4] + +### Added + +### Changed + +- Use the same burn view loader in both block validation and block processing ## [3.0.0.0.3] diff --git a/stacks-signer/CHANGELOG.md b/stacks-signer/CHANGELOG.md index 46e25b285f..a332b344ce 100644 --- a/stacks-signer/CHANGELOG.md +++ b/stacks-signer/CHANGELOG.md @@ -11,6 +11,14 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE ### Changed +## [3.0.0.0.4.0] + +### Added + +### Changed + +- Use the same burn view loader in both block validation and block processing + ## [3.0.0.0.3.0] ### Added diff --git a/stacks-signer/src/signerdb.rs b/stacks-signer/src/signerdb.rs index 9fcaa1fa1b..c0bd679a54 100644 --- a/stacks-signer/src/signerdb.rs +++ b/stacks-signer/src/signerdb.rs @@ -871,16 +871,6 @@ where .map_err(DBError::SerializationError) } -#[cfg(test)] -pub fn test_signer_db(db_path: &str) -> SignerDb { - use std::fs; - - if fs::metadata(db_path).is_ok() { - fs::remove_file(db_path).unwrap(); - } - SignerDb::new(db_path).expect("Failed to create signer db") -} - #[cfg(test)] mod tests { use std::fs; diff --git a/stackslib/src/chainstate/burn/db/sortdb.rs b/stackslib/src/chainstate/burn/db/sortdb.rs index dd543ac7f7..661342ace5 100644 --- a/stackslib/src/chainstate/burn/db/sortdb.rs +++ b/stackslib/src/chainstate/burn/db/sortdb.rs @@ -3986,7 +3986,7 @@ impl<'a> SortitionDBConn<'a> { tip, reward_cycle_id, )?; - info!("Fetching preprocessed reward set"; + debug!("Fetching preprocessed reward set"; "tip_sortition_id" => %tip, "reward_cycle_id" => reward_cycle_id, "prepare_phase_start_sortition_id" => %first_sortition, diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index ca37e30121..dbaf226015 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -1788,6 +1788,73 @@ impl NakamotoChainState { } } + /// Get the current burnchain view + /// This is either: + /// (1) set by the tenure change tx if one exists + /// (2) the same as parent block id + pub fn get_block_burn_view( + sort_db: &SortitionDB, + next_ready_block: &NakamotoBlock, + parent_header_info: &StacksHeaderInfo, + ) -> Result { + let burnchain_view = if let Some(tenure_change) = next_ready_block.get_tenure_tx_payload() { + if let Some(ref parent_burn_view) = parent_header_info.burn_view { + // check that the tenure_change's burn view descends from the parent + let parent_burn_view_sn = SortitionDB::get_block_snapshot_consensus( + sort_db.conn(), + parent_burn_view, + )? + .ok_or_else(|| { + warn!( + "Cannot process Nakamoto block: could not find parent block's burnchain view"; + "consensus_hash" => %next_ready_block.header.consensus_hash, + "stacks_block_hash" => %next_ready_block.header.block_hash(), + "stacks_block_id" => %next_ready_block.header.block_id(), + "parent_block_id" => %next_ready_block.header.parent_block_id + ); + ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) + })?; + let handle = sort_db.index_handle_at_ch(&tenure_change.burn_view_consensus_hash)?; + let connected_sort_id = get_ancestor_sort_id(&handle, parent_burn_view_sn.block_height, &handle.context.chain_tip)? + .ok_or_else(|| { + warn!( + "Cannot process Nakamoto block: could not find parent block's burnchain view"; + "consensus_hash" => %next_ready_block.header.consensus_hash, + "stacks_block_hash" => %next_ready_block.header.block_hash(), + "stacks_block_id" => %next_ready_block.header.block_id(), + "parent_block_id" => %next_ready_block.header.parent_block_id + ); + ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) + })?; + if connected_sort_id != parent_burn_view_sn.sortition_id { + warn!( + "Cannot process Nakamoto block: parent block's burnchain view does not connect to own burn view"; + "consensus_hash" => %next_ready_block.header.consensus_hash, + "stacks_block_hash" => %next_ready_block.header.block_hash(), + "stacks_block_id" => %next_ready_block.header.block_id(), + "parent_block_id" => %next_ready_block.header.parent_block_id + ); + return Err(ChainstateError::InvalidStacksBlock( + "Does not connect to burn view of parent block ID".into(), + )); + } + } + tenure_change.burn_view_consensus_hash + } else { + parent_header_info.burn_view.clone().ok_or_else(|| { + warn!( + "Cannot process Nakamoto block: parent block does not have a burnchain view and current block has no tenure tx"; + "consensus_hash" => %next_ready_block.header.consensus_hash, + "stacks_block_hash" => %next_ready_block.header.block_hash(), + "stacks_block_id" => %next_ready_block.header.block_id(), + "parent_block_id" => %next_ready_block.header.parent_block_id + ); + ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) + })? + }; + Ok(burnchain_view) + } + /// Process the next ready block. /// If there exists a ready Nakamoto block, then this method returns Ok(Some(..)) with the /// receipt. Otherwise, it returns Ok(None). @@ -1920,62 +1987,8 @@ impl NakamotoChainState { // this is either: // (1) set by the tenure change tx if one exists // (2) the same as parent block id - - let burnchain_view = if let Some(tenure_change) = next_ready_block.get_tenure_tx_payload() { - if let Some(ref parent_burn_view) = parent_header_info.burn_view { - // check that the tenure_change's burn view descends from the parent - let parent_burn_view_sn = SortitionDB::get_block_snapshot_consensus( - sort_db.conn(), - parent_burn_view, - )? - .ok_or_else(|| { - warn!( - "Cannot process Nakamoto block: could not find parent block's burnchain view"; - "consensus_hash" => %next_ready_block.header.consensus_hash, - "stacks_block_hash" => %next_ready_block.header.block_hash(), - "stacks_block_id" => %next_ready_block.header.block_id(), - "parent_block_id" => %next_ready_block.header.parent_block_id - ); - ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) - })?; - let handle = sort_db.index_handle_at_ch(&tenure_change.burn_view_consensus_hash)?; - let connected_sort_id = get_ancestor_sort_id(&handle, parent_burn_view_sn.block_height, &handle.context.chain_tip)? - .ok_or_else(|| { - warn!( - "Cannot process Nakamoto block: could not find parent block's burnchain view"; - "consensus_hash" => %next_ready_block.header.consensus_hash, - "stacks_block_hash" => %next_ready_block.header.block_hash(), - "stacks_block_id" => %next_ready_block.header.block_id(), - "parent_block_id" => %next_ready_block.header.parent_block_id - ); - ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) - })?; - if connected_sort_id != parent_burn_view_sn.sortition_id { - warn!( - "Cannot process Nakamoto block: parent block's burnchain view does not connect to own burn view"; - "consensus_hash" => %next_ready_block.header.consensus_hash, - "stacks_block_hash" => %next_ready_block.header.block_hash(), - "stacks_block_id" => %next_ready_block.header.block_id(), - "parent_block_id" => %next_ready_block.header.parent_block_id - ); - return Err(ChainstateError::InvalidStacksBlock( - "Does not connect to burn view of parent block ID".into(), - )); - } - } - tenure_change.burn_view_consensus_hash - } else { - parent_header_info.burn_view.clone().ok_or_else(|| { - warn!( - "Cannot process Nakamoto block: parent block does not have a burnchain view and current block has no tenure tx"; - "consensus_hash" => %next_ready_block.header.consensus_hash, - "stacks_block_hash" => %next_ready_block.header.block_hash(), - "stacks_block_id" => %next_ready_block.header.block_id(), - "parent_block_id" => %next_ready_block.header.parent_block_id - ); - ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into()) - })? - }; + let burnchain_view = + Self::get_block_burn_view(sort_db, &next_ready_block, &parent_header_info)?; let Some(burnchain_view_sn) = SortitionDB::get_block_snapshot_consensus(sort_db.conn(), &burnchain_view)? else { diff --git a/stackslib/src/net/api/postblock_proposal.rs b/stackslib/src/net/api/postblock_proposal.rs index b6f91c59b8..2d93c4a789 100644 --- a/stackslib/src/net/api/postblock_proposal.rs +++ b/stackslib/src/net/api/postblock_proposal.rs @@ -374,9 +374,30 @@ impl NakamotoBlockProposal { }); } - let sort_tip = SortitionDB::get_canonical_sortition_tip(sortdb.conn())?; - let burn_dbconn: SortitionHandleConn = sortdb.index_handle(&sort_tip); - let mut db_handle = sortdb.index_handle(&sort_tip); + // open sortition view to the current burn view. + // If the block has a TenureChange with an Extend cause, then the burn view is whatever is + // indicated in the TenureChange. + // Otherwise, it's the same as the block's parent's burn view. + let parent_stacks_header = NakamotoChainState::get_block_header( + chainstate.db(), + &self.block.header.parent_block_id, + )? + .ok_or_else(|| BlockValidateRejectReason { + reason_code: ValidateRejectCode::InvalidBlock, + reason: "Invalid parent block".into(), + })?; + + let burn_view_consensus_hash = + NakamotoChainState::get_block_burn_view(sortdb, &self.block, &parent_stacks_header)?; + let sort_tip = + SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &burn_view_consensus_hash)? + .ok_or_else(|| BlockValidateRejectReason { + reason_code: ValidateRejectCode::NoSuchTenure, + reason: "Failed to find sortition for block tenure".to_string(), + })?; + + let burn_dbconn: SortitionHandleConn = sortdb.index_handle(&sort_tip.sortition_id); + let mut db_handle = sortdb.index_handle(&sort_tip.sortition_id); // (For the signer) // Verify that the block's tenure is on the canonical sortition history @@ -413,14 +434,6 @@ impl NakamotoBlockProposal { )?; // Validate txs against chainstate - let parent_stacks_header = NakamotoChainState::get_block_header( - chainstate.db(), - &self.block.header.parent_block_id, - )? - .ok_or_else(|| BlockValidateRejectReason { - reason_code: ValidateRejectCode::InvalidBlock, - reason: "Invalid parent block".into(), - })?; // Validate the block's timestamp. It must be: // - Greater than the parent block's timestamp diff --git a/testnet/stacks-node/src/nakamoto_node/miner.rs b/testnet/stacks-node/src/nakamoto_node/miner.rs index b15d0f4c7e..745ae03fc9 100644 --- a/testnet/stacks-node/src/nakamoto_node/miner.rs +++ b/testnet/stacks-node/src/nakamoto_node/miner.rs @@ -143,7 +143,11 @@ pub struct BlockMinerThread { registered_key: RegisteredKey, /// Burnchain block snapshot which elected this miner burn_election_block: BlockSnapshot, - /// Current burnchain tip + /// Current burnchain tip as of the last TenureChange + /// * if the last tenure-change was a BlockFound, then this is the same as the + /// `burn_election_block`. + /// * otherwise, if the last tenure-change is an Extend, then this is the sortition of the burn + /// view consensus hash in the TenureChange burn_block: BlockSnapshot, /// The start of the parent tenure for this tenure parent_tenure_id: StacksBlockId, diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 2486043ccc..cd3488bd07 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -506,7 +506,7 @@ fn block_proposal_rejection() { signer_test.wait_for_validate_reject_response(short_timeout, block_signer_signature_hash_2); assert!(matches!( reject.reason_code, - ValidateRejectCode::UnknownParent + ValidateRejectCode::InvalidBlock )); let start_polling = Instant::now(); @@ -532,7 +532,10 @@ fn block_proposal_rejection() { assert!(matches!(reason_code, RejectCode::SortitionViewMismatch)); } else if signer_signature_hash == block_signer_signature_hash_2 { found_signer_signature_hash_2 = true; - assert!(matches!(reason_code, RejectCode::ValidationFailed(_))); + assert!(matches!( + reason_code, + RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock) + )); } else { continue; }