From ba1ceac7452fd88b97a7fa61b200963a2a25f284 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 14:41:35 -0700 Subject: [PATCH 01/22] Remove handle_affirmation_reorg, check_pox_anchor_affirmation Signed-off-by: Jacinta Ferrant --- stacks-node/src/tests/signer/multiversion.rs | 2 +- stackslib/src/chainstate/coordinator/mod.rs | 861 +------------------ 2 files changed, 14 insertions(+), 849 deletions(-) diff --git a/stacks-node/src/tests/signer/multiversion.rs b/stacks-node/src/tests/signer/multiversion.rs index 39d903ff82..7435f1875d 100644 --- a/stacks-node/src/tests/signer/multiversion.rs +++ b/stacks-node/src/tests/signer/multiversion.rs @@ -14,7 +14,7 @@ // along with this program. If not, see . use std::sync::mpsc::TryRecvError; use std::thread; -use std::time::{Duration, SystemTime}; +use std::time::Duration; use libsigner::v0::messages::{SignerMessage, StateMachineUpdate}; use libsigner::v0::signer_state::{MinerState, ReplayTransactionSet, SignerStateMachine}; diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index cc21170543..7cb0adf654 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -29,7 +29,7 @@ use stacks_common::util::get_epoch_time_secs; pub use self::comm::CoordinatorCommunication; use super::stacks::boot::{RewardSet, RewardSetData}; use super::stacks::db::blocks::DummyEventDispatcher; -use crate::burnchains::affirmation::{AffirmationMap, AffirmationMapEntry}; +use crate::burnchains::affirmation::AffirmationMap; use crate::burnchains::db::{BurnchainBlockData, BurnchainDB, BurnchainHeaderReader}; use crate::burnchains::{ Burnchain, BurnchainBlockHeader, Error as BurnchainError, PoxConstants, Txid, @@ -1671,595 +1671,6 @@ impl< Ok(()) } - /// Compare the coordinator's heaviest affirmation map to the heaviest affirmation map in the - /// burnchain DB. If they are different, then invalidate all sortitions not represented on - /// the coordinator's heaviest affirmation map that are now represented by the burnchain DB's - /// heaviest affirmation map. - /// - /// Care must be taken to ensure that a sortition that was already created, but invalidated, is - /// not re-created. This can happen if the affirmation map flaps, causing a sortition that was - /// created and invalidated to become valid again. The code here addresses this by considering - /// three ranges of sortitions (grouped by reward cycle) when processing a new heaviest - /// affirmation map: - /// - /// * The range of sortitions that are valid in both affirmation maps. These sortitions - /// correspond to the affirmation maps' common prefix. - /// * The range of sortitions that exists and are invalid on the coordinator's current - /// affirmation map, but are valid on the new heaviest affirmation map. These sortitions - /// come strictly after the common prefix, and are identified by the variables - /// `first_invalid_start_block` and `last_invalid_start_block` (which identifies their lowest - /// and highest block heights). - /// * The range of sortitions that are currently valid, and need to be invalidated. This range - /// comes strictly after the aforementioned previously-invalid-but-now-valid sortition range. - /// - /// The code does not modify any sortition state for the common prefix of sortitions. - /// - /// The code identifies the second range of previously-invalid-but-now-valid sortitions and marks them - /// as valid once again. In addition, it updates the Stacks chainstate DB such that any Stacks - /// blocks that were orphaned and never processed can be retried with the now-revalidated - /// sortition. - /// - /// The code identifies the third range of now-invalid sortitions and marks them as invalid in - /// the sortition DB. - /// - /// Note that regardless of the affirmation map status, a Stacks block will remain processed - /// once it gets accepted. Its underlying sortition may become invalidated, in which case, the - /// Stacks block would no longer be considered as part of the canonical Stacks fork (since the - /// canonical Stacks chain tip must reside on a valid sortition). However, a Stacks block that - /// should be processed at the end of the day may temporarily be considered orphaned if there - /// is a "deep" affirmation map reorg that causes at least one reward cycle's sortitions to - /// be treated as invalid. This is what necessitates retrying Stacks blocks that have been - /// downloaded and considered orphaned because they were never processed -- they may in fact be - /// valid and processable once the node has identified the canonical sortition history! - /// - /// The only kinds of errors returned here are database query errors. - fn handle_affirmation_reorg(&mut self) -> Result<(), Error> { - // find the stacks chain's affirmation map - let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - - let sortition_tip = self.canonical_sortition_tip.as_ref().expect( - "FAIL: processing an affirmation reorg, but don't have a canonical sortition tip", - ); - - let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - - let sortition_height = - SortitionDB::get_block_snapshot(self.sortition_db.conn(), sortition_tip)? - .unwrap_or_else(|| panic!("FATAL: no sortition {sortition_tip}")) - .block_height; - - let sortition_reward_cycle = self - .burnchain - .block_height_to_reward_cycle(sortition_height) - .unwrap_or(0); - - let heaviest_am = self.get_heaviest_affirmation_map(sortition_tip)?; - - if let Some(changed_reward_cycle) = - self.check_chainstate_against_burnchain_affirmations()? - { - debug!( - "Canonical sortition tip is {sortition_tip} height {sortition_height} (rc {sortition_reward_cycle}); changed reward cycle is {changed_reward_cycle}" - ); - - if changed_reward_cycle >= sortition_reward_cycle { - // nothing we can do - debug!("Changed reward cycle is {changed_reward_cycle} but canonical sortition is in {sortition_reward_cycle}, so no affirmation reorg is possible"); - return Ok(()); - } - - let current_reward_cycle = self - .burnchain - .block_height_to_reward_cycle(canonical_burnchain_tip.block_height) - .unwrap_or(0); - - // sortitions between [first_invalidate_start_block, last_invalidate_start_block) will - // be invalidated. Any orphaned Stacks blocks in this range will be forgotten, so they - // can be retried later with the new sortitions in this burnchain block range. - // - // valid_sortitions include all sortitions in this range that are now valid (i.e. - // they were invalidated before, but will be valid again as a result of this reorg). - let (first_invalidate_start_block, last_invalidate_start_block, valid_sortitions) = - match self.find_invalid_and_revalidated_sortitions( - &heaviest_am, - changed_reward_cycle, - current_reward_cycle, - )? { - Some(x) => x, - None => { - // the sortition AM is consistent with the heaviest AM. - // If the sortition AM is not consistent with the canonical AM, then it - // means that we have new anchor blocks to consider - let canonical_affirmation_map = - self.get_canonical_affirmation_map(sortition_tip)?; - let sort_am = self - .sortition_db - .find_sortition_tip_affirmation_map(sortition_tip)?; - let revalidation_params = if canonical_affirmation_map.len() - == sort_am.len() - && canonical_affirmation_map != sort_am - { - if let Some(diverged_rc) = - canonical_affirmation_map.find_divergence(&sort_am) - { - debug!( - "Sortition AM `{sort_am}` diverges from canonical AM `{canonical_affirmation_map}` at cycle {diverged_rc}" - ); - let (last_invalid_sortition_height, valid_sortitions) = self - .find_valid_sortitions( - &canonical_affirmation_map, - self.burnchain.reward_cycle_to_block_height(diverged_rc), - canonical_burnchain_tip.block_height, - )?; - Some(( - last_invalid_sortition_height, - self.burnchain - .reward_cycle_to_block_height(sort_am.len() as u64), - valid_sortitions, - )) - } else { - None - } - } else { - None - }; - if let Some(x) = revalidation_params { - debug!( - "Sortition AM `{sort_am}` is not consistent with canonical AM `{canonical_affirmation_map}`" - ); - x - } else { - // everything is consistent. - // Just update the canonical stacks block pointer on the highest valid - // sortition. - let last_2_05_rc = - self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - - let mut sort_tx = self.sortition_db.tx_begin()?; - let (canonical_ch, canonical_bhh, canonical_height) = - Self::find_highest_stacks_block_with_compatible_affirmation_map( - &heaviest_am, - sortition_tip, - &self.burnchain_blocks_db, - &mut sort_tx, - self.chain_state_db.db(), - )?; - - let stacks_am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(sortition_tip)?, - &sort_tx, - &canonical_ch, - &canonical_bhh, - )?; - - debug!("Canonical Stacks tip for highest valid sortition {} ({}) is {}/{} height {} am `{}`", &sortition_tip, sortition_height, &canonical_ch, &canonical_bhh, canonical_height, &stacks_am); - - SortitionDB::revalidate_snapshot_with_block( - &sort_tx, - sortition_tip, - &canonical_ch, - &canonical_bhh, - canonical_height, - Some(true), - )?; - sort_tx.commit()?; - return Ok(()); - } - } - }; - - // check valid_sortitions -- it may correspond to a range of sortitions beyond our - // current highest-valid sortition (in which case, *do not* revalidate them) - let valid_sortitions = if let Some(first_sn) = valid_sortitions.first() { - if first_sn.block_height > sortition_height { - debug!("No sortitions to revalidate: highest is {},{}, first candidate is {},{}. Will not revalidate.", sortition_height, &sortition_tip, first_sn.block_height, &first_sn.sortition_id); - vec![] - } else { - valid_sortitions - } - } else { - valid_sortitions - }; - - // find our ancestral sortition ID that's the end of the last reward cycle - // the new affirmation map would have in common with the old affirmation - // map, and invalidate its descendants - let ic = self.sortition_db.index_conn(); - - // find the burnchain block hash and height of the first burnchain block in which we'll - // invalidate all descendant sortitions, but retain some previously-invalidated - // sortitions - let revalidated_burn_header = BurnchainDB::get_burnchain_header( - self.burnchain_blocks_db.conn(), - &self.burnchain_indexer, - first_invalidate_start_block - 1, - ) - .expect("FATAL: failed to read burnchain DB") - .unwrap_or_else(|| { - panic!( - "FATAL: no burnchain block {}", - first_invalidate_start_block - 1 - ) - }); - - // find the burnchain block hash and height of the first burnchain block in which we'll - // invalidate all descendant sortitions, no matter what. - let invalidated_burn_header = BurnchainDB::get_burnchain_header( - self.burnchain_blocks_db.conn(), - &self.burnchain_indexer, - last_invalidate_start_block - 1, - ) - .expect("FATAL: failed to read burnchain DB") - .unwrap_or_else(|| { - panic!( - "FATAL: no burnchain block {}", - last_invalidate_start_block - 1 - ) - }); - - // let invalidation_height = revalidate_sn.block_height; - let invalidation_height = revalidated_burn_header.block_height; - - debug!("Invalidate all descendants of {} (after height {}), revalidate some sortitions at and after height {}, and retry all orphaned Stacks blocks at or after height {}", - &revalidated_burn_header.block_hash, revalidated_burn_header.block_height, invalidated_burn_header.block_height, first_invalidate_start_block); - - let mut highest_valid_sortition_id = - if sortition_height > last_invalidate_start_block - 1 { - let invalidate_sn = SortitionDB::get_ancestor_snapshot( - &ic, - last_invalidate_start_block - 1, - sortition_tip, - )? - .unwrap_or_else(|| { - panic!( - "BUG: no ancestral sortition at height {}", - last_invalidate_start_block - 1 - ) - }); - - valid_sortitions - .last() - .unwrap_or(&invalidate_sn) - .sortition_id - .clone() - } else { - sortition_tip.clone() - }; - - let mut stacks_blocks_to_unorphan = vec![]; - let chainstate_db_conn = self.chain_state_db.db(); - - self.sortition_db.invalidate_descendants_with_closures( - &revalidated_burn_header.block_hash, - |_sort_tx, burn_header, _invalidate_queue| { - // do this once in the transaction, after we've invalidated all other - // sibling blocks to these now-valid sortitions - test_debug!( - "Invalidate all sortitions descending from {} ({} remaining)", - &burn_header, - _invalidate_queue.len() - ); - stacks_blocks_to_unorphan.push((burn_header.clone(), invalidation_height)); - }, - |sort_tx| { - // no more sortitions to invalidate -- all now-incompatible - // sortitions have been invalidated. - let (canonical_ch, canonical_bhh, canonical_height) = Self::find_highest_stacks_block_with_compatible_affirmation_map(&heaviest_am, &highest_valid_sortition_id, &self.burnchain_blocks_db, sort_tx, chainstate_db_conn) - .expect("FATAL: could not find a valid parent Stacks block"); - - let stacks_am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(&highest_valid_sortition_id).expect("FATAL: failed to query stacks DB"), - sort_tx, - &canonical_ch, - &canonical_bhh - ) - .expect("FATAL: failed to query stacks DB"); - - debug!("Canonical Stacks tip after invalidations is {}/{} height {} am `{}`", &canonical_ch, &canonical_bhh, canonical_height, &stacks_am); - - // Revalidate sortitions, and declare that we have their Stacks blocks. - for valid_sn in valid_sortitions.iter() { - test_debug!("Revalidate snapshot {},{}", valid_sn.block_height, &valid_sn.sortition_id); - let block_known = StacksChainState::is_stacks_block_processed( - chainstate_db_conn, - &valid_sn.consensus_hash, - &valid_sn.winning_stacks_block_hash, - ).expect("FATAL: failed to query chainstate DB"); - - SortitionDB::revalidate_snapshot_with_block(sort_tx, &valid_sn.sortition_id, &canonical_ch, &canonical_bhh, canonical_height, Some(block_known)).unwrap_or_else(|_| panic!("FATAL: failed to revalidate sortition {}", - valid_sn.sortition_id)); - } - - // recalculate highest valid sortition with revalidated snapshots - highest_valid_sortition_id = if sortition_height > last_invalidate_start_block - 1 { - let invalidate_sn = SortitionDB::get_ancestor_snapshot_tx( - sort_tx, - last_invalidate_start_block - 1, - sortition_tip, - ) - .expect("FATAL: failed to query the sortition DB") - .unwrap_or_else(|| panic!("BUG: no ancestral sortition at height {}", - last_invalidate_start_block - 1)); - - valid_sortitions - .last() - .unwrap_or(&invalidate_sn) - .sortition_id - .clone() - } - else { - sortition_tip.clone() - }; - - // recalculate highest valid stacks tip - let (canonical_ch, canonical_bhh, canonical_height) = Self::find_highest_stacks_block_with_compatible_affirmation_map(&heaviest_am, &highest_valid_sortition_id, &self.burnchain_blocks_db, sort_tx, chainstate_db_conn) - .expect("FATAL: could not find a valid parent Stacks block"); - - let stacks_am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(&highest_valid_sortition_id).expect("FATAL: failed to query stacks DB"), - sort_tx, - &canonical_ch, - &canonical_bhh - ) - .expect("FATAL: failed to query stacks DB"); - - debug!("Canonical Stacks tip after invalidations and revalidations is {}/{} height {} am `{}`", &canonical_ch, &canonical_bhh, canonical_height, &stacks_am); - - // update dirty canonical block pointers. - let dirty_snapshots = SortitionDB::find_snapshots_with_dirty_canonical_block_pointers(sort_tx, canonical_height) - .expect("FATAL: failed to find dirty snapshots"); - - for dirty_sort_id in dirty_snapshots.iter() { - test_debug!("Revalidate dirty snapshot {}", dirty_sort_id); - - let dirty_sort_sn = SortitionDB::get_block_snapshot(sort_tx, dirty_sort_id) - .expect("FATAL: failed to query sortition DB") - .expect("FATAL: no such dirty sortition"); - - let block_known = StacksChainState::is_stacks_block_processed( - chainstate_db_conn, - &dirty_sort_sn.consensus_hash, - &dirty_sort_sn.winning_stacks_block_hash, - ).expect("FATAL: failed to query chainstate DB"); - - SortitionDB::revalidate_snapshot_with_block(sort_tx, dirty_sort_id, &canonical_ch, &canonical_bhh, canonical_height, Some(block_known)).unwrap_or_else(|_| panic!("FATAL: failed to revalidate dirty sortition {}", - dirty_sort_id)); - } - - // recalculate highest valid stacks tip once more - let (canonical_ch, canonical_bhh, canonical_height) = Self::find_highest_stacks_block_with_compatible_affirmation_map(&heaviest_am, &highest_valid_sortition_id, &self.burnchain_blocks_db, sort_tx, chainstate_db_conn) - .expect("FATAL: could not find a valid parent Stacks block"); - - let stacks_am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(&highest_valid_sortition_id).expect("FATAL: failed to query stacks DB"), - sort_tx, - &canonical_ch, - &canonical_bhh - ) - .expect("FATAL: failed to query stacks DB"); - - debug!("Canonical Stacks tip after invalidations, revalidations, and processed dirty snapshots is {}/{} height {} am `{}`", &canonical_ch, &canonical_bhh, canonical_height, &stacks_am); - - let highest_valid_sn = SortitionDB::get_block_snapshot(sort_tx, &highest_valid_sortition_id) - .expect("FATAL: failed to query sortition ID") - .expect("FATAL: highest valid sortition ID does not have a snapshot"); - - let block_known = StacksChainState::is_stacks_block_processed( - chainstate_db_conn, - &highest_valid_sn.consensus_hash, - &highest_valid_sn.winning_stacks_block_hash, - ).expect("FATAL: failed to query chainstate DB"); - - SortitionDB::revalidate_snapshot_with_block(sort_tx, &highest_valid_sortition_id, &canonical_ch, &canonical_bhh, canonical_height, Some(block_known)).unwrap_or_else(|_| panic!("FATAL: failed to revalidate highest valid sortition {}", - &highest_valid_sortition_id)); - }, - )?; - - let ic = self.sortition_db.index_conn(); - - let mut chainstate_db_tx = self.chain_state_db.db_tx_begin()?; - for (burn_header, invalidation_height) in stacks_blocks_to_unorphan { - // permit re-processing of any associated stacks blocks if they're - // orphaned - forget_orphan_stacks_blocks( - &ic, - &mut chainstate_db_tx, - &burn_header, - invalidation_height, - )?; - } - - // un-orphan blocks that had been orphaned but were tied to this now-revalidated sortition history - Self::undo_stacks_block_orphaning( - self.burnchain_blocks_db.conn(), - &self.burnchain_indexer, - &ic, - &mut chainstate_db_tx, - first_invalidate_start_block, - last_invalidate_start_block, - )?; - - // by holding this lock as long as we do, we ensure that the sortition DB's - // view of the canonical stacks chain tip can't get changed (since no - // Stacks blocks can be processed). - chainstate_db_tx.commit().map_err(DBError::SqliteError)?; - - let highest_valid_snapshot = SortitionDB::get_block_snapshot( - self.sortition_db.conn(), - &highest_valid_sortition_id, - )? - .expect("FATAL: highest valid sortition doesn't exist"); - - let stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &highest_valid_snapshot.sortition_id, - &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, - &highest_valid_snapshot.canonical_stacks_tip_hash, - )?; - - debug!( - "Highest valid sortition (changed) is {} ({} in height {}, affirmation map {}); Stacks tip is {}/{} height {} (affirmation map {}); heaviest AM is {}", - &highest_valid_snapshot.sortition_id, - &highest_valid_snapshot.burn_header_hash, - highest_valid_snapshot.block_height, - &self.sortition_db.find_sortition_tip_affirmation_map(&highest_valid_snapshot.sortition_id)?, - &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, - &highest_valid_snapshot.canonical_stacks_tip_hash, - highest_valid_snapshot.canonical_stacks_tip_height, - &stacks_tip_affirmation_map, - &heaviest_am - ); - - self.canonical_sortition_tip = Some(highest_valid_snapshot.sortition_id); - } else { - let highest_valid_snapshot = - SortitionDB::get_block_snapshot(self.sortition_db.conn(), sortition_tip)? - .expect("FATAL: highest valid sortition doesn't exist"); - - let stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &highest_valid_snapshot.sortition_id, - &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, - &highest_valid_snapshot.canonical_stacks_tip_hash, - )?; - - debug!( - "Highest valid sortition (not changed) is {} ({} in height {}, affirmation map {}); Stacks tip is {}/{} height {} (affirmation map {}); heaviest AM is {}", - &highest_valid_snapshot.sortition_id, - &highest_valid_snapshot.burn_header_hash, - highest_valid_snapshot.block_height, - &self.sortition_db.find_sortition_tip_affirmation_map(&highest_valid_snapshot.sortition_id)?, - &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, - &highest_valid_snapshot.canonical_stacks_tip_hash, - highest_valid_snapshot.canonical_stacks_tip_height, - &stacks_tip_affirmation_map, - &heaviest_am - ); - } - - Ok(()) - } - - /// Use the network's affirmations to re-interpret our local PoX anchor block status into what - /// the network affirmed was their PoX anchor block statuses. - /// If we're blocked on receiving a new anchor block that we don't have (i.e. the network - /// affirmed that it exists), then indicate so by returning its hash. - fn reinterpret_affirmed_pox_anchor_block_status( - &self, - canonical_affirmation_map: &AffirmationMap, - header: &BurnchainBlockHeader, - rc_info: &mut RewardCycleInfo, - ) -> Result, Error> { - // re-calculate the reward cycle info's anchor block status, based on what - // the network has affirmed in each prepare phase. - - // is this anchor block affirmed? Only process it if so! - let new_reward_cycle = self - .burnchain - .block_height_to_reward_cycle(header.block_height) - .expect("BUG: processed block before start of epoch 2.1"); - - test_debug!( - "Verify affirmation against PoX info in reward cycle {} canonical affirmation map {}", - new_reward_cycle, - canonical_affirmation_map - ); - - let new_status = if new_reward_cycle > 0 - && new_reward_cycle <= (canonical_affirmation_map.len() as u64) - { - let affirmed_rc = new_reward_cycle - 1; - - // we're processing an anchor block from an earlier reward cycle, - // meaning that we're in the middle of an affirmation reorg. - let affirmation = canonical_affirmation_map - .at(affirmed_rc) - .expect("BUG: checked index overflow") - .to_owned(); - test_debug!("Affirmation '{affirmation}' for anchor block of previous reward cycle {affirmed_rc} canonical affirmation map {canonical_affirmation_map}"); - - // switch reward cycle info assessment based on what the network - // affirmed. - match &rc_info.anchor_status { - PoxAnchorBlockStatus::SelectedAndKnown(block_hash, txid, reward_set) => { - match affirmation { - AffirmationMapEntry::PoxAnchorBlockPresent => { - // matches affirmation - PoxAnchorBlockStatus::SelectedAndKnown( - block_hash.clone(), - txid.clone(), - reward_set.clone(), - ) - } - AffirmationMapEntry::PoxAnchorBlockAbsent => { - // network actually affirms that this anchor block - // is absent. - warn!("Chose PoX anchor block for reward cycle {affirmed_rc}, but it is affirmed absent by the network"; "affirmation map" => %&canonical_affirmation_map); - PoxAnchorBlockStatus::SelectedAndUnknown( - block_hash.clone(), - txid.clone(), - ) - } - AffirmationMapEntry::Nothing => { - // no anchor block selected either way - PoxAnchorBlockStatus::NotSelected - } - } - } - PoxAnchorBlockStatus::SelectedAndUnknown(ref block_hash, ref txid) => { - match affirmation { - AffirmationMapEntry::PoxAnchorBlockPresent => { - // the network affirms that this anchor block - // exists, but we don't have it locally. Stop - // processing here and wait for it to arrive, via - // the downloader. - info!("Anchor block {block_hash} (txid {txid}) for reward cycle {affirmed_rc} is affirmed by the network ({canonical_affirmation_map}), but must be downloaded"); - return Ok(Some(block_hash.clone())); - } - AffirmationMapEntry::PoxAnchorBlockAbsent => { - // matches affirmation - PoxAnchorBlockStatus::SelectedAndUnknown( - block_hash.clone(), - txid.clone(), - ) - } - AffirmationMapEntry::Nothing => { - // no anchor block selected either way - PoxAnchorBlockStatus::NotSelected - } - } - } - PoxAnchorBlockStatus::NotSelected => { - // no anchor block selected either way - PoxAnchorBlockStatus::NotSelected - } - } - } else { - // no-op: our view of the set of anchor blocks is consistent with - // the canonical affirmation map, so the status of this new anchor - // block is whatever it was calculated to be. - rc_info.anchor_status.clone() - }; - - // update new status - debug!( - "Update anchor block status for reward cycle {} from {:?} to {:?}", - new_reward_cycle, &rc_info.anchor_status, &new_status - ); - rc_info.anchor_status = new_status; - Ok(None) - } - /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// @@ -2329,7 +1740,6 @@ impl< fn check_missing_anchor_block( &self, header: &BurnchainBlockHeader, - canonical_affirmation_map: &AffirmationMap, rc_info: &mut RewardCycleInfo, ) -> Result, Error> { let cur_epoch = @@ -2338,45 +1748,18 @@ impl< panic!("BUG: no epoch defined at height {}", header.block_height) }); - if self.config.assume_present_anchor_blocks { + if cur_epoch.epoch_id >= StacksEpochId::Epoch21 || self.config.assume_present_anchor_blocks + { // anchor blocks are always assumed to be present in the chain history, // so report its absence if we don't have it. if let PoxAnchorBlockStatus::SelectedAndUnknown(missing_anchor_block, _) = &rc_info.anchor_status { - info!( - "Currently missing PoX anchor block {}, which is assumed to be present", - &missing_anchor_block - ); + info!("Currently missing PoX anchor block {missing_anchor_block}, which is assumed to be present"); return Ok(Some(missing_anchor_block.clone())); } } - if cur_epoch.epoch_id >= StacksEpochId::Epoch21 || self.config.always_use_affirmation_maps { - // potentially have an anchor block, but only process the next reward cycle (and - // subsequent reward cycles) with it if the prepare-phase block-commits affirm its - // presence. This only gets checked in Stacks 2.1 or later (unless overridden - // in the config) - - // NOTE: this mutates rc_info if it returns None - if let Some(missing_anchor_block) = self.reinterpret_affirmed_pox_anchor_block_status( - canonical_affirmation_map, - header, - rc_info, - )? { - if self.config.require_affirmed_anchor_blocks { - // missing this anchor block -- cannot proceed until we have it - info!( - "Burnchain block processing stops due to missing affirmed anchor stacks block hash {missing_anchor_block}" - ); - return Ok(Some(missing_anchor_block)); - } else { - // this and descendant sortitions might already exist - info!("Burnchain block processing will continue in spite of missing affirmed anchor stacks block hash {missing_anchor_block}"); - } - } - } - test_debug!( "Reward cycle info at height {}: {:?}", &header.block_height, @@ -2411,10 +1794,7 @@ impl< let canonical_snapshot = match self.canonical_sortition_tip.as_ref() { Some(sn_tip) => SortitionDB::get_block_snapshot(self.sortition_db.conn(), sn_tip)? .unwrap_or_else(|| { - panic!( - "FATAL: do not have previously-calculated highest valid sortition tip {}", - sn_tip - ) + panic!("FATAL: do not have previously-calculated highest valid sortition tip {sn_tip}") }), None => SortitionDB::get_canonical_burn_chain_tip(self.sortition_db.conn())?, }; @@ -2464,50 +1844,16 @@ impl< let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - // first, see if the canonical affirmation map has changed. If so, this will wind back the - // canonical sortition tip. - // - // only do this if affirmation maps are supported in this epoch. - let before_canonical_snapshot = match self.canonical_sortition_tip.as_ref() { - Some(sn_tip) => SortitionDB::get_block_snapshot(self.sortition_db.conn(), sn_tip)? - .unwrap_or_else(|| { - panic!( - "FATAL: do not have previously-calculated highest valid sortition tip {}", - sn_tip - ) - }), - None => SortitionDB::get_canonical_burn_chain_tip(self.sortition_db.conn())?, - }; - let cur_epoch = SortitionDB::get_stacks_epoch( - self.sortition_db.conn(), - before_canonical_snapshot.block_height, - )? - .unwrap_or_else(|| { - panic!( - "BUG: no epoch defined at height {}", - before_canonical_snapshot.block_height - ) - }); - - if self.affirmation_maps_active(&cur_epoch.epoch_id) { - self.handle_affirmation_reorg()?; - } - // Retrieve canonical burnchain chain tip from the BurnchainBlocksDB let canonical_snapshot = match self.canonical_sortition_tip.as_ref() { Some(sn_tip) => SortitionDB::get_block_snapshot(self.sortition_db.conn(), sn_tip)? .unwrap_or_else(|| { - panic!( - "FATAL: do not have previously-calculated highest valid sortition tip {}", - sn_tip - ) + panic!("FATAL: do not have previously-calculated highest valid sortition tip {sn_tip}") }), None => SortitionDB::get_canonical_burn_chain_tip(self.sortition_db.conn())?, }; let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - let canonical_affirmation_map = - self.get_canonical_affirmation_map(&canonical_snapshot.sortition_id)?; let heaviest_am = self.get_heaviest_affirmation_map(&canonical_snapshot.sortition_id)?; @@ -2594,8 +1940,8 @@ impl< .unwrap_or(u64::MAX); debug!( - "Process burn block {} reward cycle {} in {}", - header.block_height, reward_cycle, &self.burnchain.working_dir, + "Process burn block {} reward cycle {reward_cycle} in {}", + header.block_height, &self.burnchain.working_dir, ); // calculate paid rewards during this burnchain block if we announce @@ -2616,12 +1962,9 @@ impl< if let Some(rc_info) = reward_cycle_info.as_mut() { if let Some(missing_anchor_block) = - self.check_missing_anchor_block(&header, &canonical_affirmation_map, rc_info)? + self.check_missing_anchor_block(&header, rc_info)? { - info!( - "Burnchain block processing stops due to missing affirmed anchor stacks block hash {}", - &missing_anchor_block - ); + info!("Burnchain block processing stops due to missing affirmed anchor stacks block hash {missing_anchor_block}"); return Ok(Some(missing_anchor_block)); } } @@ -3101,148 +2444,6 @@ impl< Ok(()) } - /// Verify that a PoX anchor block candidate is affirmed by the network. - /// Returns Ok(Some(pox_anchor)) if so. - /// Returns Ok(None) if not. - /// Returns Err(Error::NotPoXAnchorBlock) if this block got F*w confirmations but is not the - /// heaviest-confirmed burnchain block. - fn check_pox_anchor_affirmation( - &self, - pox_anchor: &BlockHeaderHash, - winner_snapshot: &BlockSnapshot, - ) -> Result, Error> { - if BurnchainDB::is_anchor_block( - self.burnchain_blocks_db.conn(), - &winner_snapshot.burn_header_hash, - &winner_snapshot.winning_block_txid, - )? { - // affirmed? - let canonical_sortition_tip = self.canonical_sortition_tip.clone().expect( - "FAIL: processing a new Stacks block, but don't have a canonical sortition tip", - ); - let heaviest_am = self.get_heaviest_affirmation_map(&canonical_sortition_tip)?; - - let commit = BurnchainDB::get_block_commit( - self.burnchain_blocks_db.conn(), - &winner_snapshot.burn_header_hash, - &winner_snapshot.winning_block_txid, - )? - .expect("BUG: no commit metadata in DB for existing commit"); - - let commit_md = BurnchainDB::get_commit_metadata( - self.burnchain_blocks_db.conn(), - &winner_snapshot.burn_header_hash, - &winner_snapshot.winning_block_txid, - )? - .expect("BUG: no commit metadata in DB for existing commit"); - - let reward_cycle = commit_md - .anchor_block - .expect("BUG: anchor block commit has no anchor block reward cycle"); - - if heaviest_am - .at(reward_cycle) - .unwrap_or(&AffirmationMapEntry::PoxAnchorBlockPresent) - == &AffirmationMapEntry::PoxAnchorBlockPresent - { - // yup, we're expecting this - debug!("Discovered an old anchor block: {}", pox_anchor; - "height" => commit.block_height, - "burn_block_hash" => %commit.burn_header_hash, - "stacks_block_hash" => %commit.block_header_hash, - "reward_cycle" => reward_cycle, - "heaviest_affirmation_map" => %heaviest_am - ); - info!("Discovered an old anchor block: {}", pox_anchor; - "height" => commit.block_height, - "burn_block_hash" => %commit.burn_header_hash, - "stacks_block_hash" => %commit.block_header_hash, - "reward_cycle" => reward_cycle - ); - return Ok(Some(pox_anchor.clone())); - } else { - // nope -- can ignore - debug!("Discovered unaffirmed old anchor block: {}", pox_anchor; - "height" => commit.block_height, - "burn_block_hash" => %commit.burn_header_hash, - "stacks_block_hash" => %commit.block_header_hash, - "reward_cycle" => reward_cycle, - "heaviest_affirmation_map" => %heaviest_am - ); - return Ok(None); - } - } else { - debug!("Stacks block {} received F*w confirmations but is not the heaviest-confirmed burnchain block, so treating as non-anchor block", pox_anchor); - return Err(Error::NotPoXAnchorBlock); - } - } - - /// Figure out what to do with a newly-discovered anchor block, based on the canonical - /// affirmation map. If the anchor block is affirmed, then returns Some(anchor-block-hash). - /// Otherwise, returns None. - /// - /// Returning Some(...) means "we need to go and process the reward cycle info from this anchor - /// block." - /// - /// Returning None means "we can keep processing Stacks blocks" - #[cfg_attr(test, mutants::skip)] - fn consider_pox_anchor( - &self, - pox_anchor: &BlockHeaderHash, - pox_anchor_snapshot: &BlockSnapshot, - ) -> Result, Error> { - // use affirmation maps even if they're not supported yet. - // if the chain is healthy, this won't cause a chain split. - match self.check_pox_anchor_affirmation(pox_anchor, pox_anchor_snapshot) { - Ok(Some(pox_anchor)) => { - // yup, affirmed. Report it for subsequent reward cycle calculation. - let block_id = StacksBlockId::new(&pox_anchor_snapshot.consensus_hash, &pox_anchor); - if !StacksChainState::has_stacks_block(self.chain_state_db.db(), &block_id)? { - debug!( - "Have NOT processed anchor block {}/{}", - &pox_anchor_snapshot.consensus_hash, pox_anchor - ); - } else { - // already have it - debug!( - "Already have processed anchor block {}/{}", - &pox_anchor_snapshot.consensus_hash, pox_anchor - ); - } - return Ok(Some(pox_anchor)); - } - Ok(None) => { - // unaffirmed old anchor block, so no rewind is needed. - debug!( - "Unaffirmed old anchor block {}/{}", - &pox_anchor_snapshot.consensus_hash, pox_anchor - ); - return Ok(None); - } - Err(Error::NotPoXAnchorBlock) => { - // what epoch is this block in? - let cur_epoch = SortitionDB::get_stacks_epoch( - self.sortition_db.conn(), - pox_anchor_snapshot.block_height, - )? - .unwrap_or_else(|| { - panic!( - "BUG: no epoch defined at height {}", - pox_anchor_snapshot.block_height - ) - }); - if cur_epoch.epoch_id < StacksEpochId::Epoch21 { - panic!("FATAL: found Stacks block that 2.0/2.05 rules would treat as an anchor block, but that 2.1+ would not"); - } - return Ok(None); - } - Err(e) => { - error!("Failed to check PoX affirmation: {:?}", &e); - return Err(e); - } - } - } - /// /// Process any ready staging blocks until there are either: /// * there are no more to process @@ -3316,13 +2517,6 @@ impl< ); let block_hash = block_receipt.header.anchored_header.block_hash(); - let winner_snapshot = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &self.sortition_db.index_conn(), - &canonical_sortition_tip, - &block_hash, - ) - .expect("FAIL: could not find block snapshot for winning block hash") - .expect("FAIL: could not find block snapshot for winning block hash"); // update cost estimator if let Some(ref mut estimator) = self.cost_estimator { @@ -3363,37 +2557,9 @@ impl< .sortition_db .is_stacks_block_pox_anchor(&block_hash, &canonical_sortition_tip)? { - debug!( - "Discovered PoX anchor block {} off of canonical sortition tip {}", - &block_hash, &canonical_sortition_tip - ); + debug!("Discovered PoX anchor block {block_hash} off of canonical sortition tip {canonical_sortition_tip}"); - // what epoch is this block in? - let cur_epoch = SortitionDB::get_stacks_epoch( - self.sortition_db.conn(), - winner_snapshot.block_height, - )? - .unwrap_or_else(|| { - panic!( - "BUG: no epoch defined at height {}", - winner_snapshot.block_height - ) - }); - - if self.affirmation_maps_active(&cur_epoch.epoch_id) { - if let Some(pox_anchor) = - self.consider_pox_anchor(&pox_anchor, &winner_snapshot)? - { - return Ok(Some(pox_anchor)); - } - } else { - // 2.0/2.05 behavior: only consult the sortition DB - // if, just after processing the block, we _know_ that this block is a pox anchor, that means - // that sortitions have already begun processing that didn't know about this pox anchor. - // we need to trigger an unwind - info!("Discovered an old anchor block: {}", &pox_anchor); - return Ok(Some(pox_anchor)); - } + return Ok(Some(pox_anchor)); } } } @@ -3434,8 +2600,7 @@ impl< let mut prep_end = self .sortition_db .get_prepare_end_for(sortition_id, &block_id)? - .unwrap_or_else(|| panic!("FAIL: expected to get a sortition for a chosen anchor block {}, but not found.", - &block_id)); + .unwrap_or_else(|| panic!("FAIL: expected to get a sortition for a chosen anchor block {block_id}, but not found.")); // was this block a pox anchor for an even earlier reward cycle? while let Some(older_prep_end) = self From c695609858eca93f063c5fca7a54bc876c32bb97 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 15:12:34 -0700 Subject: [PATCH 02/22] Remove check_chainstate_against_burnchain_affirmations Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 119 -------------------- 1 file changed, 119 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 7cb0adf654..9a00462f1d 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1334,125 +1334,6 @@ impl< return Ok((FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, 0)); } - /// Did the network affirm a different history of sortitions than what our sortition DB and - /// stacks DB indicate? This checks both the affirmation map represented by the Stacks chain - /// tip and the affirmation map represented by the sortition tip against the heaviest - /// affirmation map. Both checks are necessary, because both Stacks and sortition state may - /// need to be invalidated in order to process the new set of sortitions and Stacks blocks that - /// are consistent with the heaviest affirmation map. - /// - /// If so, then return the reward cycle at which they diverged. - /// If not, return None. - fn check_chainstate_against_burnchain_affirmations(&self) -> Result, Error> { - let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - let (canonical_ch, canonical_bhh) = - SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortition_db.conn())?; - - let sortition_tip = match &self.canonical_sortition_tip { - Some(tip) => tip.clone(), - None => { - let sn = - SortitionDB::get_canonical_burn_chain_tip(self.burnchain_blocks_db.conn())?; - sn.sortition_id - } - }; - let stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &sortition_tip, - &canonical_ch, - &canonical_bhh, - )?; - - let sortition_tip_affirmation_map = self - .sortition_db - .find_sortition_tip_affirmation_map(&sortition_tip)?; - - let heaviest_am = self.get_heaviest_affirmation_map(&sortition_tip)?; - - let canonical_affirmation_map = self.get_canonical_affirmation_map(&sortition_tip)?; - - debug!( - "Heaviest anchor block affirmation map is `{}` at height {}, Stacks tip is `{}`, sortition tip is `{}`, canonical is `{}`", - &heaviest_am, - canonical_burnchain_tip.block_height, - &stacks_tip_affirmation_map, - &sortition_tip_affirmation_map, - &canonical_affirmation_map, - ); - - // NOTE: a.find_divergence(b) will be `Some(..)` even if a and b have the same prefix, - // but b happens to be longer. So, we need to check both `stacks_tip_affirmation_map` - // and `heaviest_am` against each other depending on their lengths. - let stacks_changed_reward_cycle_opt = { - if heaviest_am.len() <= stacks_tip_affirmation_map.len() { - stacks_tip_affirmation_map.find_divergence(&heaviest_am) - } else { - heaviest_am.find_divergence(&stacks_tip_affirmation_map) - } - }; - - let mut sortition_changed_reward_cycle_opt = { - if heaviest_am.len() <= sortition_tip_affirmation_map.len() { - sortition_tip_affirmation_map.find_divergence(&heaviest_am) - } else { - heaviest_am.find_divergence(&sortition_tip_affirmation_map) - } - }; - - if sortition_changed_reward_cycle_opt.is_none() - && sortition_tip_affirmation_map.len() >= heaviest_am.len() - && sortition_tip_affirmation_map.len() <= canonical_affirmation_map.len() - { - if let Some(divergence_rc) = - canonical_affirmation_map.find_divergence(&sortition_tip_affirmation_map) - { - if divergence_rc + 1 >= (heaviest_am.len() as u64) { - // this can arise if there are unaffirmed PoX anchor blocks that are not - // reflected in the sortiiton affirmation map - debug!("Update sortition-changed reward cycle to {} from canonical affirmation map `{}` (sortition AM is `{}`)", - divergence_rc, &canonical_affirmation_map, &sortition_tip_affirmation_map); - - sortition_changed_reward_cycle_opt = Some(divergence_rc); - } - } - } - - // find the lowest of the two - let lowest_changed_reward_cycle_opt = match ( - stacks_changed_reward_cycle_opt, - sortition_changed_reward_cycle_opt, - ) { - (Some(a), Some(b)) => { - if a < b { - Some(a) - } else { - Some(b) - } - } - (Some(a), None) => Some(a), - (None, Some(b)) => Some(b), - (None, None) => None, - }; - - // did the canonical affirmation map change? - if let Some(changed_reward_cycle) = lowest_changed_reward_cycle_opt { - let current_reward_cycle = self - .burnchain - .block_height_to_reward_cycle(canonical_burnchain_tip.block_height) - .unwrap_or(0); - if changed_reward_cycle < current_reward_cycle { - info!("Sortition anchor block affirmation map `{}` and/or Stacks affirmation map `{}` is no longer compatible with heaviest affirmation map {} in reward cycles {}-{}", - &sortition_tip_affirmation_map, &stacks_tip_affirmation_map, &heaviest_am, changed_reward_cycle, current_reward_cycle); - - return Ok(Some(changed_reward_cycle)); - } - } - - // no reorog - Ok(None) - } - /// Find valid sortitions between two given heights, and given the correct affirmation map. /// Returns a height-sorted list of block snapshots whose affirmation maps are cosnistent with /// the correct affirmation map. From da251cfb81582e0b6460466af808f9aae369d3b3 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 15:26:17 -0700 Subject: [PATCH 03/22] Remove undo_stacks_block_orphaning Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 42 --------------------- 1 file changed, 42 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 9a00462f1d..938ef47a52 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1510,48 +1510,6 @@ impl< } } - /// Forget that stacks blocks for now-invalidated sortitions are orphaned, because they might - /// now be valid. In particular, this applies to a Stacks block that got mined in two PoX - /// forks. This can happen at most once between the two forks, but we need to ensure that the - /// block can be re-processed in that event. - fn undo_stacks_block_orphaning( - burnchain_conn: &DBConn, - burnchain_indexer: &B, - ic: &SortitionDBConn, - chainstate_db_tx: &mut DBTx, - first_invalidate_start_block: u64, - last_invalidate_start_block: u64, - ) -> Result<(), Error> { - debug!( - "Clear all orphans in burn range {} - {}", - first_invalidate_start_block, last_invalidate_start_block - ); - for burn_height in first_invalidate_start_block..(last_invalidate_start_block + 1) { - let burn_header = match BurnchainDB::get_burnchain_header( - burnchain_conn, - burnchain_indexer, - burn_height, - )? { - Some(hdr) => hdr, - None => { - continue; - } - }; - - debug!( - "Clear all orphans at {},{}", - &burn_header.block_hash, burn_header.block_height - ); - forget_orphan_stacks_blocks( - ic, - chainstate_db_tx, - &burn_header.block_hash, - burn_height.saturating_sub(1), - )?; - } - Ok(()) - } - /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// From fac38a77acc4965c06c2d7f1071189e5cdbf617b Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 15:47:52 -0700 Subject: [PATCH 04/22] Remove affirmation_maps_active Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 938ef47a52..4e1e684359 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -34,9 +34,7 @@ use crate::burnchains::db::{BurnchainBlockData, BurnchainDB, BurnchainHeaderRead use crate::burnchains::{ Burnchain, BurnchainBlockHeader, Error as BurnchainError, PoxConstants, Txid, }; -use crate::chainstate::burn::db::sortdb::{ - SortitionDB, SortitionDBConn, SortitionDBTx, SortitionHandleTx, -}; +use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionDBTx, SortitionHandleTx}; use crate::chainstate::burn::operations::leader_block_commit::RewardSetInfo; use crate::chainstate::burn::operations::BlockstackOperationType; use crate::chainstate::burn::{BlockSnapshot, ConsensusHash}; @@ -1658,16 +1656,6 @@ impl< }) } - /// Are affirmation maps active during the epoch? - fn affirmation_maps_active(&self, epoch: &StacksEpochId) -> bool { - if *epoch >= StacksEpochId::Epoch21 { - return true; - } else if self.config.always_use_affirmation_maps { - return true; - } - return false; - } - // TODO: add tests from mutation testing results #4852 #[cfg_attr(test, mutants::skip)] /// Handle a new burnchain block, optionally rolling back the canonical PoX sortition history From 4c7abfc5b17b46863619f5b35c89549b1ac14393 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 16:12:59 -0700 Subject: [PATCH 05/22] Remove find_invalid_and_revalidated_sortitions, get_snapshots_and_affirmation_maps_at_height, and find_valid_sortitions Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 197 -------------------- 1 file changed, 197 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 4e1e684359..b8ae9bb86f 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1130,27 +1130,6 @@ impl< } } - /// Get all block snapshots and their affirmation maps at a given burnchain block height. - fn get_snapshots_and_affirmation_maps_at_height( - &self, - height: u64, - ) -> Result, Error> { - let sort_ids = SortitionDB::get_sortition_ids_at_height(self.sortition_db.conn(), height)?; - let mut ret = Vec::with_capacity(sort_ids.len()); - - for sort_id in sort_ids.iter() { - let sn = SortitionDB::get_block_snapshot(self.sortition_db.conn(), sort_id)? - .expect("FATAL: have sortition ID without snapshot"); - - let sort_am = self - .sortition_db - .find_sortition_tip_affirmation_map(sort_id)?; - ret.push((sn, sort_am)); - } - - Ok(ret) - } - fn get_heaviest_affirmation_map( &self, sortition_tip: &SortitionId, @@ -1332,182 +1311,6 @@ impl< return Ok((FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, 0)); } - /// Find valid sortitions between two given heights, and given the correct affirmation map. - /// Returns a height-sorted list of block snapshots whose affirmation maps are cosnistent with - /// the correct affirmation map. - fn find_valid_sortitions( - &self, - compare_am: &AffirmationMap, - start_height: u64, - end_height: u64, - ) -> Result<(u64, Vec), Error> { - // careful -- we might have already procesed sortitions in this - // reward cycle with this PoX ID, but that were never confirmed - // by a subsequent prepare phase. - let mut last_invalidate_start_block = start_height; - let mut valid_sortitions = vec![]; - for height in start_height..(end_height + 1) { - let snapshots_and_ams = self.get_snapshots_and_affirmation_maps_at_height(height)?; - let num_sns = snapshots_and_ams.len(); - debug!("{} snapshots at {}", num_sns, height); - - let mut found = false; - for (sn, sn_am) in snapshots_and_ams.into_iter() { - debug!( - "Snapshot {} height {} has AM `{sn_am}` (is prefix of `{compare_am}`?: {})", - &sn.sortition_id, - sn.block_height, - &compare_am.has_prefix(&sn_am), - ); - if compare_am.has_prefix(&sn_am) { - // have already processed this sortitoin - debug!("Already processed sortition {} at height {} with AM `{sn_am}` on comparative affirmation map {compare_am}", &sn.sortition_id, sn.block_height); - found = true; - last_invalidate_start_block = height; - debug!( - "last_invalidate_start_block = {}", - last_invalidate_start_block - ); - valid_sortitions.push(sn); - break; - } - } - if !found && num_sns > 0 { - // there are snapshots, and they're all diverged - debug!( - "No snapshot at height {} has an affirmation map that is a prefix of `{}`", - height, &compare_am - ); - break; - } - } - Ok((last_invalidate_start_block, valid_sortitions)) - } - - /// Find out which sortitions will need to be invalidated as part of a PoX reorg, and which - /// ones will need to be re-validated. - /// - /// Returns (first-invalidation-height, last-invalidation-height, revalidation-sort-ids). - /// * All sortitions in the range [first-invalidation-height, last-invalidation-height) must be - /// invalidated, since they are no longer consistent with the heaviest affirmation map. These - /// heights fall into the reward cycles identified by `changed_reward_cycle` and - /// `current_reward_cycle`. - /// * The sortitions identified by `revalidate-sort-ids` are sortitions whose heights come - /// at or after `last-invalidation-height` but are now valid again (i.e. because they are - /// consistent with the heaviest affirmation map). - fn find_invalid_and_revalidated_sortitions( - &self, - compare_am: &AffirmationMap, - changed_reward_cycle: u64, - current_reward_cycle: u64, - ) -> Result)>, Error> { - // find the lowest reward cycle we have to reprocess (which starts at burn - // block rc_start_block). - - // burn chain height at which we'll invalidate *all* sortitions - let mut last_invalidate_start_block = 0; - - // burn chain height at which we'll re-try orphaned Stacks blocks, and - // revalidate the sortitions that were previously invalid but have now been - // made valid. - let mut first_invalidate_start_block = 0; - - // set of sortitions that are currently invalid, but could need to be reset - // as valid. - let mut valid_sortitions = vec![]; - - let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - let mut diverged = false; - for rc in changed_reward_cycle..current_reward_cycle { - debug!( - "Find invalidated and revalidated sortitions at reward cycle {}", - rc - ); - - last_invalidate_start_block = self.burnchain.reward_cycle_to_block_height(rc); - first_invalidate_start_block = last_invalidate_start_block; - - // + 1 because the first sortition of a reward cycle is congruent to 1 mod - // reward_cycle_length. - let sort_ids = SortitionDB::get_sortition_ids_at_height( - self.sortition_db.conn(), - last_invalidate_start_block + 1, - )?; - - // find the sortition ID with the shortest affirmation map that is NOT a prefix - // of the heaviest affirmation map - let mut found_diverged = false; - for sort_id in sort_ids.iter() { - let sort_am = self - .sortition_db - .find_sortition_tip_affirmation_map(sort_id)?; - - debug!( - "Compare {compare_am} as prefix of {sort_am}? {}", - compare_am.has_prefix(&sort_am) - ); - if compare_am.has_prefix(&sort_am) { - continue; - } - - let mut prior_compare_am = compare_am.clone(); - prior_compare_am.pop(); - - let mut prior_sort_am = sort_am.clone(); - prior_sort_am.pop(); - - debug!( - "Compare {} as a prior prefix of {}? {}", - &prior_compare_am, - &prior_sort_am, - prior_compare_am.has_prefix(&prior_sort_am) - ); - if prior_compare_am.has_prefix(&prior_sort_am) { - // this is the first reward cycle where history diverged. - found_diverged = true; - debug!("{sort_am} diverges from {compare_am}"); - - // careful -- we might have already procesed sortitions in this - // reward cycle with this PoX ID, but that were never confirmed - // by a subsequent prepare phase. - let (new_last_invalidate_start_block, mut next_valid_sortitions) = self - .find_valid_sortitions( - compare_am, - last_invalidate_start_block, - canonical_burnchain_tip.block_height, - )?; - last_invalidate_start_block = new_last_invalidate_start_block; - valid_sortitions.append(&mut next_valid_sortitions); - break; - } - } - - if !found_diverged { - continue; - } - - // we may have processed some sortitions correctly within this reward - // cycle. Advance forward until we find one that we haven't. - info!( - "Re-playing sortitions starting within reward cycle {} burn height {}", - rc, last_invalidate_start_block - ); - - diverged = true; - break; - } - - if diverged { - Ok(Some(( - first_invalidate_start_block, - last_invalidate_start_block, - valid_sortitions, - ))) - } else { - Ok(None) - } - } - /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// From 32aa1a642e5108315db6b5b3d433687042b238f2 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 16:24:32 -0700 Subject: [PATCH 06/22] Remove check for compatible_stacks_blocks in handle_new_epoch2_burnchain_block Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 42 ++------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index b8ae9bb86f..975881451f 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1333,8 +1333,7 @@ impl< let parent_pox = { let mut sortition_db_handle = SortitionHandleTx::begin(&mut self.sortition_db, &parent_sort_id)?; - let parent_pox = sortition_db_handle.get_pox_id()?; - parent_pox + sortition_db_handle.get_pox_id()? }; let new_sortition_id = @@ -1345,8 +1344,8 @@ impl< if let Some(sortition) = sortition_opt { // existing sortition -- go revalidate it info!( - "Revalidate already-processed snapshot {} height {} to have canonical tip {}/{} height {}", - &new_sortition_id, sortition.block_height, + "Revalidate already-processed snapshot {new_sortition_id} height {} to have canonical tip {}/{} height {}", + sortition.block_height, &canonical_snapshot.canonical_stacks_tip_consensus_hash, &canonical_snapshot.canonical_stacks_tip_hash, canonical_snapshot.canonical_stacks_tip_height, @@ -1684,44 +1683,11 @@ impl< // don't process this burnchain block again in this recursive call. already_processed_burn_blocks.insert(next_snapshot.burn_header_hash); - let mut compatible_stacks_blocks = vec![]; - { - // get borrow checker to drop sort_tx - let mut sort_tx = self.sortition_db.tx_begin()?; - for (ch, bhh, height) in stacks_blocks_to_reaccept.into_iter() { - debug!( - "Check if Stacks block {}/{} height {} is compatible with `{}`", - &ch, &bhh, height, &heaviest_am - ); - - let am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(&next_snapshot.sortition_id)?, - &sort_tx, - &ch, - &bhh, - )?; - if StacksChainState::is_block_compatible_with_affirmation_map( - &am, - &heaviest_am, - )? { - debug!( - "Stacks block {}/{} height {} is compatible with `{}`; will reaccept", - &ch, &bhh, height, &heaviest_am - ); - compatible_stacks_blocks.push((ch, bhh, height)); - } else { - debug!("Stacks block {}/{} height {} is NOT compatible with `{}`; will NOT reaccept", &ch, &bhh, height, &heaviest_am); - } - } - } - // reaccept any stacks blocks let mut sortition_db_handle = SortitionHandleTx::begin(&mut self.sortition_db, &next_snapshot.sortition_id)?; - for (ch, bhh, height) in compatible_stacks_blocks.into_iter() { + for (ch, bhh, height) in stacks_blocks_to_reaccept.into_iter() { debug!("Re-accept Stacks block {}/{} height {}", &ch, &bhh, height); revalidated_stacks_block = true; sortition_db_handle.set_stacks_block_accepted(&ch, &bhh, height)?; From 2d57bce026236b89bf0e1c88a65331b09b1a752d Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 23 Jul 2025 17:02:59 -0700 Subject: [PATCH 07/22] Remove find_highest_stacks_block_with_compatible_affirmation_map Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 233 -------------------- 1 file changed, 233 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 975881451f..9bba474011 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1156,161 +1156,6 @@ impl< sortition_tip, ) } - - /// Find the canonical Stacks tip at a given sortition, whose affirmation map is compatible - /// with the heaviest affirmation map. - fn find_highest_stacks_block_with_compatible_affirmation_map( - heaviest_am: &AffirmationMap, - sort_tip: &SortitionId, - burnchain_db: &BurnchainDB, - sort_tx: &mut SortitionDBTx, - chainstate_conn: &DBConn, - ) -> Result<(ConsensusHash, BlockHeaderHash, u64), Error> { - let mut search_height = StacksChainState::get_max_header_height(chainstate_conn)?; - let last_2_05_rc = SortitionDB::static_get_last_epoch_2_05_reward_cycle( - sort_tx, - sort_tx.context.first_block_height, - &sort_tx.context.pox_constants, - )?; - let sort_am = sort_tx.find_sortition_tip_affirmation_map(sort_tip)?; - loop { - let mut search_weight = StacksChainState::get_max_affirmation_weight_at_height( - chainstate_conn, - search_height, - )? as i64; - while search_weight >= 0 { - let all_headers = StacksChainState::get_all_headers_at_height_and_weight( - chainstate_conn, - search_height, - search_weight as u64, - )?; - debug!( - "Headers with weight {} height {}: {}", - search_weight, - search_height, - all_headers.len() - ); - - search_weight -= 1; - - for hdr in all_headers { - // load this block's affirmation map - let am = match inner_static_get_stacks_tip_affirmation_map( - burnchain_db, - last_2_05_rc, - &sort_am, - sort_tx, - &hdr.consensus_hash, - &hdr.anchored_header.block_hash(), - ) { - Ok(am) => am, - Err(Error::ChainstateError(ChainstateError::DBError( - DBError::InvalidPoxSortition, - ))) => { - debug!( - "Stacks tip {}/{} is not on a valid sortition", - &hdr.consensus_hash, - &hdr.anchored_header.block_hash() - ); - continue; - } - Err(e) => { - error!("Failed to query affirmation map: {e:?}"); - return Err(e); - } - }; - - // must be compatible with the heaviest AM - match StacksChainState::is_block_compatible_with_affirmation_map( - &am, - heaviest_am, - ) { - Ok(compat) => { - if !compat { - debug!("Stacks tip {}/{} affirmation map {} is incompatible with heaviest affirmation map {}", - &hdr.consensus_hash, &hdr.anchored_header.block_hash(), &am, &heaviest_am); - continue; - } - } - Err(ChainstateError::DBError(DBError::InvalidPoxSortition)) => { - debug!( - "Stacks tip {}/{} affirmation map {} is not on a valid sortition", - &hdr.consensus_hash, - &hdr.anchored_header.block_hash(), - &am - ); - continue; - } - Err(e) => { - error!("Failed to query affirmation compatibility: {:?}", &e); - return Err(e.into()); - } - } - - // must reside on this sortition fork - let ancestor_sn = match SortitionDB::get_ancestor_snapshot_tx( - sort_tx, - hdr.burn_header_height.into(), - sort_tip, - ) { - Ok(Some(sn)) => sn, - Ok(None) | Err(DBError::InvalidPoxSortition) => { - debug!("Stacks tip {}/{} affirmation map {} is not on a chain tipped by sortition {}", - &hdr.consensus_hash, &hdr.anchored_header.block_hash(), &am, sort_tip); - continue; - } - Err(e) => { - error!( - "Failed to query snapshot ancestor at height {} from {}: {:?}", - hdr.burn_header_height, sort_tip, &e - ); - return Err(e.into()); - } - }; - if !ancestor_sn.sortition - || ancestor_sn.winning_stacks_block_hash != hdr.anchored_header.block_hash() - || ancestor_sn.consensus_hash != hdr.consensus_hash - { - debug!( - "Stacks tip {}/{} affirmation map {} is not attched to {},{}", - &hdr.consensus_hash, - &hdr.anchored_header.block_hash(), - &am, - &ancestor_sn.burn_header_hash, - ancestor_sn.block_height - ); - continue; - } - - // found it! - debug!( - "Canonical Stacks tip of {} is now {}/{} height {} burn height {} AM `{}` weight {}", - sort_tip, - &hdr.consensus_hash, - &hdr.anchored_header.block_hash(), - hdr.stacks_block_height, - hdr.burn_header_height, - &am, - am.weight() - ); - return Ok(( - hdr.consensus_hash, - hdr.anchored_header.block_hash(), - hdr.stacks_block_height, - )); - } - } - if search_height == 0 { - break; - } else { - search_height -= 1; - } - } - - // empty chainstate - return Ok((FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, 0)); - } - /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// @@ -1483,9 +1328,6 @@ impl< }; let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - - let heaviest_am = self.get_heaviest_affirmation_map(&canonical_snapshot.sortition_id)?; - debug!("Handle new canonical burnchain tip"; "height" => %canonical_burnchain_tip.block_height, "block_hash" => %canonical_burnchain_tip.block_hash.to_string()); @@ -1754,81 +1596,6 @@ impl< } } - // make sure our memoized canonical stacks tip is correct - let chainstate_db_conn = self.chain_state_db.db(); - let mut sort_tx = self.sortition_db.tx_begin()?; - - // Retrieve canonical burnchain chain tip from the BurnchainBlocksDB - let canonical_snapshot = match self.canonical_sortition_tip.as_ref() { - Some(sn_tip) => { - SortitionDB::get_block_snapshot(&sort_tx, sn_tip)?.unwrap_or_else(|| { - panic!( - "FATAL: do not have previously-calculated highest valid sortition tip {}", - sn_tip - ) - }) - } - None => SortitionDB::get_canonical_burn_chain_tip(&sort_tx)?, - }; - let highest_valid_sortition_id = canonical_snapshot.sortition_id; - - let (canonical_ch, canonical_bhh, canonical_height) = - Self::find_highest_stacks_block_with_compatible_affirmation_map( - &heaviest_am, - &highest_valid_sortition_id, - &self.burnchain_blocks_db, - &mut sort_tx, - chainstate_db_conn, - ) - .expect("FATAL: could not find a valid parent Stacks block"); - - let stacks_am = inner_static_get_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - last_2_05_rc, - &sort_tx.find_sortition_tip_affirmation_map(&highest_valid_sortition_id)?, - &sort_tx, - &canonical_ch, - &canonical_bhh, - ) - .expect("FATAL: failed to query stacks DB"); - - debug!( - "Canonical Stacks tip after burnchain processing is {}/{} height {} am `{}`", - &canonical_ch, &canonical_bhh, canonical_height, &stacks_am - ); - debug!( - "Canonical sortition tip after burnchain processing is {},{}", - &highest_valid_sortition_id, canonical_snapshot.block_height - ); - - let highest_valid_sn = - SortitionDB::get_block_snapshot(&sort_tx, &highest_valid_sortition_id)? - .expect("FATAL: no snapshot for highest valid sortition ID"); - - let block_known = StacksChainState::is_stacks_block_processed( - chainstate_db_conn, - &highest_valid_sn.consensus_hash, - &highest_valid_sn.winning_stacks_block_hash, - ) - .expect("FATAL: failed to query chainstate DB"); - - SortitionDB::revalidate_snapshot_with_block( - &sort_tx, - &highest_valid_sortition_id, - &canonical_ch, - &canonical_bhh, - canonical_height, - Some(block_known), - ) - .unwrap_or_else(|_| { - panic!( - "FATAL: failed to revalidate highest valid sortition {}", - &highest_valid_sortition_id - ) - }); - - sort_tx.commit()?; - debug!("Done handling new burnchain blocks"); Ok(None) From 4f43f7f962e8dfa901a40284992d6fb9572b72ce Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 24 Jul 2025 09:45:23 -0700 Subject: [PATCH 08/22] Remove consolidate_affirmation_maps, *_get_*_affirmation_map, is_block_compatible_with_affirmation_map Signed-off-by: Jacinta Ferrant --- stacks-node/src/run_loop/neon.rs | 190 +------------------ stackslib/src/chainstate/coordinator/mod.rs | 164 +--------------- stackslib/src/chainstate/stacks/db/blocks.rs | 23 --- stackslib/src/net/api/getinfo.rs | 8 +- stackslib/src/net/api/tests/mod.rs | 28 +-- stackslib/src/net/inv/epoch2x.rs | 23 +-- stackslib/src/net/mod.rs | 3 +- stackslib/src/net/p2p.rs | 65 +------ stackslib/src/net/tests/inv/epoch2x.rs | 2 +- 9 files changed, 25 insertions(+), 481 deletions(-) diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index bdb370527d..a4c9b7787a 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -13,9 +13,8 @@ use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash}; use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers}; use stacks::chainstate::coordinator::{ - migrate_chainstate_dbs, static_get_canonical_affirmation_map, - static_get_heaviest_affirmation_map, static_get_stacks_tip_affirmation_map, ChainsCoordinator, - ChainsCoordinatorConfig, CoordinatorCommunication, Error as coord_error, + migrate_chainstate_dbs, ChainsCoordinator, ChainsCoordinatorConfig, CoordinatorCommunication, + Error as coord_error, }; use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState}; use stacks::chainstate::stacks::miner::{signal_mining_blocked, signal_mining_ready, MinerStatus}; @@ -794,8 +793,6 @@ impl RunLoop { fn drive_pox_reorg_stacks_block_processing( globals: &Globals, config: &Config, - burnchain: &Burnchain, - sortdb: &SortitionDB, last_stacks_pox_reorg_recover_time: &mut u128, ) { let miner_config = config.get_miner_config(); @@ -812,92 +809,10 @@ impl RunLoop { return; } - // compare stacks and heaviest AMs - let burnchain_db = burnchain - .open_burnchain_db(false) - .expect("FATAL: failed to open burnchain DB"); - - let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) - .expect("FATAL: could not read sortition DB"); - - let indexer = make_bitcoin_indexer(config, Some(globals.should_keep_running.clone())); - - let heaviest_affirmation_map = match static_get_heaviest_affirmation_map( - burnchain, - &indexer, - &burnchain_db, - sortdb, - &sn.sortition_id, - ) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find heaviest affirmation map: {e:?}"); - return; - } - }; - - let highest_sn = SortitionDB::get_highest_known_burn_chain_tip(sortdb.conn()) - .expect("FATAL: could not read sortition DB"); - - let canonical_burnchain_tip = burnchain_db - .get_canonical_chain_tip() - .expect("FATAL: could not read burnchain DB"); - - let sortition_tip_affirmation_map = - match SortitionDB::find_sortition_tip_affirmation_map(sortdb, &sn.sortition_id) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find sortition affirmation map: {e:?}"); - return; - } - }; - - let stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( - &burnchain_db, - sortdb, - &sn.sortition_id, - &sn.canonical_stacks_tip_consensus_hash, - &sn.canonical_stacks_tip_hash, - ) - .expect("FATAL: could not query stacks DB"); - - if stacks_tip_affirmation_map.len() < heaviest_affirmation_map.len() - || stacks_tip_affirmation_map - .find_divergence(&heaviest_affirmation_map) - .is_some() - { - // the sortition affirmation map might also be inconsistent, so we'll need to fix that - // (i.e. the underlying sortitions) before we can fix the stacks fork - if sortition_tip_affirmation_map.len() < heaviest_affirmation_map.len() - || sortition_tip_affirmation_map - .find_divergence(&heaviest_affirmation_map) - .is_some() - { - debug!("Drive burn block processing: possible PoX reorg (sortition tip: {sortition_tip_affirmation_map}, heaviest: {heaviest_affirmation_map})"); - globals.coord().announce_new_burn_block(); - } else if highest_sn.block_height == sn.block_height - && sn.block_height == canonical_burnchain_tip.block_height - { - // need to force an affirmation reorg because there will be no more burn block - // announcements. - debug!("Drive burn block processing: possible PoX reorg (sortition tip: {sortition_tip_affirmation_map}, heaviest: {heaviest_affirmation_map}, burn height {})", sn.block_height); - globals.coord().announce_new_burn_block(); - } - - debug!( - "Drive stacks block processing: possible PoX reorg (stacks tip: {stacks_tip_affirmation_map}, heaviest: {heaviest_affirmation_map})" - ); - globals.coord().announce_new_stacks_block(); - } else { - debug!( - "Drive stacks block processing: no need (stacks tip: {stacks_tip_affirmation_map}, heaviest: {heaviest_affirmation_map})" - ); - - // announce a new stacks block to force the chains coordinator - // to wake up anyways. this isn't free, so we have to make sure - // the chain-liveness thread doesn't wake up too often - globals.coord().announce_new_stacks_block(); - } + // announce a new stacks block to force the chains coordinator + // to wake up anyways. this isn't free, so we have to make sure + // the chain-liveness thread doesn't wake up too often + globals.coord().announce_new_stacks_block(); *last_stacks_pox_reorg_recover_time = get_epoch_time_secs().into(); } @@ -913,7 +828,6 @@ impl RunLoop { config: &Config, burnchain: &Burnchain, sortdb: &SortitionDB, - chain_state_db: &StacksChainState, last_burn_pox_reorg_recover_time: &mut u128, last_announce_time: &mut u128, ) { @@ -953,82 +867,6 @@ impl RunLoop { return; } - // NOTE: this could be lower than the highest_sn - let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) - .expect("FATAL: could not read sortition DB"); - - let sortition_tip_affirmation_map = - match SortitionDB::find_sortition_tip_affirmation_map(sortdb, &sn.sortition_id) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find sortition affirmation map: {e:?}"); - return; - } - }; - - let indexer = make_bitcoin_indexer(config, Some(globals.should_keep_running.clone())); - - let heaviest_affirmation_map = match static_get_heaviest_affirmation_map( - burnchain, - &indexer, - &burnchain_db, - sortdb, - &sn.sortition_id, - ) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find heaviest affirmation map: {e:?}"); - return; - } - }; - - let canonical_affirmation_map = match static_get_canonical_affirmation_map( - burnchain, - &indexer, - &burnchain_db, - sortdb, - chain_state_db, - &sn.sortition_id, - ) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find canonical affirmation map: {e:?}"); - return; - } - }; - - if sortition_tip_affirmation_map.len() < heaviest_affirmation_map.len() - || sortition_tip_affirmation_map - .find_divergence(&heaviest_affirmation_map) - .is_some() - || sn.block_height < highest_sn.block_height - { - debug!("Drive burn block processing: possible PoX reorg (sortition tip: {sortition_tip_affirmation_map}, heaviest: {heaviest_affirmation_map}, {} = heaviest_affirmation_map.len() - && sortition_tip_affirmation_map.len() <= canonical_affirmation_map.len() - { - if let Some(divergence_rc) = - canonical_affirmation_map.find_divergence(&sortition_tip_affirmation_map) - { - if divergence_rc + 1 >= (heaviest_affirmation_map.len() as u64) { - // we have unaffirmed PoX anchor blocks that are not yet processed in the sortition history - debug!("Drive burnchain processing: possible PoX reorg from unprocessed anchor block(s) (sortition tip: {sortition_tip_affirmation_map}, heaviest: {heaviest_affirmation_map}, canonical: {canonical_affirmation_map})"); - globals.coord().announce_new_burn_block(); - globals.coord().announce_new_stacks_block(); - *last_announce_time = get_epoch_time_secs().into(); - } - } - } else { - debug!( - "Drive burn block processing: no need (sortition tip: {sortition_tip_affirmation_map}, heaviest: {heaviest_affirmation_map}, {} AffirmationMap { - let mut am_entries = vec![]; - for i in 0..last_2_05_rc { - if let Some(am_entry) = sort_am.affirmations.get(i) { - am_entries.push(*am_entry); - } else { - return AffirmationMap::new(am_entries); - } - } - for am_entry in given_am.affirmations.iter().skip(last_2_05_rc) { - am_entries.push(*am_entry); - } - - AffirmationMap::new(am_entries) -} - -/// Get the heaviest affirmation map, when considering epochs. -/// * In epoch 2.05 and prior, the heaviest AM was the sortition AM. -/// * In epoch 2.1, the reward cycles prior to the 2.1 boundary remain the sortition AM. -pub fn static_get_heaviest_affirmation_map( - burnchain: &Burnchain, - indexer: &B, - burnchain_blocks_db: &BurnchainDB, - sortition_db: &SortitionDB, - sortition_tip: &SortitionId, -) -> Result { - let last_2_05_rc = sortition_db.get_last_epoch_2_05_reward_cycle()? as usize; - - let sort_am = sortition_db.find_sortition_tip_affirmation_map(sortition_tip)?; - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_blocks_db.conn(), - burnchain, - indexer, - )?; - - Ok(consolidate_affirmation_maps( - heaviest_am, - &sort_am, - last_2_05_rc, - )) -} - -/// Get the canonical affirmation map, when considering epochs. -/// * In epoch 2.05 and prior, the heaviest AM was the sortition AM. -/// * In epoch 2.1, the reward cycles prior to the 2.1 boundary remain the sortition AM. -pub fn static_get_canonical_affirmation_map( - burnchain: &Burnchain, - indexer: &B, - burnchain_blocks_db: &BurnchainDB, - sortition_db: &SortitionDB, - chain_state_db: &StacksChainState, - sortition_tip: &SortitionId, -) -> Result { - let last_2_05_rc = sortition_db.get_last_epoch_2_05_reward_cycle()? as usize; - - let sort_am = sortition_db.find_sortition_tip_affirmation_map(sortition_tip)?; - - let canonical_am = StacksChainState::find_canonical_affirmation_map( - burnchain, - indexer, - burnchain_blocks_db, - chain_state_db, - )?; - - Ok(consolidate_affirmation_maps( - canonical_am, - &sort_am, - last_2_05_rc, - )) -} - -fn inner_static_get_stacks_tip_affirmation_map( - burnchain_blocks_db: &BurnchainDB, - last_2_05_rc: u64, - sort_am: &AffirmationMap, - sortdb_conn: &DBConn, - canonical_ch: &ConsensusHash, - canonical_bhh: &BlockHeaderHash, -) -> Result { - let last_2_05_rc = last_2_05_rc as usize; - - let stacks_am = StacksChainState::find_stacks_tip_affirmation_map( - burnchain_blocks_db, - sortdb_conn, - canonical_ch, - canonical_bhh, - )?; - - Ok(consolidate_affirmation_maps( - stacks_am, - sort_am, - last_2_05_rc, - )) -} - -/// Get the canonical Stacks tip affirmation map, when considering epochs. -/// * In epoch 2.05 and prior, the heaviest AM was the sortition AM. -/// * In epoch 2.1, the reward cycles prior to the 2.1 boundary remain the sortition AM -pub fn static_get_stacks_tip_affirmation_map( - burnchain_blocks_db: &BurnchainDB, - sortition_db: &SortitionDB, - sortition_tip: &SortitionId, - canonical_ch: &ConsensusHash, - canonical_bhh: &BlockHeaderHash, -) -> Result { - let last_2_05_rc = sortition_db.get_last_epoch_2_05_reward_cycle()?; - let sort_am = sortition_db.find_sortition_tip_affirmation_map(sortition_tip)?; - inner_static_get_stacks_tip_affirmation_map( - burnchain_blocks_db, - last_2_05_rc, - &sort_am, - sortition_db.conn(), - canonical_ch, - canonical_bhh, - ) -} - impl< T: BlockEventDispatcher, N: CoordinatorNotices, @@ -1130,32 +998,6 @@ impl< } } - fn get_heaviest_affirmation_map( - &self, - sortition_tip: &SortitionId, - ) -> Result { - static_get_heaviest_affirmation_map( - &self.burnchain, - &self.burnchain_indexer, - &self.burnchain_blocks_db, - &self.sortition_db, - sortition_tip, - ) - } - - fn get_canonical_affirmation_map( - &self, - sortition_tip: &SortitionId, - ) -> Result { - static_get_canonical_affirmation_map( - &self.burnchain, - &self.burnchain_indexer, - &self.burnchain_blocks_db, - &self.sortition_db, - &self.chain_state_db, - sortition_tip, - ) - } /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// @@ -1316,8 +1158,6 @@ impl< ) -> Result, Error> { debug!("Handle new burnchain block"); - let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - // Retrieve canonical burnchain chain tip from the BurnchainBlocksDB let canonical_snapshot = match self.canonical_sortition_tip.as_ref() { Some(sn_tip) => SortitionDB::get_block_snapshot(self.sortition_db.conn(), sn_tip)? diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 7fe642ee71..6bb07fd94a 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -2230,29 +2230,6 @@ impl StacksChainState { ) } - /// Is a block compatible with the heaviest affirmation map? - pub fn is_block_compatible_with_affirmation_map( - stacks_tip_affirmation_map: &AffirmationMap, - heaviest_am: &AffirmationMap, - ) -> Result { - // NOTE: a.find_divergence(b) will be `Some(..)` even if a and b have the same prefix, - // but b happens to be longer. So, we need to check both `stacks_tip_affirmation_map` - // and `heaviest_am` against each other depending on their lengths. - if (stacks_tip_affirmation_map.len() > heaviest_am.len() - && stacks_tip_affirmation_map - .find_divergence(heaviest_am) - .is_some()) - || (stacks_tip_affirmation_map.len() <= heaviest_am.len() - && heaviest_am - .find_divergence(stacks_tip_affirmation_map) - .is_some()) - { - return Ok(false); - } else { - return Ok(true); - } - } - /// Delete a microblock's data from the DB fn delete_microblock_data( tx: &mut DBTx, diff --git a/stackslib/src/net/api/getinfo.rs b/stackslib/src/net/api/getinfo.rs index acdcf1f10f..311b8c38ca 100644 --- a/stackslib/src/net/api/getinfo.rs +++ b/stackslib/src/net/api/getinfo.rs @@ -147,10 +147,10 @@ impl RPCPeerInfoData { node_public_key: Some(public_key_buf), node_public_key_hash: Some(public_key_hash), affirmations: Some(RPCAffirmationData { - heaviest: network.heaviest_affirmation_map.clone(), - stacks_tip: network.stacks_tip_affirmation_map.clone(), - sortition_tip: network.sortition_tip_affirmation_map.clone(), - tentative_best: network.tentative_best_affirmation_map.clone(), + heaviest: AffirmationMap::empty(), + stacks_tip: AffirmationMap::empty(), + sortition_tip: AffirmationMap::empty(), + tentative_best: AffirmationMap::empty(), }), last_pox_anchor: Some(RPCLastPoxAnchorData { anchor_block_hash: network.last_anchor_block_hash.clone(), diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index b227af0763..762082468f 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -681,12 +681,7 @@ impl<'a> TestRPC<'a> { let mut peer_1_stacks_node = peer_1.stacks_node.take().unwrap(); let _ = peer_1 .network - .refresh_burnchain_view( - &peer_1_indexer, - &peer_1_sortdb, - &mut peer_1_stacks_node.chainstate, - false, - ) + .refresh_burnchain_view(&peer_1_sortdb, &mut peer_1_stacks_node.chainstate, false) .unwrap(); peer_1.sortdb = Some(peer_1_sortdb); peer_1.stacks_node = Some(peer_1_stacks_node); @@ -695,12 +690,7 @@ impl<'a> TestRPC<'a> { let mut peer_2_stacks_node = peer_2.stacks_node.take().unwrap(); let _ = peer_2 .network - .refresh_burnchain_view( - &peer_2_indexer, - &peer_2_sortdb, - &mut peer_2_stacks_node.chainstate, - false, - ) + .refresh_burnchain_view(&peer_2_sortdb, &mut peer_2_stacks_node.chainstate, false) .unwrap(); peer_2.sortdb = Some(peer_2_sortdb); peer_2.stacks_node = Some(peer_2_stacks_node); @@ -1174,12 +1164,7 @@ impl<'a> TestRPC<'a> { let _ = peer_2 .network - .refresh_burnchain_view( - &peer_2_indexer, - &peer_2_sortdb, - &mut peer_2_stacks_node.chainstate, - false, - ) + .refresh_burnchain_view(&peer_2_sortdb, &mut peer_2_stacks_node.chainstate, false) .unwrap(); if unconfirmed_state { @@ -1225,12 +1210,7 @@ impl<'a> TestRPC<'a> { let _ = peer_1 .network - .refresh_burnchain_view( - &peer_1_indexer, - &peer_1_sortdb, - &mut peer_1_stacks_node.chainstate, - false, - ) + .refresh_burnchain_view(&peer_1_sortdb, &mut peer_1_stacks_node.chainstate, false) .unwrap(); if unconfirmed_state { diff --git a/stackslib/src/net/inv/epoch2x.rs b/stackslib/src/net/inv/epoch2x.rs index 06e21e416f..2815cd06da 100644 --- a/stackslib/src/net/inv/epoch2x.rs +++ b/stackslib/src/net/inv/epoch2x.rs @@ -1808,14 +1808,7 @@ impl PeerNetwork { /// Determine at which reward cycle to begin scanning inventories pub(crate) fn get_block_scan_start(&self, sortdb: &SortitionDB) -> u64 { - // see if the stacks tip affirmation map and heaviest affirmation map diverge. If so, then - // start scaning at the reward cycle just before that. - let am_rescan_rc = self - .stacks_tip_affirmation_map - .find_inv_search(&self.heaviest_affirmation_map); - - // affirmation maps are compatible, so just resume scanning off of wherever we are at the - // tip. + // Resume scanning off of wherever we are at the tip. // NOTE: This code path only works in Stacks 2.x, but that's okay because this whole state // machine is only used in Stacks 2.x let (consensus_hash, _) = SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn()) @@ -1834,19 +1827,9 @@ impl PeerNetwork { .block_height_to_reward_cycle(stacks_tip_burn_block_height) .unwrap_or(0); - let start_reward_cycle = - stacks_tip_rc.saturating_sub(self.connection_opts.inv_reward_cycles); + let rescan_rc = stacks_tip_rc.saturating_sub(self.connection_opts.inv_reward_cycles); - let rescan_rc = cmp::min(am_rescan_rc, start_reward_cycle); - - test_debug!( - "begin blocks inv scan at {} = min({},{}) stacks_tip_am={} heaviest_am={}", - rescan_rc, - am_rescan_rc, - start_reward_cycle, - &self.stacks_tip_affirmation_map, - &self.heaviest_affirmation_map - ); + test_debug!("begin blocks inv scan at {rescan_rc}"); rescan_rc } diff --git a/stackslib/src/net/mod.rs b/stackslib/src/net/mod.rs index dff1812593..a3d2df8959 100644 --- a/stackslib/src/net/mod.rs +++ b/stackslib/src/net/mod.rs @@ -37,7 +37,6 @@ use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PublicKey}; use {rusqlite, url}; use self::dns::*; -use crate::burnchains::affirmation::AffirmationMap; use crate::burnchains::{Burnchain, Error as burnchain_error, Txid}; use crate::chainstate::burn::db::sortdb::SortitionDB; use crate::chainstate::burn::ConsensusHash; @@ -3530,7 +3529,7 @@ pub mod test { let indexer = BitcoinIndexer::new_unit_test(&self.config.burnchain.working_dir); self.network - .refresh_burnchain_view(&indexer, &sortdb, &mut stacks_node.chainstate, false) + .refresh_burnchain_view(&sortdb, &mut stacks_node.chainstate, false) .unwrap(); self.sortdb = Some(sortdb); diff --git a/stackslib/src/net/p2p.rs b/stackslib/src/net/p2p.rs index 3a6e5501ce..23a6bd295d 100644 --- a/stackslib/src/net/p2p.rs +++ b/stackslib/src/net/p2p.rs @@ -38,10 +38,7 @@ use crate::burnchains::db::{BurnchainDB, BurnchainHeaderReader}; use crate::burnchains::{Burnchain, BurnchainView}; use crate::chainstate::burn::db::sortdb::{get_ancestor_sort_id, BlockHeaderCache, SortitionDB}; use crate::chainstate::burn::BlockSnapshot; -use crate::chainstate::coordinator::{ - static_get_canonical_affirmation_map, static_get_heaviest_affirmation_map, - static_get_stacks_tip_affirmation_map, OnChainRewardSetProvider, RewardCycleInfo, -}; +use crate::chainstate::coordinator::{OnChainRewardSetProvider, RewardCycleInfo}; use crate::chainstate::nakamoto::coordinator::load_nakamoto_reward_set; use crate::chainstate::stacks::boot::RewardSet; use crate::chainstate::stacks::db::{StacksBlockHeaderTypes, StacksChainState}; @@ -487,10 +484,6 @@ pub struct PeerNetwork { pub current_reward_sets: BTreeMap, // information about the state of the network's anchor blocks - pub heaviest_affirmation_map: AffirmationMap, - pub stacks_tip_affirmation_map: AffirmationMap, - pub sortition_tip_affirmation_map: AffirmationMap, - pub tentative_best_affirmation_map: AffirmationMap, pub last_anchor_block_hash: BlockHeaderHash, pub last_anchor_block_txid: Txid, @@ -692,10 +685,6 @@ impl PeerNetwork { chain_view, chain_view_stable_consensus_hash: ConsensusHash([0u8; 20]), ast_rules: ASTRules::Typical, - heaviest_affirmation_map: AffirmationMap::empty(), - stacks_tip_affirmation_map: AffirmationMap::empty(), - sortition_tip_affirmation_map: AffirmationMap::empty(), - tentative_best_affirmation_map: AffirmationMap::empty(), last_anchor_block_hash: BlockHeaderHash([0x00; 32]), last_anchor_block_txid: Txid([0x00; 32]), burnchain_tip: BlockSnapshot::initial( @@ -4716,9 +4705,8 @@ impl PeerNetwork { /// * hint to the download state machine to start looking for the new block at the new /// stable sortition height /// * hint to the antientropy protocol to reset to the latest reward cycle - pub fn refresh_burnchain_view( + pub fn refresh_burnchain_view( &mut self, - indexer: &B, sortdb: &SortitionDB, chainstate: &mut StacksChainState, ibd: bool, @@ -4878,38 +4866,6 @@ impl PeerNetwork { // update tx validation information self.ast_rules = SortitionDB::get_ast_rules(sortdb.conn(), canonical_sn.block_height)?; - if self.get_current_epoch().epoch_id < StacksEpochId::Epoch30 { - // update heaviest affirmation map view - self.heaviest_affirmation_map = static_get_heaviest_affirmation_map( - &self.burnchain, - indexer, - &self.burnchain_db, - sortdb, - &canonical_sn.sortition_id, - ) - .map_err(|_| { - net_error::Transient("Unable to query heaviest affirmation map".to_string()) - })?; - - self.tentative_best_affirmation_map = static_get_canonical_affirmation_map( - &self.burnchain, - indexer, - &self.burnchain_db, - sortdb, - chainstate, - &canonical_sn.sortition_id, - ) - .map_err(|_| { - net_error::Transient("Unable to query canonical affirmation map".to_string()) - })?; - - self.sortition_tip_affirmation_map = - SortitionDB::find_sortition_tip_affirmation_map( - sortdb, - &canonical_sn.sortition_id, - )?; - } - // update last anchor data let ih = sortdb.index_handle(&canonical_sn.sortition_id); self.last_anchor_block_hash = ih @@ -4932,21 +4888,6 @@ impl PeerNetwork { self.refresh_stacker_db_configs(sortdb, chainstate)?; } - if stacks_tip_changed && self.get_current_epoch().epoch_id < StacksEpochId::Epoch30 { - // update stacks tip affirmation map view - // (NOTE: this check has to happen _after_ self.chain_view gets updated!) - self.stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( - &self.burnchain_db, - sortdb, - &canonical_sn.sortition_id, - &canonical_sn.canonical_stacks_tip_consensus_hash, - &canonical_sn.canonical_stacks_tip_hash, - ) - .map_err(|_| { - net_error::Transient("Unable to query stacks tip affirmation map".to_string()) - })?; - } - // can't fail after this point let mut ret = PendingMessages::new(); if burnchain_tip_changed { @@ -5519,7 +5460,7 @@ impl PeerNetwork { // update burnchain view, before handling any HTTP connections let unsolicited_buffered_messages = - match self.refresh_burnchain_view(indexer, sortdb, chainstate, ibd) { + match self.refresh_burnchain_view(sortdb, chainstate, ibd) { Ok(msgs) => msgs, Err(e) => { warn!("Failed to refresh burnchain view: {:?}", &e); diff --git a/stackslib/src/net/tests/inv/epoch2x.rs b/stackslib/src/net/tests/inv/epoch2x.rs index e9d994e703..1f3d2091c3 100644 --- a/stackslib/src/net/tests/inv/epoch2x.rs +++ b/stackslib/src/net/tests/inv/epoch2x.rs @@ -777,7 +777,7 @@ fn test_sync_inv_make_inv_messages() { .with_network_state(|sortdb, chainstate, network, _relayer, _mempool| { network.refresh_local_peer().unwrap(); network - .refresh_burnchain_view(&indexer, sortdb, chainstate, false) + .refresh_burnchain_view(sortdb, chainstate, false) .unwrap(); network.refresh_sortition_view(sortdb).unwrap(); Ok(()) From d1fdb69d2b918b2b62b94aaed30b3307d392b444 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 24 Jul 2025 12:00:13 -0700 Subject: [PATCH 09/22] Fix failing test_inv_sync_start_reward_cycle Signed-off-by: Jacinta Ferrant --- stackslib/src/net/tests/inv/epoch2x.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/tests/inv/epoch2x.rs b/stackslib/src/net/tests/inv/epoch2x.rs index 1f3d2091c3..23105b3c51 100644 --- a/stackslib/src/net/tests/inv/epoch2x.rs +++ b/stackslib/src/net/tests/inv/epoch2x.rs @@ -1254,7 +1254,7 @@ fn test_inv_sync_start_reward_cycle() { let block_scan_start = peer_1 .network .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); - assert_eq!(block_scan_start, 7); + assert_eq!(block_scan_start, 8); peer_1.network.connection_opts.inv_reward_cycles = 1; From 36c427bbfaab3c50118b9e451f60fb7f2d340d95 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 24 Jul 2025 12:12:14 -0700 Subject: [PATCH 10/22] Remove AffirmationMaps from getinfo endpoint Signed-off-by: Jacinta Ferrant --- stacks-signer/src/client/mod.rs | 1 - stackslib/src/net/api/getinfo.rs | 19 ------------------- 2 files changed, 20 deletions(-) diff --git a/stacks-signer/src/client/mod.rs b/stacks-signer/src/client/mod.rs index 974297cb9e..04fda6c3d6 100644 --- a/stacks-signer/src/client/mod.rs +++ b/stacks-signer/src/client/mod.rs @@ -341,7 +341,6 @@ pub(crate) mod tests { genesis_chainstate_hash: Sha256Sum::zero(), node_public_key: Some(public_key_buf), node_public_key_hash: Some(public_key_hash), - affirmations: None, last_pox_anchor: None, stackerdbs: Some( stackerdb_contract_ids diff --git a/stackslib/src/net/api/getinfo.rs b/stackslib/src/net/api/getinfo.rs index 311b8c38ca..40eedfb4c5 100644 --- a/stackslib/src/net/api/getinfo.rs +++ b/stackslib/src/net/api/getinfo.rs @@ -22,7 +22,6 @@ use stacks_common::types::net::PeerHost; use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{Hash160, Sha256Sum}; -use crate::burnchains::affirmation::AffirmationMap; use crate::burnchains::Txid; use crate::chainstate::stacks::db::StacksChainState; use crate::net::http::{ @@ -44,15 +43,6 @@ impl RPCPeerInfoRequestHandler { Self {} } } - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct RPCAffirmationData { - pub heaviest: AffirmationMap, - pub stacks_tip: AffirmationMap, - pub sortition_tip: AffirmationMap, - pub tentative_best: AffirmationMap, -} - /// Information about the last PoX anchor block #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RPCLastPoxAnchorData { @@ -88,9 +78,6 @@ pub struct RPCPeerInfoData { pub node_public_key_hash: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub affirmations: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub last_pox_anchor: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -146,12 +133,6 @@ impl RPCPeerInfoData { genesis_chainstate_hash: genesis_chainstate_hash.clone(), node_public_key: Some(public_key_buf), node_public_key_hash: Some(public_key_hash), - affirmations: Some(RPCAffirmationData { - heaviest: AffirmationMap::empty(), - stacks_tip: AffirmationMap::empty(), - sortition_tip: AffirmationMap::empty(), - tentative_best: AffirmationMap::empty(), - }), last_pox_anchor: Some(RPCLastPoxAnchorData { anchor_block_hash: network.last_anchor_block_hash.clone(), anchor_block_txid: network.last_anchor_block_txid.clone(), From 70b773a324c53d175c07300712372e4a8f3b284e Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 28 Jul 2025 11:12:02 -0700 Subject: [PATCH 11/22] Remove require_affirmed_anchor_blocks config options Signed-off-by: Jacinta Ferrant --- .../tests/fixtures/minimal_config.json | 3 +-- stacks-node/src/run_loop/nakamoto.rs | 3 --- stacks-node/src/run_loop/neon.rs | 3 --- stacks-node/src/tests/epoch_21.rs | 23 ------------------- stacks-node/src/tests/epoch_22.rs | 4 ---- stackslib/src/chainstate/coordinator/mod.rs | 5 ---- stackslib/src/config/mod.rs | 19 --------------- 7 files changed, 1 insertion(+), 59 deletions(-) diff --git a/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json b/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json index 3ede4781ca..0cce5ccbd4 100644 --- a/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json +++ b/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json @@ -48,7 +48,7 @@ }, { "name": "miner", - "description": "Flag indicating whether this node should activate its mining logic and attempt to\nproduce Stacks blocks. Setting this to `true` typically requires providing\nnecessary private keys (either [`NodeConfig::seed`] or [`MinerConfig::mining_key`]).\nIt also influences default behavior for settings like\n[`NodeConfig::require_affirmed_anchor_blocks`].", + "description": "Flag indicating whether this node should activate its mining logic and attempt to\nproduce Stacks blocks. Setting this to `true` typically requires providing\nnecessary private keys (either [`NodeConfig::seed`] or [`MinerConfig::mining_key`]).", "default_value": "`false`", "notes": null, "deprecated": null, @@ -75,6 +75,5 @@ "MinerConfig::mining_key": null, "NodeConfig::miner": null, "NodeConfig::mine_microblocks": null, - "NodeConfig::require_affirmed_anchor_blocks": null } } \ No newline at end of file diff --git a/stacks-node/src/run_loop/nakamoto.rs b/stacks-node/src/run_loop/nakamoto.rs index 3029d10754..acbca38974 100644 --- a/stacks-node/src/run_loop/nakamoto.rs +++ b/stacks-node/src/run_loop/nakamoto.rs @@ -325,9 +325,6 @@ impl RunLoop { let coord_config = ChainsCoordinatorConfig { assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, always_use_affirmation_maps: moved_config.node.always_use_affirmation_maps, - require_affirmed_anchor_blocks: moved_config - .node - .require_affirmed_anchor_blocks, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index a4c9b7787a..485b528b96 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -703,9 +703,6 @@ impl RunLoop { let coord_config = ChainsCoordinatorConfig { assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, always_use_affirmation_maps: moved_config.node.always_use_affirmation_maps, - require_affirmed_anchor_blocks: moved_config - .node - .require_affirmed_anchor_blocks, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stacks-node/src/tests/epoch_21.rs b/stacks-node/src/tests/epoch_21.rs index a9043d5e0c..52c4ace13d 100644 --- a/stacks-node/src/tests/epoch_21.rs +++ b/stacks-node/src/tests/epoch_21.rs @@ -2008,8 +2008,6 @@ fn test_pox_reorgs_three_flaps() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -2068,8 +2066,6 @@ fn test_pox_reorgs_three_flaps() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; @@ -2528,8 +2524,6 @@ fn test_pox_reorg_one_flap() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -2588,8 +2582,6 @@ fn test_pox_reorg_one_flap() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; @@ -2932,8 +2924,6 @@ fn test_pox_reorg_flap_duel() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -2992,8 +2982,6 @@ fn test_pox_reorg_flap_duel() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; @@ -3351,8 +3339,6 @@ fn test_pox_reorg_flap_reward_cycles() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -3411,8 +3397,6 @@ fn test_pox_reorg_flap_reward_cycles() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; @@ -3761,8 +3745,6 @@ fn test_pox_missing_five_anchor_blocks() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -3821,8 +3803,6 @@ fn test_pox_missing_five_anchor_blocks() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; @@ -4138,7 +4118,6 @@ fn test_sortition_divergence_pre_21() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; conf_template.node.always_use_affirmation_maps = false; // make epoch 2.1 start after we have created this error condition @@ -4199,8 +4178,6 @@ fn test_sortition_divergence_pre_21() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; conf.node.always_use_affirmation_maps = false; diff --git a/stacks-node/src/tests/epoch_22.rs b/stacks-node/src/tests/epoch_22.rs index 3a9df900b9..4f28ba3052 100644 --- a/stacks-node/src/tests/epoch_22.rs +++ b/stacks-node/src/tests/epoch_22.rs @@ -1256,8 +1256,6 @@ fn test_pox_reorg_one_flap() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.require_affirmed_anchor_blocks = false; - // make epoch 2.1 and 2.2 start in the middle of boot-up let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -1320,8 +1318,6 @@ fn test_pox_reorg_one_flap() { conf.burnchain.max_rbf = conf_template.burnchain.max_rbf; conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.require_affirmed_anchor_blocks = - conf_template.node.require_affirmed_anchor_blocks; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 3126dfeb3a..a41b56e18d 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -188,9 +188,6 @@ pub struct ChainsCoordinatorConfig { /// true: use affirmation maps before 2.1 /// false: only use affirmation maps in 2.1 or later pub always_use_affirmation_maps: bool, - /// true: always wait for canonical anchor blocks, even if it stalls the chain - /// false: proceed to process new chain history even if we're missing an anchor block. - pub require_affirmed_anchor_blocks: bool, /// true: enable transactions indexing /// false: no transactions indexing pub txindex: bool, @@ -200,7 +197,6 @@ impl ChainsCoordinatorConfig { pub fn new() -> ChainsCoordinatorConfig { ChainsCoordinatorConfig { always_use_affirmation_maps: true, - require_affirmed_anchor_blocks: true, assume_present_anchor_blocks: true, txindex: false, } @@ -209,7 +205,6 @@ impl ChainsCoordinatorConfig { pub fn test_new(txindex: bool) -> ChainsCoordinatorConfig { ChainsCoordinatorConfig { always_use_affirmation_maps: false, - require_affirmed_anchor_blocks: false, assume_present_anchor_blocks: false, txindex, } diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 4f3c03c94f..419ee3bd56 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -2116,8 +2116,6 @@ pub struct NodeConfig { /// Flag indicating whether this node should activate its mining logic and attempt to /// produce Stacks blocks. Setting this to `true` typically requires providing /// necessary private keys (either [`NodeConfig::seed`] or [`MinerConfig::mining_key`]). - /// It also influences default behavior for settings like - /// [`NodeConfig::require_affirmed_anchor_blocks`]. /// --- /// @default: `false` pub miner: bool, @@ -2242,18 +2240,6 @@ pub struct NodeConfig { /// --- /// @default: `true` pub always_use_affirmation_maps: bool, - /// Controls if the node must wait for locally missing but burnchain-affirmed PoX - /// anchor blocks. If an anchor block is confirmed by the affirmation map but not - /// yet processed by this node: - /// - If `true`: Burnchain processing halts until the affirmed block is acquired. - /// Ensures strict adherence to the affirmed canonical chain, typical for - /// followers. - /// - If `false`: Burnchain processing continues without waiting. Allows miners to - /// operate optimistically but may necessitate unwinding later if the affirmed - /// block alters the chain state. - /// --- - /// @default: Derived from the inverse of [`NodeConfig::miner`] value. - pub require_affirmed_anchor_blocks: bool, /// Controls if the node must strictly wait for any PoX anchor block selected by /// the core consensus mechanism. /// - If `true`: Halts burnchain processing immediately whenever a selected anchor @@ -2578,7 +2564,6 @@ impl Default for NodeConfig { pox_sync_sample_secs: 30, use_test_genesis_chainstate: None, always_use_affirmation_maps: true, - require_affirmed_anchor_blocks: true, assume_present_anchor_blocks: true, fault_injection_block_push_fail_probability: None, fault_injection_hide_blocks: false, @@ -3918,7 +3903,6 @@ pub struct NodeConfigFile { pub pox_sync_sample_secs: Option, pub use_test_genesis_chainstate: Option, pub always_use_affirmation_maps: Option, - pub require_affirmed_anchor_blocks: Option, pub assume_present_anchor_blocks: Option, /// At most, how often should the chain-liveness thread /// wake up the chains-coordinator. Defaults to 300s (5 min). @@ -4000,9 +3984,6 @@ impl NodeConfigFile { always_use_affirmation_maps: self .always_use_affirmation_maps .unwrap_or(default_node_config.always_use_affirmation_maps), - // miners should always try to mine, even if they don't have the anchored - // blocks in the canonical affirmation map. Followers, however, can stall. - require_affirmed_anchor_blocks: self.require_affirmed_anchor_blocks.unwrap_or(!miner), // as of epoch 3.0, all prepare phases have anchor blocks. // at the start of epoch 3.0, the chain stalls without anchor blocks. // only set this to false if you're doing some very extreme testing. From 86d281c38562f83290aeab768521883d1e395f32 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 28 Jul 2025 11:41:16 -0700 Subject: [PATCH 12/22] Remove always_use_affirmation_maps config option Signed-off-by: Jacinta Ferrant --- stacks-node/src/main.rs | 1 - stacks-node/src/neon_node.rs | 1 - stacks-node/src/run_loop/nakamoto.rs | 1 - stacks-node/src/run_loop/neon.rs | 1 - stacks-node/src/tests/epoch_21.rs | 4 ---- stacks-node/src/tests/neon_integrations.rs | 15 ------------- stackslib/src/chainstate/coordinator/mod.rs | 24 ++++++--------------- stackslib/src/config/mod.rs | 17 --------------- stackslib/src/main.rs | 1 - stackslib/src/net/mod.rs | 1 - 10 files changed, 7 insertions(+), 59 deletions(-) diff --git a/stacks-node/src/main.rs b/stacks-node/src/main.rs index 9ffbcd8bb4..c1b7c9e99a 100644 --- a/stacks-node/src/main.rs +++ b/stacks-node/src/main.rs @@ -139,7 +139,6 @@ fn cli_get_miner_spend( &mut sortdb, &burnchain, &OnChainRewardSetProvider(no_dispatcher), - config.node.always_use_affirmation_maps, ) .unwrap(); diff --git a/stacks-node/src/neon_node.rs b/stacks-node/src/neon_node.rs index 1594b885f4..8cb59fe7e7 100644 --- a/stacks-node/src/neon_node.rs +++ b/stacks-node/src/neon_node.rs @@ -2109,7 +2109,6 @@ impl BlockMinerThread { burn_db, &self.burnchain, &OnChainRewardSetProvider::new(), - self.config.node.always_use_affirmation_maps, ) { Ok(x) => x, Err(e) => { diff --git a/stacks-node/src/run_loop/nakamoto.rs b/stacks-node/src/run_loop/nakamoto.rs index acbca38974..c20436f768 100644 --- a/stacks-node/src/run_loop/nakamoto.rs +++ b/stacks-node/src/run_loop/nakamoto.rs @@ -324,7 +324,6 @@ impl RunLoop { let coord_config = ChainsCoordinatorConfig { assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, - always_use_affirmation_maps: moved_config.node.always_use_affirmation_maps, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index 485b528b96..f30b0d9209 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -702,7 +702,6 @@ impl RunLoop { let coord_config = ChainsCoordinatorConfig { assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, - always_use_affirmation_maps: moved_config.node.always_use_affirmation_maps, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stacks-node/src/tests/epoch_21.rs b/stacks-node/src/tests/epoch_21.rs index 52c4ace13d..16aa232806 100644 --- a/stacks-node/src/tests/epoch_21.rs +++ b/stacks-node/src/tests/epoch_21.rs @@ -4118,8 +4118,6 @@ fn test_sortition_divergence_pre_21() { conf_template.node.wait_time_for_blocks = 1_000; conf_template.burnchain.pox_2_activation = Some(v1_unlock_height); - conf_template.node.always_use_affirmation_maps = false; - // make epoch 2.1 start after we have created this error condition let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -4179,8 +4177,6 @@ fn test_sortition_divergence_pre_21() { conf.burnchain.epochs = conf_template.burnchain.epochs.clone(); conf.burnchain.pox_2_activation = conf_template.burnchain.pox_2_activation; - conf.node.always_use_affirmation_maps = false; - // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; diff --git a/stacks-node/src/tests/neon_integrations.rs b/stacks-node/src/tests/neon_integrations.rs index 2557ef6534..3df12f8c10 100644 --- a/stacks-node/src/tests/neon_integrations.rs +++ b/stacks-node/src/tests/neon_integrations.rs @@ -5171,9 +5171,6 @@ fn pox_integration_test() { test_observer::spawn(); test_observer::register_any(&mut conf); - // required for testing post-sunset behavior - conf.node.always_use_affirmation_maps = false; - let first_bal = 6_000_000_000 * u64::from(core::MICROSTACKS_PER_STACKS); let second_bal = 2_000_000_000 * u64::from(core::MICROSTACKS_PER_STACKS); let third_bal = 2_000_000_000 * u64::from(core::MICROSTACKS_PER_STACKS); @@ -5677,8 +5674,6 @@ fn atlas_integration_test() { .initial_balances .push(initial_balance_user_1.clone()); - conf_bootstrap_node.node.always_use_affirmation_maps = false; - // Prepare the config of the follower node let (mut conf_follower_node, _) = neon_integration_test_conf(); let bootstrap_node_url = format!( @@ -5703,8 +5698,6 @@ fn atlas_integration_test() { disable_retries: false, }); - conf_follower_node.node.always_use_affirmation_maps = false; - // Our 2 nodes will share the bitcoind node let mut btcd_controller = BitcoinCoreController::new(conf_bootstrap_node.clone()); btcd_controller @@ -6210,8 +6203,6 @@ fn antientropy_integration_test() { conf_bootstrap_node.burnchain.max_rbf = 1000000; conf_bootstrap_node.node.wait_time_for_blocks = 1_000; - conf_bootstrap_node.node.always_use_affirmation_maps = false; - // Prepare the config of the follower node let (mut conf_follower_node, _) = neon_integration_test_conf(); let bootstrap_node_url = format!( @@ -6246,8 +6237,6 @@ fn antientropy_integration_test() { conf_follower_node.burnchain.max_rbf = 1000000; conf_follower_node.node.wait_time_for_blocks = 1_000; - conf_follower_node.node.always_use_affirmation_maps = false; - // Our 2 nodes will share the bitcoind node let mut btcd_controller = BitcoinCoreController::new(conf_bootstrap_node.clone()); btcd_controller @@ -6484,8 +6473,6 @@ fn atlas_stress_integration_test() { conf_bootstrap_node.burnchain.max_rbf = 1000000; conf_bootstrap_node.node.wait_time_for_blocks = 1_000; - conf_bootstrap_node.node.always_use_affirmation_maps = false; - let user_1 = users.pop().unwrap(); let initial_balance_user_1 = initial_balances.pop().unwrap(); @@ -7944,8 +7931,6 @@ fn spawn_follower_node( conf.connection_options.inv_sync_interval = 3; - conf.node.always_use_affirmation_maps = false; - let mut run_loop = neon::RunLoop::new(conf.clone()); let blocks_processed = run_loop.get_blocks_processed_arc(); let channel = run_loop.get_coordinator_channel().unwrap(); diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index a41b56e18d..605e1df45f 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -185,9 +185,6 @@ pub struct ChainsCoordinatorConfig { /// true: assume all anchor blocks are present, and block chain sync until they arrive /// false: process sortitions in reward cycles without anchor blocks pub assume_present_anchor_blocks: bool, - /// true: use affirmation maps before 2.1 - /// false: only use affirmation maps in 2.1 or later - pub always_use_affirmation_maps: bool, /// true: enable transactions indexing /// false: no transactions indexing pub txindex: bool, @@ -196,7 +193,6 @@ pub struct ChainsCoordinatorConfig { impl ChainsCoordinatorConfig { pub fn new() -> ChainsCoordinatorConfig { ChainsCoordinatorConfig { - always_use_affirmation_maps: true, assume_present_anchor_blocks: true, txindex: false, } @@ -204,7 +200,6 @@ impl ChainsCoordinatorConfig { pub fn test_new(txindex: bool) -> ChainsCoordinatorConfig { ChainsCoordinatorConfig { - always_use_affirmation_maps: false, assume_present_anchor_blocks: false, txindex, } @@ -716,7 +711,6 @@ pub fn get_next_recipients( sort_db: &mut SortitionDB, burnchain: &Burnchain, provider: &U, - always_use_affirmation_maps: bool, ) -> Result, Error> { let burnchain_db = BurnchainDB::open(&burnchain.get_burnchaindb_path(), false)?; let reward_cycle_info = get_reward_cycle_info( @@ -728,7 +722,6 @@ pub fn get_next_recipients( chain_state, sort_db, provider, - always_use_affirmation_maps, )?; sort_db .get_next_block_recipients(burnchain, sortition_tip, reward_cycle_info.as_ref()) @@ -749,7 +742,6 @@ pub fn get_reward_cycle_info( chain_state: &mut StacksChainState, sort_db: &mut SortitionDB, provider: &U, - always_use_affirmation_maps: bool, ) -> Result, Error> { let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), burn_height)? .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {}", burn_height)); @@ -780,13 +772,13 @@ pub fn get_reward_cycle_info( let reward_cycle_info = { let ic = sort_db.index_handle(sortition_tip); - let burnchain_db_conn_opt = - if epoch_at_height.epoch_id >= StacksEpochId::Epoch21 || always_use_affirmation_maps { - // use the new block-commit-based PoX anchor block selection rules - Some(burnchain_db.conn()) - } else { - None - }; + // TODO: always use block-commit-based PoX anchor block selection rules? + let burnchain_db_conn_opt = if epoch_at_height.epoch_id >= StacksEpochId::Epoch21 { + // use the new block-commit-based PoX anchor block selection rules + Some(burnchain_db.conn()) + } else { + None + }; ic.get_chosen_pox_anchor(burnchain_db_conn_opt, parent_bhh, &burnchain.pox_constants) }?; @@ -1459,7 +1451,6 @@ impl< &mut self.chain_state_db, &mut self.sortition_db, &self.reward_set_provider, - self.config.always_use_affirmation_maps, ) } @@ -1954,7 +1945,6 @@ impl SortitionDBMigrator { &mut chainstate, sort_db, &OnChainRewardSetProvider::new(), - true, ) .map_err(|e| DBError::Other(format!("get_reward_cycle_info: {:?}", &e))); diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 419ee3bd56..66058ab549 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -2228,18 +2228,6 @@ pub struct NodeConfig { /// @notes: /// - This is intended strictly for testing purposes and is disallowed on mainnet. pub use_test_genesis_chainstate: Option, - /// Controls if Stacks Epoch 2.1+ affirmation map logic should be applied even - /// before Epoch 2.1. - /// - If `true` (default), the node consistently uses the newer (Epoch 2.1) rules - /// for PoX anchor block validation and affirmation-based reorg handling, even in - /// earlier epochs. - /// - If `false`, the node strictly follows the rules defined for the specific epoch - /// it is currently processing, only applying 2.1+ logic from Epoch 2.1 onwards. - /// Differences in this setting between nodes prior to Epoch 2.1 could lead to - /// consensus forks. - /// --- - /// @default: `true` - pub always_use_affirmation_maps: bool, /// Controls if the node must strictly wait for any PoX anchor block selected by /// the core consensus mechanism. /// - If `true`: Halts burnchain processing immediately whenever a selected anchor @@ -2563,7 +2551,6 @@ impl Default for NodeConfig { marf_defer_hashing: true, pox_sync_sample_secs: 30, use_test_genesis_chainstate: None, - always_use_affirmation_maps: true, assume_present_anchor_blocks: true, fault_injection_block_push_fail_probability: None, fault_injection_hide_blocks: false, @@ -3902,7 +3889,6 @@ pub struct NodeConfigFile { pub marf_defer_hashing: Option, pub pox_sync_sample_secs: Option, pub use_test_genesis_chainstate: Option, - pub always_use_affirmation_maps: Option, pub assume_present_anchor_blocks: Option, /// At most, how often should the chain-liveness thread /// wake up the chains-coordinator. Defaults to 300s (5 min). @@ -3981,9 +3967,6 @@ impl NodeConfigFile { .pox_sync_sample_secs .unwrap_or(default_node_config.pox_sync_sample_secs), use_test_genesis_chainstate: self.use_test_genesis_chainstate, - always_use_affirmation_maps: self - .always_use_affirmation_maps - .unwrap_or(default_node_config.always_use_affirmation_maps), // as of epoch 3.0, all prepare phases have anchor blocks. // at the start of epoch 3.0, the chain stalls without anchor blocks. // only set this to false if you're doing some very extreme testing. diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 8961eb413e..031b7d4339 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -1946,7 +1946,6 @@ fn analyze_sortition_mev(argv: Vec) { &mut chainstate, &mut sortdb, &OnChainRewardSetProvider::new(), - false, ) .unwrap(); diff --git a/stackslib/src/net/mod.rs b/stackslib/src/net/mod.rs index a3d2df8959..25a55720b4 100644 --- a/stackslib/src/net/mod.rs +++ b/stackslib/src/net/mod.rs @@ -4426,7 +4426,6 @@ pub mod test { &mut sortdb, &self.config.burnchain, &OnChainRewardSetProvider::new(), - true, ) { Ok(recipients) => { block_commit_op.commit_outs = match recipients { From e40e0cf8d18d5e27dba3cd8e803de4a120c43255 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Mon, 28 Jul 2025 13:36:27 -0700 Subject: [PATCH 13/22] Remove assume_present_anchor_blocks config option Signed-off-by: Jacinta Ferrant --- stacks-node/src/run_loop/nakamoto.rs | 1 - stacks-node/src/run_loop/neon.rs | 1 - stackslib/src/chainstate/coordinator/mod.rs | 17 ++++----------- stackslib/src/config/mod.rs | 24 --------------------- 4 files changed, 4 insertions(+), 39 deletions(-) diff --git a/stacks-node/src/run_loop/nakamoto.rs b/stacks-node/src/run_loop/nakamoto.rs index c20436f768..362d314d24 100644 --- a/stacks-node/src/run_loop/nakamoto.rs +++ b/stacks-node/src/run_loop/nakamoto.rs @@ -323,7 +323,6 @@ impl RunLoop { let mut fee_estimator = moved_config.make_fee_estimator(); let coord_config = ChainsCoordinatorConfig { - assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index f30b0d9209..30937bf555 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -701,7 +701,6 @@ impl RunLoop { let mut fee_estimator = moved_config.make_fee_estimator(); let coord_config = ChainsCoordinatorConfig { - assume_present_anchor_blocks: moved_config.node.assume_present_anchor_blocks, txindex: moved_config.node.txindex, }; ChainsCoordinator::run( diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 605e1df45f..cf69f6aa44 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -182,9 +182,6 @@ pub trait BlockEventDispatcher { } pub struct ChainsCoordinatorConfig { - /// true: assume all anchor blocks are present, and block chain sync until they arrive - /// false: process sortitions in reward cycles without anchor blocks - pub assume_present_anchor_blocks: bool, /// true: enable transactions indexing /// false: no transactions indexing pub txindex: bool, @@ -192,17 +189,11 @@ pub struct ChainsCoordinatorConfig { impl ChainsCoordinatorConfig { pub fn new() -> ChainsCoordinatorConfig { - ChainsCoordinatorConfig { - assume_present_anchor_blocks: true, - txindex: false, - } + ChainsCoordinatorConfig { txindex: false } } pub fn test_new(txindex: bool) -> ChainsCoordinatorConfig { - ChainsCoordinatorConfig { - assume_present_anchor_blocks: false, - txindex, - } + ChainsCoordinatorConfig { txindex } } } @@ -1061,8 +1052,8 @@ impl< panic!("BUG: no epoch defined at height {}", header.block_height) }); - if cur_epoch.epoch_id >= StacksEpochId::Epoch21 || self.config.assume_present_anchor_blocks - { + // TODO: Always stop if the anchor block is not present + if cur_epoch.epoch_id >= StacksEpochId::Epoch21 { // anchor blocks are always assumed to be present in the chain history, // so report its absence if we don't have it. if let PoxAnchorBlockStatus::SelectedAndUnknown(missing_anchor_block, _) = diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 66058ab549..ae11518792 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -2228,24 +2228,6 @@ pub struct NodeConfig { /// @notes: /// - This is intended strictly for testing purposes and is disallowed on mainnet. pub use_test_genesis_chainstate: Option, - /// Controls if the node must strictly wait for any PoX anchor block selected by - /// the core consensus mechanism. - /// - If `true`: Halts burnchain processing immediately whenever a selected anchor - /// block is missing locally (`SelectedAndUnknown` status), regardless of - /// affirmation status. - /// - If `false` (primarily for testing): Skips this immediate halt, allowing - /// processing to proceed to affirmation map checks. - /// Normal operation requires this to be `true`; setting to `false` will likely - /// break consensus adherence. - /// --- - /// @default: `true` - /// @notes: - /// - This parameter cannot be set via the configuration file; it must be modified - /// programmatically. - /// - This is intended strictly for testing purposes. - /// - The halt check runs *before* affirmation checks. - /// - In Nakamoto (Epoch 3.0+), all prepare phases have anchor blocks. - pub assume_present_anchor_blocks: bool, /// Fault injection setting for testing purposes. If set to `Some(p)`, where `p` is /// between 0 and 100, the node will have a `p` percent chance of intentionally /// *not* pushing a newly processed block to its peers. @@ -2551,7 +2533,6 @@ impl Default for NodeConfig { marf_defer_hashing: true, pox_sync_sample_secs: 30, use_test_genesis_chainstate: None, - assume_present_anchor_blocks: true, fault_injection_block_push_fail_probability: None, fault_injection_hide_blocks: false, chain_liveness_poll_time_secs: 300, @@ -3889,7 +3870,6 @@ pub struct NodeConfigFile { pub marf_defer_hashing: Option, pub pox_sync_sample_secs: Option, pub use_test_genesis_chainstate: Option, - pub assume_present_anchor_blocks: Option, /// At most, how often should the chain-liveness thread /// wake up the chains-coordinator. Defaults to 300s (5 min). pub chain_liveness_poll_time_secs: Option, @@ -3967,10 +3947,6 @@ impl NodeConfigFile { .pox_sync_sample_secs .unwrap_or(default_node_config.pox_sync_sample_secs), use_test_genesis_chainstate: self.use_test_genesis_chainstate, - // as of epoch 3.0, all prepare phases have anchor blocks. - // at the start of epoch 3.0, the chain stalls without anchor blocks. - // only set this to false if you're doing some very extreme testing. - assume_present_anchor_blocks: true, // chainstate fault_injection activation for hide_blocks. // you can't set this in the config file. fault_injection_hide_blocks: false, From b72a6ab4bf25e530f97d679ffb6bdcc345817446 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 10:05:51 -0700 Subject: [PATCH 14/22] check_missing_anchor_block should stall if missing anchor block regardless of epoch Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 32 +++------ stackslib/src/chainstate/coordinator/tests.rs | 67 +++++-------------- 2 files changed, 27 insertions(+), 72 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index cf69f6aa44..1470035cbf 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -1034,7 +1034,7 @@ impl< } /// Check to see if the discovery of a PoX anchor block means it's time to process a new reward - /// cycle. Based on the canonical affirmation map, this may not always be the case. + /// cycle. /// /// This mutates `rc_info` to be the affirmed anchor block status. /// @@ -1043,31 +1043,21 @@ impl< /// Returns Ok(None) if not fn check_missing_anchor_block( &self, - header: &BurnchainBlockHeader, + _header: &BurnchainBlockHeader, rc_info: &mut RewardCycleInfo, ) -> Result, Error> { - let cur_epoch = - SortitionDB::get_stacks_epoch(self.sortition_db.conn(), header.block_height)? - .unwrap_or_else(|| { - panic!("BUG: no epoch defined at height {}", header.block_height) - }); - - // TODO: Always stop if the anchor block is not present - if cur_epoch.epoch_id >= StacksEpochId::Epoch21 { - // anchor blocks are always assumed to be present in the chain history, - // so report its absence if we don't have it. - if let PoxAnchorBlockStatus::SelectedAndUnknown(missing_anchor_block, _) = - &rc_info.anchor_status - { - info!("Currently missing PoX anchor block {missing_anchor_block}, which is assumed to be present"); - return Ok(Some(missing_anchor_block.clone())); - } + // anchor blocks are always assumed to be present in the chain history, + // so report its absence if we don't have it. + if let PoxAnchorBlockStatus::SelectedAndUnknown(missing_anchor_block, _) = + &rc_info.anchor_status + { + info!("Currently missing PoX anchor block {missing_anchor_block}, which is assumed to be present"); + return Ok(Some(missing_anchor_block.clone())); } test_debug!( - "Reward cycle info at height {}: {:?}", - &header.block_height, - &rc_info + "Reward cycle info at height {}: {rc_info:?}", + &_header.block_height ); Ok(None) } diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 4887bcad4f..7d3ce51c4d 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -6448,7 +6448,7 @@ fn test_pox_fork_out_of_order() { let burnchain_blinded = get_burnchain_db(path_blinded, None); let b = get_burnchain(path, None); - eprintln!("Making block {}", ix); + eprintln!("Making block {ix}"); let (op, block) = if ix == 0 { make_genesis_block( &b, @@ -6461,9 +6461,7 @@ fn test_pox_fork_out_of_order() { ix as u32, ) } else { - let parent = if ix == 1 { - stacks_blocks[0].1.header.block_hash() - } else if ix == 6 { + let parent = if ix == 1 || ix == 6 { stacks_blocks[0].1.header.block_hash() } else if ix == 11 { stacks_blocks[5].1.header.block_hash() @@ -6506,8 +6504,7 @@ fn test_pox_fork_out_of_order() { let ic = sort_db.index_handle_at_tip(); let bhh = ic.get_last_anchor_block_hash().unwrap().unwrap(); eprintln!( - "Anchor block={}, selected at height={}", - &bhh, + "Anchor block={bhh}, selected at height={}", SortitionDB::get_block_snapshot_for_winning_stacks_block( &sort_db.index_conn(), &ic.context.chain_tip, @@ -6537,7 +6534,7 @@ fn test_pox_fork_out_of_order() { // load the block into staging let block_hash = block.header.block_hash(); - eprintln!("Block hash={}, ix={}", &block_hash, ix); + eprintln!("Block hash={block_hash}, ix={ix}"); assert_eq!(&tip.winning_stacks_block_hash, &block_hash); stacks_blocks.push((tip.sortition_id.clone(), block.clone())); @@ -6560,10 +6557,11 @@ fn test_pox_fork_out_of_order() { assert_eq!(&pox_id.to_string(), "11111"); } + // Because we no longer continue processing without an anchor block, the blinded signer has not advanced. { let ic = sort_db_blind.index_handle_at_tip(); let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "11000"); + assert_eq!(&pox_id.to_string(), "11"); } // now, we reveal to the blinded coordinator, but out of order. @@ -6586,30 +6584,19 @@ fn test_pox_fork_out_of_order() { { let ic = sort_db_blind.index_handle_at_tip(); let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "11110"); + assert_eq!(&pox_id.to_string(), "1111"); } let block_height = eval_at_chain_tip(path_blinded, &sort_db_blind, "block-height"); assert_eq!(block_height, Value::UInt(1)); // reveal [6-10] - for (_sort_id, block) in stacks_blocks[6..=10].iter() { - // cannot use sort_id from stacks_blocks, because the blinded coordinator - // has different sortition_id's for blocks 6-10 (because it's missing - // the 2nd anchor block). - let sort_id = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &sort_db_blind.index_conn(), - &SortitionDB::get_canonical_sortition_tip(sort_db_blind.conn()).unwrap(), - &block.header.block_hash(), - ) - .unwrap() - .unwrap() - .sortition_id; + for (sort_id, block) in stacks_blocks[6..=10].iter() { reveal_block( path_blinded, &sort_db_blind, &mut coord_blind, - &sort_id, + sort_id, block, ); } @@ -6617,7 +6604,7 @@ fn test_pox_fork_out_of_order() { { let ic = sort_db_blind.index_handle_at_tip(); let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "11110"); + assert_eq!(&pox_id.to_string(), "1111"); } let block_height = eval_at_chain_tip(path_blinded, &sort_db_blind, "block-height"); @@ -6637,19 +6624,7 @@ fn test_pox_fork_out_of_order() { ); // reveal [1-5] - for (_sort_id, block) in stacks_blocks[1..=5].iter() { - // cannot use sort_id from stacks_blocks, because the blinded coordinator - // has different sortition_id's for blocks 6-10 (because it's missing - // the 2nd anchor block). - let sort_id = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &sort_db_blind.index_conn(), - &SortitionDB::get_canonical_sortition_tip(sort_db_blind.conn()).unwrap(), - &block.header.block_hash(), - ) - .unwrap() - .unwrap() - .sortition_id; - + for (sort_id, block) in stacks_blocks[1..=5].iter() { // before processing the last of these blocks, the stacks_block[9] should still // be the canonical tip let block_hash = eval_at_chain_tip( @@ -6670,7 +6645,7 @@ fn test_pox_fork_out_of_order() { path_blinded, &sort_db_blind, &mut coord_blind, - &sort_id, + sort_id, block, ); } @@ -6685,24 +6660,12 @@ fn test_pox_fork_out_of_order() { assert_eq!(block_height, Value::UInt(6)); // reveal [11-14] - for (_sort_id, block) in stacks_blocks[11..].iter() { - // cannot use sort_id from stacks_blocks, because the blinded coordinator - // has different sortition_id's for blocks 6-10 (because it's missing - // the 2nd anchor block). - let sort_id = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &sort_db_blind.index_conn(), - &SortitionDB::get_canonical_sortition_tip(sort_db_blind.conn()).unwrap(), - &block.header.block_hash(), - ) - .unwrap() - .unwrap() - .sortition_id; - + for (sort_id, block) in stacks_blocks[11..].iter() { reveal_block( path_blinded, &sort_db_blind, &mut coord_blind, - &sort_id, + sort_id, block, ); } @@ -6765,6 +6728,8 @@ fn reveal_block Date: Tue, 29 Jul 2025 10:39:11 -0700 Subject: [PATCH 15/22] Fix test_pox_no_anchor_selected Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/tests.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 7d3ce51c4d..2d0335f193 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -6234,7 +6234,7 @@ fn test_pox_no_anchor_selected() { let burnchain_tip = burnchain.get_canonical_chain_tip().unwrap(); let burnchain_blinded = get_burnchain_db(path_blinded, None); - eprintln!("Making block {}", ix); + eprintln!("Making block {ix}"); let (op, block) = if ix == 0 { make_genesis_block( &b, @@ -6300,8 +6300,7 @@ fn test_pox_no_anchor_selected() { let ic = sort_db.index_handle_at_tip(); let bhh = ic.get_last_anchor_block_hash().unwrap().unwrap(); eprintln!( - "Anchor block={}, selected at height={}", - &bhh, + "Anchor block={bhh}, selected at height={}", SortitionDB::get_block_snapshot_for_winning_stacks_block( &sort_db.index_conn(), &ic.context.chain_tip, @@ -6333,7 +6332,7 @@ fn test_pox_no_anchor_selected() { // load the block into staging let block_hash = block.header.block_hash(); - eprintln!("Block hash={}, ix={}", &block_hash, ix); + eprintln!("Block hash={block_hash}, ix={ix}"); assert_eq!(&tip.winning_stacks_block_hash, &block_hash); stacks_blocks.push((tip.sortition_id.clone(), block.clone())); @@ -6356,10 +6355,12 @@ fn test_pox_no_anchor_selected() { assert_eq!(&pox_id.to_string(), "1111"); } + // Because there is a missing anchor block, the blinded coordinator will not advance on any + // fork that does not build upon one { let ic = sort_db_blind.index_handle_at_tip(); let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "1101"); + assert_eq!(&pox_id.to_string(), "11"); } for (sort_id, block) in stacks_blocks.iter() { From ea15d55984a38540fa6d90444d35522dc6f2ef96 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 10:39:54 -0700 Subject: [PATCH 16/22] Fix test_pox_no_anchor_selected Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 2d0335f193..9d4063d469 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -6355,7 +6355,7 @@ fn test_pox_no_anchor_selected() { assert_eq!(&pox_id.to_string(), "1111"); } - // Because there is a missing anchor block, the blinded coordinator will not advance on any + // Because there is a missing anchor block, the blinded coordinator will not advance on any // fork that does not build upon one { let ic = sort_db_blind.index_handle_at_tip(); From 33fbe1882b085965cf496763fa0afb4c5b86dd57 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 11:03:22 -0700 Subject: [PATCH 17/22] Fix test_simple_setup Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/tests.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 9d4063d469..b441f2214c 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -2175,7 +2175,7 @@ fn test_simple_setup() { let pox_id = ic.get_pox_id().unwrap(); assert_eq!( &pox_id.to_string(), - "110000000000", + "11", "PoX ID should reflect the initial 'known' reward cycle at genesis" ); } @@ -2203,12 +2203,8 @@ fn test_simple_setup() { pox_id_string.push('1'); } - println!("=> {}", pox_id_string); - assert_eq!( - pox_id_at_tip.to_string(), - // right-pad pox_id_string to 11 characters - format!("1{:0<11}", pox_id_string) - ); + println!("=> {pox_id_string}"); + assert_eq!(pox_id_at_tip.to_string(), format!("1{pox_id_string}")); } } From cd1b4cc150be6dbe17ef97af73188235de3f699d Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 14:03:01 -0700 Subject: [PATCH 18/22] Remove affirmation_overrides config Signed-off-by: Jacinta Ferrant --- stacks-node/src/run_loop/neon.rs | 12 +- stackslib/src/config/mod.rs | 214 +------------------------------ 2 files changed, 4 insertions(+), 222 deletions(-) diff --git a/stacks-node/src/run_loop/neon.rs b/stacks-node/src/run_loop/neon.rs index 30937bf555..11ee5bbb00 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -1028,19 +1028,9 @@ impl RunLoop { let liveness_thread = self.spawn_chain_liveness_thread(globals.clone()); // Wait for all pending sortitions to process - let mut burnchain_db = burnchain_config + let burnchain_db = burnchain_config .open_burnchain_db(true) .expect("FATAL: failed to open burnchain DB"); - if !self.config.burnchain.affirmation_overrides.is_empty() { - let tx = burnchain_db - .tx_begin() - .expect("FATAL: failed to begin burnchain DB tx"); - for (reward_cycle, affirmation) in self.config.burnchain.affirmation_overrides.iter() { - tx.set_override_affirmation_map(*reward_cycle, affirmation.clone()).unwrap_or_else(|_| panic!("FATAL: failed to set affirmation override ({affirmation}) for reward cycle {reward_cycle}")); - } - tx.commit() - .expect("FATAL: failed to commit burnchain DB tx"); - } let burnchain_db_tip = burnchain_db .get_canonical_chain_tip() .expect("FATAL: failed to query burnchain DB"); diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index ae11518792..2be8ad94d7 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -36,9 +36,8 @@ use stacks_common::util::get_epoch_time_ms; use stacks_common::util::hash::hex_bytes; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; -use crate::burnchains::affirmation::AffirmationMap; use crate::burnchains::bitcoin::BitcoinNetworkType; -use crate::burnchains::{Burnchain, MagicBytes, PoxConstants, BLOCKSTACK_MAGIC_MAINNET}; +use crate::burnchains::{Burnchain, MagicBytes, BLOCKSTACK_MAGIC_MAINNET}; use crate::chainstate::nakamoto::signer_set::NakamotoSigners; use crate::chainstate::stacks::boot::MINERS_NAME; use crate::chainstate::stacks::index::marf::MARFOpenOpts; @@ -49,8 +48,7 @@ use crate::config::chain_data::MinerStats; use crate::core::mempool::{MemPoolWalkSettings, MemPoolWalkStrategy, MemPoolWalkTxTypes}; use crate::core::{ MemPoolDB, StacksEpoch, StacksEpochExtension, StacksEpochId, - BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT, BITCOIN_TESTNET_STACKS_25_BURN_HEIGHT, - BITCOIN_TESTNET_STACKS_25_REORGED_HEIGHT, CHAIN_ID_MAINNET, CHAIN_ID_TESTNET, + CHAIN_ID_MAINNET, CHAIN_ID_TESTNET, PEER_VERSION_MAINNET, PEER_VERSION_TESTNET, STACKS_EPOCHS_REGTEST, STACKS_EPOCHS_TESTNET, }; use crate::cost_estimates::fee_medians::WeightedMedianFeeRateEstimator; @@ -229,7 +227,7 @@ impl ConfigFile { } pub fn xenon() -> ConfigFile { - let mut burnchain = BurnchainConfigFile { + let burnchain = BurnchainConfigFile { mode: Some("xenon".to_string()), rpc_port: Some(18332), peer_port: Some(18333), @@ -238,8 +236,6 @@ impl ConfigFile { ..BurnchainConfigFile::default() }; - burnchain.add_affirmation_overrides_xenon(); - let node = NodeConfigFile { bootstrap_node: Some("029266faff4c8e0ca4f934f34996a96af481df94a89b0c9bd515f3536a95682ddc@seed.testnet.hiro.so:30444".to_string()), miner: Some(false), @@ -1587,25 +1583,6 @@ pub struct BurnchainConfig { /// @default: `None` /// @deprecated: This setting is ignored in Epoch 3.0+. pub ast_precheck_size_height: Option, - /// Overrides for the burnchain block affirmation map for specific reward cycles. - /// Allows manually setting the miner affirmation ('p'resent/'n'ot-present/'a'bsent) - /// map for a given cycle, bypassing the map normally derived from sortition results. - /// - /// Special defaults are added when [`BurnchainConfig::mode`] is "xenon", but - /// config entries take precedence. At startup, these overrides are written to - /// the `BurnchainDB` (`overrides` table). - /// --- - /// @default: Empty map - /// @deprecated: This setting is ignored in Epoch 3.0+. Only used in the neon chain mode. - /// @notes: - /// - Primarily used for testing or recovering from network issues. - /// - Configured as a list `[[burnchain.affirmation_overrides]]` in TOML, each with - /// `reward_cycle` (integer) and `affirmation` (string of 'p'/'n'/'a', length `reward_cycle - 1`). - /// @toml_example: | - /// [[burnchain.affirmation_overrides]] - /// reward_cycle = 413 - /// affirmation = "pna..." # Must be 412 chars long - pub affirmation_overrides: HashMap, /// Fault injection setting for testing. Introduces an artificial delay (in /// milliseconds) before processing each burnchain block download. Simulates a /// slow burnchain connection. @@ -1672,7 +1649,6 @@ impl BurnchainConfig { sunset_end: None, wallet_name: "".to_string(), ast_precheck_size_height: None, - affirmation_overrides: HashMap::new(), fault_injection_burnchain_block_delay: 0, max_unspent_utxos: Some(1024), } @@ -1773,76 +1749,11 @@ pub struct BurnchainConfigFile { pub sunset_end: Option, pub wallet_name: Option, pub ast_precheck_size_height: Option, - pub affirmation_overrides: Option>, pub fault_injection_burnchain_block_delay: Option, pub max_unspent_utxos: Option, } impl BurnchainConfigFile { - /// Add affirmation overrides required to sync Xenon Testnet node. - /// - /// The Xenon Testnet Stacks 2.4 activation height occurred before the finalized SIP-024 updates and release of the stacks-node versioned 2.4.0.0.0. - /// This caused the Stacks Xenon testnet to undergo a deep reorg when 2.4.0.0.0 was finalized. This deep reorg meant that 3 reward cycles were - /// invalidated, which requires overrides in the affirmation map to continue correct operation. Those overrides are required for cycles 413, 414, and 415. - #[allow(clippy::indexing_slicing)] // bad affirmation map override should panic - pub fn add_affirmation_overrides_xenon(&mut self) { - let mut default_overrides = vec![ - AffirmationOverride { - reward_cycle: 413, - affirmation: "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpa".to_string() - }, - AffirmationOverride { - reward_cycle: 414, - affirmation: "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpaa".to_string() - }, - AffirmationOverride { - reward_cycle: 415, - affirmation: "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpaaa".to_string() - }]; - - // Now compute the 2.5 overrides. - let affirmations_pre_2_5 = "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpaaaapppppppnnnnnnnnnnnnnnnnnnnnnnnpnppnppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnpnpppppppppnppnnnnnnnnnnnnnnnnnnnnnnnnnppnppppppppp"; - let xenon_pox_consts = PoxConstants::testnet_default(); - let last_present_cycle = xenon_pox_consts - .block_height_to_reward_cycle( - BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT, - BITCOIN_TESTNET_STACKS_25_BURN_HEIGHT, - ) - .unwrap(); - assert_eq!( - u64::try_from(affirmations_pre_2_5.len()).unwrap(), - last_present_cycle - 1 - ); - let last_override = xenon_pox_consts - .block_height_to_reward_cycle( - BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT, - BITCOIN_TESTNET_STACKS_25_REORGED_HEIGHT, - ) - .unwrap(); - let override_values = ["a", "n"]; - - for (override_index, reward_cycle) in (last_present_cycle + 1..=last_override).enumerate() { - assert!(override_values.len() > override_index); - let overrides = override_values[..(override_index + 1)].join(""); - let affirmation = format!("{affirmations_pre_2_5}{overrides}"); - default_overrides.push(AffirmationOverride { - reward_cycle, - affirmation, - }); - } - - if let Some(affirmation_overrides) = self.affirmation_overrides.as_mut() { - for affirmation in default_overrides { - // insert at front, so that the hashmap uses the configured overrides - // instead of the defaults (the configured overrides will write over the - // the defaults because they come later in the list). - affirmation_overrides.insert(0, affirmation); - } - } else { - self.affirmation_overrides = Some(default_overrides); - }; - } - fn into_config_default( mut self, default_burnchain_config: BurnchainConfig, @@ -1851,7 +1762,6 @@ impl BurnchainConfigFile { if self.magic_bytes.is_none() { self.magic_bytes = ConfigFile::xenon().burnchain.unwrap().magic_bytes; } - self.add_affirmation_overrides_xenon(); } let mode = self.mode.unwrap_or(default_burnchain_config.mode); @@ -1870,25 +1780,6 @@ impl BurnchainConfigFile { } } - let mut affirmation_overrides = HashMap::new(); - if let Some(aos) = self.affirmation_overrides { - for ao in aos { - let Some(affirmation_map) = AffirmationMap::decode(&ao.affirmation) else { - return Err(format!( - "Invalid affirmation override for reward cycle {}: {}", - ao.reward_cycle, ao.affirmation - )); - }; - if u64::try_from(affirmation_map.len()).unwrap() != ao.reward_cycle - 1 { - return Err(format!( - "Invalid affirmation override for reward cycle {}. Map len = {}, but expected {}.", - ao.reward_cycle, affirmation_map.len(), ao.reward_cycle - 1, - )); - } - affirmation_overrides.insert(ao.reward_cycle, affirmation_map); - } - } - let mut config = BurnchainConfig { chain: self.chain.unwrap_or(default_burnchain_config.chain), chain_id: match self.chain_id { @@ -1993,7 +1884,6 @@ impl BurnchainConfigFile { pox_prepare_length: self .pox_prepare_length .or(default_burnchain_config.pox_prepare_length), - affirmation_overrides, fault_injection_burnchain_block_delay: self .fault_injection_burnchain_block_delay .unwrap_or(default_burnchain_config.fault_injection_burnchain_block_delay), @@ -4844,104 +4734,6 @@ mod tests { ); } - #[test] - fn should_load_affirmation_map() { - let affirmation_string = "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpa"; - let affirmation = - AffirmationMap::decode(affirmation_string).expect("Failed to decode affirmation map"); - let config = Config::from_config_file( - ConfigFile::from_str(&format!( - r#" - [[burnchain.affirmation_overrides]] - reward_cycle = 413 - affirmation = "{affirmation_string}" - "# - )) - .expect("Expected to be able to parse config file from string"), - false, - ) - .expect("Expected to be able to parse affirmation map from file"); - - assert_eq!(config.burnchain.affirmation_overrides.len(), 1); - assert_eq!(config.burnchain.affirmation_overrides.get(&0), None); - assert_eq!( - config.burnchain.affirmation_overrides.get(&413), - Some(&affirmation) - ); - } - - #[test] - fn should_fail_to_load_invalid_affirmation_map() { - let bad_affirmation_string = "bad_map"; - let file = ConfigFile::from_str(&format!( - r#" - [[burnchain.affirmation_overrides]] - reward_cycle = 1 - affirmation = "{bad_affirmation_string}" - "# - )) - .expect("Expected to be able to parse config file from string"); - - assert!(Config::from_config_file(file, false).is_err()); - } - - #[test] - fn should_load_empty_affirmation_map() { - let config = Config::from_config_file( - ConfigFile::from_str(r#""#) - .expect("Expected to be able to parse config file from string"), - false, - ) - .expect("Expected to be able to parse affirmation map from file"); - - assert!(config.burnchain.affirmation_overrides.is_empty()); - } - - #[test] - fn should_include_xenon_default_affirmation_overrides() { - let config = Config::from_config_file( - ConfigFile::from_str( - r#" - [burnchain] - chain = "bitcoin" - mode = "xenon" - "#, - ) - .expect("Expected to be able to parse config file from string"), - false, - ) - .expect("Expected to be able to parse affirmation map from file"); - // Should default add xenon affirmation overrides - assert_eq!(config.burnchain.affirmation_overrides.len(), 5); - } - - #[test] - fn should_override_xenon_default_affirmation_overrides() { - let affirmation_string = "aaapnnnnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnpppppnnnnnnnnnnnnnnnnnnnnnnnpppppppppppppppnnnnnnnnnnnnnnnnnnnnnnnppppppppppnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnppppppppnnnnnnnnnnnnnnnnnnnnnnnppnppnnnnnnnnnnnnnnnnnnnnnnnppppnnnnnnnnnnnnnnnnnnnnnnnnnppppppnnnnnnnnnnnnnnnnnnnnnnnnnppnnnnnnnnnnnnnnnnnnnnnnnnnpppppppnnnnnnnnnnnnnnnnnnnnnnnnnnpnnnnnnnnnnnnnnnnnnnnnnnnnpppnppppppppppppppnnppppnpa"; - let affirmation = - AffirmationMap::decode(affirmation_string).expect("Failed to decode affirmation map"); - - let config = Config::from_config_file( - ConfigFile::from_str(&format!( - r#" - [burnchain] - chain = "bitcoin" - mode = "xenon" - - [[burnchain.affirmation_overrides]] - reward_cycle = 413 - affirmation = "{affirmation_string}" - "#, - )) - .expect("Expected to be able to parse config file from string"), - false, - ) - .expect("Expected to be able to parse affirmation map from file"); - // Should default add xenon affirmation overrides, but overwrite with the configured one above - assert_eq!(config.burnchain.affirmation_overrides.len(), 5); - assert_eq!(config.burnchain.affirmation_overrides[&413], affirmation); - } - #[test] fn test_into_config_default_chain_id() { // Helper function to create BurnchainConfigFile with mode and optional chain_id From b74cf107062a6e64465cf5a4f64f89c13108c280 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 17:03:15 -0700 Subject: [PATCH 19/22] Fix test_generate_markdown_with_real_fixture_data Signed-off-by: Jacinta Ferrant --- .../tests/fixtures/minimal_config.json | 2 +- stackslib/src/config/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json b/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json index 0cce5ccbd4..7d0b8edd52 100644 --- a/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json +++ b/contrib/tools/config-docs-generator/tests/fixtures/minimal_config.json @@ -74,6 +74,6 @@ "referenced_constants": { "MinerConfig::mining_key": null, "NodeConfig::miner": null, - "NodeConfig::mine_microblocks": null, + "NodeConfig::mine_microblocks": null } } \ No newline at end of file diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 2be8ad94d7..43d3772454 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -47,9 +47,9 @@ use crate::chainstate::stacks::MAX_BLOCK_LEN; use crate::config::chain_data::MinerStats; use crate::core::mempool::{MemPoolWalkSettings, MemPoolWalkStrategy, MemPoolWalkTxTypes}; use crate::core::{ - MemPoolDB, StacksEpoch, StacksEpochExtension, StacksEpochId, - CHAIN_ID_MAINNET, CHAIN_ID_TESTNET, - PEER_VERSION_MAINNET, PEER_VERSION_TESTNET, STACKS_EPOCHS_REGTEST, STACKS_EPOCHS_TESTNET, + MemPoolDB, StacksEpoch, StacksEpochExtension, StacksEpochId, CHAIN_ID_MAINNET, + CHAIN_ID_TESTNET, PEER_VERSION_MAINNET, PEER_VERSION_TESTNET, STACKS_EPOCHS_REGTEST, + STACKS_EPOCHS_TESTNET, }; use crate::cost_estimates::fee_medians::WeightedMedianFeeRateEstimator; use crate::cost_estimates::fee_rate_fuzzer::FeeRateFuzzer; From e11434517ef18bb321b283a0b86ab03209879023 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Tue, 29 Jul 2025 17:03:34 -0700 Subject: [PATCH 20/22] Fix test_get_block_availability Signed-off-by: Jacinta Ferrant --- stackslib/src/net/tests/download/epoch2x.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stackslib/src/net/tests/download/epoch2x.rs b/stackslib/src/net/tests/download/epoch2x.rs index 3d13ae5b06..b1462a1ec9 100644 --- a/stackslib/src/net/tests/download/epoch2x.rs +++ b/stackslib/src/net/tests/download/epoch2x.rs @@ -101,8 +101,10 @@ fn test_get_block_availability() { TestPeer::set_ops_burn_header_hash(&mut burn_ops, &burn_header_hash); - peer_1.next_burnchain_block_raw(burn_ops); - + // We do not have the anchor block for peer 1, therefore it cannot advance its tip. + if i < 6 { + peer_1.next_burnchain_block_raw(burn_ops); + } let sn = SortitionDB::get_canonical_burn_chain_tip(peer_2.sortdb.as_ref().unwrap().conn()) .unwrap(); From 53bf9ea46fe25f56111d4201bce85ef69b6cfeb9 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 7 Aug 2025 10:26:46 -0700 Subject: [PATCH 21/22] Remove TODO comment Signed-off-by: Jacinta Ferrant --- stackslib/src/chainstate/coordinator/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index 1470035cbf..885a23f4af 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -763,7 +763,6 @@ pub fn get_reward_cycle_info( let reward_cycle_info = { let ic = sort_db.index_handle(sortition_tip); - // TODO: always use block-commit-based PoX anchor block selection rules? let burnchain_db_conn_opt = if epoch_at_height.epoch_id >= StacksEpochId::Epoch21 { // use the new block-commit-based PoX anchor block selection rules Some(burnchain_db.conn()) From 9cad3868a67e6371ad27f413a719bc941783cd28 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 7 Aug 2025 10:31:51 -0700 Subject: [PATCH 22/22] Make test_nakamoto_inv_sync_across_epoch_change panic instead of timing out CI Signed-off-by: Jacinta Ferrant --- stackslib/src/net/tests/inv/nakamoto.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stackslib/src/net/tests/inv/nakamoto.rs b/stackslib/src/net/tests/inv/nakamoto.rs index df85ea84db..a510c992c8 100644 --- a/stackslib/src/net/tests/inv/nakamoto.rs +++ b/stackslib/src/net/tests/inv/nakamoto.rs @@ -1008,6 +1008,8 @@ fn test_nakamoto_inv_sync_across_epoch_change() { .block_height_to_reward_cycle(tip.block_height) .unwrap(); + let timeout = std::time::Duration::from_secs(30); + let start = std::time::Instant::now(); // run peer and other_peer until they connect loop { let _ = peer.step_with_ibd(false); @@ -1019,6 +1021,10 @@ fn test_nakamoto_inv_sync_across_epoch_change() { if event_ids.count() > 0 && other_event_ids.count() > 0 { break; } + assert!( + start.elapsed() < timeout, + "Timed out waiting for peer's to connect" + ); } debug!("Peers are connected"); @@ -1036,6 +1042,7 @@ fn test_nakamoto_inv_sync_across_epoch_change() { let burn_tip_start = peer.network.get_current_epoch().start_height; + let start = std::time::Instant::now(); while inv_1_count < num_epoch2_blocks || inv_2_count < num_epoch2_blocks || highest_rc_1 < total_rcs @@ -1098,6 +1105,11 @@ fn test_nakamoto_inv_sync_across_epoch_change() { info!( "Nakamoto state machine: Peer 1: {highest_rc_1}, Peer 2: {highest_rc_2} (total {total_rcs})" ); + + assert!( + start.elapsed() < timeout, + "Timed out waiting for inv sync across epoch. Ran {round} rounds." + ); } }