From b1f7b52f7f8c50d6a4193a35be96489bb81ff554 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 8 Jan 2023 23:35:50 -0500 Subject: [PATCH 01/27] fix: fix #3470 by evaluating runtime cost after argument evaluation (as happened before when comparators were native functions) --- clarity/src/vm/functions/arithmetic.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clarity/src/vm/functions/arithmetic.rs b/clarity/src/vm/functions/arithmetic.rs index a1b6e945b..ec493d9de 100644 --- a/clarity/src/vm/functions/arithmetic.rs +++ b/clarity/src/vm/functions/arithmetic.rs @@ -389,9 +389,9 @@ fn special_geq_v1( context: &LocalContext, ) -> InterpreterResult { check_argument_count(2, args)?; - runtime_cost(ClarityCostFunction::Geq, env, 0)?; let a = eval(&args[0], env, context)?; let b = eval(&args[1], env, context)?; + runtime_cost(ClarityCostFunction::Geq, env, args.len())?; type_force_binary_comparison_v1!(geq, a, b) } @@ -432,9 +432,9 @@ fn special_leq_v1( context: &LocalContext, ) -> InterpreterResult { check_argument_count(2, args)?; - runtime_cost(ClarityCostFunction::Leq, env, 0)?; let a = eval(&args[0], env, context)?; let b = eval(&args[1], env, context)?; + runtime_cost(ClarityCostFunction::Leq, env, args.len())?; type_force_binary_comparison_v1!(leq, a, b) } @@ -474,9 +474,9 @@ fn special_greater_v1( context: &LocalContext, ) -> InterpreterResult { check_argument_count(2, args)?; - runtime_cost(ClarityCostFunction::Ge, env, 0)?; let a = eval(&args[0], env, context)?; let b = eval(&args[1], env, context)?; + runtime_cost(ClarityCostFunction::Ge, env, args.len())?; type_force_binary_comparison_v1!(greater, a, b) } @@ -516,9 +516,9 @@ fn special_less_v1( context: &LocalContext, ) -> InterpreterResult { check_argument_count(2, args)?; - runtime_cost(ClarityCostFunction::Le, env, 0)?; let a = eval(&args[0], env, context)?; let b = eval(&args[1], env, context)?; + runtime_cost(ClarityCostFunction::Le, env, args.len())?; type_force_binary_comparison_v1!(less, a, b) } From e5fdb87035503f54843d0977076aa5a4d99b8542 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 8 Jan 2023 23:36:32 -0500 Subject: [PATCH 02/27] fix: gate global variable lookup by Clarity version (#3471) --- clarity/src/vm/variables.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clarity/src/vm/variables.rs b/clarity/src/vm/variables.rs index e88d963e3..ca5154b81 100644 --- a/clarity/src/vm/variables.rs +++ b/clarity/src/vm/variables.rs @@ -63,7 +63,9 @@ pub fn lookup_reserved_variable( _context: &LocalContext, env: &mut Environment, ) -> Result> { - if let Some(variable) = NativeVariables::lookup_by_name(name) { + if let Some(variable) = + NativeVariables::lookup_by_name_at_version(name, env.contract_context.get_clarity_version()) + { match variable { NativeVariables::TxSender => { let sender = env From b0a2c8492a176de7a7d95103f0338a6ecd016215 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 8 Jan 2023 23:36:53 -0500 Subject: [PATCH 03/27] fix: the `marf-get` command in `stacks-inspect` is mainly used on MARFs with external blobs, so expect that --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 331c5e7c2..65c73c71a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -943,7 +943,8 @@ simulating a miner. let itip = StacksBlockHeader::make_index_block_hash(&consensustip, &tip); let key = &argv[5]; - let marf_opts = MARFOpenOpts::default(); + let mut marf_opts = MARFOpenOpts::default(); + marf_opts.external_blobs = true; let mut marf = MARF::from_path(path, marf_opts).unwrap(); let res = marf.get(&itip, key).expect("MARF error."); match res { From 5694fb4483c63ca3d86d0d969abadca29c4b0b77 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Tue, 10 Jan 2023 17:14:53 -0500 Subject: [PATCH 04/27] fix: unconditionally wakeup the coordinator to check for more work to do every 5 minutes --- testnet/stacks-node/src/run_loop/neon.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 12c0e676e..a87642c86 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -730,6 +730,7 @@ impl RunLoop { sortdb: &SortitionDB, chain_state_db: &StacksChainState, last_burn_pox_reorg_recover_time: &mut u128, + last_announce_time: &mut u128, ) { let delay = cmp::max( 1, @@ -809,6 +810,7 @@ impl RunLoop { debug!("Drive burn block processing: possible PoX reorg (sortition tip: {}, heaviest: {}, {} = heaviest_affirmation_map.len() && sortition_tip_affirmation_map.len() <= canonical_affirmation_map.len() { @@ -820,6 +822,7 @@ impl RunLoop { debug!("Drive burnchain processing: possible PoX reorg from unprocessed anchor block(s) (sortition tip: {}, heaviest: {}, canonical: {})", &sortition_tip_affirmation_map, &heaviest_affirmation_map, &canonical_affirmation_map); globals.coord().announce_new_burn_block(); globals.coord().announce_new_stacks_block(); + *last_announce_time = get_epoch_time_secs().into(); } } } else { @@ -833,6 +836,16 @@ impl RunLoop { } *last_burn_pox_reorg_recover_time = get_epoch_time_secs().into(); + + // unconditionally bump every 5 minutes, just in case. + // this can get the node un-stuck if we're short on sortition processing but are unable to + // sync with the remote node because it keeps NACK'ing us, leading to a runloop stall. + if *last_announce_time + 300 < get_epoch_time_secs().into() { + debug!("Drive burnchain processing: unconditional bump"); + globals.coord().announce_new_burn_block(); + globals.coord().announce_new_stacks_block(); + *last_announce_time = get_epoch_time_secs().into(); + } } /// In a separate thread, periodically drive coordinator liveness by checking to see if there's @@ -846,6 +859,7 @@ impl RunLoop { ) { let mut last_burn_pox_reorg_recover_time = 0; let mut last_stacks_pox_reorg_recover_time = 0; + let mut last_burn_announce_time = 0; debug!("Chain-liveness thread start!"); @@ -858,6 +872,7 @@ impl RunLoop { &sortdb, &chain_state_db, &mut last_burn_pox_reorg_recover_time, + &mut last_burn_announce_time, ); Self::drive_pox_reorg_stacks_block_processing( &globals, From a91109f8ef79333c90c36169d0e4a717d4a1f232 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Tue, 10 Jan 2023 22:48:19 -0500 Subject: [PATCH 05/27] fix: always_use_affirmation_maps is now false by default, since it prevents 2.1 nodes from syncing with testnet --- testnet/stacks-node/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index eec52d852..6ae3ed306 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -1726,7 +1726,7 @@ impl NodeConfig { marf_defer_hashing: true, pox_sync_sample_secs: 30, use_test_genesis_chainstate: None, - always_use_affirmation_maps: true, + always_use_affirmation_maps: false, require_affirmed_anchor_blocks: true, fault_injection_hide_blocks: false, } From 5dc9593ddab33f6f0c7cb27c6b8b33680417fc0b Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 09:30:52 -0500 Subject: [PATCH 06/27] chore: make affirmations public since we will need to construct affirmation maps elsewhere --- src/burnchains/affirmation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/burnchains/affirmation.rs b/src/burnchains/affirmation.rs index b63b87d1b..56612e36c 100644 --- a/src/burnchains/affirmation.rs +++ b/src/burnchains/affirmation.rs @@ -290,7 +290,7 @@ impl AffirmationMapEntry { /// list behind accessor and mutator methods. #[derive(Clone, PartialEq)] pub struct AffirmationMap { - affirmations: Vec, + pub affirmations: Vec, } impl fmt::Display for AffirmationMapEntry { From 19c4270d52840c8d94011be4da3ee45b56515c8d Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 09:31:58 -0500 Subject: [PATCH 07/27] feat: add helper method to get the last reward cycle in epoch 2.05 --- src/chainstate/burn/db/sortdb.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index 2c6e0160f..8fd4c4753 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -3771,7 +3771,6 @@ impl SortitionDB { /// Find the affirmation map represented by a given sortition ID. pub fn find_sortition_tip_affirmation_map( - _burnchain_db: &BurnchainDB, sortition_db: &SortitionDB, tip_id: &SortitionId, ) -> Result { @@ -4417,6 +4416,22 @@ impl SortitionDB { query_row(conn, sql, args) } + /// Get the last reward cycle in epoch 2.05 + pub fn get_last_epoch_2_05_reward_cycle(&self) -> Result { + let epochs = SortitionDB::get_stacks_epochs(&self.conn())?; + + for epoch in epochs { + if epoch.epoch_id == StacksEpochId::Epoch2_05 { + return Ok(self + .pox_constants + .block_height_to_reward_cycle(self.first_block_height, epoch.end_height) + .expect("FATAL: end block of epoch 2.05 is before system start height")); + } + } + + Ok(u64::MAX) + } + /// Get the latest block snapshot on this fork where a sortition occured. /// Search snapshots up to (but excluding) the given block height. /// Will always return a snapshot -- even if it's the initial sentinel snapshot. From c71ee0c9d5c4c42460120fc6911934a6fa44842d Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 09:32:20 -0500 Subject: [PATCH 08/27] fix: the heaviest affirmation map calculation is different in epoch 2.05 -- it's the same as the sortition affirmation map. Normally, there's no difference because the heaviest block-commit by BTC spend (the 2.1 rule) is the same as the Stacks block-commit with the most confirmations (the 2.05 rule). However, in the current testnet, this is not the case for reward cycle #360. This means that once the 2.1 rules take effect, the node will get stuck -- it will think that the stacks and sortition affirmation maps are not in sync with the heaviest affirmation map, and the node will never sync as a result. This fix makes it so the heaviest affirmation map -- and by extension, the canonical affirmation map -- is calculated from the sortition affirmation map for reward cycle in epoch 2.05 and earlier. --- src/chainstate/coordinator/mod.rs | 228 +++++++++++++++++++----------- 1 file changed, 144 insertions(+), 84 deletions(-) diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index ddc4e8c9c..8411b6083 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -682,6 +682,70 @@ fn forget_orphan_stacks_blocks( Ok(()) } +/// 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, + 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 = SortitionDB::find_sortition_tip_affirmation_map(&sortition_db, sortition_tip)?; + + let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( + burnchain_blocks_db.conn(), + burnchain, + )?; + + let mut am_entries = vec![]; + for i in 0..last_2_05_rc { + if i < sort_am.affirmations.len() { + am_entries.push(sort_am.affirmations[i]); + } + } + for i in last_2_05_rc..heaviest_am.len() { + am_entries.push(heaviest_am.affirmations[i]); + } + + Ok(AffirmationMap::new(am_entries)) +} + +/// 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, + 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 = SortitionDB::find_sortition_tip_affirmation_map(&sortition_db, sortition_tip)?; + + let canonical_am = StacksChainState::find_canonical_affirmation_map( + burnchain, + burnchain_blocks_db, + chain_state_db, + )?; + + let mut am_entries = vec![]; + for i in 0..last_2_05_rc { + if i < sort_am.affirmations.len() { + am_entries.push(sort_am.affirmations[i]); + } + } + for i in last_2_05_rc..canonical_am.len() { + am_entries.push(canonical_am.affirmations[i]); + } + + Ok(AffirmationMap::new(am_entries)) +} + impl< 'a, T: BlockEventDispatcher, @@ -714,17 +778,39 @@ impl< let sn = SortitionDB::get_block_snapshot(self.sortition_db.conn(), &sort_id)? .expect("FATAL: have sortition ID without snapshot"); - let sort_am = SortitionDB::find_sortition_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &sort_id, - )?; + let sort_am = + SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &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_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_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( @@ -882,6 +968,7 @@ impl< 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 last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; let stacks_tip_affirmation_map = StacksChainState::find_stacks_tip_affirmation_map( &self.burnchain_blocks_db, @@ -899,23 +986,12 @@ impl< } }; - let sortition_tip_affirmation_map = SortitionDB::find_sortition_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &sortition_tip, - )?; + let sortition_tip_affirmation_map = + SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &sortition_tip)?; - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - self.burnchain_blocks_db.conn(), - &self.burnchain, - )?; + let heaviest_am = self.get_heaviest_affirmation_map(&sortition_tip)?; - let canonical_affirmation_map = StacksChainState::find_canonical_affirmation_map( - &self.burnchain, - &self.burnchain_blocks_db, - &self.chain_state_db, - ) - .expect("FATAL: failed to load canonical Stacks affirmation map"); + 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 `{}`", @@ -1013,6 +1089,8 @@ impl< // by a subsequent prepare phase. let mut last_invalidate_start_block = start_height; let mut valid_sortitions = vec![]; + let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; + 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(); @@ -1026,9 +1104,9 @@ impl< sn.block_height, &sn_am, &compare_am, - &compare_am.has_prefix(&sn_am), + &compare_am.has_sortition_prefix(&sn_am, last_2_05_rc), ); - if compare_am.has_prefix(&sn_am) { + if compare_am.has_sortition_prefix(&sn_am, last_2_05_rc) { // have already processed this sortitoin debug!("Already processed sortition {} at height {} with AM `{}` on comparative affirmation map {}", &sn.sortition_id, sn.block_height, &sn_am, &compare_am); found = true; @@ -1085,6 +1163,8 @@ impl< // as valid. let mut valid_sortitions = vec![]; + let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; + 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 { @@ -1107,19 +1187,16 @@ impl< // of the heaviest affirmation map let mut found_diverged = false; for sort_id in sort_ids.iter() { - let sort_am = SortitionDB::find_sortition_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db, - &sort_id, - )?; + let sort_am = + SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &sort_id)?; debug!( "Compare {} as prefix of {}? {}", &compare_am, &sort_am, - compare_am.has_prefix(&sort_am) + compare_am.has_sortition_prefix(&sort_am, last_2_05_rc) ); - if compare_am.has_prefix(&sort_am) { + if compare_am.has_sortition_prefix(&sort_am, last_2_05_rc) { continue; } @@ -1133,9 +1210,9 @@ impl< "Compare {} as a prior prefix of {}? {}", &prior_compare_am, &prior_sort_am, - prior_compare_am.has_prefix(&prior_sort_am) + prior_compare_am.has_sortition_prefix(&prior_sort_am, last_2_05_rc) ); - if prior_compare_am.has_prefix(&prior_sort_am) { + if prior_compare_am.has_sortition_prefix(&prior_sort_am, last_2_05_rc) { // this is the first reward cycle where history diverged. found_diverged = true; debug!("{} diverges from {}", &sort_am, &compare_am); @@ -1269,6 +1346,8 @@ impl< "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)? .expect(&format!("FATAL: no sortition {}", &sortition_tip)) @@ -1279,10 +1358,7 @@ impl< .block_height_to_reward_cycle(sortition_height) .unwrap_or(0); - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - self.burnchain_blocks_db.conn(), - &self.burnchain, - )?; + let heaviest_am = self.get_heaviest_affirmation_map(&sortition_tip)?; if let Some(changed_reward_cycle) = self.check_chainstate_against_burnchain_affirmations()? @@ -1321,13 +1397,8 @@ impl< // 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 = - StacksChainState::find_canonical_affirmation_map( - &self.burnchain, - &self.burnchain_blocks_db, - &self.chain_state_db, - )?; + self.get_canonical_affirmation_map(&sortition_tip)?; let sort_am = SortitionDB::find_sortition_tip_affirmation_map( - &self.burnchain_blocks_db, &self.sortition_db, &sortition_tip, )?; @@ -1335,25 +1406,28 @@ impl< == sort_am.len() && canonical_affirmation_map != sort_am { - let diverged_rc = canonical_affirmation_map - .find_divergence(&sort_am) - .expect("FATAL: unequal affirmations maps should diverge"); - debug!( - "Sortition AM `{}` diverges from canonical AM `{}` at cycle {}", - &sort_am, &canonical_affirmation_map, 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, - )) + if let Some(diverged_rc) = + canonical_affirmation_map.find_divergence(&sort_am) + { + debug!( + "Sortition AM `{}` diverges from canonical AM `{}` at cycle {}", + &sort_am, &canonical_affirmation_map, 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 }; @@ -1635,7 +1709,7 @@ impl< &highest_valid_snapshot.sortition_id, &highest_valid_snapshot.burn_header_hash, highest_valid_snapshot.block_height, - &SortitionDB::find_sortition_tip_affirmation_map(&self.burnchain_blocks_db, &self.sortition_db, &highest_valid_snapshot.sortition_id)?, + &SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &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, @@ -1654,7 +1728,7 @@ impl< &highest_valid_snapshot.sortition_id, &highest_valid_snapshot.burn_header_hash, highest_valid_snapshot.block_height, - &SortitionDB::find_sortition_tip_affirmation_map(&self.burnchain_blocks_db, &self.sortition_db, &highest_valid_snapshot.sortition_id)?, + &SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &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, @@ -1776,19 +1850,6 @@ impl< Ok(None) } - /// Get the canonical affirmation map in the system. - /// This is the heaviest affirmation map, concatenated with our tentative affirmation status - /// for unaffirmed PoX anchor blocks (i.e. we treat them as affirmed if we have them, and - /// unaffirmed if we don't). - pub fn get_canonical_affirmation_map(&self) -> Result { - StacksChainState::find_canonical_affirmation_map( - &self.burnchain, - &self.burnchain_blocks_db, - &self.chain_state_db, - ) - .map_err(|e| e.into()) - } - /// Try to revalidate a sortition if it exists already. This can happen if the node flip/flops /// between two PoX forks. /// @@ -1953,12 +2014,11 @@ impl< }; let canonical_burnchain_tip = self.burnchain_blocks_db.get_canonical_chain_tip()?; - let canonical_affirmation_map = self.get_canonical_affirmation_map()?; + // let canonical_affirmation_map = self.get_canonical_affirmation_map()?; + let canonical_affirmation_map = + self.get_canonical_affirmation_map(&canonical_snapshot.sortition_id)?; - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - self.burnchain_blocks_db.conn(), - &self.burnchain, - )?; + let heaviest_am = self.get_heaviest_affirmation_map(&canonical_snapshot.sortition_id)?; debug!("Handle new canonical burnchain tip"; "height" => %canonical_burnchain_tip.block_height, @@ -2532,10 +2592,10 @@ impl< &winner_snapshot.winning_block_txid, )? { // affirmed? - let heaviest_am = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - self.burnchain_blocks_db.conn(), - &self.burnchain, - )?; + 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(), From fd76bb0d34ef586746ec7f86087287103e9aef65 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:06:54 -0500 Subject: [PATCH 09/27] fix: add a way to get the sortition AM from a Sortition DBTx --- src/chainstate/burn/db/sortdb.rs | 35 ++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index 8fd4c4753..e012a7192 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -3009,6 +3009,25 @@ impl SortitionDB { } } +impl<'a> SortitionDBTx<'a> { + 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 affirmation_map.len() > 0 { + Ok(AffirmationMap::new(affirmation_map.as_slice()[1..].to_vec())) + } else { + Ok(AffirmationMap::empty()) + } + } +} + impl<'a> SortitionDBConn<'a> { pub fn as_handle<'b>(&'b self, chain_tip: &SortitionId) -> SortitionHandleConn<'b> { SortitionHandleConn { @@ -3771,10 +3790,10 @@ impl SortitionDB { /// Find the affirmation map represented by a given sortition ID. pub fn find_sortition_tip_affirmation_map( - sortition_db: &SortitionDB, + &self, tip_id: &SortitionId, ) -> Result { - let ih = sortition_db.index_handle(tip_id); + 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 @@ -4418,13 +4437,17 @@ impl SortitionDB { /// Get the last reward cycle in epoch 2.05 pub fn get_last_epoch_2_05_reward_cycle(&self) -> Result { - let epochs = SortitionDB::get_stacks_epochs(&self.conn())?; + Self::static_get_last_epoch_2_05_reward_cycle(self.conn(), self.first_block_height, &self.pox_constants) + } + + /// Get the last reward cycle in epoch 2.05 + pub fn static_get_last_epoch_2_05_reward_cycle(conn: &DBConn, first_block_height: u64, pox_constants: &PoxConstants) -> Result { + let epochs = SortitionDB::get_stacks_epochs(conn)?; for epoch in epochs { if epoch.epoch_id == StacksEpochId::Epoch2_05 { - return Ok(self - .pox_constants - .block_height_to_reward_cycle(self.first_block_height, epoch.end_height) + return Ok(pox_constants + .block_height_to_reward_cycle(first_block_height, epoch.end_height) .expect("FATAL: end block of epoch 2.05 is before system start height")); } } From 21ac465fdc0487072c000d0e9c4299e32aac4ca8 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:07:34 -0500 Subject: [PATCH 10/27] fix: the stacks affirmation map must reflect 2.05 rules calculations --- src/chainstate/coordinator/mod.rs | 188 +++++++++++++++++++++++------- 1 file changed, 148 insertions(+), 40 deletions(-) diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 8411b6083..354108a64 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -693,7 +693,7 @@ pub fn static_get_heaviest_affirmation_map( ) -> Result { let last_2_05_rc = sortition_db.get_last_epoch_2_05_reward_cycle()? as usize; - let sort_am = SortitionDB::find_sortition_tip_affirmation_map(&sortition_db, sortition_tip)?; + 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(), @@ -725,7 +725,7 @@ pub fn static_get_canonical_affirmation_map( ) -> Result { let last_2_05_rc = sortition_db.get_last_epoch_2_05_reward_cycle()? as usize; - let sort_am = SortitionDB::find_sortition_tip_affirmation_map(&sortition_db, sortition_tip)?; + let sort_am = sortition_db.find_sortition_tip_affirmation_map(sortition_tip)?; let canonical_am = StacksChainState::find_canonical_affirmation_map( burnchain, @@ -746,6 +746,59 @@ pub fn static_get_canonical_affirmation_map( Ok(AffirmationMap::new(am_entries)) } +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 + )?; + + let mut am_entries = vec![]; + for i in 0..last_2_05_rc { + if i < sort_am.affirmations.len() { + am_entries.push(sort_am.affirmations[i]); + } + } + for i in last_2_05_rc..stacks_am.len() { + am_entries.push(stacks_am.affirmations[i]); + } + + Ok(AffirmationMap::new(am_entries)) +} + +/// 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< 'a, T: BlockEventDispatcher, @@ -779,7 +832,7 @@ impl< .expect("FATAL: have sortition ID without snapshot"); let sort_am = - SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &sort_id)?; + self.sortition_db.find_sortition_tip_affirmation_map(&sort_id)?; ret.push((sn, sort_am)); } @@ -821,6 +874,8 @@ impl< 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, @@ -843,14 +898,16 @@ impl< for hdr in all_headers { // load this block's affirmation map - let am = match StacksChainState::find_stacks_tip_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(), + &hdr.anchored_header.block_hash() ) { Ok(am) => am, - Err(ChainstateError::DBError(DBError::InvalidPoxSortition)) => { + Err(Error::ChainstateError(ChainstateError::DBError(DBError::InvalidPoxSortition))) => { debug!( "Stacks tip {}/{} is not on a valid sortition", &hdr.consensus_hash, @@ -970,12 +1027,6 @@ impl< SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortition_db.conn())?; let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - let stacks_tip_affirmation_map = StacksChainState::find_stacks_tip_affirmation_map( - &self.burnchain_blocks_db, - &self.sortition_db.conn(), - &canonical_ch, - &canonical_bhh, - )?; let sortition_tip = match &self.canonical_sortition_tip { Some(tip) => tip.clone(), @@ -985,9 +1036,16 @@ impl< 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 = - SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &sortition_tip)?; + self.sortition_db.find_sortition_tip_affirmation_map(&sortition_tip)?; let heaviest_am = self.get_heaviest_affirmation_map(&sortition_tip)?; @@ -1104,9 +1162,9 @@ impl< sn.block_height, &sn_am, &compare_am, - &compare_am.has_sortition_prefix(&sn_am, last_2_05_rc), + &compare_am.has_prefix(&sn_am), ); - if compare_am.has_sortition_prefix(&sn_am, last_2_05_rc) { + if compare_am.has_prefix(&sn_am) { // have already processed this sortitoin debug!("Already processed sortition {} at height {} with AM `{}` on comparative affirmation map {}", &sn.sortition_id, sn.block_height, &sn_am, &compare_am); found = true; @@ -1188,15 +1246,15 @@ impl< let mut found_diverged = false; for sort_id in sort_ids.iter() { let sort_am = - SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &sort_id)?; + self.sortition_db.find_sortition_tip_affirmation_map(&sort_id)?; debug!( "Compare {} as prefix of {}? {}", &compare_am, &sort_am, - compare_am.has_sortition_prefix(&sort_am, last_2_05_rc) + compare_am.has_prefix(&sort_am) ); - if compare_am.has_sortition_prefix(&sort_am, last_2_05_rc) { + if compare_am.has_prefix(&sort_am) { continue; } @@ -1210,9 +1268,9 @@ impl< "Compare {} as a prior prefix of {}? {}", &prior_compare_am, &prior_sort_am, - prior_compare_am.has_sortition_prefix(&prior_sort_am, last_2_05_rc) + prior_compare_am.has_prefix(&prior_sort_am) ); - if prior_compare_am.has_sortition_prefix(&prior_sort_am, last_2_05_rc) { + if prior_compare_am.has_prefix(&prior_sort_am) { // this is the first reward cycle where history diverged. found_diverged = true; debug!("{} diverges from {}", &sort_am, &compare_am); @@ -1398,8 +1456,7 @@ impl< // means that we have new anchor blocks to consider let canonical_affirmation_map = self.get_canonical_affirmation_map(&sortition_tip)?; - let sort_am = SortitionDB::find_sortition_tip_affirmation_map( - &self.sortition_db, + let sort_am = self.sortition_db.find_sortition_tip_affirmation_map( &sortition_tip, )?; let revalidation_params = if canonical_affirmation_map.len() @@ -1441,6 +1498,8 @@ impl< // 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( @@ -1450,11 +1509,14 @@ impl< &mut sort_tx, &self.chain_state_db.db(), )?; - let stacks_am = StacksChainState::find_stacks_tip_affirmation_map( + + let stacks_am = inner_static_get_stacks_tip_affirmation_map( &self.burnchain_blocks_db, - &mut sort_tx, + last_2_05_rc, + &sort_tx.find_sortition_tip_affirmation_map(&sortition_tip)?, + &sort_tx, &canonical_ch, - &canonical_bhh, + &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); @@ -1563,8 +1625,16 @@ impl< // 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 = StacksChainState::find_stacks_tip_affirmation_map(&self.burnchain_blocks_db, sort_tx, &canonical_ch, &canonical_bhh) - .expect("FATAL: failed to query stacks 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(&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); @@ -1611,8 +1681,16 @@ impl< // 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 = StacksChainState::find_stacks_tip_affirmation_map(&self.burnchain_blocks_db, sort_tx, &canonical_ch, &canonical_bhh) - .expect("FATAL: failed to query stacks 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(&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); @@ -1644,8 +1722,16 @@ impl< // 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 = StacksChainState::find_stacks_tip_affirmation_map(&self.burnchain_blocks_db, sort_tx, &canonical_ch, &canonical_bhh) - .expect("FATAL: failed to query stacks 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(&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); @@ -1704,16 +1790,24 @@ impl< )? .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, - &SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &highest_valid_snapshot.sortition_id)?, + &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, - &StacksChainState::find_stacks_tip_affirmation_map(&self.burnchain_blocks_db, &self.sortition_db.conn(), &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, &highest_valid_snapshot.canonical_stacks_tip_hash)?, + &stacks_tip_affirmation_map, &heaviest_am ); @@ -1723,16 +1817,24 @@ impl< 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, - &SortitionDB::find_sortition_tip_affirmation_map(&self.sortition_db, &highest_valid_snapshot.sortition_id)?, + &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, - &StacksChainState::find_stacks_tip_affirmation_map(&self.burnchain_blocks_db, &self.sortition_db.conn(), &highest_valid_snapshot.canonical_stacks_tip_consensus_hash, &highest_valid_snapshot.canonical_stacks_tip_hash)?, + &stacks_tip_affirmation_map, &heaviest_am ); } @@ -1977,6 +2079,8 @@ impl< already_processed_burn_blocks: &mut HashSet, ) -> 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. @@ -2220,11 +2324,13 @@ impl< &ch, &bhh, height, &heaviest_am ); - let am = StacksChainState::find_stacks_tip_affirmation_map( + let am = inner_static_get_stacks_tip_affirmation_map( &self.burnchain_blocks_db, - &mut sort_tx, + last_2_05_rc, + &sort_tx.find_sortition_tip_affirmation_map(&next_snapshot.sortition_id)?, + &sort_tx, &ch, - &bhh, + &bhh )?; if StacksChainState::is_block_compatible_with_affirmation_map( &am, @@ -2338,9 +2444,11 @@ impl< ) .expect("FATAL: could not find a valid parent Stacks block"); - let stacks_am = StacksChainState::find_stacks_tip_affirmation_map( + let stacks_am = inner_static_get_stacks_tip_affirmation_map( &self.burnchain_blocks_db, - &mut sort_tx, + last_2_05_rc, + &sort_tx.find_sortition_tip_affirmation_map(&highest_valid_sortition_id)?, + &sort_tx, &canonical_ch, &canonical_bhh, ) From b8c88909ef11d210f8f7de32c24dde352b1877ea Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:07:52 -0500 Subject: [PATCH 11/27] docs: update API contract for getting the stacks tip affirmation map --- src/chainstate/stacks/db/blocks.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chainstate/stacks/db/blocks.rs b/src/chainstate/stacks/db/blocks.rs index 71d2c3694..7b0dfbd96 100644 --- a/src/chainstate/stacks/db/blocks.rs +++ b/src/chainstate/stacks/db/blocks.rs @@ -2581,7 +2581,8 @@ impl StacksChainState { Ok(AffirmationMap::empty()) } - /// Get the affirmation map represented by the Stacks chain tip + /// 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, From 4673459b35a50da9016a70b582d28e2ea4b54e6e Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:08:08 -0500 Subject: [PATCH 12/27] feat: store miner spend in the miner status struct --- src/chainstate/stacks/miner.rs | 37 +++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/chainstate/stacks/miner.rs b/src/chainstate/stacks/miner.rs index 2075d6da4..39c4fea4e 100644 --- a/src/chainstate/stacks/miner.rs +++ b/src/chainstate/stacks/miner.rs @@ -83,12 +83,14 @@ use clarity::vm::types::TypeSignature; #[derive(Debug, Clone, PartialEq)] pub struct MinerStatus { blockers: HashSet, + spend_amount: u64, } impl MinerStatus { - pub fn make_ready() -> MinerStatus { + pub fn make_ready(spend_amount: u64) -> MinerStatus { MinerStatus { blockers: HashSet::new(), + spend_amount, } } @@ -108,6 +110,13 @@ impl MinerStatus { false } } + + pub fn get_spend_amount(&self) -> u64 { + return self.spend_amount; + } + pub fn set_spend_amount(&mut self, amt: u64) { + self.spend_amount = amt; + } } impl std::fmt::Display for MinerStatus { @@ -140,6 +149,28 @@ pub fn signal_mining_ready(miner_status: Arc>) { } } +/// get the mining amount +pub fn get_mining_spend_amount(miner_status: Arc>) -> u64 { + match miner_status.lock() { + Ok(status) => status.get_spend_amount(), + Err(_e) => { + panic!("FATAL: mutex poisoned"); + } + } +} + +/// set the mining amount +pub fn set_mining_spend_amount(miner_status: Arc>, amt: u64) { + match miner_status.lock() { + Ok(mut status) => { + status.set_spend_amount(amt); + } + Err(_e) => { + panic!("FATAL: mutex poisoned"); + } + } +} + #[derive(Debug, Clone)] pub struct BlockBuilderSettings { pub max_miner_time_ms: u64, @@ -152,7 +183,7 @@ impl BlockBuilderSettings { BlockBuilderSettings { max_miner_time_ms: u64::max_value(), mempool_settings: MemPoolWalkSettings::default(), - miner_status: Arc::new(Mutex::new(MinerStatus::make_ready())), + miner_status: Arc::new(Mutex::new(MinerStatus::make_ready(0))), } } @@ -160,7 +191,7 @@ impl BlockBuilderSettings { BlockBuilderSettings { max_miner_time_ms: u64::max_value(), mempool_settings: MemPoolWalkSettings::zero(), - miner_status: Arc::new(Mutex::new(MinerStatus::make_ready())), + miner_status: Arc::new(Mutex::new(MinerStatus::make_ready(0))), } } } From cfc5459edf3608775f480d7c20f9f504f0333bfd Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:08:36 -0500 Subject: [PATCH 13/27] fix: API sync --- src/net/p2p.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/net/p2p.rs b/src/net/p2p.rs index 03a88900d..93ad0f725 100644 --- a/src/net/p2p.rs +++ b/src/net/p2p.rs @@ -46,6 +46,9 @@ use crate::burnchains::BurnchainView; use crate::burnchains::PublicKey; use crate::chainstate::burn::db::sortdb::{BlockHeaderCache, SortitionDB}; use crate::chainstate::burn::BlockSnapshot; +use crate::chainstate::coordinator::{ + static_get_canonical_affirmation_map, static_get_heaviest_affirmation_map, +}; use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::{MAX_BLOCK_LEN, MAX_TRANSACTION_LEN}; use crate::monitoring::{update_inbound_neighbors, update_outbound_neighbors}; @@ -4983,20 +4986,30 @@ impl PeerNetwork { // update heaviest affirmation map view let burnchain_db = self.burnchain.open_burnchain_db(false)?; - self.heaviest_affirmation_map = BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), + + self.heaviest_affirmation_map = static_get_heaviest_affirmation_map( &self.burnchain, - )?; - self.tentative_best_affirmation_map = StacksChainState::find_canonical_affirmation_map( - &self.burnchain, - &burnchain_db, - chainstate, - )?; - self.sortition_tip_affirmation_map = SortitionDB::find_sortition_tip_affirmation_map( &burnchain_db, sortdb, &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, + &burnchain_db, + sortdb, + chainstate, + &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, &sn.sortition_id)?; // update last anchor data let ih = sortdb.index_handle(&sn.sortition_id); From 06be7bcfcf1e0a7e17da99968648f2b387a52015 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:46:35 -0500 Subject: [PATCH 14/27] fix: remove unused variables --- src/chainstate/coordinator/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 354108a64..73bd2d433 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -1025,8 +1025,6 @@ impl< 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 last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - let sortition_tip = match &self.canonical_sortition_tip { Some(tip) => tip.clone(), @@ -1147,8 +1145,6 @@ impl< // by a subsequent prepare phase. let mut last_invalidate_start_block = start_height; let mut valid_sortitions = vec![]; - let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - 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(); @@ -1221,8 +1217,6 @@ impl< // as valid. let mut valid_sortitions = vec![]; - let last_2_05_rc = self.sortition_db.get_last_epoch_2_05_reward_cycle()?; - 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 { From e3c42380afc4c6f53ee6488de964f10fa4b397dc Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:46:45 -0500 Subject: [PATCH 15/27] fix: default to not requiring all anchor blocks to be affirmed in testnet --- testnet/stacks-node/src/config.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 6ae3ed306..c859e66ad 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -611,6 +611,7 @@ impl Config { pub fn from_config_file(config_file: ConfigFile) -> Result { let default_node_config = NodeConfig::default(); + let mut has_require_affirmed_anchor_blocks = false; let (mut node, bootstrap_node, deny_nodes) = match config_file.node { Some(node) => { let rpc_bind = node.rpc_bind.unwrap_or(default_node_config.rpc_bind); @@ -669,9 +670,16 @@ impl Config { .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: node - .require_affirmed_anchor_blocks - .unwrap_or(!node.miner.unwrap_or(!default_node_config.miner)), + require_affirmed_anchor_blocks: match node.require_affirmed_anchor_blocks { + Some(x) => { + has_require_affirmed_anchor_blocks = true; + x + } + None => { + has_require_affirmed_anchor_blocks = false; + !node.miner.unwrap_or(!default_node_config.miner) + } + }, // chainstate fault_injection activation for hide_blocks. // you can't set this in the config file. fault_injection_hide_blocks: false, @@ -718,6 +726,15 @@ impl Config { } } } + else { + // testnet requires that we use the 2.05 rules for anchor block affirmations, + // because reward cycle 360 (and possibly future ones) has a different anchor + // block choice in 2.05 rules than in 2.1 rules. + if !has_require_affirmed_anchor_blocks { + debug!("Set `require_affirmed_anchor_blocks` to `false` for non-mainnet config"); + node.require_affirmed_anchor_blocks = false; + } + } let mut result = BurnchainConfig { chain: burnchain.chain.unwrap_or(default_burnchain_config.chain), @@ -1726,7 +1743,7 @@ impl NodeConfig { marf_defer_hashing: true, pox_sync_sample_secs: 30, use_test_genesis_chainstate: None, - always_use_affirmation_maps: false, + always_use_affirmation_maps: true, require_affirmed_anchor_blocks: true, fault_injection_hide_blocks: false, } From 017abe43f0ef31c7b00dfa49f1e7e91ef85afadf Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:47:00 -0500 Subject: [PATCH 16/27] fix: poll burn amount from miner status --- testnet/stacks-node/src/neon_node.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index d83a69af3..59317e44b 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -166,8 +166,9 @@ use stacks::chainstate::stacks::db::{StacksChainState, MINER_REWARD_MATURITY}; use stacks::chainstate::stacks::Error as ChainstateError; use stacks::chainstate::stacks::StacksPublicKey; use stacks::chainstate::stacks::{ - miner::signal_mining_blocked, miner::signal_mining_ready, miner::BlockBuilderSettings, - miner::MinerStatus, miner::StacksMicroblockBuilder, StacksBlockBuilder, StacksBlockHeader, + miner::get_mining_spend_amount, miner::signal_mining_blocked, miner::signal_mining_ready, + miner::BlockBuilderSettings, miner::MinerStatus, miner::StacksMicroblockBuilder, + StacksBlockBuilder, StacksBlockHeader, }; use stacks::chainstate::stacks::{ CoinbasePayload, StacksBlock, StacksMicroblock, StacksTransaction, StacksTransactionSigner, @@ -1712,7 +1713,8 @@ impl BlockMinerThread { } }; - let burn_fee_cap = self.config.burnchain.burn_fee_cap; + // let burn_fee_cap = self.config.burnchain.burn_fee_cap; + let burn_fee_cap = get_mining_spend_amount(self.globals.get_miner_status()); let sunset_burn = self.burnchain.expected_sunset_burn( self.burn_block.block_height + 1, burn_fee_cap, From a0f51324236cbf16a2a6bbd395d573300cbe5824 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:47:24 -0500 Subject: [PATCH 17/27] fix: API sync; also, use miner status for conveying burn amount --- testnet/stacks-node/src/run_loop/neon.rs | 96 ++++++++++++++---------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index a87642c86..01a4a88f4 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -17,14 +17,14 @@ use stacks::deps::ctrlc as termination; use stacks::deps::ctrlc::SignalId; use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType}; -use stacks::burnchains::db::BurnchainDB; use stacks::burnchains::Burnchain; use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::burn::BlockSnapshot; use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers}; use stacks::chainstate::coordinator::{ - migrate_chainstate_dbs, ChainsCoordinator, ChainsCoordinatorConfig, CoordinatorCommunication, - Error as coord_error, + migrate_chainstate_dbs, static_get_canonical_affirmation_map, static_get_stacks_tip_affirmation_map, + static_get_heaviest_affirmation_map, ChainsCoordinator, ChainsCoordinatorConfig, + CoordinatorCommunication, Error as coord_error, }; use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState}; use stacks::core::StacksEpochId; @@ -59,6 +59,12 @@ pub type RunLoopCounter = Arc; #[cfg(not(test))] pub type RunLoopCounter = (); +#[cfg(test)] +const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 30; + +#[cfg(not(test))] +const UNCONDITIONAL_CHAIN_LIVENESS_CHECK: u64 = 300; + #[derive(Clone)] pub struct Counters { pub blocks_processed: RunLoopCounter, @@ -174,7 +180,9 @@ impl RunLoop { let channels = CoordinatorCommunication::instantiate(); let should_keep_running = Arc::new(AtomicBool::new(true)); let pox_watchdog_comms = PoxSyncWatchdogComms::new(should_keep_running.clone()); - let miner_status = Arc::new(Mutex::new(MinerStatus::make_ready())); + let miner_status = Arc::new(Mutex::new(MinerStatus::make_ready( + config.burnchain.burn_fee_cap, + ))); let mut event_dispatcher = EventDispatcher::new(); for observer in config.events_observers.iter() { @@ -635,9 +643,14 @@ impl RunLoop { .open_burnchain_db(false) .expect("FATAL: failed to open burnchain DB"); - let heaviest_affirmation_map = match BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - burnchain, + let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) + .expect("FATAL: could not read sortition DB"); + + let heaviest_affirmation_map = match static_get_heaviest_affirmation_map( + &burnchain, + &burnchain_db, + sortdb, + &sn.sortition_id, ) { Ok(am) => am, Err(e) => { @@ -646,9 +659,6 @@ impl RunLoop { } }; - let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()) - .expect("FATAL: could not read sortition DB"); - let highest_sn = SortitionDB::get_highest_known_burn_chain_tip(sortdb.conn()) .expect("FATAL: could not read sortition DB"); @@ -656,21 +666,19 @@ impl RunLoop { .get_canonical_chain_tip() .expect("FATAL: could not read burnchain DB"); - let sortition_tip_affirmation_map = match SortitionDB::find_sortition_tip_affirmation_map( + 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, - ) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find sortition affirmation map: {:?}", &e); - return; - } - }; - - let stacks_tip_affirmation_map = StacksChainState::find_stacks_tip_affirmation_map( - &burnchain_db, - sortdb.conn(), &sn.canonical_stacks_tip_consensus_hash, &sn.canonical_stacks_tip_hash, ) @@ -744,7 +752,7 @@ impl RunLoop { // too soon return; } - + // compare sortition and heaviest AMs let burnchain_db = burnchain .open_burnchain_db(false) @@ -771,21 +779,20 @@ impl RunLoop { 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( + 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 heaviest_affirmation_map = match static_get_heaviest_affirmation_map( + &burnchain, &burnchain_db, sortdb, &sn.sortition_id, - ) { - Ok(am) => am, - Err(e) => { - warn!("Failed to find sortition affirmation map: {:?}", &e); - return; - } - }; - - let heaviest_affirmation_map = match BurnchainDB::get_heaviest_anchor_block_affirmation_map( - burnchain_db.conn(), - burnchain, ) { Ok(am) => am, Err(e) => { @@ -794,12 +801,19 @@ impl RunLoop { } }; - let canonical_affirmation_map = StacksChainState::find_canonical_affirmation_map( + let canonical_affirmation_map = match static_get_canonical_affirmation_map( &burnchain, &burnchain_db, - chain_state_db, - ) - .expect("FATAL: failed to load canonical Stacks affirmation map"); + 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 @@ -840,7 +854,9 @@ impl RunLoop { // unconditionally bump every 5 minutes, just in case. // this can get the node un-stuck if we're short on sortition processing but are unable to // sync with the remote node because it keeps NACK'ing us, leading to a runloop stall. - if *last_announce_time + 300 < get_epoch_time_secs().into() { + if *last_announce_time + (UNCONDITIONAL_CHAIN_LIVENESS_CHECK as u128) + < get_epoch_time_secs().into() + { debug!("Drive burnchain processing: unconditional bump"); globals.coord().announce_new_burn_block(); globals.coord().announce_new_stacks_block(); From aa1a5e378e8bb372e7e5463471d73d2a81c58f7e Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:47:43 -0500 Subject: [PATCH 18/27] fix: add integration test for handling the epoch transition to 2.1 where the 2.05 affirmation map rules _must_ be used to handle PoX reorgs --- testnet/stacks-node/src/tests/epoch_21.rs | 481 ++++++++++++++++++++-- 1 file changed, 449 insertions(+), 32 deletions(-) diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index eab1bd3a3..d5154779b 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -52,6 +52,7 @@ use stacks_common::util::hash::Sha256Sum; use stacks_common::util::secp256k1::Secp256k1PublicKey; use stacks::chainstate::coordinator::comm::CoordinatorChannels; +use stacks::chainstate::stacks::miner::set_mining_spend_amount; use stacks::chainstate::stacks::miner::signal_mining_blocked; use stacks::chainstate::stacks::miner::signal_mining_ready; @@ -1979,9 +1980,6 @@ fn transition_empty_blocks() { fn wait_pox_stragglers(confs: &[Config], max_stacks_tip: u64, block_time_ms: u64) { loop { let mut straggler = false; - let mut stacker_am = None; - let mut heaviest_am = None; - let mut sortition_am = None; let mut stacks_tip_ch = None; let mut stacks_tip_bhh = None; @@ -1993,22 +1991,6 @@ fn wait_pox_stragglers(confs: &[Config], max_stacks_tip: u64, block_time_ms: u64 straggler = true; } - let affirmations = tip_info.affirmations.unwrap(); - - if let Some(stacker_am) = stacker_am.as_ref() { - if affirmations.stacks_tip != *stacker_am { - straggler = true; - } - } else { - stacker_am = Some(affirmations.stacks_tip); - } - if let Some(heaviest_am) = heaviest_am.as_ref() { - if affirmations.heaviest != *heaviest_am { - straggler = true; - } - } else { - heaviest_am = Some(affirmations.heaviest); - } if let Some(stacks_tip_ch) = stacks_tip_ch.as_ref() { if *stacks_tip_ch != tip_info.stacks_tip_consensus_hash { straggler = true; @@ -2023,19 +2005,6 @@ fn wait_pox_stragglers(confs: &[Config], max_stacks_tip: u64, block_time_ms: u64 } else { stacks_tip_bhh = Some(tip_info.stacks_tip); } - - // sortition affirmation map can differ by the last affirmation - if let Some(sortition_am) = sortition_am.as_ref() { - let mut tip_sort_am = affirmations.sortition_tip.clone(); - tip_sort_am.pop(); - if tip_sort_am != *sortition_am { - straggler = true; - } - } else { - let mut tip_sort_am = affirmations.sortition_tip.clone(); - tip_sort_am.pop(); - sortition_am = Some(tip_sort_am); - } } if !straggler { break; @@ -4261,6 +4230,454 @@ fn test_pox_missing_five_anchor_blocks() { } } +#[test] +#[ignore] +/// Verify that if the sortition AM declares that an anchor block is present in epoch 2.05, but the +/// heaviest AM declares it absent, that this is _not_ treated as a divergence of this behavior +/// manifests in epoch 2.05. +fn test_sortition_divergence_pre_21() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let num_miners = 2; + + let reward_cycle_len = 10; + let prepare_phase_len = 5; + let v1_unlock_height = 242; + + let (mut conf_template, _) = neon_integration_test_conf(); + let block_time_ms = 10_000; + conf_template.node.mine_microblocks = true; + conf_template.miner.microblock_attempt_time_ms = 2_000; + conf_template.node.wait_time_for_microblocks = 0; + conf_template.node.microblock_frequency = 0; + conf_template.miner.first_attempt_time_ms = 2_000; + conf_template.miner.subsequent_attempt_time_ms = 5_000; + conf_template.burnchain.max_rbf = 1000000; + 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 = core::STACKS_EPOCHS_REGTEST.to_vec(); + epochs[1].end_height = 101; + epochs[2].start_height = 101; + epochs[2].end_height = 241; + epochs[3].start_height = 241; + conf_template.burnchain.epochs = Some(epochs); + + let privks: Vec<_> = (0..5) + .into_iter() + .map(|_| StacksPrivateKey::new()) + .collect(); + + let stack_privks: Vec<_> = (0..5) + .into_iter() + .map(|_| StacksPrivateKey::new()) + .collect(); + + let balances: Vec<_> = privks + .iter() + .map(|privk| { + let addr = to_addr(privk); + InitialBalance { + address: addr.into(), + amount: 30_000_000, + } + }) + .collect(); + + let stack_balances: Vec<_> = stack_privks + .iter() + .map(|privk| { + let addr = to_addr(privk); + InitialBalance { + address: addr.into(), + amount: 2_000_000_000_000_000, + } + }) + .collect(); + + let mut confs = vec![]; + let mut burnchain_configs = vec![]; + let mut blocks_processed = vec![]; + let mut channels = vec![]; + let mut miner_status = vec![]; + + for i in 0..num_miners { + let seed = StacksPrivateKey::new().to_bytes(); + let (mut conf, _) = neon_integration_test_conf_with_seed(seed); + + conf.initial_balances.clear(); + conf.initial_balances.append(&mut balances.clone()); + conf.initial_balances.append(&mut stack_balances.clone()); + + conf.node.mine_microblocks = conf_template.node.mine_microblocks; + conf.miner.microblock_attempt_time_ms = conf_template.miner.microblock_attempt_time_ms; + conf.node.wait_time_for_microblocks = conf_template.node.wait_time_for_microblocks; + conf.node.microblock_frequency = conf_template.node.microblock_frequency; + conf.miner.first_attempt_time_ms = conf_template.miner.first_attempt_time_ms; + conf.miner.subsequent_attempt_time_ms = conf_template.miner.subsequent_attempt_time_ms; + conf.node.wait_time_for_blocks = conf_template.node.wait_time_for_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.clone(); + 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; + + // nodes will selectively hide blocks from one another + conf.node.fault_injection_hide_blocks = true; + + conf.connection_options.inv_sync_interval = 6; + + let rpc_port = 41113 + 10 * i; + let p2p_port = 41113 + 10 * i + 1; + conf.node.rpc_bind = format!("127.0.0.1:{}", rpc_port); + conf.node.data_url = format!("http://127.0.0.1:{}", rpc_port); + conf.node.p2p_bind = format!("127.0.0.1:{}", p2p_port); + + confs.push(conf); + } + + let node_privkey_1 = + StacksNode::make_node_private_key_from_seed(&confs[0].node.local_peer_seed); + for i in 1..num_miners { + let chain_id = confs[0].burnchain.chain_id; + let peer_version = confs[0].burnchain.peer_version; + let p2p_bind = confs[0].node.p2p_bind.clone(); + + confs[i].node.set_bootstrap_nodes( + format!( + "{}@{}", + &StacksPublicKey::from_private(&node_privkey_1).to_hex(), + p2p_bind + ), + chain_id, + peer_version, + ); + } + + // use short reward cycles + for i in 0..num_miners { + let mut burnchain_config = Burnchain::regtest(&confs[i].get_burn_db_path()); + let pox_constants = PoxConstants::new( + reward_cycle_len, + prepare_phase_len, + 3, + 5, + 15, + (1600 * reward_cycle_len - 1).into(), + (1700 * reward_cycle_len).into(), + v1_unlock_height, + ); + burnchain_config.pox_constants = pox_constants.clone(); + + burnchain_configs.push(burnchain_config); + } + + let mut btcd_controller = BitcoinCoreController::new(confs[0].clone()); + btcd_controller + .start_bitcoind() + .map_err(|_e| ()) + .expect("Failed starting bitcoind"); + + let mut btc_regtest_controller = BitcoinRegtestController::with_burnchain( + confs[0].clone(), + None, + Some(burnchain_configs[0].clone()), + None, + ); + + btc_regtest_controller.bootstrap_chain(1); + + // make sure all miners have BTC + for i in 1..num_miners { + let old_mining_pubkey = btc_regtest_controller.get_mining_pubkey().unwrap(); + btc_regtest_controller + .set_mining_pubkey(confs[i].burnchain.local_mining_public_key.clone().unwrap()); + btc_regtest_controller.bootstrap_chain(1); + btc_regtest_controller.set_mining_pubkey(old_mining_pubkey); + } + + btc_regtest_controller.bootstrap_chain((199 - num_miners) as u64); + + eprintln!("Chain bootstrapped..."); + + for (i, burnchain_config) in burnchain_configs.into_iter().enumerate() { + let mut run_loop = neon::RunLoop::new(confs[i].clone()); + let blocks_processed_arc = run_loop.get_blocks_processed_arc(); + let channel = run_loop.get_coordinator_channel().unwrap(); + let this_miner_status = run_loop.get_miner_status(); + + blocks_processed.push(blocks_processed_arc); + channels.push(channel); + miner_status.push(this_miner_status); + + thread::spawn(move || run_loop.start(Some(burnchain_config), 0)); + } + + let http_origin = format!("http://{}", &confs[0].node.rpc_bind); + + // give the run loops some time to start up! + for i in 0..num_miners { + wait_for_runloop(&blocks_processed[i as usize]); + } + + // activate miners + eprintln!("\n\nBoot miner 0\n\n"); + loop { + let tip_info_opt = get_chain_info_opt(&confs[0]); + if let Some(tip_info) = tip_info_opt { + eprintln!("\n\nMiner 0: {:?}\n\n", &tip_info); + if tip_info.stacks_tip_height > 0 { + break; + } + } else { + eprintln!("\n\nWaiting for miner 0...\n\n"); + } + next_block_and_iterate( + &mut btc_regtest_controller, + &blocks_processed[0], + block_time_ms, + ); + } + + for i in 1..num_miners { + eprintln!("\n\nBoot miner {}\n\n", i); + loop { + let tip_info_opt = get_chain_info_opt(&confs[i]); + if let Some(tip_info) = tip_info_opt { + eprintln!("\n\nMiner 2: {:?}\n\n", &tip_info); + if tip_info.stacks_tip_height > 0 { + break; + } + } else { + eprintln!("\n\nWaiting for miner {}...\n\n", i); + } + next_block_and_iterate( + &mut btc_regtest_controller, + &blocks_processed[i as usize], + 5_000, + ); + } + } + + eprintln!("\n\nBegin transactions\n\n"); + + let pox_pubkey = Secp256k1PublicKey::from_hex( + "02f006a09b59979e2cb8449f58076152af6b124aa29b948a3714b8d5f15aa94ede", + ) + .unwrap(); + let pox_pubkey_hash = bytes_to_hex( + &Hash160::from_node_public_key(&pox_pubkey) + .to_bytes() + .to_vec(), + ); + + let sort_height = channels[0].get_sortitions_processed(); + + // make everyone stack + let stacking_txs: Vec<_> = stack_privks + .iter() + .enumerate() + .map(|(_i, pk)| { + make_contract_call( + pk, + 0, + 1360, + &StacksAddress::from_string("ST000000000000000000002AMW42H").unwrap(), + "pox", + "stack-stx", + &[ + Value::UInt(2_000_000_000_000_000 - 30_000_000), + execute( + &format!("{{ hashbytes: 0x{}, version: 0x00 }}", pox_pubkey_hash), + ClarityVersion::Clarity1, + ) + .unwrap() + .unwrap(), + Value::UInt((sort_height + 1) as u128), + Value::UInt(12), + ], + ) + }) + .collect(); + + // keeps the mempool full, and makes it so miners will spend a nontrivial amount of time + // building blocks + let all_txs: Vec<_> = privks + .iter() + .enumerate() + .map(|(i, pk)| make_random_tx_chain(pk, (25 * i) as u64, false)) + .collect(); + + // everyone locks up + let mut cnt = 0; + for tx in stacking_txs { + eprintln!("\n\nSubmit stacking tx {}\n\n", &cnt); + submit_tx(&http_origin, &tx); + cnt += 1; + } + + // run a reward cycle + let mut at_220 = false; + while !at_220 { + btc_regtest_controller.build_next_block(1); + sleep_ms(block_time_ms); + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + if tip_info.burn_block_height == 220 { + at_220 = true; + } + } + } + + // blast out the rest + let mut cnt = 0; + for tx_chain in all_txs { + for tx in tx_chain { + eprintln!("\n\nSubmit tx {}\n\n", &cnt); + submit_tx(&http_origin, &tx); + cnt += 1; + } + } + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + assert!(tip_info.burn_block_height <= 220); + } + + eprintln!("\n\nBegin mining\n\n"); + + info!("####################### end of cycle ##############################"); + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + } + info!("####################### end of cycle ##############################"); + + let mut max_stacks_tip = 0; + + // prevent Stacks at these heights from propagating + env::set_var( + "STACKS_HIDE_BLOCKS_AT_HEIGHT", + "[223,224,225,226,227,228,229,230]", + ); + + // mine a reward cycle in which the 2.05 rules choose a PoX anchor block, but the 2.1 rules do + // not. + for i in 0..10 { + eprintln!("\n\nBuild block {}\n\n", i); + btc_regtest_controller.build_next_block(1); + sleep_ms(block_time_ms); + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + } + + if i >= reward_cycle_len - prepare_phase_len && i < reward_cycle_len - prepare_phase_len + 3 + { + // only miner 0 mines. + signal_mining_ready(miner_status[0].clone()); + signal_mining_blocked(miner_status[1].clone()); + } else if i >= reward_cycle_len - prepare_phase_len + 3 && i < reward_cycle_len { + // only miner 1 mines, and they mine *hard*. + signal_mining_blocked(miner_status[0].clone()); + signal_mining_ready(miner_status[1].clone()); + set_mining_spend_amount( + miner_status[1].clone(), + 10 * conf_template.burnchain.burn_fee_cap, + ); + } + } + + signal_mining_ready(miner_status[0].clone()); + signal_mining_ready(miner_status[1].clone()); + set_mining_spend_amount( + miner_status[1].clone(), + conf_template.burnchain.burn_fee_cap, + ); + + info!("####################### end of cycle ##############################"); + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + max_stacks_tip = std::cmp::max(tip_info.stacks_tip_height, max_stacks_tip); + } + info!("####################### end of cycle ##############################"); + + for i in 0..10 { + eprintln!("\n\nBuild block {}\n\n", i); + btc_regtest_controller.build_next_block(1); + sleep_ms(block_time_ms); + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + } + } + + info!("####################### end of cycle ##############################"); + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + max_stacks_tip = std::cmp::max(tip_info.stacks_tip_height, max_stacks_tip); + } + info!("####################### end of cycle ##############################"); + + // run some cycles in 2.1 + for j in 0..2 { + for i in 0..10 { + eprintln!("\n\nBuild block {}\n\n", i); + btc_regtest_controller.build_next_block(1); + sleep_ms(block_time_ms); + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + } + } + } + + // advance to start of next reward cycle + eprintln!("\n\nBuild final block\n\n"); + btc_regtest_controller.build_next_block(1); + sleep_ms(block_time_ms); + + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Tip for miner {}: {:?}", i, &tip_info); + } + + env::set_var("STACKS_HIDE_BLOCKS_AT_HEIGHT", "[]"); + + // wait for all blocks to propagate. + // miner 1 should learn about all of miner 0's blocks + info!( + "Wait for all blocks to propagate; stacks tip height is {}", + max_stacks_tip + ); + wait_pox_stragglers(&confs, max_stacks_tip, block_time_ms); + + // nodes now agree on stacks affirmation map + for (i, c) in confs.iter().enumerate() { + let tip_info = get_chain_info(&c); + info!("Final tip for miner {}: {:?}", i, &tip_info); + } +} + /* #[test] #[ignore] From a7dca61fb3c9c609121373d32e11f8ca34b4ebe3 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:48:57 -0500 Subject: [PATCH 19/27] feat: run new integration test --- .github/workflows/bitcoin-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/bitcoin-tests.yml b/.github/workflows/bitcoin-tests.yml index 6879c3fee..b38014763 100644 --- a/.github/workflows/bitcoin-tests.yml +++ b/.github/workflows/bitcoin-tests.yml @@ -93,6 +93,7 @@ jobs: - tests::epoch_21::test_pox_reorg_flap_duel - tests::epoch_21::test_pox_reorg_flap_reward_cycles - tests::epoch_21::test_pox_missing_five_anchor_blocks + - tests::epoch_21::test_sortition_divergence_pre_21 - tests::neon_integrations::bad_microblock_pubkey steps: - uses: actions/checkout@v2 From b46cd6b9bd28d2fea9790397a453d0f8b72fe181 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 13 Jan 2023 17:49:20 -0500 Subject: [PATCH 20/27] chore: cargo fmt --- src/chainstate/burn/db/sortdb.rs | 23 +++++++--- src/chainstate/coordinator/mod.rs | 53 ++++++++++++++---------- testnet/stacks-node/src/config.rs | 3 +- testnet/stacks-node/src/run_loop/neon.rs | 8 ++-- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index e012a7192..b846fb40a 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -3010,7 +3010,10 @@ impl SortitionDB { } impl<'a> SortitionDBTx<'a> { - pub fn find_sortition_tip_affirmation_map(&mut self, chain_tip: &SortitionId) -> Result { + 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") @@ -3021,7 +3024,9 @@ impl<'a> SortitionDBTx<'a> { // remove the first entry -- it's always `n` based on the way we construct it, while the // heaviest affirmation map just has nothing. if affirmation_map.len() > 0 { - Ok(AffirmationMap::new(affirmation_map.as_slice()[1..].to_vec())) + Ok(AffirmationMap::new( + affirmation_map.as_slice()[1..].to_vec(), + )) } else { Ok(AffirmationMap::empty()) } @@ -4437,11 +4442,19 @@ impl SortitionDB { /// Get the last reward cycle in epoch 2.05 pub fn get_last_epoch_2_05_reward_cycle(&self) -> Result { - Self::static_get_last_epoch_2_05_reward_cycle(self.conn(), self.first_block_height, &self.pox_constants) + Self::static_get_last_epoch_2_05_reward_cycle( + self.conn(), + self.first_block_height, + &self.pox_constants, + ) } - + /// Get the last reward cycle in epoch 2.05 - pub fn static_get_last_epoch_2_05_reward_cycle(conn: &DBConn, first_block_height: u64, pox_constants: &PoxConstants) -> Result { + pub fn static_get_last_epoch_2_05_reward_cycle( + conn: &DBConn, + first_block_height: u64, + pox_constants: &PoxConstants, + ) -> Result { let epochs = SortitionDB::get_stacks_epochs(conn)?; for epoch in epochs { diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 73bd2d433..96d691687 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -752,7 +752,7 @@ fn inner_static_get_stacks_tip_affirmation_map( sort_am: &AffirmationMap, sortdb_conn: &DBConn, canonical_ch: &ConsensusHash, - canonical_bhh: &BlockHeaderHash + canonical_bhh: &BlockHeaderHash, ) -> Result { let last_2_05_rc = last_2_05_rc as usize; @@ -760,7 +760,7 @@ fn inner_static_get_stacks_tip_affirmation_map( burnchain_blocks_db, sortdb_conn, canonical_ch, - canonical_bhh + canonical_bhh, )?; let mut am_entries = vec![]; @@ -784,7 +784,7 @@ pub fn static_get_stacks_tip_affirmation_map( sortition_db: &SortitionDB, sortition_tip: &SortitionId, canonical_ch: &ConsensusHash, - canonical_bhh: &BlockHeaderHash + 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)?; @@ -794,11 +794,10 @@ pub fn static_get_stacks_tip_affirmation_map( &sort_am, sortition_db.conn(), canonical_ch, - canonical_bhh + canonical_bhh, ) } - impl< 'a, T: BlockEventDispatcher, @@ -831,8 +830,9 @@ impl< 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)?; + let sort_am = self + .sortition_db + .find_sortition_tip_affirmation_map(&sort_id)?; ret.push((sn, sort_am)); } @@ -874,7 +874,11 @@ impl< 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 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( @@ -904,10 +908,12 @@ impl< &sort_am, sort_tx, &hdr.consensus_hash, - &hdr.anchored_header.block_hash() + &hdr.anchored_header.block_hash(), ) { Ok(am) => am, - Err(Error::ChainstateError(ChainstateError::DBError(DBError::InvalidPoxSortition))) => { + Err(Error::ChainstateError(ChainstateError::DBError( + DBError::InvalidPoxSortition, + ))) => { debug!( "Stacks tip {}/{} is not on a valid sortition", &hdr.consensus_hash, @@ -1039,11 +1045,12 @@ impl< &self.sortition_db, &sortition_tip, &canonical_ch, - &canonical_bhh + &canonical_bhh, )?; - let sortition_tip_affirmation_map = - self.sortition_db.find_sortition_tip_affirmation_map(&sortition_tip)?; + 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)?; @@ -1239,8 +1246,9 @@ impl< // 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)?; + let sort_am = self + .sortition_db + .find_sortition_tip_affirmation_map(&sort_id)?; debug!( "Compare {} as prefix of {}? {}", @@ -1450,9 +1458,9 @@ impl< // 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 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 @@ -1492,7 +1500,8 @@ impl< // 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 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) = @@ -1510,7 +1519,7 @@ impl< &sort_tx.find_sortition_tip_affirmation_map(&sortition_tip)?, &sort_tx, &canonical_ch, - &canonical_bhh + &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); @@ -2073,7 +2082,7 @@ impl< already_processed_burn_blocks: &mut HashSet, ) -> 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 @@ -2324,7 +2333,7 @@ impl< &sort_tx.find_sortition_tip_affirmation_map(&next_snapshot.sortition_id)?, &sort_tx, &ch, - &bhh + &bhh, )?; if StacksChainState::is_block_compatible_with_affirmation_map( &am, diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index c859e66ad..487de0339 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -725,8 +725,7 @@ impl Config { )); } } - } - else { + } else { // testnet requires that we use the 2.05 rules for anchor block affirmations, // because reward cycle 360 (and possibly future ones) has a different anchor // block choice in 2.05 rules than in 2.1 rules. diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 01a4a88f4..3dd8ac01b 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -22,9 +22,9 @@ use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::burn::BlockSnapshot; use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers}; use stacks::chainstate::coordinator::{ - migrate_chainstate_dbs, static_get_canonical_affirmation_map, static_get_stacks_tip_affirmation_map, - static_get_heaviest_affirmation_map, ChainsCoordinator, ChainsCoordinatorConfig, - CoordinatorCommunication, Error as coord_error, + 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, }; use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState}; use stacks::core::StacksEpochId; @@ -752,7 +752,7 @@ impl RunLoop { // too soon return; } - + // compare sortition and heaviest AMs let burnchain_db = burnchain .open_burnchain_db(false) From 46f06ca09a33d2dbd9e7b5cf432d590e648518de Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 15 Jan 2023 23:55:38 -0500 Subject: [PATCH 21/27] chore: defensive programming for affirmation map calculation in 2.05 rules --- src/chainstate/coordinator/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 96d691687..fb7ae3895 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -704,6 +704,8 @@ pub fn static_get_heaviest_affirmation_map( for i in 0..last_2_05_rc { if i < sort_am.affirmations.len() { am_entries.push(sort_am.affirmations[i]); + } else { + return Ok(AffirmationMap::new(am_entries)); } } for i in last_2_05_rc..heaviest_am.len() { @@ -737,6 +739,8 @@ pub fn static_get_canonical_affirmation_map( for i in 0..last_2_05_rc { if i < sort_am.affirmations.len() { am_entries.push(sort_am.affirmations[i]); + } else { + return Ok(AffirmationMap::new(am_entries)); } } for i in last_2_05_rc..canonical_am.len() { @@ -767,6 +771,8 @@ fn inner_static_get_stacks_tip_affirmation_map( for i in 0..last_2_05_rc { if i < sort_am.affirmations.len() { am_entries.push(sort_am.affirmations[i]); + } else { + return Ok(AffirmationMap::new(am_entries)); } } for i in last_2_05_rc..stacks_am.len() { From 8fb2789bb644746d48f6f4ddb4cfc8c1d3b51716 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 15 Jan 2023 23:55:58 -0500 Subject: [PATCH 22/27] fix: report 2.05-rule-aware stacks affirmation map --- src/net/p2p.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/net/p2p.rs b/src/net/p2p.rs index 93ad0f725..c5096e8fa 100644 --- a/src/net/p2p.rs +++ b/src/net/p2p.rs @@ -48,6 +48,7 @@ use crate::chainstate::burn::db::sortdb::{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, }; use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::{MAX_BLOCK_LEN, MAX_TRANSACTION_LEN}; @@ -5027,12 +5028,16 @@ impl PeerNetwork { { // update stacks tip affirmation map view let burnchain_db = self.burnchain.open_burnchain_db(false)?; - self.stacks_tip_affirmation_map = StacksChainState::find_stacks_tip_affirmation_map( + self.stacks_tip_affirmation_map = static_get_stacks_tip_affirmation_map( &burnchain_db, - sortdb.conn(), + sortdb, + &sn.sortition_id, &sn.canonical_stacks_tip_consensus_hash, &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 From a96ac186f402ff86643eb9d386f9e7ac16621bfc Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 15 Jan 2023 23:56:22 -0500 Subject: [PATCH 23/27] chore: remove compiler warnings --- testnet/stacks-node/src/tests/epoch_21.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testnet/stacks-node/src/tests/epoch_21.rs b/testnet/stacks-node/src/tests/epoch_21.rs index d5154779b..c14c228e2 100644 --- a/testnet/stacks-node/src/tests/epoch_21.rs +++ b/testnet/stacks-node/src/tests/epoch_21.rs @@ -3,7 +3,6 @@ use std::collections::HashSet; use std::env; use std::thread; -use stacks::burnchains::affirmation::AffirmationMap; use stacks::burnchains::Burnchain; use stacks::chainstate::stacks::db::StacksChainState; use stacks::chainstate::stacks::StacksBlockHeader; @@ -4638,7 +4637,7 @@ fn test_sortition_divergence_pre_21() { info!("####################### end of cycle ##############################"); // run some cycles in 2.1 - for j in 0..2 { + for _ in 0..2 { for i in 0..10 { eprintln!("\n\nBuild block {}\n\n", i); btc_regtest_controller.build_next_block(1); From 8e97c88b0c4017f8326104f959f80011b28bf825 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Sun, 15 Jan 2023 23:56:35 -0500 Subject: [PATCH 24/27] fix: use 2.05-series affirmation map calculation for problematic tx tests --- testnet/stacks-node/src/tests/neon_integrations.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 2398439ee..a45ecd7e8 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -8286,6 +8286,10 @@ fn spawn_follower_node( conf.burnchain.ast_precheck_size_height = initial_conf.burnchain.ast_precheck_size_height.clone(); + 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(); @@ -9500,6 +9504,8 @@ fn test_problematic_microblocks_are_not_relayed_or_stored() { conf.miner.microblock_attempt_time_ms = 1_000; conf.node.wait_time_for_microblocks = 0; + conf.connection_options.inv_sync_interval = 3; + test_observer::spawn(); conf.events_observers.push(EventObserverConfig { From ab5b21c3979599d1bc12bd646ce3dfe4dc2424c8 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 20 Jan 2023 10:49:52 -0500 Subject: [PATCH 25/27] fix: fix some failing integration tests --- testnet/stacks-node/src/tests/neon_integrations.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index a45ecd7e8..5ff49faae 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -6003,6 +6003,8 @@ 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!( @@ -6025,6 +6027,8 @@ fn atlas_integration_test() { events_keys: vec![EventKeyType::AnyEvent], }); + conf_follower_node.node.always_use_affirmation_maps = false; + // Our 2 nodes will share the bitcoind node let mut btcd_controller = BitcoinCoreController::new(conf_bootstrap_node.clone()); btcd_controller @@ -6538,6 +6542,8 @@ 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!( @@ -6570,6 +6576,8 @@ fn antientropy_integration_test() { conf_follower_node.burnchain.max_rbf = 1000000; conf_follower_node.node.wait_time_for_blocks = 1_000; + conf_follower_node.node.always_use_affirmation_maps = false; + // Our 2 nodes will share the bitcoind node let mut btcd_controller = BitcoinCoreController::new(conf_bootstrap_node.clone()); btcd_controller @@ -6812,6 +6820,8 @@ 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(); From 56f81ca890a62385d93214a8ad52f2b6400f709b Mon Sep 17 00:00:00 2001 From: Sergey Shandar Date: Fri, 20 Jan 2023 12:02:31 -0800 Subject: [PATCH 26/27] proposed changes --- src/chainstate/burn/db/sortdb.rs | 11 ++++------- src/chainstate/stacks/miner.rs | 12 ++++-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index b846fb40a..c21c57c9e 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -3023,13 +3023,10 @@ impl<'a> SortitionDBTx<'a> { // remove the first entry -- it's always `n` based on the way we construct it, while the // heaviest affirmation map just has nothing. - if affirmation_map.len() > 0 { - Ok(AffirmationMap::new( - affirmation_map.as_slice()[1..].to_vec(), - )) - } else { - Ok(AffirmationMap::empty()) - } + Ok(match affirmation_map.as_slice() { + [] => AffirmationMap::empty(), + a => AffirmationMap::new(a[1..].to_vec()), + }) } } diff --git a/src/chainstate/stacks/miner.rs b/src/chainstate/stacks/miner.rs index 39c4fea4e..a65eeab78 100644 --- a/src/chainstate/stacks/miner.rs +++ b/src/chainstate/stacks/miner.rs @@ -161,14 +161,10 @@ pub fn get_mining_spend_amount(miner_status: Arc>) -> u64 { /// set the mining amount pub fn set_mining_spend_amount(miner_status: Arc>, amt: u64) { - match miner_status.lock() { - Ok(mut status) => { - status.set_spend_amount(amt); - } - Err(_e) => { - panic!("FATAL: mutex poisoned"); - } - } + miner_status + .lock() + .expect("FATAL: mutex poisoned") + .set_spend_amount(amt); } #[derive(Debug, Clone)] From 65dd1e6abe417b2df0cd556cc4330d79570f5471 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Fri, 20 Jan 2023 16:18:04 -0500 Subject: [PATCH 27/27] fix: consolidate affirmation maps in a single method --- src/chainstate/coordinator/mod.rs | 81 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index fb7ae3895..e6fdc5d1b 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -682,6 +682,33 @@ 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 i < sort_am.affirmations.len() { + am_entries.push(sort_am.affirmations[i]); + } else { + return AffirmationMap::new(am_entries); + } + } + for i in last_2_05_rc..given_am.len() { + am_entries.push(given_am.affirmations[i]); + } + + 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. @@ -700,19 +727,11 @@ pub fn static_get_heaviest_affirmation_map( burnchain, )?; - let mut am_entries = vec![]; - for i in 0..last_2_05_rc { - if i < sort_am.affirmations.len() { - am_entries.push(sort_am.affirmations[i]); - } else { - return Ok(AffirmationMap::new(am_entries)); - } - } - for i in last_2_05_rc..heaviest_am.len() { - am_entries.push(heaviest_am.affirmations[i]); - } - - Ok(AffirmationMap::new(am_entries)) + Ok(consolidate_affirmation_maps( + heaviest_am, + &sort_am, + last_2_05_rc, + )) } /// Get the canonical affirmation map, when considering epochs. @@ -735,19 +754,11 @@ pub fn static_get_canonical_affirmation_map( chain_state_db, )?; - let mut am_entries = vec![]; - for i in 0..last_2_05_rc { - if i < sort_am.affirmations.len() { - am_entries.push(sort_am.affirmations[i]); - } else { - return Ok(AffirmationMap::new(am_entries)); - } - } - for i in last_2_05_rc..canonical_am.len() { - am_entries.push(canonical_am.affirmations[i]); - } - - Ok(AffirmationMap::new(am_entries)) + Ok(consolidate_affirmation_maps( + canonical_am, + &sort_am, + last_2_05_rc, + )) } fn inner_static_get_stacks_tip_affirmation_map( @@ -767,19 +778,11 @@ fn inner_static_get_stacks_tip_affirmation_map( canonical_bhh, )?; - let mut am_entries = vec![]; - for i in 0..last_2_05_rc { - if i < sort_am.affirmations.len() { - am_entries.push(sort_am.affirmations[i]); - } else { - return Ok(AffirmationMap::new(am_entries)); - } - } - for i in last_2_05_rc..stacks_am.len() { - am_entries.push(stacks_am.affirmations[i]); - } - - Ok(AffirmationMap::new(am_entries)) + Ok(consolidate_affirmation_maps( + stacks_am, + sort_am, + last_2_05_rc, + )) } /// Get the canonical Stacks tip affirmation map, when considering epochs.