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..7d0b8edd52 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, @@ -74,7 +74,6 @@ "referenced_constants": { "MinerConfig::mining_key": null, "NodeConfig::miner": null, - "NodeConfig::mine_microblocks": null, - "NodeConfig::require_affirmed_anchor_blocks": null + "NodeConfig::mine_microblocks": null } } \ No newline at end of file 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 9fddda7d7e..8d09a3abc8 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/node.rs b/stacks-node/src/node.rs index 3ef15d45ab..cdef2a85ff 100644 --- a/stacks-node/src/node.rs +++ b/stacks-node/src/node.rs @@ -840,22 +840,13 @@ impl Node { parent_consensus_hash }; - let burnchain = self.config.get_burnchain(); - let burnchain_db = - BurnchainDB::connect(&burnchain.get_burnchaindb_path(), &burnchain, true) - .expect("FATAL: failed to connect to burnchain DB"); - let atlas_config = Self::make_atlas_config(); let mut processed_blocks = vec![]; loop { let mut process_blocks_at_tip = { let tx = db.tx_begin_at_tip(); - self.chain_state.process_blocks( - burnchain_db.conn(), - tx, - 1, - Some(&self.event_dispatcher), - ) + self.chain_state + .process_blocks(tx, 1, Some(&self.event_dispatcher)) }; match process_blocks_at_tip { Err(e) => panic!("Error while processing block - {e:?}"), diff --git a/stacks-node/src/run_loop/nakamoto.rs b/stacks-node/src/run_loop/nakamoto.rs index 3029d10754..362d314d24 100644 --- a/stacks-node/src/run_loop/nakamoto.rs +++ b/stacks-node/src/run_loop/nakamoto.rs @@ -323,11 +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, - 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 bdb370527d..2b2ec97857 100644 --- a/stacks-node/src/run_loop/neon.rs +++ b/stacks-node/src/run_loop/neon.rs @@ -3,8 +3,8 @@ use std::sync::atomic::AtomicU64; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::sync_channel; use std::sync::{Arc, Mutex}; +use std::thread; use std::thread::JoinHandle; -use std::{cmp, thread}; use libc; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; @@ -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}; @@ -28,7 +27,6 @@ use stacks_common::deps_common::ctrlc as termination; use stacks_common::deps_common::ctrlc::SignalId; use stacks_common::types::PublicKey; use stacks_common::util::hash::Hash160; -use stacks_common::util::{get_epoch_time_secs, sleep_ms}; use stx_genesis::GenesisData; use super::RunLoopCallbacks; @@ -66,12 +64,6 @@ pub struct RunLoopCounter(pub Arc); #[derive(Clone)] pub struct RunLoopCounter(); -#[cfg(test)] -const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 30; - -#[cfg(not(test))] -const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 300; - impl Default for RunLoopCounter { #[cfg(test)] fn default() -> Self { @@ -702,11 +694,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, - 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( @@ -787,328 +774,6 @@ impl RunLoop { ) } - /// Wake up and drive stacks block processing if there's been a PoX reorg. - /// Be careful not to saturate calls to announce new stacks blocks, because that will disable - /// mining (which would prevent a miner attempting to fix a hidden PoX anchor block from making - /// progress). - 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(); - let delay = cmp::max( - config.node.chain_liveness_poll_time_secs, - cmp::max( - miner_config.first_attempt_time_ms, - miner_config.subsequent_attempt_time_ms, - ) / 1000, - ); - - if *last_stacks_pox_reorg_recover_time + (delay as u128) >= get_epoch_time_secs().into() { - // too soon - 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(); - } - - *last_stacks_pox_reorg_recover_time = get_epoch_time_secs().into(); - } - - /// Wake up and drive sortition processing if there's been a PoX reorg. - /// Be careful not to saturate calls to announce new burn blocks, because that will disable - /// mining (which would prevent a miner attempting to fix a hidden PoX anchor block from making - /// progress). - /// - /// only call if no in ibd - fn drive_pox_reorg_burn_block_processing( - globals: &Globals, - config: &Config, - burnchain: &Burnchain, - sortdb: &SortitionDB, - chain_state_db: &StacksChainState, - last_burn_pox_reorg_recover_time: &mut u128, - last_announce_time: &mut u128, - ) { - let miner_config = config.get_miner_config(); - let delay = cmp::max( - config.node.chain_liveness_poll_time_secs, - cmp::max( - miner_config.first_attempt_time_ms, - miner_config.subsequent_attempt_time_ms, - ) / 1000, - ); - - if *last_burn_pox_reorg_recover_time + (delay as u128) >= get_epoch_time_secs().into() { - // too soon - return; - } - - // compare sortition and heaviest AMs - let burnchain_db = burnchain - .open_burnchain_db(false) - .expect("FATAL: failed to open burnchain DB"); - - 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"); - - if canonical_burnchain_tip.block_height > highest_sn.block_height { - // still processing sortitions - test_debug!( - "Drive burn block processing: still processing sortitions ({} > {})", - canonical_burnchain_tip.block_height, - highest_sn.block_height - ); - 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}, {} JoinHandle<()> { - let config = self.config.clone(); - let burnchain = self.get_burnchain(); - let sortdb = burnchain - .open_sortition_db(true) - .expect("FATAL: could not open sortition DB"); - - let (chain_state_db, _) = StacksChainState::open( - config.is_mainnet(), - config.burnchain.chain_id, - &config.get_chainstate_path_str(), - Some(config.node.get_marf_opts()), - ) - .unwrap(); - - thread::Builder::new() - .name(format!("chain-liveness-{}", config.node.rpc_bind)) - .stack_size(BLOCK_PROCESSOR_STACK_SIZE) - .spawn(move || { - Self::drive_chain_liveness(globals, config, burnchain, sortdb, chain_state_db) - }) - .expect("FATAL: failed to spawn chain liveness thread") - } - /// Starts the node runloop. /// /// This function will block by looping infinitely. @@ -1206,22 +871,11 @@ impl RunLoop { // Boot up the p2p network and relayer, and figure out how many sortitions we have so far // (it could be non-zero if the node is resuming from chainstate) let mut node = StacksNode::spawn(self, globals.clone(), relay_recv); - 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"); @@ -1252,7 +906,6 @@ impl RunLoop { globals.coord().stop_chains_coordinator(); coordinator_thread_handle.join().unwrap(); let peer_network = node.join(); - liveness_thread.join().unwrap(); // Data that will be passed to Nakamoto run loop // Only gets transfered on clean shutdown of neon run loop @@ -1376,7 +1029,6 @@ impl RunLoop { globals.coord().stop_chains_coordinator(); coordinator_thread_handle.join().unwrap(); let peer_network = node.join(); - liveness_thread.join().unwrap(); // Data that will be passed to Nakamoto run loop // Only gets transfered on clean shutdown of neon run loop @@ -1449,7 +1101,6 @@ impl RunLoop { globals.coord().stop_chains_coordinator(); coordinator_thread_handle.join().unwrap(); let peer_network = node.join(); - liveness_thread.join().unwrap(); // Data that will be passed to Nakamoto run loop // Only gets transfered on clean shutdown of neon run loop diff --git a/stacks-node/src/tests/epoch_21.rs b/stacks-node/src/tests/epoch_21.rs index b742234001..957b73ee5d 100644 --- a/stacks-node/src/tests/epoch_21.rs +++ b/stacks-node/src/tests/epoch_21.rs @@ -2004,8 +2004,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; @@ -2064,8 +2062,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; @@ -2524,8 +2520,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; @@ -2584,8 +2578,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; @@ -2928,8 +2920,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; @@ -2988,8 +2978,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; @@ -3347,8 +3335,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; @@ -3407,8 +3393,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; @@ -3757,8 +3741,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; @@ -3817,8 +3799,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; @@ -4134,9 +4114,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 let mut epochs = EpochList::new(&*core::STACKS_EPOCHS_REGTEST); epochs[StacksEpochId::Epoch20].end_height = 101; @@ -4195,10 +4172,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; // multiple nodes so they must download from each other conf.miner.wait_for_block_download = true; diff --git a/stacks-node/src/tests/epoch_22.rs b/stacks-node/src/tests/epoch_22.rs index a306b430f4..8ac2407d87 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/stacks-node/src/tests/neon_integrations.rs b/stacks-node/src/tests/neon_integrations.rs index 1b5feab35b..ae36d34091 100644 --- a/stacks-node/src/tests/neon_integrations.rs +++ b/stacks-node/src/tests/neon_integrations.rs @@ -5180,9 +5180,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); @@ -5686,8 +5683,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!( @@ -5712,8 +5707,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::from_stx_config(&conf_bootstrap_node); btcd_controller @@ -6220,8 +6213,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!( @@ -6256,8 +6247,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::from_stx_config(&conf_bootstrap_node); btcd_controller @@ -6494,8 +6483,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(); @@ -7954,8 +7941,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/stacks-signer/src/client/mod.rs b/stacks-signer/src/client/mod.rs index f750dcad25..a5319bf6df 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/burnchains/affirmation.rs b/stackslib/src/burnchains/affirmation.rs index a890270668..79d986fda1 100644 --- a/stackslib/src/burnchains/affirmation.rs +++ b/stackslib/src/burnchains/affirmation.rs @@ -1158,92 +1158,3 @@ pub fn find_pox_anchor_block( .map(|(anchor_block_commit, descendancy, ..)| (anchor_block_commit, descendancy)), )) } - -/// Update a completed reward cycle's affirmation maps -pub fn update_pox_affirmation_maps( - burnchain_db: &mut BurnchainDB, - indexer: &B, - reward_cycle: u64, - burnchain: &Burnchain, -) -> Result<(), Error> { - debug!("Process PoX affirmations for reward cycle {}", reward_cycle); - - let tx = burnchain_db.tx_begin()?; - - let (prepare_ops, pox_anchor_block_info_opt) = - find_pox_anchor_block(&tx, reward_cycle, indexer, burnchain)?; - - if let Some((anchor_block, descendancy)) = pox_anchor_block_info_opt { - debug!( - "PoX anchor block elected in reward cycle {} for reward cycle {} is {}", - reward_cycle, - reward_cycle + 1, - &anchor_block.block_header_hash - ); - - // anchor block found for this upcoming reward cycle - tx.set_anchor_block(&anchor_block, reward_cycle + 1)?; - assert_eq!(descendancy.len(), prepare_ops.len()); - - // mark the prepare-phase commits that elected this next reward cycle's anchor block as - // having descended or not descended from this anchor block. - for (block_idx, block_ops) in prepare_ops.iter().enumerate() { - let descendancy = descendancy - .get(block_idx) - .ok_or_else(|| Error::ProcessorError)?; - assert_eq!(block_ops.len(), descendancy.len()); - - for (tx_idx, tx_op) in block_ops.iter().enumerate() { - test_debug!( - "Make affirmation map for block-commit at {},{}", - tx_op.block_height, - tx_op.vtxindex - ); - let is_tx_descendant = descendancy - .get(tx_idx) - .ok_or_else(|| Error::ProcessorError)?; - tx.make_prepare_phase_affirmation_map( - indexer, - burnchain, - reward_cycle + 1, - tx_op, - Some(&anchor_block), - *is_tx_descendant, - )?; - } - } - } else { - debug!("PoX anchor block selected in reward cycle {} is None. Reward cycle {} has no anchor block", reward_cycle, reward_cycle + 1); - - // anchor block not found for this upcoming reward cycle - tx.clear_anchor_block(reward_cycle + 1)?; - - // mark all prepare-phase commits as NOT having descended from the next reward cycle's anchor - // block as NOT having descended from any anchor block (since one was not chosen) - for block_ops in prepare_ops.iter() { - for tx_op in block_ops.iter() { - test_debug!( - "Make affirmation map for block-commit at {},{}", - tx_op.block_height, - tx_op.vtxindex - ); - tx.make_prepare_phase_affirmation_map( - indexer, - burnchain, - reward_cycle + 1, - tx_op, - None, - false, - )?; - } - } - } - - tx.commit()?; - debug!( - "Processed PoX affirmations for reward cycle {}", - reward_cycle - ); - - Ok(()) -} diff --git a/stackslib/src/burnchains/burnchain.rs b/stackslib/src/burnchains/burnchain.rs index 24af4362c0..9d0d8953be 100644 --- a/stackslib/src/burnchains/burnchain.rs +++ b/stackslib/src/burnchains/burnchain.rs @@ -33,7 +33,6 @@ use stacks_common::util::vrf::VRFPublicKey; use stacks_common::util::{get_epoch_time_ms, sleep_ms}; use super::EpochList; -use crate::burnchains::affirmation::update_pox_affirmation_maps; use crate::burnchains::bitcoin::BitcoinTxOutput; use crate::burnchains::db::{BurnchainDB, BurnchainHeaderReader}; use crate::burnchains::indexer::{ @@ -1067,48 +1066,11 @@ impl Burnchain { let _blockstack_txs = burnchain_db.store_new_burnchain_block(burnchain, indexer, block, epoch_id)?; - Burnchain::process_affirmation_maps( - burnchain, - burnchain_db, - indexer, - block.block_height(), - )?; let header = block.header(); Ok(header) } - /// Update the affirmation maps for the previous reward cycle's commits. - /// This is a no-op unless the given burnchain block height falls on a reward cycle boundary. In that - /// case, the previous reward cycle's block commits' affirmation maps are all re-calculated. - pub fn process_affirmation_maps( - burnchain: &Burnchain, - burnchain_db: &mut BurnchainDB, - indexer: &B, - block_height: u64, - ) -> Result<(), burnchain_error> { - let this_reward_cycle = burnchain - .block_height_to_reward_cycle(block_height) - .unwrap_or(0); - - let prev_reward_cycle = burnchain - .block_height_to_reward_cycle(block_height.saturating_sub(1)) - .unwrap_or(0); - - if this_reward_cycle != prev_reward_cycle { - // at reward cycle boundary - info!( - "Update PoX affirmation maps for reward cycle"; - "prev_reward_cycle" => %prev_reward_cycle, - "this_reward_cycle" => %this_reward_cycle, - "block_height" => %block_height, - "cycle_length" => %burnchain.pox_constants.reward_cycle_length, - ); - update_pox_affirmation_maps(burnchain_db, indexer, prev_reward_cycle, burnchain)?; - } - Ok(()) - } - /// Hand off the block to the ChainsCoordinator _and_ process the sortition /// *only* to be used by legacy stacks node interfaces, like the Helium node. /// diff --git a/stackslib/src/burnchains/db.rs b/stackslib/src/burnchains/db.rs index bc960963bc..015e9f1f69 100644 --- a/stackslib/src/burnchains/db.rs +++ b/stackslib/src/burnchains/db.rs @@ -339,50 +339,6 @@ impl BurnchainDBTransaction<'_> { Ok(()) } - /// Add an affirmation map into the database. Returns the affirmation map ID. - pub fn insert_block_commit_affirmation_map( - &self, - affirmation_map: &AffirmationMap, - ) -> Result { - let weight = affirmation_map.weight(); - let sql = "INSERT INTO affirmation_maps (affirmation_map,weight) VALUES (?1,?2)"; - let args = params![affirmation_map.encode(), u64_to_sql(weight)?]; - match self.sql_tx.execute(sql, args) { - Ok(_) => { - let am_id = BurnchainDB::get_affirmation_map_id(&self.sql_tx, affirmation_map)? - .expect("BUG: no affirmation ID for affirmation map we just inserted"); - Ok(am_id) - } - Err(e) => Err(DBError::SqliteError(e)), - } - } - - /// Update a block-commit's affirmation state -- namely, record the reward cycle that this - /// block-commit affirms, if any (anchor_block_descendant), and record the affirmation map ID - /// for this block-commit (affirmation_id). - pub fn update_block_commit_affirmation( - &self, - block_commit: &LeaderBlockCommitOp, - anchor_block_descendant: Option, - affirmation_id: u64, - ) -> Result<(), DBError> { - let sql = "UPDATE block_commit_metadata SET affirmation_id = ?1, anchor_block_descendant = ?2 WHERE burn_block_hash = ?3 AND txid = ?4"; - let args = params![ - u64_to_sql(affirmation_id)?, - opt_u64_to_sql(anchor_block_descendant)?, - block_commit.burn_header_hash, - block_commit.txid, - ]; - match self.sql_tx.execute(sql, args) { - Ok(_) => { - test_debug!("Set affirmation map ID of {} - {},{},{} (parent {},{}) to {} (anchor block descendant? {:?})", - &block_commit.burn_header_hash, &block_commit.txid, block_commit.block_height, block_commit.vtxindex, block_commit.parent_block_ptr, block_commit.parent_vtxindex, affirmation_id, &anchor_block_descendant); - Ok(()) - } - Err(e) => Err(DBError::SqliteError(e)), - } - } - /// Mark a block-commit as being the anchor block commit for a particular reward cycle. pub fn set_anchor_block( &self, @@ -427,451 +383,6 @@ impl BurnchainDBTransaction<'_> { .map_err(DBError::SqliteError) } - /// Calculate a burnchain block's block-commits' descendancy information. - /// Only fails on DB errors. - pub fn update_block_descendancy( - &self, - indexer: &B, - hdr: &BurnchainBlockHeader, - burnchain: &Burnchain, - ) -> Result<(), BurnchainError> { - // find all block-commits for this block - let commits: Vec = { - let block_ops_qry = - "SELECT DISTINCT * FROM burnchain_db_block_ops WHERE block_hash = ?"; - let block_ops = query_rows(&self.sql_tx, block_ops_qry, &[&hdr.block_hash])?; - block_ops - .into_iter() - .filter_map(|op| { - if let BlockstackOperationType::LeaderBlockCommit(opdata) = op { - Some(opdata) - } else { - None - } - }) - .collect() - }; - if commits.is_empty() { - test_debug!("No block-commits for block {}", hdr.block_height); - return Ok(()); - } - - // for each commit[i], find its parent commit - let mut parent_commits = vec![]; - for commit in commits.iter() { - let parent_commit_opt = if commit.parent_block_ptr != 0 || commit.parent_vtxindex != 0 { - // parent is not genesis - BurnchainDB::get_commit_at( - &self.sql_tx, - indexer, - commit.parent_block_ptr, - commit.parent_vtxindex, - )? - } else { - // parent is genesis - test_debug!( - "Parent block-commit of {},{},{} is the genesis commit", - &commit.txid, - commit.block_height, - commit.vtxindex - ); - None - }; - - parent_commits.push(parent_commit_opt); - } - assert_eq!(parent_commits.len(), commits.len()); - - // for each parent block-commit and block-commit, calculate the block-commit's new - // affirmation map - for (parent_commit_opt, commit) in parent_commits.iter().zip(commits.iter()) { - if let Some(parent_commit) = parent_commit_opt.as_ref() { - if get_parent_child_reward_cycles(parent_commit, commit, burnchain).is_some() { - // we have enough info to calculate this commit's affirmation - self.make_reward_phase_affirmation_map(burnchain, commit, parent_commit)?; - } else { - // parent is invalid - test_debug!( - "No block-commit parent reward cycle found for {},{},{}", - &commit.txid, - commit.block_height, - commit.vtxindex - ); - self.update_block_commit_affirmation(commit, None, 0)?; - } - } else { - if commit.parent_block_ptr == 0 && commit.parent_vtxindex == 0 { - test_debug!( - "Block-commit parent of {},{},{} is genesis", - &commit.txid, - commit.block_height, - commit.vtxindex - ); - } else { - // this is an invalid commit -- no parent found - test_debug!( - "No block-commit parent {},{} found for {},{},{} (in {})", - commit.parent_block_ptr, - commit.parent_vtxindex, - &commit.txid, - commit.block_height, - commit.vtxindex, - &commit.burn_header_hash - ); - } - self.update_block_commit_affirmation(commit, None, 0)?; - } - } - - Ok(()) - } - - /// Create a prepare-phase affirmation map. This is only done at the very end of a reward - /// cycle, once the anchor block is chosen and a new reward cycle is about to begin. This - /// method updates the prepare-phase block-commit's affirmation map to reflect what its miner - /// believes to be the state of all anchor blocks, _including_ this new reward cycle's anchor - /// block. - /// Returns the ID of the affirmation map in the database on success. - /// This can be used to later look up the affirmation map. - pub fn make_prepare_phase_affirmation_map( - &self, - indexer: &B, - burnchain: &Burnchain, - reward_cycle: u64, - block_commit: &LeaderBlockCommitOp, - anchor_block: Option<&LeaderBlockCommitOp>, - descends_from_anchor_block: bool, - ) -> Result { - test_debug!( - "Make affirmation map for {},{},{} (parent {},{}) in reward cycle {}", - &block_commit.txid, - block_commit.block_height, - block_commit.vtxindex, - block_commit.parent_block_ptr, - block_commit.parent_vtxindex, - reward_cycle - ); - - let parent = match BurnchainDB::get_commit_at( - &self.sql_tx, - indexer, - block_commit.parent_block_ptr, - block_commit.parent_vtxindex, - )? { - Some(p) => p, - None => { - if block_commit.parent_block_ptr == 0 && block_commit.vtxindex == 0 { - debug!( - "Prepare-phase commit {},{},{} builds off of genesis", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex - ); - } else { - debug!( - "Prepare-phase commit {},{},{} has no parent, so must be invalid", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex - ); - } - return Ok(0); - } - }; - - let parent_metadata = - BurnchainDB::get_commit_metadata(&self.sql_tx, &parent.burn_header_hash, &parent.txid)? - .unwrap_or_else(|| { - panic!( - "BUG: no metadata found for parent block-commit {},{},{} in {}", - parent.block_height, - parent.vtxindex, - &parent.txid, - &parent.burn_header_hash - ) - }); - - let (am, affirmed_reward_cycle) = if anchor_block.is_some() && descends_from_anchor_block { - // this block-commit assumes the affirmation map of the anchor block as a prefix of its - // own affirmation map. - let anchor_block = anchor_block.unwrap(); - let anchor_am_id = - BurnchainDB::get_block_commit_affirmation_id(&self.sql_tx, anchor_block)? - .expect("BUG: anchor block has no affirmation map"); - - let mut am = BurnchainDB::get_affirmation_map(&self.sql_tx, anchor_am_id)? - .ok_or(BurnchainError::DBError(DBError::NotFoundError))?; - - test_debug!("Prepare-phase commit {},{},{} descends from anchor block {},{},{} for reward cycle {}", - &block_commit.block_header_hash, block_commit.block_height, block_commit.vtxindex, &anchor_block.block_header_hash, anchor_block.block_height, anchor_block.vtxindex, reward_cycle); - - let num_affirmed = am.len() as u64; - for rc in (num_affirmed + 1)..reward_cycle { - // it's possible that this anchor block is more than one reward cycle back; if so, - // then back-fill all of the affirmations made between then and now. - if BurnchainDB::has_anchor_block(&self.sql_tx, rc)? { - test_debug!( - "Commit {},{},{} skips reward cycle {} with anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::PoxAnchorBlockAbsent); - } else { - // affirmation weight increases even if there's no decision made, because - // the lack of a decision is still an affirmation of all prior decisions - test_debug!( - "Commit {},{},{} skips reward cycle {} without anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::Nothing); - } - } - - am.push(AffirmationMapEntry::PoxAnchorBlockPresent); - (am, Some(reward_cycle)) - } else { - // this block-commit assumes the affirmation map of its parent as a prefix of its own - // affirmation map. - let (parent_reward_cycle, _) = - get_parent_child_reward_cycles(&parent, block_commit, burnchain) - .ok_or(BurnchainError::DBError(DBError::NotFoundError))?; - - // load up the affirmation map for the last anchor block the parent affirmed - let (mut am, parent_rc_opt) = match parent_metadata.anchor_block_descendant { - Some(parent_ab_rc) => { - // parent affirmed some past anchor block - let ab_metadata = BurnchainDB::get_canonical_anchor_block_commit_metadata(&self.sql_tx, indexer, parent_ab_rc)? - .unwrap_or_else(|| panic!("BUG: parent descends from a reward cycle with an anchor block ({}), but no anchor block found", parent_ab_rc)); - - let mut am = - BurnchainDB::get_affirmation_map(&self.sql_tx, ab_metadata.affirmation_id)? - .expect("BUG: no affirmation map for parent commit's anchor block"); - - test_debug!("Prepare-phase commit {},{},{} does nothing for reward cycle {}, but it builds on its parent which affirms anchor block for reward cycle {} ({}) (affirms? {})", - &block_commit.block_header_hash, block_commit.block_height, block_commit.vtxindex, reward_cycle, parent_ab_rc, &am, (am.len() as u64) < parent_ab_rc); - - if (am.len() as u64) < parent_ab_rc { - // child is affirming the parent - am.push(AffirmationMapEntry::PoxAnchorBlockPresent); - } - - (am, Some(parent_ab_rc)) - } - None => { - let mut parent_am = BurnchainDB::get_affirmation_map( - &self.sql_tx, - parent_metadata.affirmation_id, - )? - .expect("BUG: no affirmation map for parent commit"); - - // parent affirms no anchor blocks - test_debug!("Prepare-phase commit {},{},{} does nothing for reward cycle {}, and it builds on a parent {},{} {} which affirms no anchor block (affirms? {})", - &block_commit.block_header_hash, block_commit.block_height, block_commit.vtxindex, reward_cycle, block_commit.parent_block_ptr, block_commit.parent_vtxindex, &parent_am, (parent_am.len() as u64) < parent_reward_cycle); - - if (parent_am.len() as u64) < parent_reward_cycle { - // child is affirming the parent - parent_am.push(AffirmationMapEntry::Nothing); - } - - (parent_am, None) - } - }; - - let num_affirmed = am.len() as u64; - for rc in (num_affirmed + 1)..(reward_cycle + 1) { - if BurnchainDB::has_anchor_block(&self.sql_tx, rc)? { - test_debug!( - "Commit {},{},{} skips reward cycle {} with anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::PoxAnchorBlockAbsent); - } else { - // affirmation weight increases even if there's no decision made, because - // the lack of a decision is still an affirmation of all prior decisions - test_debug!( - "Commit {},{},{} skips reward cycle {} without anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::Nothing); - } - } - - debug!( - "Prepare-phase commit {},{} affirms parent {},{} with {} descended from {:?}", - block_commit.block_height, - block_commit.vtxindex, - parent.block_height, - parent.vtxindex, - &am, - &parent_metadata.anchor_block_descendant - ); - - (am, parent_rc_opt) - }; - - if let Some(am_id) = BurnchainDB::get_affirmation_map_id(&self.sql_tx, &am)? { - // child doesn't represent any new affirmations by the network, since its - // affirmation map already exists. - if cfg!(test) { - let _am_weight = BurnchainDB::get_affirmation_weight(&self.sql_tx, am_id)? - .unwrap_or_else(|| panic!("BUG: no affirmation map {}", &am_id)); - - test_debug!("Affirmation map of prepare-phase block-commit {},{},{} (parent {},{}) is old: {:?} weight {} affirmed {:?}", - &block_commit.txid, block_commit.block_height, block_commit.vtxindex, block_commit.parent_block_ptr, block_commit.parent_vtxindex, &am, _am_weight, &affirmed_reward_cycle); - } - - self.update_block_commit_affirmation(block_commit, affirmed_reward_cycle, am_id)?; - Ok(am_id) - } else { - test_debug!("Affirmation map of prepare-phase block-commit {},{},{} (parent {},{}) is new: {:?} weight {} affirmed {:?}", - &block_commit.txid, block_commit.block_height, block_commit.vtxindex, block_commit.parent_block_ptr, block_commit.parent_vtxindex, &am, am.weight(), &affirmed_reward_cycle); - - let am_id = self.insert_block_commit_affirmation_map(&am)?; - self.update_block_commit_affirmation(block_commit, affirmed_reward_cycle, am_id)?; - Ok(am_id) - } - } - - /// Make an affirmation map for a block commit in a reward phase (or an in-progress prepare - /// phase). This is done once per Bitcoin block, as block-commits are stored. Affirmation - /// maps for prepare-phase commits will be recomputed once the reward cycle finishes. - fn make_reward_phase_affirmation_map( - &self, - burnchain: &Burnchain, - block_commit: &LeaderBlockCommitOp, - parent: &LeaderBlockCommitOp, - ) -> Result { - assert_eq!(block_commit.parent_block_ptr as u64, parent.block_height); - assert_eq!(block_commit.parent_vtxindex as u32, parent.vtxindex); - - let parent_metadata = - BurnchainDB::get_commit_metadata(&self.sql_tx, &parent.burn_header_hash, &parent.txid)? - .unwrap_or_else(|| { - panic!( - "BUG: no metadata found for existing block commit {},{},{} in {}", - parent.block_height, - parent.vtxindex, - &parent.txid, - &parent.burn_header_hash - ) - }); - - test_debug!( - "Reward-phase commit {},{},{} has parent {},{}, anchor block {:?}", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - parent.block_height, - parent.vtxindex, - &parent_metadata.anchor_block_descendant - ); - - let child_reward_cycle = burnchain - .block_height_to_reward_cycle(block_commit.block_height) - .expect("BUG: block commit exists before first block height"); - - let (am, affirmed_anchor_block_reward_cycle) = - if let Some(parent_ab_rc) = parent_metadata.anchor_block_descendant { - let am_id = parent_metadata.affirmation_id; - let mut am = BurnchainDB::get_affirmation_map(&self.sql_tx, am_id)? - .expect("BUG: no affirmation map for parent commit"); - - test_debug!("Affirmation map of parent is {}", &am); - - let start_rc = am.len() as u64; - for rc in (start_rc + 1)..(child_reward_cycle + 1) { - if BurnchainDB::has_anchor_block(&self.sql_tx, rc)? { - test_debug!( - "Commit {},{},{} skips reward cycle {} with anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::PoxAnchorBlockAbsent); - } else { - test_debug!( - "Commit {},{},{} skips reward cycle {} without anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::Nothing); - } - } - - (am, Some(parent_ab_rc)) - } else { - let mut am = AffirmationMap::empty(); - for rc in 1..(child_reward_cycle + 1) { - if BurnchainDB::has_anchor_block(&self.sql_tx, rc)? { - test_debug!( - "Commit {},{},{} skips reward cycle {} with anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::PoxAnchorBlockAbsent); - } else { - test_debug!( - "Commit {},{},{} skips reward cycle {} without anchor block", - &block_commit.block_header_hash, - block_commit.block_height, - block_commit.vtxindex, - rc - ); - am.push(AffirmationMapEntry::Nothing); - } - } - (am, None) - }; - - if let Some(am_id) = BurnchainDB::get_affirmation_map_id(&self.sql_tx, &am)? { - // child doesn't represent any new affirmations by the network, since its - // affirmation map already exists. - if cfg!(test) { - let _am_weight = BurnchainDB::get_affirmation_weight(&self.sql_tx, am_id)? - .unwrap_or_else(|| panic!("BUG: no affirmation map {}", &am_id)); - - test_debug!("Affirmation map of reward-phase block-commit {},{},{} (parent {},{}) is old: {:?} weight {}", - &block_commit.txid, block_commit.block_height, block_commit.vtxindex, block_commit.parent_block_ptr, block_commit.parent_vtxindex, &am, _am_weight); - } - - self.update_block_commit_affirmation( - block_commit, - affirmed_anchor_block_reward_cycle, - am_id, - )?; - Ok(am_id) - } else { - test_debug!("Affirmation map of reward-phase block-commit {},{},{} (parent {},{}) is new: {:?} weight {}", - &block_commit.txid, block_commit.block_height, block_commit.vtxindex, block_commit.parent_block_ptr, block_commit.parent_vtxindex, &am, am.weight()); - - let am_id = self.insert_block_commit_affirmation_map(&am)?; - - self.update_block_commit_affirmation( - block_commit, - affirmed_anchor_block_reward_cycle, - am_id, - )?; - - Ok(am_id) - } - } - fn insert_block_commit_metadata(&self, bcm: BlockCommitMetadata) -> Result<(), BurnchainError> { let commit_metadata_sql = "INSERT OR REPLACE INTO block_commit_metadata (burn_block_hash, txid, block_height, vtxindex, anchor_block, anchor_block_descendant, affirmation_id) @@ -890,10 +401,8 @@ impl BurnchainDBTransaction<'_> { Ok(()) } - pub(crate) fn store_blockstack_ops( + pub(crate) fn store_blockstack_ops( &self, - burnchain: &Burnchain, - indexer: &B, block_header: &BurnchainBlockHeader, block_ops: &[BlockstackOperationType], ) -> Result<(), BurnchainError> { @@ -930,7 +439,6 @@ impl BurnchainDBTransaction<'_> { } } - self.update_block_descendancy(indexer, block_header, burnchain)?; Ok(()) } @@ -1400,10 +908,8 @@ impl BurnchainDB { // do NOT call directly; only call directly in tests. // This is only `pub` because the tests for it live in a different file. - pub fn store_new_burnchain_block_ops_unchecked( + pub fn store_new_burnchain_block_ops_unchecked( &mut self, - burnchain: &Burnchain, - indexer: &B, block_header: &BurnchainBlockHeader, blockstack_ops: &[BlockstackOperationType], ) -> Result<(), BurnchainError> { @@ -1416,7 +922,7 @@ impl BurnchainDB { blockstack_ops.len() ); db_tx.store_burnchain_db_entry(block_header)?; - db_tx.store_blockstack_ops(burnchain, indexer, block_header, blockstack_ops)?; + db_tx.store_blockstack_ops(block_header, blockstack_ops)?; db_tx.commit()?; Ok(()) @@ -1440,7 +946,7 @@ impl BurnchainDB { self.get_blockstack_transactions(burnchain, indexer, block, &header, epoch_id); apply_blockstack_txs_safety_checks(header.block_height, &mut blockstack_ops); - self.store_new_burnchain_block_ops_unchecked(burnchain, indexer, &header, &blockstack_ops)?; + self.store_new_burnchain_block_ops_unchecked(&header, &blockstack_ops)?; Ok(blockstack_ops) } diff --git a/stackslib/src/burnchains/tests/affirmation.rs b/stackslib/src/burnchains/tests/affirmation.rs index 7ccb11a501..393bf2e2e9 100644 --- a/stackslib/src/burnchains/tests/affirmation.rs +++ b/stackslib/src/burnchains/tests/affirmation.rs @@ -392,7 +392,7 @@ pub fn make_reward_cycle_with_vote( }; burnchain_db - .store_new_burnchain_block_ops_unchecked(burnchain, headers, &block_header, &ops) + .store_new_burnchain_block_ops_unchecked(&block_header, &ops) .unwrap(); headers.push(block_header.clone()); @@ -1192,1717 +1192,3 @@ fn test_find_heaviest_parent_commit_many_commits() { assert_eq!(total_confs, 3); assert_eq!(total_burns, 1 + 2 + 3); } - -#[test] -fn test_update_pox_affirmation_maps_3_forks() { - // Create three forks, such that each subsequent reward cycle only affirms the first reward cycle's anchor - // block. That is, reward cycle 2 affirms reward cycle 1's anchor block; reward cycle 3 - // affirms reward cycle 1's anchor block but not 2's, and reward cycle 4 affirms reward cycle - // 1's anchor block but not 2's or 3's. Each affirmation map has the same weight, but verify - // that the canonical affirmation map is the *last-discovered* affirmation map (i.e. the one - // with the highest affirmed anchor block -- in this case, the fork in which reward cycle 4 - // affirms reward cycle 1's anchor block, but not 2's or 3's). - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(10, 5, 3, 3); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // first reward cycle is all (linear) commits, so it must elect an anchor block - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: before update: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("n").unwrap()); - - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block in the chain so far - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - - // the anchor block itself affirms nothing, since it isn't built on an anchor block - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: after update: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - let anchor_block_0 = - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .unwrap() - .0; - eprintln!("anchor block 1 at height {}", anchor_block_0.block_height); - assert!(anchor_block_0.block_height < commits_0[7][0].as_ref().unwrap().block_height); - - // descend from a prepare-phase commit in rc 0, so affirms rc 0's anchor block - let (next_headers, commits_1) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[7][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // there's two anchor blocks so far -- one for reward cycle 1, and one for reward cycle 2. - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("p").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pp").unwrap()); - - // descend from a prepare-phase commit in rc 0, so affirms rc 0's anchor block but not rc - // 1's - assert!(anchor_block_0.block_height < commits_0[6][0].as_ref().unwrap().block_height); - let (next_headers, commits_2) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[6][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 2, &burnchain).unwrap(); - - // there's three anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - - // there are two equivalently heavy affirmation maps, but the affirmation map discovered later - // is the heaviest. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=2: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pa").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pap").unwrap()); - - // descend from a prepare-phase commit in rc 0, so affirms rc 0's anchor block, but not rc - // 1's or rc 2's - assert!(anchor_block_0.block_height < commits_0[8][0].as_ref().unwrap().block_height); - let (next_headers, commits_3) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[8][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 3, &burnchain).unwrap(); - - // there are three equivalently heavy affirmation maps, but the affirmation map discovered last - // is the heaviest. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=3: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("paa").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("paap").unwrap()); -} - -#[test] -fn test_update_pox_affirmation_maps_unique_anchor_block() { - // Verify that if two reward cycles choose the same anchor block, the second reward cycle to do - // so will actually have no anchor block at all (since a block-commit can be an anchor block - // for at most one reward cycle). - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(10, 5, 3, 3); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // first reward cycle is all (linear) commits, so it must elect an anchor block - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: before update: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("n").unwrap()); - - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - - // the anchor block itself affirms nothing, since it isn't built on an anchor block - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: after update: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - let anchor_block_0 = - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .unwrap() - .0; - eprintln!("anchor block 1 at height {}", anchor_block_0.block_height); - assert!(anchor_block_0.block_height < commits_0[7][0].as_ref().unwrap().block_height); - - // try and select the same anchor block, twice - let mut dup_commits = commits_0.clone(); - for (i, cmts) in dup_commits.iter_mut().enumerate() { - let block_header = BurnchainBlockHeader { - block_height: (i + commits_0.len() + 1) as u64, - block_hash: next_burn_header_hash(), - parent_block_hash: headers - .last() - .map(|blk| blk.block_hash.clone()) - .unwrap_or(first_bhh.clone()), - num_txs: cmts.len() as u64, - timestamp: (i + commits_0.len()) as u64, - }; - - for cmt_opt in cmts.iter_mut() { - if let Some(cmt) = cmt_opt.as_mut() { - cmt.block_height = block_header.block_height; - cmt.parent_block_ptr = anchor_block_0.block_height as u32; - cmt.parent_vtxindex = anchor_block_0.vtxindex as u16; - cmt.burn_parent_modulus = - ((cmt.block_height - 1) % BURN_BLOCK_MINED_AT_MODULUS) as u8; - cmt.burn_header_hash = block_header.block_hash.clone(); - cmt.block_header_hash = next_block_hash(); - } - } - - headers.push(block_header.clone()); - - let cmt_ops: Vec = cmts - .iter() - .filter_map(|op| op.clone()) - .map(BlockstackOperationType::LeaderBlockCommit) - .collect(); - - burnchain_db - .store_new_burnchain_block_ops_unchecked(&burnchain, &headers, &block_header, &cmt_ops) - .unwrap(); - } - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // there's still only one anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_none() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pn").unwrap()); -} - -#[test] -fn test_update_pox_affirmation_maps_absent() { - // Create two fork histories, both of which affirm the *absence* of different anchor blocks, - // and both of which contain stretches of reward cycles in which no reward cycle was chosen. - // Verify that an affirmation map becomes canonical only by affirming the *presence* of more - // anchor blocks than others -- i.e. affirmation maps that grow by adding reward cycles in - // which there was no anchor block chosen do *not* increase in weight (and thus the canonical - // affirmation map does *not* change even though multiple reward cycles pass with no anchor - // block chosen). - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(10, 5, 3, 3); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // make two histories -- one with an anchor block, and one without. - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None, None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - assert_eq!(heaviest_am, AffirmationMap::empty()); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block, and it's at vtxindex 1 (not 0) - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert_eq!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .unwrap() - .0 - .vtxindex, - 1 - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_none() - ); - - // the anchor block itself affirms nothing - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - for i in 5..10 { - let block_commit = BurnchainDB::get_block_commit( - burnchain_db.conn(), - &commits_0[i][0].as_ref().unwrap().burn_header_hash, - &commits_0[i][0].as_ref().unwrap().txid, - ) - .unwrap() - .unwrap(); - assert_eq!(block_commit.vtxindex, 0); - - let block_commit_metadata = BurnchainDB::get_commit_metadata( - burnchain_db.conn(), - &block_commit.burn_header_hash, - &block_commit.txid, - ) - .unwrap() - .unwrap(); - assert_eq!(block_commit_metadata.anchor_block_descendant, None); - } - - // build a second reward cycle off of a commit that does _not_ affirm the first anchor - // block - let (next_headers, commits_1) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[9][1].clone(), commits_0[9][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // the second anchor block affirms that the first anchor block is missing. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("a").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("ap").unwrap()); - - // build a third reward cycle off of a commit in the second reward cycle, but make it so - // that there is no anchor block mined - let (next_headers, commits_2) = make_reward_cycle_without_anchor( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_1[9][0].clone(), commits_1[9][1].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 2, &burnchain).unwrap(); - - // there isn't a third anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - - // heaviest _anchor block_ affirmation map is unchanged. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=2: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("a").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("apn").unwrap()); - - // build a fourth reward cycle off of a commit in the third reward cycle, but make it so - // that there is no anchor block mined - assert!(commits_2[5][0].is_some()); - assert!(commits_2[5][1].is_some()); - let (next_headers, commits_3) = make_reward_cycle_without_anchor( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_2[5][0].clone(), commits_2[5][1].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 3, &burnchain).unwrap(); - - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_none() - ); - - // heaviest _anchor block_ affirmation map is unchanged. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=3: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("a").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("apnn").unwrap()); - - // make a fourth fifth cycle, again with a missing anchor block - assert!(commits_3[5][0].is_some()); - assert!(commits_3[5][1].is_some()); - let (next_headers, commits_4) = make_reward_cycle_without_anchor( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_3[5][0].clone(), commits_3[5][1].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 4, &burnchain).unwrap(); - - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 4) - .unwrap() - .is_none() - ); - - // heaviest _anchor block_ affirmation map advances - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=4: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("a").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("apnnn").unwrap()); - - // make a fifth reward cycle, but with an anchor block. Affirms the first anchor block by - // descending from a chain that descends from it. - assert!(commits_4[5][0].is_some()); - assert!(commits_4[5][1].is_some()); - let (next_headers, commits_5) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_4[5][1].clone(), commits_4[5][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 5, &burnchain).unwrap(); - - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 4) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 5) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 6) - .unwrap() - .is_some() - ); - - // heaviest _anchor block_ affirmation map advances, since the new anchor block affirms the - // last 4 reward cycles, including the anchor block mined in the first reward cycle - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=5: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - // anchor block was chosen in the last reward cycle, and in doing so created the heaviest - // affirmation map for an anchor block, so the canonical affirmation map is - // whatever that last anchor block affirmed - assert_eq!(heaviest_am, AffirmationMap::decode("pannn").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pannnp").unwrap()); - - // make a third history that affirms _nothing_. It should eventually overtake this last - // heaviest affirmation map - let mut start = vec![commits_0[3][1].clone()]; - for i in 0..6 { - let (next_headers, commits) = make_reward_cycle_with_vote( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - start, - false, - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 6 + i, &burnchain).unwrap(); - start = vec![commits[5][0].clone()]; - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc={}: heaviest = {}, canonical = {}", - 6 + i, - &heaviest_am, - &canonical_am - ); - } - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=11: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pannn").unwrap()); - assert_eq!( - canonical_am, - AffirmationMap::decode("pannnpnnnnnn").unwrap() - ); - - // other affirmation map should be present - let unaffirmed_am = AffirmationMap::decode("aannnannnnnn").unwrap(); - let am_id = BurnchainDB::get_affirmation_map_id(burnchain_db.conn(), &unaffirmed_am) - .unwrap() - .unwrap(); - let weight = BurnchainDB::get_affirmation_weight(burnchain_db.conn(), am_id) - .unwrap() - .unwrap(); - assert_eq!(weight, 9); -} - -#[test] -fn test_update_pox_affirmation_maps_nothing() { - // Create a sequence of reward cycles that alternate between selecting (and affirming) an - // anchor block, and not selecting an anchor block at all. Verify that in all cases the - // canonical affirmation map is still the affirmation map with the most affirmed anchor blocks - // (`pn`), and verify that the heaviest affirmation map (given the unconfirmed anchor block oracle - // closure) can alternate between either `pnpn` or `pnan` based on whether or not the oracle - // declares an anchor block present or absent in the chain state. - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(10, 5, 3, 3); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // first reward cycle is all (linear) commits, so it must elect an anchor block - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - assert_eq!(heaviest_am, AffirmationMap::empty()); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - - // the anchor block itself affirms nothing, since it isn't built on an anchor block - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - // build a second reward cycle off of the first, but with no anchor block - let (next_headers, commits_1) = make_reward_cycle_with_vote( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[9][0].clone()], - false, - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // there's still one anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_none() - ); - - // second reward cycle doesn't have an anchor block, so there's no heaviest anchor block - // affirmation map yet - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pn").unwrap()); - - // build a 3rd reward cycle, but it affirms an anchor block - let last_commit_1 = { - let mut last_commit = None; - for i in 0..commits_1.len() { - if commits_1[i][0].is_some() { - last_commit = commits_1[i][0].clone(); - } - } - last_commit - }; - - let (next_headers, commits_2) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![last_commit_1], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 2, &burnchain).unwrap(); - - // there's two anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 4) - .unwrap() - .is_none() - ); - - // there's no anchor block in rc 1 - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=2: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pn").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pnp").unwrap()); - - // build a fourth reward cycle, with no vote - let (next_headers, commits_3) = make_reward_cycle_with_vote( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_2[9][0].clone()], - false, - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 3, &burnchain).unwrap(); - - // there are three equivalently heavy affirmation maps, but the affirmation map discovered last - // is the heaviest. BUT THIS TIME, MAKE THE UNCONFIRMED ORACLE DENY THAT THIS LAST - // ANCHORED BLOCK EXISTS. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| false, - ) - .unwrap(); - eprintln!( - "rc=3 (deny): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pn").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pnan").unwrap()); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=3 (exist): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pn").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pnpn").unwrap()); -} - -#[test] -fn test_update_pox_affirmation_fork_2_cycles() { - // Create two forks, where miners work on each fork for two cycles (so, there are four reward - // cycles in total, but miners spend the first two reward cycles on fork 1 and the next two - // reward cycles on fork 2). The second fork does NOT affirm the anchor blocks in the first - // fork. Verify that the canonical affirmation map progresses from `paa` to `aap` once the - // second fork affirms two anchor blocks (note that ties in affirmation map weights are broken - // by most-recently-affirmed anchor block). - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(5, 2, 2, 25); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // first reward cycle is all (linear) commits, so it must elect an anchor block - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - assert_eq!(heaviest_am, AffirmationMap::empty()); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - - // the anchor block itself affirms nothing, since it isn't built on an anchor block - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0 (true): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| false, - ) - .unwrap(); - eprintln!( - "rc=0 (false): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(canonical_am, AffirmationMap::decode("a").unwrap()); - - // build a second reward cycle off of the first - let (next_headers, commits_1) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[4][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // there's two anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - - // the network affirms two anchor blocks, but the second anchor block only affirms the - // first anchor block. - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1 (true): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("p").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pp").unwrap()); - - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| false, - ) - .unwrap(); - eprintln!( - "rc=1 (false): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(canonical_am, AffirmationMap::decode("pa").unwrap()); - - // build a third reward cycle off of the first, before the 2nd's anchor block - let (next_headers, commits_2) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[1][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 2, &burnchain).unwrap(); - - // there's four anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=2 (true): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("p").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("ppp").unwrap()); - - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| false, - ) - .unwrap(); - eprintln!( - "rc=2 (false): heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(canonical_am, AffirmationMap::decode("paa").unwrap()); - - // build a fourth reward cycle off of the third - let (next_headers, commits_3) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_2[4][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 3, &burnchain).unwrap(); - - // there's four anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 4) - .unwrap() - .is_some() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=3: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("aap").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("aapp").unwrap()); -} - -#[test] -fn test_update_pox_affirmation_fork_duel() { - // Create two forks where miners alternate between working on forks (i.e. selecting anchor - // blocks) at each reward cycle. That is, in odd reward cycles, miners work on fork #1, and in - // even reward cycles, they work on fork #2. Verify that the canonical affirmation map - // flip-flops between that of fork #1 and fork #2 as anchor blocks are subsequently affirmed. - let first_bhh = BurnchainHeaderHash([0; 32]); - let first_timestamp = 0; - let first_height = 0; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = make_test_pox(5, 2, 2, 25); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let key_register = make_simple_key_register(&first_block_header.block_hash, 0, 1); - - // first reward cycle is all (linear) commits, so it must elect an anchor block - let (next_headers, commits_0) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![None], - ); - - // no anchor blocks recorded, yet! - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - assert_eq!(heaviest_am, AffirmationMap::empty()); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_none() - ); - - update_pox_affirmation_maps(&mut burnchain_db, &headers, 0, &burnchain).unwrap(); - - // there's only one anchor block - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - - // the anchor block itself affirms nothing, since it isn't built on an anchor block - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=0: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("p").unwrap()); - - // build a second reward cycle off of the first, but at the start - assert!(commits_0[1][0].is_some()); - let (next_headers, commits_1) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[1][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 1, &burnchain).unwrap(); - - // there's two anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - - // the network affirms two anchor blocks, but the second one wins - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=1: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("a").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("ap").unwrap()); - - // build a third reward cycle off of the first - assert!(commits_0[4][0].clone().unwrap().block_height == 5); - let (next_headers, commits_2) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_0[4][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 2, &burnchain).unwrap(); - - // there's four anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=2: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("pa").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("pap").unwrap()); - - // build a fourth reward cycle off of the second - assert!(commits_1[4][0].clone().unwrap().block_height == 10); - let (next_headers, commits_3) = make_reward_cycle( - &mut burnchain_db, - &burnchain, - &key_register, - &mut headers, - vec![commits_1[4][0].clone()], - ); - update_pox_affirmation_maps(&mut burnchain_db, &headers, 3, &burnchain).unwrap(); - - // there's four anchor blocks - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 0) - .unwrap() - .is_none() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 1) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 2) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 3) - .unwrap() - .is_some() - ); - assert!( - BurnchainDB::get_canonical_anchor_block_commit(burnchain_db.conn(), &headers, 4) - .unwrap() - .is_some() - ); - - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - ) - .unwrap(); - let canonical_am = BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - &burnchain, - &headers, - |_, _| true, - ) - .unwrap(); - eprintln!( - "rc=3: heaviest = {}, canonical = {}", - &heaviest_am, &canonical_am - ); - - assert_eq!(heaviest_am, AffirmationMap::decode("apa").unwrap()); - assert_eq!(canonical_am, AffirmationMap::decode("apap").unwrap()); -} diff --git a/stackslib/src/burnchains/tests/db.rs b/stackslib/src/burnchains/tests/db.rs index 424abadd9a..2686a08967 100644 --- a/stackslib/src/burnchains/tests/db.rs +++ b/stackslib/src/burnchains/tests/db.rs @@ -24,7 +24,6 @@ use stacks_common::types::sqlite::NO_PARAMS; use stacks_common::util::hash::*; use super::*; -use crate::burnchains::affirmation::AffirmationMap; use crate::burnchains::bitcoin::address::*; use crate::burnchains::bitcoin::blocks::*; use crate::burnchains::bitcoin::*; @@ -82,7 +81,7 @@ impl BurnchainDB { ); db_tx.store_burnchain_db_entry(&header)?; - db_tx.store_blockstack_ops(burnchain, indexer, &header, &blockstack_ops)?; + db_tx.store_blockstack_ops(&header, &blockstack_ops)?; db_tx.commit()?; @@ -626,8 +625,6 @@ fn test_get_commit_at() { ); burnchain_db .store_new_burnchain_block_ops_unchecked( - &burnchain, - &headers, block_header, &vec![BlockstackOperationType::LeaderBlockCommit(cmt.clone())], ) @@ -669,8 +666,6 @@ fn test_get_commit_at() { burnchain_db .store_new_burnchain_block_ops_unchecked( - &burnchain, - &fork_headers, &fork_block_header, &vec![BlockstackOperationType::LeaderBlockCommit(fork_cmt.clone())], ) @@ -736,8 +731,6 @@ fn test_get_set_check_anchor_block() { ); burnchain_db .store_new_burnchain_block_ops_unchecked( - &burnchain, - &headers, block_header, &vec![BlockstackOperationType::LeaderBlockCommit(cmt.clone())], ) @@ -771,301 +764,6 @@ fn test_get_set_check_anchor_block() { .unwrap()); } -#[test] -fn test_update_block_descendancy() { - let first_bhh = BurnchainHeaderHash::from_hex(BITCOIN_REGTEST_FIRST_BLOCK_HASH).unwrap(); - let first_timestamp = 0; - let first_height = 1; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = burn_db_test_pox(); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let mut parent = None; - let mut parent_block_header: Option = None; - let mut cmts = vec![]; - let mut cmts_genesis = vec![]; - let mut cmts_invalid = vec![]; - - for i in 0..5 { - let hdr = BurnchainHeaderHash([(i + 1) as u8; 32]); - let block_header = BurnchainBlockHeader { - block_height: first_height + i, - block_hash: hdr, - parent_block_hash: parent_block_header - .as_ref() - .map(|blk| blk.block_hash.clone()) - .unwrap_or(first_block_header.block_hash.clone()), - num_txs: 3, - timestamp: i, - }; - - headers.push(block_header.clone()); - parent_block_header = Some(block_header); - } - - let mut am_id = 0; - - for i in 0..5 { - let block_header = &headers[i + 1]; - - let cmt = make_simple_block_commit( - &burnchain, - parent.as_ref(), - block_header, - BlockHeaderHash([((i + 1) as u8) | 0x80; 32]), - ); - - // make a second commit that builds off of genesis - let mut cmt_genesis = cmt.clone(); - cmt_genesis.parent_block_ptr = 0; - cmt_genesis.parent_vtxindex = 0; - cmt_genesis.block_header_hash = BlockHeaderHash([((i + 1) as u8) | 0xa0; 32]); - cmt_genesis.txid = next_txid(); - - // make an invalid commit - let mut cmt_invalid = cmt.clone(); - cmt_invalid.parent_vtxindex += 1; - cmt_invalid.block_header_hash = BlockHeaderHash([((i + 1) as u8) | 0xc0; 32]); - cmt_invalid.txid = next_txid(); - - burnchain_db - .store_new_burnchain_block_ops_unchecked( - &burnchain, - &headers, - block_header, - &vec![ - BlockstackOperationType::LeaderBlockCommit(cmt.clone()), - BlockstackOperationType::LeaderBlockCommit(cmt_genesis.clone()), - BlockstackOperationType::LeaderBlockCommit(cmt_invalid.clone()), - ], - ) - .unwrap(); - - cmts.push(cmt.clone()); - cmts_genesis.push(cmt_genesis.clone()); - cmts_invalid.push(cmt_invalid.clone()); - - parent = Some(cmt); - - if i == 0 { - am_id = { - let tx = burnchain_db.tx_begin().unwrap(); - tx.set_anchor_block(&cmts[0], 1).unwrap(); - let am_id = tx - .insert_block_commit_affirmation_map(&AffirmationMap::decode("p").unwrap()) - .unwrap(); - tx.update_block_commit_affirmation(&cmts[0], Some(1), am_id) - .unwrap(); - tx.commit().unwrap(); - am_id - }; - assert_ne!(am_id, 0); - } - } - - // each valid commit should have cmts[0]'s affirmation map - for i in 1..5 { - let cmt_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts[i]).unwrap(); - assert_eq!(cmt_am_id.unwrap(), am_id); - - let genesis_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts_genesis[i]) - .unwrap(); - assert_eq!(genesis_am_id.unwrap(), 0); - - let invalid_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts_invalid[i]) - .unwrap(); - assert_eq!(invalid_am_id.unwrap(), 0); - } -} - -#[test] -fn test_update_block_descendancy_with_fork() { - let first_bhh = BurnchainHeaderHash::from_hex(BITCOIN_REGTEST_FIRST_BLOCK_HASH).unwrap(); - let first_timestamp = 0; - let first_height = 1; - - let mut burnchain = Burnchain::regtest(":memory:"); - burnchain.pox_constants = burn_db_test_pox(); - burnchain.first_block_height = first_height; - burnchain.first_block_hash = first_bhh.clone(); - burnchain.first_block_timestamp = first_timestamp; - - let mut burnchain_db = BurnchainDB::connect(":memory:", &burnchain, true).unwrap(); - - let first_block_header = burnchain_db.get_canonical_chain_tip().unwrap(); - - let mut headers = vec![first_block_header.clone()]; - let mut fork_headers = vec![first_block_header.clone()]; - - let mut parent = None; - let mut parent_block_header: Option = None; - let mut cmts = vec![]; - let mut cmts_genesis = vec![]; - let mut cmts_invalid = vec![]; - - let mut fork_cmts = vec![]; - - for i in 0..5 { - let hdr = BurnchainHeaderHash([(i + 1) as u8; 32]); - let block_header = BurnchainBlockHeader { - block_height: first_height + i, - block_hash: hdr, - parent_block_hash: parent_block_header - .as_ref() - .map(|blk| blk.block_hash.clone()) - .unwrap_or(first_block_header.block_hash.clone()), - num_txs: 3, - timestamp: i, - }; - - headers.push(block_header.clone()); - parent_block_header = Some(block_header); - } - - for i in 0..5 { - let hdr = BurnchainHeaderHash([(i + 128 + 1) as u8; 32]); - let block_header = BurnchainBlockHeader { - block_height: first_height + i, - block_hash: hdr, - parent_block_hash: parent_block_header - .as_ref() - .map(|blk| blk.block_hash.clone()) - .unwrap_or(first_block_header.block_hash.clone()), - num_txs: 3, - timestamp: i, - }; - - fork_headers.push(block_header.clone()); - } - - let mut am_id = 0; - let mut fork_am_id = 0; - - for i in 0..5 { - let block_header = &headers[i + 1]; - let fork_block_header = &fork_headers[i + 1]; - - let cmt = make_simple_block_commit( - &burnchain, - parent.as_ref(), - block_header, - BlockHeaderHash([((i + 1) as u8) | 0x80; 32]), - ); - - // make a second commit that builds off of genesis - let mut cmt_genesis = cmt.clone(); - cmt_genesis.parent_block_ptr = 0; - cmt_genesis.parent_vtxindex = 0; - cmt_genesis.block_header_hash = BlockHeaderHash([((i + 1) as u8) | 0xa0; 32]); - cmt_genesis.txid = next_txid(); - - // make an invalid commit - let mut cmt_invalid = cmt.clone(); - cmt_invalid.parent_vtxindex += 1; - cmt_invalid.block_header_hash = BlockHeaderHash([((i + 1) as u8) | 0xc0; 32]); - cmt_invalid.txid = next_txid(); - - // make a commit on the fork - let mut fork_cmt = cmt.clone(); - fork_cmt.burn_header_hash = fork_block_header.block_hash.clone(); - fork_cmt.vtxindex = 100; - fork_cmt.parent_vtxindex = 100; - - burnchain_db - .store_new_burnchain_block_ops_unchecked( - &burnchain, - &headers, - block_header, - &vec![ - BlockstackOperationType::LeaderBlockCommit(cmt.clone()), - BlockstackOperationType::LeaderBlockCommit(cmt_genesis.clone()), - BlockstackOperationType::LeaderBlockCommit(cmt_invalid.clone()), - ], - ) - .unwrap(); - - burnchain_db - .store_new_burnchain_block_ops_unchecked( - &burnchain, - &fork_headers, - fork_block_header, - &vec![BlockstackOperationType::LeaderBlockCommit(fork_cmt.clone())], - ) - .unwrap(); - - cmts.push(cmt.clone()); - cmts_genesis.push(cmt_genesis.clone()); - cmts_invalid.push(cmt_invalid.clone()); - fork_cmts.push(fork_cmt.clone()); - - parent = Some(cmt); - - if i == 0 { - am_id = { - let tx = burnchain_db.tx_begin().unwrap(); - tx.set_anchor_block(&cmts[0], 1).unwrap(); - let am_id = tx - .insert_block_commit_affirmation_map(&AffirmationMap::decode("p").unwrap()) - .unwrap(); - tx.update_block_commit_affirmation(&cmts[0], Some(1), am_id) - .unwrap(); - tx.commit().unwrap(); - am_id - }; - assert_ne!(am_id, 0); - - fork_am_id = { - let tx = burnchain_db.tx_begin().unwrap(); - tx.set_anchor_block(&fork_cmts[0], 1).unwrap(); - let fork_am_id = tx - .insert_block_commit_affirmation_map(&AffirmationMap::decode("a").unwrap()) - .unwrap(); - tx.update_block_commit_affirmation(&fork_cmts[0], Some(1), fork_am_id) - .unwrap(); - tx.commit().unwrap(); - fork_am_id - }; - assert_ne!(fork_am_id, 0); - } - } - - // each valid commit should have cmts[0]'s affirmation map - for i in 1..5 { - let cmt_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts[i]).unwrap(); - assert_eq!(cmt_am_id.unwrap(), am_id); - - let genesis_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts_genesis[i]) - .unwrap(); - assert_eq!(genesis_am_id.unwrap(), 0); - - let invalid_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &cmts_invalid[i]) - .unwrap(); - assert_eq!(invalid_am_id.unwrap(), 0); - } - - // each valid commit should have fork_cmts[0]'s affirmation map - for i in 1..5 { - let cmt_am_id = - BurnchainDB::get_block_commit_affirmation_id(burnchain_db.conn(), &fork_cmts[i]) - .unwrap(); - assert_eq!(cmt_am_id.unwrap(), fork_am_id); - } -} - #[test] fn test_classify_delegate_stx() { let first_bhh = BurnchainHeaderHash::from_hex(BITCOIN_REGTEST_FIRST_BLOCK_HASH).unwrap(); diff --git a/stackslib/src/chainstate/burn/db/sortdb.rs b/stackslib/src/chainstate/burn/db/sortdb.rs index 88fc138a40..f8b5c5ff45 100644 --- a/stackslib/src/chainstate/burn/db/sortdb.rs +++ b/stackslib/src/chainstate/burn/db/sortdb.rs @@ -32,8 +32,6 @@ use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{hex_bytes, to_hex, Sha512Trunc256Sum}; use stacks_common::util::vrf::*; -use crate::burnchains::affirmation::{AffirmationMap, AffirmationMapEntry}; -use crate::burnchains::db::BurnchainDB; use crate::burnchains::{ Burnchain, BurnchainBlockHeader, BurnchainStateTransition, BurnchainStateTransitionOps, BurnchainView, Error as BurnchainError, PoxConstants, Txid, @@ -912,10 +910,6 @@ impl db_keys { "sortition_db::last_selected_anchor_block_txid" } - pub fn pox_affirmation_map() -> &'static str { - "sortition_db::affirmation_map" - } - pub fn pox_reward_cycle_unlocks(cycle: u64) -> String { format!("sortition_db::reward_set_unlocks::{}", cycle) } @@ -1817,18 +1811,6 @@ impl SortitionHandleTx<'_> { ); Ok(anchor_block_txid) } - - pub fn get_sortition_affirmation_map(&mut self) -> Result { - let chain_tip = self.context.chain_tip.clone(); - let affirmation_map = match self.get_indexed(&chain_tip, db_keys::pox_affirmation_map())? { - Some(am_str) => { - AffirmationMap::decode(&am_str).expect("FATAL: corrupt affirmation map") - } - None => AffirmationMap::empty(), - }; - Ok(affirmation_map) - } - pub fn get_last_selected_anchor_block_hash( &mut self, ) -> Result, db_error> { @@ -2046,17 +2028,6 @@ impl<'a> SortitionHandleConn<'a> { Ok(anchor_block_txid) } - pub fn get_sortition_affirmation_map(&self) -> Result { - let chain_tip = self.context.chain_tip.clone(); - let affirmation_map = match self.get_indexed(&chain_tip, db_keys::pox_affirmation_map())? { - Some(am_str) => { - AffirmationMap::decode(&am_str).expect("FATAL: corrupt affirmation map") - } - None => AffirmationMap::empty(), - }; - Ok(affirmation_map) - } - pub fn get_last_selected_anchor_block_hash(&self) -> Result, db_error> { let anchor_block_hash = SortitionDB::parse_last_anchor_block_hash( self.get_indexed(&self.context.chain_tip, db_keys::pox_last_selected_anchor())?, @@ -2418,147 +2389,28 @@ impl<'a> SortitionHandleConn<'a> { /// Get the chosen PoX anchor block for a reward cycle, given the last block of its prepare /// phase. /// - /// In epochs 2.05 and earlier, this was the Stacks block that received F*w confirmations. - /// - /// In epoch 2.1 and later, this was the Stacks block whose block-commit received not only F*w - /// confirmations from subsequent block-commits in the prepare phase, but also the most BTC - /// burnt from all such candidates (and if there is still a tie, then the higher block-commit - /// is chosen). In particular, the block-commit is not required to be the winning block-commit - /// or even a valid block-commit, unlike in 2.05. However, this will be a winning block-commit - /// if at least 20% of the BTC was spent honestly. - /// - /// Here, instead of reasoning about which epoch we're in, the caller will instead supply an - /// optional handle to the burnchain database. If it's `Some(..)`, then the epoch 2.1 rules - /// are used -- the PoX anchor block will be determined from block-commits. If it's `None`, - /// then the epoch 2.05 rules are used -- the PoX anchor block will be determined from present - /// Stacks blocks. - pub fn get_chosen_pox_anchor( - &self, - burnchain_db_conn: Option<&DBConn>, - prepare_end_bhh: &BurnchainHeaderHash, - pox_consts: &PoxConstants, - ) -> Result, CoordinatorError> { - match burnchain_db_conn { - Some(conn) => self.get_chosen_pox_anchor_v210(conn, prepare_end_bhh, pox_consts), - None => self.get_chosen_pox_anchor_v205(prepare_end_bhh, pox_consts), - } - } - - /// Use the epoch 2.1 method for choosing a PoX anchor block. - /// The PoX anchor block corresponds to the block-commit that received F*w confirmations in its - /// prepare phase, and had the most BTC of all such candidates, and was the highest of all such - /// candidates. This information is stored in the burnchain DB. - /// - /// Note that it does not matter if the anchor block-commit does not correspond to a valid - /// Stacks block, or even won sortition. The result is the same for the honest network - /// participants: they mark the anchor block as absent in their affirmation maps, and this PoX - /// fork's quality is degraded as such. - pub fn get_chosen_pox_anchor_v210( - &self, - burnchain_db_conn: &DBConn, - prepare_end_bhh: &BurnchainHeaderHash, - pox_consts: &PoxConstants, - ) -> Result, CoordinatorError> { - let rc = match self.get_heights_for_prepare_phase_end_block( - prepare_end_bhh, - pox_consts, - true, - )? { - Some((_, block_height)) => { - let rc = pox_consts - .block_height_to_reward_cycle( - self.context.first_block_height, - block_height.into(), - ) - .ok_or(CoordinatorError::NotPrepareEndBlock)?; - rc - } - None => { - // there can't be an anchor block - return Ok(None); - } - }; - let (anchor_block_op, anchor_block_metadata) = { - let mut res = None; - let metadatas = BurnchainDB::get_anchor_block_commit_metadatas(burnchain_db_conn, rc)?; - - // find the one on this fork - for metadata in metadatas { - let sn = SortitionDB::get_ancestor_snapshot( - self, - metadata.block_height, - &self.context.chain_tip, - )? - .expect("FATAL: accepted block-commit but no sortition at height"); - if sn.burn_header_hash == metadata.burn_block_hash { - // this is the metadata on this burnchain fork - res = match BurnchainDB::get_anchor_block_commit( - burnchain_db_conn, - &sn.burn_header_hash, - rc, - )? { - Some(x) => Some(x), - None => { - continue; - } - }; - if res.is_some() { - break; - } - } - } - if let Some(x) = res { - x - } else { - // no anchor block - test_debug!("No anchor block for reward cycle {}", rc); - return Ok(None); - } - }; - - // sanity check: we must have processed this burnchain block already - let anchor_sort_id = self.get_sortition_id_for_bhh(&anchor_block_metadata.burn_block_hash)? - .ok_or_else(|| { - warn!("Missing anchor block sortition"; "burn_header_hash" => %anchor_block_metadata.burn_block_hash, "sortition_tip" => %&self.context.chain_tip); - BurnchainError::MissingParentBlock - })?; - - let anchor_sn = SortitionDB::get_block_snapshot(self, &anchor_sort_id)? - .ok_or(BurnchainError::MissingParentBlock)?; - - // the sortition does not even need to have picked this anchor block; all that matters is - // that miners confirmed it. If the winning block hash doesn't even correspond to a Stacks - // block, then the honest miners in the network will affirm that it's absent. - let ch = anchor_sn.consensus_hash; - Ok(Some(( - ch, - anchor_block_op.block_header_hash, - anchor_block_op.txid, - ))) - } - - /// This is the method for calculating the PoX anchor block for a reward cycle in epoch 2.05 and earlier. + /// This is the method for calculating the PoX anchor block for a reward cycle. /// Return identifying information for a PoX anchor block for the reward cycle that /// begins the block after `prepare_end_bhh`. /// If a PoX anchor block is chosen, this returns Some, if a PoX anchor block was not /// selected, return `None` /// `prepare_end_bhh`: this is the burn block which is the last block in the prepare phase /// for the corresponding reward cycle - fn get_chosen_pox_anchor_v205( + pub fn get_chosen_pox_anchor( &self, prepare_end_bhh: &BurnchainHeaderHash, pox_consts: &PoxConstants, ) -> Result, CoordinatorError> { - match self.get_chosen_pox_anchor_check_position_v205(prepare_end_bhh, pox_consts, true) { + match self.get_chosen_pox_anchor_check_position(prepare_end_bhh, pox_consts, true) { Ok(Ok((c_hash, bh_hash, txid, _))) => Ok(Some((c_hash, bh_hash, txid))), Ok(Err(_)) => Ok(None), Err(e) => Err(e), } } - /// This is the method for calculating the PoX anchor block for a reward cycle in epoch 2.05 and earlier. + /// This is the method for calculating the PoX anchor block for a reward cycle /// If no PoX anchor block is found, it returns Ok(Err(maximum confirmations of all candidates)) - pub fn get_chosen_pox_anchor_check_position_v205( + pub fn get_chosen_pox_anchor_check_position( &self, prepare_end_bhh: &BurnchainHeaderHash, pox_consts: &PoxConstants, @@ -3772,28 +3624,6 @@ impl SortitionDB { } } -impl SortitionDBTx<'_> { - pub fn find_sortition_tip_affirmation_map( - &mut self, - chain_tip: &SortitionId, - ) -> Result { - let affirmation_map = match self.get_indexed(chain_tip, db_keys::pox_affirmation_map())? { - Some(am_str) => { - AffirmationMap::decode(&am_str).expect("FATAL: corrupt affirmation map") - } - None => AffirmationMap::empty(), - }; - - // remove the first entry -- it's always `n` based on the way we construct it, while the - // heaviest affirmation map just has nothing. - if let Some(skipped_first_entry) = affirmation_map.as_slice().get(1..) { - Ok(AffirmationMap::new(skipped_first_entry.to_vec())) - } else { - Ok(AffirmationMap::empty()) - } - } -} - impl SortitionDBConn<'_> { pub fn as_handle<'b>(&'b self, chain_tip: &SortitionId) -> SortitionHandleConn<'b> { SortitionHandleConn::new( @@ -4654,23 +4484,6 @@ impl SortitionDB { } } - /// Find the affirmation map represented by a given sortition ID. - pub fn find_sortition_tip_affirmation_map( - &self, - tip_id: &SortitionId, - ) -> Result { - let ih = self.index_handle(tip_id); - let am = ih.get_sortition_affirmation_map()?; - - // remove the first entry -- it's always `n` based on the way we construct it, while the - // heaviest affirmation map just has nothing. - if let Some(skipped_first_entry) = am.as_slice().get(1..) { - Ok(AffirmationMap::new(skipped_first_entry.to_vec())) - } else { - Ok(AffirmationMap::empty()) - } - } - /// Get the list of Stack-STX operations processed in a given burnchain block. /// This will be the same list in each PoX fork; it's up to the Stacks block-processing logic /// to reject them. @@ -6184,14 +5997,9 @@ impl SortitionHandleTx<'_> { pox_id.extend_with_not_present_block(); } - let mut cur_affirmation_map = self.get_sortition_affirmation_map()?; - let mut selected_anchor_block = false; - // if we have selected an anchor block (known or unknown), write that info if let Some((anchor_block, anchor_block_txid)) = reward_info.selected_anchor_block() { - selected_anchor_block = true; - keys.push(db_keys::pox_anchor_to_prepare_end(anchor_block)); values.push(parent_snapshot.sortition_id.to_hex()); @@ -6201,9 +6009,6 @@ impl SortitionHandleTx<'_> { keys.push(db_keys::pox_last_anchor_txid().to_string()); values.push(anchor_block_txid.to_hex()); - keys.push(db_keys::pox_affirmation_map().to_string()); - values.push(cur_affirmation_map.encode()); - keys.push(db_keys::pox_last_selected_anchor().to_string()); values.push(anchor_block.to_hex()); @@ -6220,9 +6025,6 @@ impl SortitionHandleTx<'_> { keys.push(db_keys::pox_last_anchor_txid().to_string()); values.push("".to_string()); - - cur_affirmation_map.push(AffirmationMapEntry::Nothing); - debug!( "No anchor block at reward cycle starting at burn height {}", snapshot.block_height @@ -6283,17 +6085,11 @@ impl SortitionHandleTx<'_> { keys.push(db_keys::pox_reward_cycle_unlocks(cycle_number)); values.push(reward_set.start_cycle_state.serialize()); } - - cur_affirmation_map.push(AffirmationMapEntry::PoxAnchorBlockPresent); } else { // no anchor block; we're burning keys.push(db_keys::pox_reward_set_size().to_string()); values.push(db_keys::reward_set_size_to_string(0)); - if selected_anchor_block { - cur_affirmation_map.push(AffirmationMapEntry::PoxAnchorBlockAbsent); - } - pox_payout_addrs = vec![]; } @@ -6301,9 +6097,6 @@ impl SortitionHandleTx<'_> { keys.push(db_keys::pox_identifier().to_string()); values.push(pox_id.to_string()); - keys.push(db_keys::pox_affirmation_map().to_string()); - values.push(cur_affirmation_map.encode()); - pox_payout_addrs } else { // if this snapshot consumed some reward set entries AND @@ -6714,6 +6507,7 @@ pub mod tests { use stacks_common::util::vrf::*; use super::*; + use crate::burnchains::db::BurnchainDB; use crate::burnchains::tests::affirmation::{make_reward_cycle, make_simple_key_register}; use crate::burnchains::*; use crate::chainstate::burn::operations::leader_block_commit::BURN_BLOCK_MINED_AT_MODULUS; @@ -10736,31 +10530,15 @@ pub mod tests { commit_set[0].clone() }; let burn_header_hash = headers[i + 1].block_hash.clone(); - let burn_block_height = headers[i + 1].block_height; - Burnchain::process_affirmation_maps( - &burnchain, - &mut burnchain_db, - &headers, - burn_block_height, - ) - .unwrap(); test_append_snapshot_with_winner(&mut db, burn_header_hash, &commit_ops, None, winner); } let tip = SortitionDB::get_canonical_burn_chain_tip(db.conn()).unwrap(); { let ic = db.index_handle(&tip.sortition_id); - let anchor_2_05 = ic - .get_chosen_pox_anchor(None, &tip.burn_header_hash, &pox_consts) - .unwrap() - .unwrap(); - let anchor_2_1 = ic - .get_chosen_pox_anchor( - Some(burnchain_db.conn()), - &tip.burn_header_hash, - &pox_consts, - ) + let anchor = ic + .get_chosen_pox_anchor(&tip.burn_header_hash, &pox_consts) .unwrap() .unwrap(); @@ -10770,21 +10548,13 @@ pub mod tests { .unwrap() .consensus_hash; assert_eq!( - anchor_2_05, + anchor, ( expected_anchor_ch.clone(), commits[6][0].as_ref().unwrap().block_header_hash.clone(), commits[6][0].as_ref().unwrap().txid.clone(), ) ); - assert_eq!( - anchor_2_1, - ( - expected_anchor_ch, - commits[6][1].as_ref().unwrap().block_header_hash.clone(), - commits[6][1].as_ref().unwrap().txid.clone(), - ) - ); } } diff --git a/stackslib/src/chainstate/coordinator/mod.rs b/stackslib/src/chainstate/coordinator/mod.rs index cc21170543..65e48e31d1 100644 --- a/stackslib/src/chainstate/coordinator/mod.rs +++ b/stackslib/src/chainstate/coordinator/mod.rs @@ -29,14 +29,11 @@ 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::db::{BurnchainBlockData, BurnchainDB, BurnchainHeaderReader}; 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, SortitionHandleTx}; use crate::chainstate::burn::operations::leader_block_commit::RewardSetInfo; use crate::chainstate::burn::operations::BlockstackOperationType; use crate::chainstate::burn::{BlockSnapshot, ConsensusHash}; @@ -58,9 +55,7 @@ use crate::chainstate::stacks::index::marf::MARFOpenOpts; use crate::chainstate::stacks::index::Error as IndexError; use crate::chainstate::stacks::miner::{signal_mining_blocked, signal_mining_ready, MinerStatus}; use crate::chainstate::stacks::{Error as ChainstateError, StacksBlockHeader, TransactionPayload}; -use crate::core::{ - StacksEpoch, StacksEpochId, FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, -}; +use crate::core::{StacksEpoch, StacksEpochId}; use crate::cost_estimates::{CostEstimator, FeeEstimator}; use crate::monitoring::{ increment_contract_calls_processed, increment_stx_blocks_processed_counter, @@ -187,15 +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: 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, @@ -203,21 +189,11 @@ pub struct ChainsCoordinatorConfig { impl ChainsCoordinatorConfig { pub fn new() -> ChainsCoordinatorConfig { - ChainsCoordinatorConfig { - always_use_affirmation_maps: true, - require_affirmed_anchor_blocks: true, - assume_present_anchor_blocks: true, - txindex: false, - } + ChainsCoordinatorConfig { txindex: false } } pub fn test_new(txindex: bool) -> ChainsCoordinatorConfig { - ChainsCoordinatorConfig { - always_use_affirmation_maps: false, - require_affirmed_anchor_blocks: false, - assume_present_anchor_blocks: false, - txindex, - } + ChainsCoordinatorConfig { txindex } } } @@ -726,19 +702,15 @@ 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( sortition_tip.block_height + 1, &sortition_tip.burn_header_hash, &sortition_tip.sortition_id, burnchain, - &burnchain_db, chain_state, sort_db, provider, - always_use_affirmation_maps, )?; sort_db .get_next_block_recipients(burnchain, sortition_tip, reward_cycle_info.as_ref()) @@ -755,11 +727,9 @@ pub fn get_reward_cycle_info( parent_bhh: &BurnchainHeaderHash, sortition_tip: &SortitionId, burnchain: &Burnchain, - burnchain_db: &BurnchainDB, 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)); @@ -790,15 +760,7 @@ 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 - }; - - ic.get_chosen_pox_anchor(burnchain_db_conn_opt, parent_bhh, &burnchain.pox_constants) + ic.get_chosen_pox_anchor(parent_bhh, &burnchain.pox_constants) }?; let reward_cycle_info = if let Some((consensus_hash, stacks_block_hash, txid)) = reward_cycle_info { @@ -983,135 +945,6 @@ fn forget_orphan_stacks_blocks( Ok(()) } -/// Consolidate affirmation maps. -/// `sort_am` will be the prefix of the resulting AM. -/// If `given_am` represents more reward cycles than `last_2_05_rc`, then its affirmations will be -/// appended to `sort_am` to compute the consolidated affirmation map. -/// -/// This way, the affirmation map reflects affirmations made under the 2.05 rules during epoch 2.05 -/// reward cycles, and affirmations made under the 2.1 rules during epoch 2.1. -fn consolidate_affirmation_maps( - given_am: AffirmationMap, - sort_am: &AffirmationMap, - last_2_05_rc: usize, -) -> 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, @@ -1132,1134 +965,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, - ) -> 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, - ) - } - - /// 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)); - } - - /// 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. - 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) - } - } - - /// 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(()) - } - - /// 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. /// @@ -2282,8 +987,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 = @@ -2294,8 +998,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, @@ -2319,7 +1023,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. /// @@ -2328,59 +1032,21 @@ impl< /// Returns Ok(None) if not fn check_missing_anchor_block( &self, - header: &BurnchainBlockHeader, - canonical_affirmation_map: &AffirmationMap, + _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) - }); - - if 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 - ); - 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}"); - } - } + // 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) } @@ -2411,10 +1077,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())?, }; @@ -2439,16 +1102,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 @@ -2462,55 +1115,16 @@ impl< ) -> Result, Error> { debug!("Handle new burnchain block"); - 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)?; - debug!("Handle new canonical burnchain tip"; "height" => %canonical_burnchain_tip.block_height, "block_hash" => %canonical_burnchain_tip.block_hash.to_string()); @@ -2594,8 +1208,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 +1230,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)); } } @@ -2711,44 +1322,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)?; @@ -2815,81 +1393,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) @@ -2914,11 +1417,9 @@ impl< &burn_header.parent_block_hash, sortition_tip_id, &self.burnchain, - &self.burnchain_blocks_db, &mut self.chain_state_db, &mut self.sortition_db, &self.reward_set_provider, - self.config.always_use_affirmation_maps, ) } @@ -3101,148 +1602,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 @@ -3256,16 +1615,12 @@ impl< "FAIL: processing a new Stacks block, but don't have a canonical sortition tip", ); - let burnchain_db_conn = self.burnchain_blocks_db.conn(); let sortdb_handle = self .sortition_db .tx_handle_begin(&canonical_sortition_tip)?; - let mut processed_blocks = self.chain_state_db.process_blocks( - burnchain_db_conn, - sortdb_handle, - 1, - self.dispatcher, - )?; + let mut processed_blocks = + self.chain_state_db + .process_blocks(sortdb_handle, 1, self.dispatcher)?; while let Some(block_result) = processed_blocks.pop() { if block_result.0.is_none() && block_result.1.is_none() { @@ -3316,13 +1671,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 +1711,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)); } } } @@ -3403,12 +1723,9 @@ impl< .sortition_db .tx_handle_begin(&canonical_sortition_tip)?; // Right before a block is set to processed, the event dispatcher will emit a new block event - processed_blocks = self.chain_state_db.process_blocks( - burnchain_db_conn, - sortdb_handle, - 1, - self.dispatcher, - )?; + processed_blocks = + self.chain_state_db + .process_blocks(sortdb_handle, 1, self.dispatcher)?; } Ok(None) @@ -3434,8 +1751,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 @@ -3587,11 +1903,9 @@ impl SortitionDBMigrator { &ancestor_sn.burn_header_hash, &ancestor_sn.sortition_id, &self.burnchain, - &self.burnchain_db, &mut chainstate, sort_db, &OnChainRewardSetProvider::new(), - true, ) .map_err(|e| DBError::Other(format!("get_reward_cycle_info: {:?}", &e))); diff --git a/stackslib/src/chainstate/coordinator/tests.rs b/stackslib/src/chainstate/coordinator/tests.rs index 4887bcad4f..c05009425a 100644 --- a/stackslib/src/chainstate/coordinator/tests.rs +++ b/stackslib/src/chainstate/coordinator/tests.rs @@ -41,7 +41,6 @@ use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::*; -use crate::burnchains::affirmation::*; use crate::burnchains::bitcoin::indexer::BitcoinIndexer; use crate::burnchains::db::*; use crate::burnchains::*; @@ -191,36 +190,10 @@ fn produce_burn_block_do_not_set_height<'a, I: Iterator {}", 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}")); } } @@ -5853,320 +5822,6 @@ fn test_sortition_with_sunset_and_epoch_switch() { } } -#[test] -// This test should panic until the MARF stability issue -// https://github.com/blockstack/stacks-blockchain/issues/1805 -// is resolved: -#[should_panic] -/// Test a block that is processable in 2 PoX forks: -/// block "11" should be processable in both `111` and `110` -/// (because its parent is block `0`, and nobody stacks in -/// this test, all block commits must burn) -fn test_pox_processable_block_in_different_pox_forks() { - let path = &test_path("pox_processable_block_in_different_pox_forks"); - // setup a second set of states that won't see the broadcasted blocks - let path_blinded = &test_path("pox_processable_block_in_different_pox_forks.blinded"); - let _r = std::fs::remove_dir_all(path); - let _r = std::fs::remove_dir_all(path_blinded); - - let pox_consts = Some(PoxConstants::new( - 5, - 2, - 2, - 25, - 5, - u64::MAX - 1, - u64::MAX, - u32::MAX, - u32::MAX, - u32::MAX, - u32::MAX, - )); - let b = get_burnchain(path, pox_consts.clone()); - let b_blind = get_burnchain(path_blinded, pox_consts.clone()); - - let vrf_keys: Vec<_> = (0..20).map(|_| VRFPrivateKey::new()).collect(); - let committers: Vec<_> = (0..20).map(|_| StacksPrivateKey::random()).collect(); - - setup_states_with_epochs( - &[path, path_blinded], - &vrf_keys, - &committers, - pox_consts.clone(), - None, - StacksEpochId::Epoch2_05, - None, - ); - - let mut coord = make_coordinator(path, Some(b)); - let mut coord_blind = make_coordinator(path_blinded, Some(b_blind)); - - coord.handle_new_burnchain_block().unwrap(); - coord_blind.handle_new_burnchain_block().unwrap(); - - let sort_db = get_sortition_db(path, pox_consts.clone()); - - let tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn()).unwrap(); - assert_eq!(tip.block_height, 1); - assert!(!tip.sortition); - let (_, ops) = sort_db - .get_sortition_result(&tip.sortition_id) - .unwrap() - .unwrap(); - - let sort_db_blind = get_sortition_db(path_blinded, pox_consts.clone()); - - let tip = SortitionDB::get_canonical_burn_chain_tip(sort_db_blind.conn()).unwrap(); - assert_eq!(tip.block_height, 1); - assert!(!tip.sortition); - let (_, ops) = sort_db_blind - .get_sortition_result(&tip.sortition_id) - .unwrap() - .unwrap(); - - // we should have all the VRF registrations accepted - assert_eq!(ops.accepted_ops.len(), vrf_keys.len()); - assert!(ops.consumed_leader_keys.is_empty()); - - // process sequential blocks, and their sortitions... - let mut stacks_blocks: Vec<(SortitionId, StacksBlock)> = vec![]; - - // setup: - // sort:1 6 11 16 21 - // |----- rc 0 --------|------ rc 1 -------|----- rc 2 ------------|-------- rc 3 ----------|----- rc 4 - // ix: X - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - // \_____________________________________ 10 _ 11 _ 12 _ 13 _ 14 _ 15 _ 16 _ 17 _ 18 _ 19 - // - // - for (ix, (vrf_key, miner)) in vrf_keys.iter().zip(committers.iter()).enumerate() { - let mut burnchain = get_burnchain_db(path, pox_consts.clone()); - let burnchain_blind = get_burnchain_db(path_blinded, pox_consts.clone()); - let mut chainstate = get_chainstate(path); - let mut chainstate_blind = get_chainstate(path_blinded); - let burnchain_tip = burnchain.get_canonical_chain_tip().unwrap(); - let burnchain_tip_blind = burnchain_blind.get_canonical_chain_tip().unwrap(); - let b = get_burnchain(path, pox_consts.clone()); - let b_blind = get_burnchain(path_blinded, pox_consts.clone()); - - eprintln!("Making block {}", ix); - let (op, block) = if ix == 0 { - make_genesis_block( - &b, - &sort_db, - &mut chainstate, - &BlockHeaderHash([0; 32]), - miner, - 10000, - vrf_key, - ix as u32, - ) - } else { - let parent = if ix == 10 { - stacks_blocks[0].1.header.block_hash() - } else { - stacks_blocks[ix - 1].1.header.block_hash() - }; - if ix < 10 { - make_stacks_block( - &sort_db, - &mut chainstate, - &b, - &parent, - burnchain_tip.block_height, - miner, - 10000, - vrf_key, - ix as u32, - ) - } else { - make_stacks_block( - &sort_db_blind, - &mut chainstate_blind, - &b_blind, - &parent, - burnchain_tip_blind.block_height, - miner, - 10000, - vrf_key, - ix as u32, - ) - } - }; - produce_burn_block( - &b, - &mut burnchain, - &burnchain_tip.block_hash, - vec![op], - [burnchain_blind].iter_mut(), - ); - - loop { - let missing_anchor_opt = coord - .handle_new_burnchain_block() - .unwrap() - .into_missing_block_hash(); - if let Some(missing_anchor) = missing_anchor_opt { - eprintln!( - "Unblinded database reports missing anchor block {:?} (ix={})", - &missing_anchor, ix - ); - for (_, blk) in stacks_blocks.iter() { - if blk.block_hash() == missing_anchor { - let ic = sort_db.index_conn(); - let tip = - SortitionDB::get_canonical_burn_chain_tip(sort_db.conn()).unwrap(); - let sn = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &ic, - &tip.sortition_id, - &blk.block_hash(), - ) - .unwrap() - .unwrap(); - - // feed this missing reward cycle data - let rc = b_blind - .block_height_to_reward_cycle(sn.block_height) - .unwrap(); - let start_height = b_blind.reward_cycle_to_block_height(rc); - for height in start_height..sn.block_height { - let asn = - SortitionDB::get_ancestor_snapshot(&ic, height, &tip.sortition_id) - .unwrap() - .unwrap(); - for (_, blk) in stacks_blocks.iter() { - if blk.block_hash() == asn.winning_stacks_block_hash { - eprintln!("Unblinded database accepts missing anchor block ancestor {} of {} (ix={})", &blk.block_hash(), &missing_anchor, ix); - preprocess_block(&mut chainstate, &sort_db, &asn, blk.clone()); - coord.handle_new_stacks_block().unwrap(); - break; - } - } - } - - // *now* process this anchor block - eprintln!( - "Unblinded database processes missing anchor block {} (ix={})", - &missing_anchor, ix - ); - preprocess_block(&mut chainstate, &sort_db, &sn, blk.clone()); - coord.handle_new_stacks_block().unwrap(); - break; - } - } - } else { - coord.handle_new_stacks_block().unwrap(); - break; - } - } - - coord_blind.handle_new_burnchain_block().unwrap(); - coord_blind.handle_new_stacks_block().unwrap(); - - let tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn()).unwrap(); - let blinded_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db_blind.conn()).unwrap(); - - if ix < 10 { - // load the block into staging and process it on the un-blinded sortition DB - let block_hash = block.header.block_hash(); - eprintln!( - "Block hash={}, parent={}, height={}, ix={} (not blind)", - &block_hash, &block.header.parent_block, block.header.total_work.work, ix - ); - - assert_eq!(&tip.winning_stacks_block_hash, &block_hash); - stacks_blocks.push((tip.sortition_id.clone(), block.clone())); - - preprocess_block(&mut chainstate, &sort_db, &tip, block.clone()); - - // handle the stacks block - coord.handle_new_stacks_block().unwrap(); - } - if ix == 0 || ix >= 10 { - // load the block into staging and process it on the blinded sortition DB - let block_hash = block.header.block_hash(); - eprintln!( - "Block hash={}, parent={}, height={}, ix={} (blind)", - &block_hash, &block.header.parent_block, block.header.total_work.work, ix - ); - - assert_eq!(&blinded_tip.winning_stacks_block_hash, &block_hash); - if ix != 0 { - stacks_blocks.push((blinded_tip.sortition_id.clone(), block.clone())); - } - - preprocess_block(&mut chainstate_blind, &sort_db_blind, &blinded_tip, block); - - // handle the stacks block - coord_blind.handle_new_stacks_block().unwrap(); - } - if ix == 18 { - // right at the end of reward cycle 3 -- feed in the blocks from the blinded DB into - // the unblinded DB - for (i, (_, block)) in stacks_blocks.iter().enumerate() { - if i >= 10 && i <= ix { - eprintln!("Mirror blocks from blinded DB to unblinded DB (simulates downloading them) i={}", i); - let ic = sort_db_blind.index_conn(); - let sn = SortitionDB::get_block_snapshot_for_winning_stacks_block( - &ic, - &tip.sortition_id, - &block.block_hash(), - ) - .unwrap() - .unwrap(); - preprocess_block(&mut chainstate, &sort_db, &sn, block.clone()); - let _ = coord.handle_new_stacks_block(); - } - } - } - if ix > 18 { - // starting in reward cycle 4 -- this should NOT panic - eprintln!("Mirror block {} to unblinded DB", ix); - preprocess_block(&mut chainstate, &sort_db, &tip, stacks_blocks[ix].1.clone()); - let _ = coord.handle_new_stacks_block(); - } - } - - // both the blinded and unblined chains should now have the same view - let block_height = eval_at_chain_tip(path, &sort_db, "block-height"); - assert_eq!(block_height, Value::UInt(11)); - - let block_height = eval_at_chain_tip(path_blinded, &sort_db_blind, "block-height"); - assert_eq!(block_height, Value::UInt(11)); - - // because of the affirmations, the canonical PoX ID deliberately omits anchor blocks - { - let ic = sort_db_blind.index_handle_at_tip(); - let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "110011"); - } - { - let ic = sort_db.index_handle_at_tip(); - let pox_id = ic.get_pox_id().unwrap(); - assert_eq!(&pox_id.to_string(), "110011"); - } - - // same canonical Stacks chain tip - let stacks_tip = SortitionDB::get_canonical_stacks_chain_tip_hash(sort_db.conn()).unwrap(); - let stacks_tip_blind = - SortitionDB::get_canonical_stacks_chain_tip_hash(sort_db_blind.conn()).unwrap(); - assert_eq!(stacks_tip, stacks_tip_blind); - - // same final consensus hash, at the start of height 20 - let tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn()).unwrap(); - let blinded_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db_blind.conn()).unwrap(); - - assert!(tip.sortition); - assert!(blinded_tip.sortition); - assert_eq!( - tip.winning_stacks_block_hash, - blinded_tip.winning_stacks_block_hash - ); - assert_eq!(tip.burn_header_hash, blinded_tip.burn_header_hash); - assert_eq!(tip.consensus_hash, blinded_tip.consensus_hash); - assert_eq!(tip.block_height, 21); - assert_eq!(blinded_tip.block_height, 21); -} - #[test] fn test_pox_no_anchor_selected() { let path = &test_path("pox_fork_no_anchor_selected"); @@ -6234,7 +5889,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 +5955,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 +5987,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 +6010,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() { @@ -6448,7 +6104,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 +6117,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 +6160,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 +6190,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 +6213,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 +6240,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 +6260,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 +6280,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 +6301,7 @@ fn test_pox_fork_out_of_order() { path_blinded, &sort_db_blind, &mut coord_blind, - &sort_id, + sort_id, block, ); } @@ -6685,24 +6316,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 +6384,8 @@ fn reveal_block( - burnchain: &Burnchain, - indexer: &B, - burnchain_db: &BurnchainDB, - chainstate: &StacksChainState, - ) -> Result { - BurnchainDB::get_canonical_affirmation_map( - burnchain_db.conn(), - burnchain, - indexer, - |anchor_block_commit, _anchor_block_metadata| { - // if we don't have an unaffirmed anchor block, and we're no longer in the initial block - // download, then assume that it's absent. Otherwise, if we are in the initial block - // download but we don't have it yet, assume that it's present. - StacksChainState::has_stacks_block_for(chainstate.db(), anchor_block_commit) - }, - ) - .map_err(|e| e.into()) - } - - /// Get the affirmation map represented by the Stacks chain tip. - /// This is the private interface, to avoid having a public function take two db connections of the - /// same type. - fn inner_find_stacks_tip_affirmation_map( - burnchain_conn: &DBConn, - sort_db_conn: &DBConn, - tip_ch: &ConsensusHash, - tip_bhh: &BlockHeaderHash, - ) -> Result { - if let Some(leader_block_commit) = - SortitionDB::get_block_commit_for_stacks_block(sort_db_conn, tip_ch, tip_bhh)? - { - if let Some(am_id) = - BurnchainDB::get_block_commit_affirmation_id(burnchain_conn, &leader_block_commit)? - { - if let Some(am) = BurnchainDB::get_affirmation_map(burnchain_conn, am_id)? { - debug!( - "Stacks tip {}/{} (txid {}) has affirmation map '{}'", - tip_ch, tip_bhh, &leader_block_commit.txid, &am - ); - return Ok(am); - } else { - debug!( - "Stacks tip {}/{} (txid {}) affirmation map ID {} has no corresponding map", - tip_ch, tip_bhh, &leader_block_commit.txid, am_id - ); - } - } else { - debug!( - "No affirmation map for stacks tip {}/{} (txid {})", - tip_ch, tip_bhh, &leader_block_commit.txid - ); - } - } else { - debug!("No block-commit for stacks tip {}/{}", tip_ch, tip_bhh); - } - - Ok(AffirmationMap::empty()) - } - - /// Get the affirmation map represented by the Stacks chain tip. - /// This uses the 2.1 rules exclusively (i.e. only block-commits are considered). - pub fn find_stacks_tip_affirmation_map( - burnchain_db: &BurnchainDB, - sort_db_conn: &DBConn, - tip_ch: &ConsensusHash, - tip_bhh: &BlockHeaderHash, - ) -> Result { - Self::inner_find_stacks_tip_affirmation_map( - burnchain_db.conn(), - sort_db_conn, - tip_ch, - tip_bhh, - ) - } - - /// 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, @@ -5418,7 +5315,6 @@ impl StacksChainState { microblocks: &[StacksMicroblock], // parent microblocks burnchain_commit_burn: u64, burnchain_sortition_burn: u64, - affirmation_weight: u64, do_not_advance: bool, ) -> Result< ( @@ -5847,7 +5743,6 @@ impl StacksChainState { burn_transfer_stx_ops, burn_delegate_stx_ops, burn_vote_for_aggregate_key_ops, - affirmation_weight, ) .expect("FATAL: failed to advance chain tip"); @@ -6058,7 +5953,6 @@ impl StacksChainState { /// consumption by future miners). pub fn process_next_staging_block( &mut self, - burnchain_dbconn: &DBConn, sort_tx: &mut SortitionHandleTx, dispatcher_opt: Option<&T>, ) -> Result<(Option, Option), Error> { @@ -6246,24 +6140,6 @@ impl StacksChainState { last_microblock_seq ); - test_debug!( - "About to load affirmation map for {}/{}", - &next_staging_block.consensus_hash, - &next_staging_block.anchored_block_hash - ); - let block_am = StacksChainState::inner_find_stacks_tip_affirmation_map( - burnchain_dbconn, - sort_tx.tx(), - &next_staging_block.consensus_hash, - &next_staging_block.anchored_block_hash, - )?; - test_debug!( - "Affirmation map for {}/{} is `{}`", - &next_staging_block.consensus_hash, - &next_staging_block.anchored_block_hash, - &block_am - ); - // attach the block to the chain state and calculate the next chain tip. // Execute the confirmed microblocks' transactions against the chain state, and then // execute the anchored block's transactions against the chain state. @@ -6283,7 +6159,6 @@ impl StacksChainState { &next_microblocks, next_staging_block.commit_burn, next_staging_block.sortition_burn, - block_am.weight(), false, ) { Ok(next_chain_tip_info) => next_chain_tip_info, @@ -6440,13 +6315,12 @@ impl StacksChainState { #[cfg(test)] pub fn process_blocks_at_tip( &mut self, - burnchain_db_conn: &DBConn, sort_db: &mut SortitionDB, max_blocks: usize, ) -> Result, Option)>, Error> { let tx = sort_db.tx_begin_at_tip(); let null_event_dispatcher: Option<&DummyEventDispatcher> = None; - self.process_blocks(burnchain_db_conn, tx, max_blocks, null_event_dispatcher) + self.process_blocks(tx, max_blocks, null_event_dispatcher) } /// Process some staging blocks, up to max_blocks. @@ -6456,7 +6330,6 @@ impl StacksChainState { /// epoch receipt if the block was invalid. pub fn process_blocks( &mut self, - burnchain_db_conn: &DBConn, mut sort_tx: SortitionHandleTx, max_blocks: usize, dispatcher_opt: Option<&T>, @@ -6489,7 +6362,7 @@ impl StacksChainState { for i in 0..max_blocks { // process up to max_blocks pending blocks - match self.process_next_staging_block(burnchain_db_conn, &mut sort_tx, dispatcher_opt) { + match self.process_next_staging_block(&mut sort_tx, dispatcher_opt) { Ok((next_tip_opt, next_microblock_poison_opt)) => match next_tip_opt { Some(next_tip) => { ret.push((Some(next_tip), next_microblock_poison_opt)); diff --git a/stackslib/src/chainstate/stacks/db/headers.rs b/stackslib/src/chainstate/stacks/db/headers.rs index d968dd6343..60fd1af3c5 100644 --- a/stackslib/src/chainstate/stacks/db/headers.rs +++ b/stackslib/src/chainstate/stacks/db/headers.rs @@ -23,8 +23,8 @@ use crate::chainstate::stacks::db::*; use crate::chainstate::stacks::{Error, *}; use crate::core::{FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH}; use crate::util_lib::db::{ - query_row, query_row_columns, query_row_panic, query_rows, u64_to_sql, DBConn, - Error as db_error, FromColumn, FromRow, + query_row, query_row_columns, query_row_panic, u64_to_sql, DBConn, Error as db_error, + FromColumn, FromRow, }; impl FromRow for StacksBlockHeader { @@ -105,7 +105,6 @@ impl StacksChainState { parent_id: &StacksBlockId, tip_info: &StacksHeaderInfo, anchored_block_cost: &ExecutionCost, - affirmation_weight: u64, ) -> Result<(), Error> { let StacksBlockHeaderTypes::Epoch2(header) = &tip_info.anchored_header else { return Err(Error::InvalidChildOfNakomotoBlock); @@ -154,7 +153,7 @@ impl StacksChainState { anchored_block_cost, block_size_str, parent_id, - u64_to_sql(affirmation_weight)?, + u64_to_sql(0)?, // TODO: remove this ]; tx.execute("INSERT INTO block_headers \ @@ -368,18 +367,6 @@ impl StacksChainState { Ok(ret) } - /// Get all headers at a given Stacks height - pub fn get_all_headers_at_height_and_weight( - conn: &Connection, - height: u64, - affirmation_weight: u64, - ) -> Result, Error> { - let qry = - "SELECT * FROM block_headers WHERE block_height = ?1 AND affirmation_weight = ?2 ORDER BY burn_header_height DESC"; - let args = params![u64_to_sql(height)?, u64_to_sql(affirmation_weight)?]; - query_rows(conn, qry, args).map_err(|e| e.into()) - } - /// Get the highest known header height pub fn get_max_header_height(conn: &Connection) -> Result { let qry = "SELECT block_height FROM block_headers ORDER BY block_height DESC LIMIT 1"; @@ -387,16 +374,4 @@ impl StacksChainState { .map(|row_opt: Option| row_opt.map(|h| h as u64).unwrap_or(0)) .map_err(|e| e.into()) } - - /// Get the highest known header affirmation weight - pub fn get_max_affirmation_weight_at_height( - conn: &Connection, - height: u64, - ) -> Result { - let qry = - "SELECT affirmation_weight FROM block_headers WHERE block_height = ?1 ORDER BY affirmation_weight DESC LIMIT 1"; - query_row(conn, qry, &[&u64_to_sql(height)?]) - .map(|row_opt: Option| row_opt.map(|h| h as u64).unwrap_or(0)) - .map_err(|e| e.into()) - } } diff --git a/stackslib/src/chainstate/stacks/db/mod.rs b/stackslib/src/chainstate/stacks/db/mod.rs index a21e022c4f..75aab07ae4 100644 --- a/stackslib/src/chainstate/stacks/db/mod.rs +++ b/stackslib/src/chainstate/stacks/db/mod.rs @@ -1717,7 +1717,6 @@ impl StacksChainState { &parent_hash, &first_tip_info, &ExecutionCost::ZERO, - 0, )?; tx.commit()?; } @@ -2603,7 +2602,6 @@ impl StacksChainState { burn_transfer_stx_ops: Vec, burn_delegate_stx_ops: Vec, burn_vote_for_aggregate_key_ops: Vec, - affirmation_weight: u64, ) -> Result { if new_tip.parent_block != FIRST_STACKS_BLOCK_HASH { // not the first-ever block, so linkage must occur @@ -2659,7 +2657,6 @@ impl StacksChainState { &parent_hash, &new_tip_info, anchor_block_cost, - affirmation_weight, )?; StacksChainState::insert_miner_payment_schedule(headers_tx.deref_mut(), block_reward)?; StacksChainState::store_burnchain_txids( diff --git a/stackslib/src/chainstate/stacks/tests/chain_histories.rs b/stackslib/src/chainstate/stacks/tests/chain_histories.rs index 6a4f48e786..af00ebcff3 100644 --- a/stackslib/src/chainstate/stacks/tests/chain_histories.rs +++ b/stackslib/src/chainstate/stacks/tests/chain_histories.rs @@ -177,11 +177,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 1, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 1) .unwrap(); let expect_success = check_oracle(&stacks_block, µblocks); @@ -361,11 +357,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 1, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 1) .unwrap(); // processed _this_ block @@ -571,11 +563,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed exactly one block, but got back two tip-infos @@ -903,11 +891,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed _one_ block @@ -1169,19 +1153,11 @@ where ); let mut tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); let mut tip_info_list_2 = node_2 .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); tip_info_list.append(&mut tip_info_list_2); @@ -1268,19 +1244,11 @@ where ); let _ = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); let _ = node_2 .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); } @@ -1510,11 +1478,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed _one_ block @@ -1763,11 +1727,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed all stacks blocks -- one on each burn chain fork @@ -2065,11 +2025,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed _one_ block @@ -2318,11 +2274,7 @@ where ); let tip_info_list = node .chainstate - .process_blocks_at_tip( - connect_burnchain_db(&burn_node.burnchain).conn(), - &mut burn_node.sortdb, - 2, - ) + .process_blocks_at_tip(&mut burn_node.sortdb, 2) .unwrap(); // processed all stacks blocks -- one on each burn chain fork @@ -2630,7 +2582,6 @@ fn miner_trace_replay_randomized(miner_trace: &mut TestMinerTrace) { let tip_info_list = node .chainstate .process_blocks_at_tip( - connect_burnchain_db(&miner_trace.burn_node.burnchain).conn(), &mut miner_trace.burn_node.sortdb, expected_num_blocks, ) @@ -2658,8 +2609,6 @@ fn miner_trace_replay_randomized(miner_trace: &mut TestMinerTrace) { let tip_info_list = node .chainstate .process_blocks_at_tip( - connect_burnchain_db(&miner_trace.burn_node.burnchain) - .conn(), &mut miner_trace.burn_node.sortdb, expected_num_blocks, ) diff --git a/stackslib/src/cli.rs b/stackslib/src/cli.rs index 2d59f2d1de..27920dfb45 100644 --- a/stackslib/src/cli.rs +++ b/stackslib/src/cli.rs @@ -30,7 +30,6 @@ use stacks_common::types::sqlite::NO_PARAMS; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::VRFProof; -use crate::burnchains::db::BurnchainDB; use crate::burnchains::Burnchain; use crate::chainstate::burn::db::sortdb::{ get_ancestor_sort_id, SortitionDB, SortitionHandleContext, @@ -553,8 +552,6 @@ fn replay_staging_block(db_path: &str, index_block_hash_hex: &str, conf: Option< let block_id = StacksBlockId::from_hex(index_block_hash_hex).unwrap(); let chain_state_path = format!("{db_path}/chainstate/"); let sort_db_path = format!("{db_path}/burnchain/sortition"); - let burn_db_path = format!("{db_path}/burnchain/burnchain.sqlite"); - let burnchain_blocks_db = BurnchainDB::open(&burn_db_path, false).unwrap(); let conf = conf.unwrap_or(&DEFAULT_MAINNET_CONFIG); @@ -613,7 +610,6 @@ fn replay_staging_block(db_path: &str, index_block_hash_hex: &str, conf: Option< sort_tx, chainstate_tx, clarity_instance, - &burnchain_blocks_db, &parent_header_info, &next_staging_block.parent_microblock_hash, next_staging_block.parent_microblock_seq, @@ -631,8 +627,6 @@ fn replay_staging_block(db_path: &str, index_block_hash_hex: &str, conf: Option< fn replay_mock_mined_block(db_path: &str, block: AssembledAnchorBlock, conf: Option<&Config>) { let chain_state_path = format!("{db_path}/chainstate/"); let sort_db_path = format!("{db_path}/burnchain/sortition"); - let burn_db_path = format!("{db_path}/burnchain/burnchain.sqlite"); - let burnchain_blocks_db = BurnchainDB::open(&burn_db_path, false).unwrap(); let conf = conf.unwrap_or(&DEFAULT_MAINNET_CONFIG); @@ -687,7 +681,6 @@ fn replay_mock_mined_block(db_path: &str, block: AssembledAnchorBlock, conf: Opt sort_tx, chainstate_tx, clarity_instance, - &burnchain_blocks_db, &parent_header_info, &block.anchored_block.header.parent_microblock, block.anchored_block.header.parent_microblock_sequence, @@ -707,7 +700,6 @@ fn replay_block( mut sort_tx: IndexDBTx, mut chainstate_tx: ChainstateTx, clarity_instance: &mut ClarityInstance, - burnchain_blocks_db: &BurnchainDB, parent_header_info: &StacksHeaderInfo, parent_microblock_hash: &BlockHeaderHash, parent_microblock_seq: u16, @@ -799,14 +791,6 @@ fn replay_block( assert_eq!(*parent_microblock_hash, last_microblock_hash); assert_eq!(parent_microblock_seq, last_microblock_seq); - let block_am = StacksChainState::find_stacks_tip_affirmation_map( - burnchain_blocks_db, - sort_tx.tx(), - block_consensus_hash, - block_hash, - ) - .unwrap(); - let pox_constants = sort_tx.context.pox_constants.clone(); match StacksChainState::append_block( @@ -824,7 +808,6 @@ fn replay_block( &next_microblocks, block_commit_burn, block_sortition_burn, - block_am.weight(), true, ) { Ok((receipt, _, _)) => { diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 71e2df44c4..791254aec2 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; @@ -48,10 +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, - BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT, BITCOIN_TESTNET_STACKS_25_BURN_HEIGHT, - BITCOIN_TESTNET_STACKS_25_REORGED_HEIGHT, 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; @@ -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), @@ -1593,25 +1589,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. @@ -1679,7 +1656,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), } @@ -1738,12 +1714,6 @@ pub const EPOCH_CONFIG_3_0_0: &str = "3.0"; pub const EPOCH_CONFIG_3_1_0: &str = "3.1"; pub const EPOCH_CONFIG_3_2_0: &str = "3.2"; -#[derive(Clone, Deserialize, Default, Debug)] -pub struct AffirmationOverride { - pub reward_cycle: u64, - pub affirmation: String, -} - #[derive(Clone, Deserialize, Default, Debug)] #[serde(deny_unknown_fields)] pub struct BurnchainConfigFile { @@ -1782,76 +1752,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, @@ -1860,7 +1765,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); @@ -1879,25 +1783,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 { @@ -2005,7 +1890,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), @@ -2128,8 +2012,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,48 +2124,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 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 - /// 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. @@ -2589,9 +2429,6 @@ impl Default for NodeConfig { marf_defer_hashing: true, 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, chain_liveness_poll_time_secs: 300, @@ -3929,9 +3766,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 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). pub chain_liveness_poll_time_secs: Option, @@ -4009,16 +3843,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), - // 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. - 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, @@ -4916,104 +4740,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 diff --git a/stackslib/src/core/mod.rs b/stackslib/src/core/mod.rs index 6c8fbe2e46..974d335c5f 100644 --- a/stackslib/src/core/mod.rs +++ b/stackslib/src/core/mod.rs @@ -123,11 +123,6 @@ pub const BITCOIN_TESTNET_STACKS_30_BURN_HEIGHT: u64 = 30_000_000; pub const BITCOIN_TESTNET_STACKS_31_BURN_HEIGHT: u64 = 30_000_001; pub const BITCOIN_TESTNET_STACKS_32_BURN_HEIGHT: u64 = 30_000_002; -/// This constant sets the approximate testnet bitcoin height at which 2.5 Xenon -/// was reorged back to 2.5 instantiation. This is only used to calculate the -/// expected affirmation maps (so it only must be accurate to the reward cycle). -pub const BITCOIN_TESTNET_STACKS_25_REORGED_HEIGHT: u64 = 2_586_000; - pub const BITCOIN_REGTEST_FIRST_BLOCK_HEIGHT: u64 = 0; pub const BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP: u32 = 0; pub const BITCOIN_REGTEST_FIRST_BLOCK_HASH: &str = diff --git a/stackslib/src/core/tests/mod.rs b/stackslib/src/core/tests/mod.rs index 5599d3fb5c..3bb28f66c0 100644 --- a/stackslib/src/core/tests/mod.rs +++ b/stackslib/src/core/tests/mod.rs @@ -144,7 +144,6 @@ pub fn make_block( &new_index_hash, &new_tip_info, &ExecutionCost::ZERO, - block_height, ) .unwrap(); diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 8961eb413e..16aec03273 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -768,7 +768,7 @@ check if the associated microblocks can be downloaded let pox_consts = PoxConstants::mainnet_default(); let result = sort_conn - .get_chosen_pox_anchor_check_position_v205( + .get_chosen_pox_anchor_check_position( &eval_tip.burn_header_hash, &pox_consts, false, @@ -1569,12 +1569,7 @@ check if the associated microblocks can be downloaded let sortition_tx = new_sortition_db.tx_handle_begin(&sortition_tip).unwrap(); let null_event_dispatcher: Option<&DummyEventDispatcher> = None; let receipts = new_chainstate - .process_blocks( - old_burnchaindb.conn(), - sortition_tx, - 1, - null_event_dispatcher, - ) + .process_blocks(sortition_tx, 1, null_event_dispatcher) .unwrap(); if receipts.is_empty() { break; @@ -1942,11 +1937,9 @@ fn analyze_sortition_mev(argv: Vec) { &burn_block.header.parent_block_hash, &tip_sort_id, &burnchain, - &burnchaindb, &mut chainstate, &mut sortdb, &OnChainRewardSetProvider::new(), - false, ) .unwrap(); diff --git a/stackslib/src/net/api/getinfo.rs b/stackslib/src/net/api/getinfo.rs index 23600366ab..617648af5b 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: 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(), - }), last_pox_anchor: Some(RPCLastPoxAnchorData { anchor_block_hash: network.last_anchor_block_hash.clone(), anchor_block_txid: network.last_anchor_block_txid.clone(), diff --git a/stackslib/src/net/api/tests/mod.rs b/stackslib/src/net/api/tests/mod.rs index d8c97e6bbe..8471bba8e9 100644 --- a/stackslib/src/net/api/tests/mod.rs +++ b/stackslib/src/net/api/tests/mod.rs @@ -684,12 +684,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); @@ -698,12 +693,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); @@ -1177,12 +1167,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 { @@ -1228,12 +1213,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 bbf07a6c3a..59c7151ca7 100644 --- a/stackslib/src/net/inv/epoch2x.rs +++ b/stackslib/src/net/inv/epoch2x.rs @@ -1758,16 +1758,13 @@ 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. + /// Determine at which reward cycle to begin scanning inventories for this particular neighbor. + pub(crate) fn get_block_scan_start( + &self, + sortdb: &SortitionDB, + peer_block_reward_cycle: u64, + ) -> u64 { + // 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()) @@ -1786,18 +1783,16 @@ 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); + // NOTE: include the last full reward cycle + let inv_rescan_rc = + stacks_tip_rc.saturating_sub(cmp::max(1, self.connection_opts.inv_reward_cycles)); - let rescan_rc = cmp::min(am_rescan_rc, start_reward_cycle); + let rescan_rc = std::cmp::min(inv_rescan_rc, peer_block_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}"; + "stacks_tip_rc" => stacks_tip_rc, + "peer_block_rc" => peer_block_reward_cycle, + "inv_rescan_rc" => inv_rescan_rc, ); rescan_rc } @@ -1817,8 +1812,7 @@ impl PeerNetwork { Some(x) => x, None => { // proceed to block scan - let scan_start_rc = self.get_block_scan_start(sortdb); - + let scan_start_rc = self.get_block_scan_start(sortdb, stats.block_reward_cycle); debug!("{:?}: cannot make any more GetPoxInv requests for {:?}; proceeding to block inventory scan at reward cycle {}", &self.local_peer, nk, scan_start_rc); stats.reset_block_scan(scan_start_rc); return Ok(()); @@ -1877,7 +1871,7 @@ impl PeerNetwork { // proceed with block scan. // If we're in IBD, then this is an always-allowed peer and we should // react to divergences by deepening our rescan. - let scan_start_rc = self.get_block_scan_start(sortdb); + let scan_start_rc = self.get_block_scan_start(sortdb, stats.block_reward_cycle); debug!( "{:?}: proceeding to block inventory scan for {:?} (diverged) at reward cycle {} (ibd={})", &self.local_peer, nk, scan_start_rc, ibd @@ -1978,7 +1972,7 @@ impl PeerNetwork { } // proceed to block scan. - let scan_start = self.get_block_scan_start(sortdb); + let scan_start = self.get_block_scan_start(sortdb, stats.block_reward_cycle); debug!( "{:?}: proceeding to block inventory scan for {:?} at reward cycle {}", &self.local_peer, nk, scan_start @@ -2360,11 +2354,21 @@ impl PeerNetwork { let broken_peers = inv_state.get_broken_peers(); let dead_peers = inv_state.get_dead_peers(); + let local_rc = network.burnchain.block_height_to_reward_cycle(network.stacks_tip.burnchain_height).unwrap_or(0); + + // find peer with lowest block_reward_cycle + let lowest_block_reward_cycle = inv_state + .block_stats + .iter() + .map(|(_nk, stats)| stats.block_reward_cycle) + .fold(local_rc, |min_block_reward_cycle, rc| cmp::min(rc, min_block_reward_cycle)); + // hint to downloader as to where to begin scanning next time inv_state.block_sortition_start = ibd_diverged_height .unwrap_or(network.burnchain.reward_cycle_to_block_height( network.get_block_scan_start( sortdb, + lowest_block_reward_cycle ), )) .saturating_sub(sortdb.first_block_height); diff --git a/stackslib/src/net/mod.rs b/stackslib/src/net/mod.rs index 2a8bb509d7..6006581b83 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::{Error as burnchain_error, Txid}; use crate::chainstate::burn::db::sortdb::SortitionDB; use crate::chainstate::burn::ConsensusHash; @@ -3552,7 +3551,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); @@ -3784,14 +3783,6 @@ pub mod test { blockstack_ops, ) .unwrap(); - - Burnchain::process_affirmation_maps( - burnchain, - &mut burnchain_db, - &indexer, - block_header.block_height, - ) - .unwrap(); } /// Generate and commit the next burnchain block with the given block operations. @@ -4449,7 +4440,6 @@ pub mod test { &mut sortdb, &self.config.burnchain, &OnChainRewardSetProvider::new(), - true, ) { Ok(recipients) => { block_commit_op.commit_outs = match recipients { diff --git a/stackslib/src/net/p2p.rs b/stackslib/src/net/p2p.rs index 1ad989a195..1b6e228019 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, @@ -696,10 +689,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( @@ -4741,9 +4730,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, @@ -4903,38 +4891,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 @@ -4957,21 +4913,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 { @@ -5544,7 +5485,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/download/epoch2x.rs b/stackslib/src/net/tests/download/epoch2x.rs index 883d40eb76..f4f512e2e4 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(); diff --git a/stackslib/src/net/tests/inv/epoch2x.rs b/stackslib/src/net/tests/inv/epoch2x.rs index 222a6a3546..1d2ff1a1a6 100644 --- a/stackslib/src/net/tests/inv/epoch2x.rs +++ b/stackslib/src/net/tests/inv/epoch2x.rs @@ -22,7 +22,6 @@ use crate::burnchains::bitcoin::indexer::BitcoinIndexer; use crate::burnchains::db::BurnchainHeaderReader; use crate::burnchains::tests::BURNCHAIN_TEST_BLOCK_TIME; use crate::burnchains::{Burnchain, BurnchainBlockHeader, BurnchainView, PoxConstants}; -use crate::chainstate::burn::db::sortdb::SortitionHandleConn; use crate::chainstate::coordinator::tests::get_burnchain; use crate::net::chat::ConversationP2P; use crate::net::inv::inv2x::*; @@ -774,7 +773,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(()) @@ -1250,36 +1249,43 @@ fn test_inv_sync_start_reward_cycle() { let block_scan_start = peer_1 .network - .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 10); assert_eq!(block_scan_start, 7); peer_1.network.connection_opts.inv_reward_cycles = 1; let block_scan_start = peer_1 .network - .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 10); assert_eq!(block_scan_start, 7); peer_1.network.connection_opts.inv_reward_cycles = 2; let block_scan_start = peer_1 .network - .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 10); assert_eq!(block_scan_start, 6); peer_1.network.connection_opts.inv_reward_cycles = 3; let block_scan_start = peer_1 .network - .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 10); assert_eq!(block_scan_start, 5); peer_1.network.connection_opts.inv_reward_cycles = 300; let block_scan_start = peer_1 .network - .get_block_scan_start(peer_1.sortdb.as_ref().unwrap()); + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 10); assert_eq!(block_scan_start, 0); + + peer_1.network.connection_opts.inv_reward_cycles = 0; + + let block_scan_start = peer_1 + .network + .get_block_scan_start(peer_1.sortdb.as_ref().unwrap(), 1); + assert_eq!(block_scan_start, 1); } #[test] @@ -1789,227 +1795,3 @@ fn test_sync_inv_2_peers_unstable() { assert!(!peer_2_inv.has_ith_microblock_stream(num_blocks - stable_confs)); }) } - -#[test] -#[ignore] -fn test_sync_inv_2_peers_different_pox_vectors() { - with_timeout(600, || { - let mut peer_1_config = TestPeerConfig::new(function_name!(), 0, 0); - let mut peer_2_config = TestPeerConfig::new(function_name!(), 0, 0); - - peer_1_config.connection_opts.inv_reward_cycles = 10; - peer_2_config.connection_opts.inv_reward_cycles = 10; - - let reward_cycle_length = peer_1_config.burnchain.pox_constants.reward_cycle_length as u64; - assert_eq!(reward_cycle_length, 5); - - let mut peer_1 = TestPeer::new(peer_1_config); - let mut peer_2 = TestPeer::new(peer_2_config); - - peer_1.add_neighbor(&mut peer_2.to_neighbor(), None, true); - peer_2.add_neighbor(&mut peer_1.to_neighbor(), None, true); - - let num_blocks = GETPOXINV_MAX_BITLEN * 3; - - let first_stacks_block_height = { - let sn = - SortitionDB::get_canonical_burn_chain_tip(peer_1.sortdb.as_ref().unwrap().conn()) - .unwrap(); - sn.block_height + 1 - }; - - // only peer 2 makes progress after the point of stability. - for i in 0..num_blocks { - let (mut burn_ops, stacks_block, microblocks) = peer_2.make_default_tenure(); - - let (_, burn_header_hash, consensus_hash) = - peer_2.next_burnchain_block(burn_ops.clone()); - peer_2.process_stacks_epoch_at_tip(&stacks_block, µblocks); - - TestPeer::set_ops_burn_header_hash(&mut burn_ops, &burn_header_hash); - - peer_1.next_burnchain_block_raw(burn_ops.clone()); - if i < num_blocks - reward_cycle_length * 2 { - peer_1.process_stacks_epoch_at_tip(&stacks_block, µblocks); - } - } - - let peer_1_pox_id = { - let tip_sort_id = - SortitionDB::get_canonical_sortition_tip(peer_1.sortdb.as_ref().unwrap().conn()) - .unwrap(); - let ic = peer_1.sortdb.as_ref().unwrap().index_conn(); - let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap(); - sortdb_reader.get_pox_id().unwrap() - }; - - let peer_2_pox_id = { - let tip_sort_id = - SortitionDB::get_canonical_sortition_tip(peer_2.sortdb.as_ref().unwrap().conn()) - .unwrap(); - let ic = peer_2.sortdb.as_ref().unwrap().index_conn(); - let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap(); - sortdb_reader.get_pox_id().unwrap() - }; - - // peers must have different PoX bit vectors -- peer 1 didn't see the last reward cycle - assert_eq!( - peer_1_pox_id, - PoxId::from_bools(vec![ - true, true, true, true, true, true, true, true, true, true, false - ]) - ); - assert_eq!( - peer_2_pox_id, - PoxId::from_bools(vec![ - true, true, true, true, true, true, true, true, true, true, true - ]) - ); - - let num_burn_blocks = { - let sn = - SortitionDB::get_canonical_burn_chain_tip(peer_1.sortdb.as_ref().unwrap().conn()) - .unwrap(); - sn.block_height + 1 - }; - - let mut round = 0; - let mut inv_1_count = 0; - let mut inv_2_count = 0; - let mut peer_1_sorts = 0; - let mut peer_2_sorts = 0; - - while inv_1_count < reward_cycle_length * 4 - || inv_2_count < num_blocks - reward_cycle_length * 2 - || peer_1_sorts < reward_cycle_length * 9 + 1 - || peer_2_sorts < reward_cycle_length * 9 + 1 - { - let _ = peer_1.step(); - let _ = peer_2.step(); - - // peer 1 should see that peer 2 has all blocks for reward cycles 5 through 9 - if let Some(ref inv) = peer_1.network.inv_state { - inv_1_count = inv.get_inv_num_blocks(&peer_2.to_neighbor().addr); - peer_1_sorts = inv.get_inv_sortitions(&peer_2.to_neighbor().addr); - }; - - // peer 2 should see that peer 1 has all blocks up to where we stopped feeding them to - // it - if let Some(ref inv) = peer_2.network.inv_state { - inv_2_count = inv.get_inv_num_blocks(&peer_1.to_neighbor().addr); - peer_2_sorts = inv.get_inv_sortitions(&peer_1.to_neighbor().addr); - }; - - if let Some(ref inv) = peer_1.network.inv_state { - info!("Peer 1 stats: {:?}", &inv.block_stats); - assert!(inv.get_broken_peers().is_empty()); - assert!(inv.get_dead_peers().is_empty()); - assert!(inv.get_diverged_peers().is_empty()); - } - - if let Some(ref inv) = peer_2.network.inv_state { - info!("Peer 2 stats: {:?}", &inv.block_stats); - assert!(inv.get_broken_peers().is_empty()); - assert!(inv.get_dead_peers().is_empty()); - assert!(inv.get_diverged_peers().is_empty()); - } - - round += 1; - - test_debug!( - "\n\ninv_1_count = {} 0 { - assert!(peer_2_inv.has_ith_microblock_stream(i + first_stacks_block_height)); - } else { - assert!(!peer_2_inv.has_ith_microblock_stream(i + first_stacks_block_height)); - } - } - - // peer 2 should have learned about all of peer 1's blocks - for i in 0..(num_blocks - 2 * reward_cycle_length) { - assert!(peer_1_inv.has_ith_block(i + first_stacks_block_height)); - if i > 0 && i != num_blocks - 2 * reward_cycle_length - 1 { - // peer 1 doesn't have the final microblock stream, since no anchor block confirmed it - assert!(peer_1_inv.has_ith_microblock_stream(i + first_stacks_block_height)); - } - } - - assert!(!peer_1_inv.has_ith_block(reward_cycle_length * 4)); - assert!(!peer_1_inv.has_ith_microblock_stream(reward_cycle_length * 4)); - - assert!(!peer_2_inv.has_ith_block(num_blocks - 2 * reward_cycle_length)); - assert!(!peer_2_inv.has_ith_microblock_stream(num_blocks - 2 * reward_cycle_length)); - }) -} 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." + ); } }