From f31752fa1b09ada6125c55b3a416e1fbaf764592 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Wed, 9 Jul 2025 12:11:32 -0500 Subject: [PATCH 1/3] fix: tenure downloader reward set error #6234 --- .../nakamoto/download_state_machine.rs | 41 ++- stackslib/src/net/download/nakamoto/mod.rs | 9 - .../nakamoto/tenure_downloader_unconfirmed.rs | 48 +-- testnet/stacks-node/src/tests/signer/v0.rs | 300 +++++++++++++++++- 4 files changed, 352 insertions(+), 46 deletions(-) diff --git a/stackslib/src/net/download/nakamoto/download_state_machine.rs b/stackslib/src/net/download/nakamoto/download_state_machine.rs index 8616418d18..3072b0eaed 100644 --- a/stackslib/src/net/download/nakamoto/download_state_machine.rs +++ b/stackslib/src/net/download/nakamoto/download_state_machine.rs @@ -28,8 +28,8 @@ use crate::chainstate::stacks::db::StacksChainState; use crate::net::chat::ConversationP2P; use crate::net::connection::ConnectionOptions; use crate::net::download::nakamoto::{ - downloader_block_height_to_reward_cycle, AvailableTenures, NakamotoTenureDownloader, - NakamotoTenureDownloaderSet, NakamotoUnconfirmedTenureDownloader, TenureStartEnd, WantedTenure, + AvailableTenures, NakamotoTenureDownloader, NakamotoTenureDownloaderSet, + NakamotoUnconfirmedTenureDownloader, TenureStartEnd, WantedTenure, }; use crate::net::inv::nakamoto::NakamotoTenureInv; use crate::net::neighbors::rpc::NeighborRPC; @@ -122,6 +122,21 @@ impl NakamotoDownloadStateMachine { } } + /// Return the reward cycle which could be confirmed by a nakamoto block commit + /// in burn block height `burn_height`. + /// + /// Nakamoto block commits point at the parent of the tenure that + /// starts at `burn_height`. Therefore, a commit at height N + /// could confirm a tenure in the reward cycle active at height + /// `N-1`. + fn get_confirmable_reward_cycle( + pox_constants: &PoxConstants, + first_burn_height: u64, + burn_height: u64, + ) -> Option { + pox_constants.block_height_to_reward_cycle(first_burn_height, burn_height.saturating_sub(1)) + } + /// Get a range of wanted tenures between two burnchain blocks. /// Each wanted tenure's .processed flag will be set to false. /// @@ -220,7 +235,13 @@ impl NakamotoDownloadStateMachine { sortdb: &SortitionDB, loaded_so_far: &[WantedTenure], ) -> Result, NetError> { - let tip_rc = downloader_block_height_to_reward_cycle( + // we want tenures that are *confirmable* from the current sortition tip. + // any miner commitment chosen in the sortition tip confirms a tenure with a lower + // block height, so only consider the reward cycle at height - 1. + // *Note: its possible that a the second or later sortition of a RC also confirms a tenure + // in the previous RC, but for the purposes of the wanted tenures calculations, we only + // are loading up wanted tenures in the current RC. + let tip_rc = Self::get_confirmable_reward_cycle( &sortdb.pox_constants, sortdb.first_block_height, tip.block_height, @@ -234,12 +255,9 @@ impl NakamotoDownloadStateMachine { } else if let Some(last_tip) = last_tip.as_ref() { last_tip.block_height.saturating_add(1) } else { - // careful -- need .saturating_sub(1) since this calculation puts the reward cycle start at - // block height 1 mod reward cycle len, but we really want 0 mod reward cycle len sortdb .pox_constants - .reward_cycle_to_block_height(sortdb.first_block_height, tip_rc) - .saturating_sub(1) + .nakamoto_first_block_of_cycle(sortdb.first_block_height, tip_rc) }; // be extra careful with last_block_height -- we not only account for the above, but also @@ -247,8 +265,7 @@ impl NakamotoDownloadStateMachine { // of the last block height (but we want this!) let last_block_height = sortdb .pox_constants - .reward_cycle_to_block_height(sortdb.first_block_height, tip_rc.saturating_add(1)) - .saturating_sub(1) + .nakamoto_first_block_of_cycle(sortdb.first_block_height, tip_rc.saturating_add(1)) .min(tip.block_height.saturating_add(1)); debug!( @@ -395,7 +412,7 @@ impl NakamotoDownloadStateMachine { .expect("FATAL: usize cannot support reward cycle length") { // this is the first-ever pass, so load up the last full reward cycle - let prev_sort_rc = downloader_block_height_to_reward_cycle( + let prev_sort_rc = Self::get_confirmable_reward_cycle( &sortdb.pox_constants, sortdb.first_block_height, sort_tip.block_height, @@ -419,7 +436,7 @@ impl NakamotoDownloadStateMachine { } if self.wanted_tenures.is_empty() { // this is the first-ever pass, so load up the current reward cycle - let sort_rc = downloader_block_height_to_reward_cycle( + let sort_rc = Self::get_confirmable_reward_cycle( &sortdb.pox_constants, sortdb.first_block_height, sort_tip.block_height, @@ -472,7 +489,7 @@ impl NakamotoDownloadStateMachine { self.initialize_wanted_tenures(sort_tip, sortdb)?; let last_sort_height_opt = self.last_sort_tip.as_ref().map(|sn| sn.block_height); let last_sort_height = last_sort_height_opt.unwrap_or(sort_tip.block_height); - let sort_rc = downloader_block_height_to_reward_cycle( + let sort_rc = Self::get_confirmable_reward_cycle( &sortdb.pox_constants, sortdb.first_block_height, last_sort_height, diff --git a/stackslib/src/net/download/nakamoto/mod.rs b/stackslib/src/net/download/nakamoto/mod.rs index ae2fc949f1..800c5f4e54 100644 --- a/stackslib/src/net/download/nakamoto/mod.rs +++ b/stackslib/src/net/download/nakamoto/mod.rs @@ -114,7 +114,6 @@ use std::collections::HashMap; use stacks_common::types::chainstate::ConsensusHash; use stacks_common::types::StacksEpochId; -use crate::burnchains::PoxConstants; use crate::chainstate::burn::db::sortdb::SortitionDB; use crate::chainstate::nakamoto::NakamotoBlock; use crate::chainstate::stacks::db::StacksChainState; @@ -139,14 +138,6 @@ pub use crate::net::download::nakamoto::tenure_downloader_unconfirmed::{ NakamotoUnconfirmedDownloadState, NakamotoUnconfirmedTenureDownloader, }; -pub fn downloader_block_height_to_reward_cycle( - pox_constants: &PoxConstants, - first_block_height: u64, - block_height: u64, -) -> Option { - pox_constants.block_height_to_reward_cycle(first_block_height, block_height.saturating_sub(1)) -} - impl PeerNetwork { /// Set up the Nakamoto block downloader pub fn init_nakamoto_block_downloader(&mut self) { diff --git a/stackslib/src/net/download/nakamoto/tenure_downloader_unconfirmed.rs b/stackslib/src/net/download/nakamoto/tenure_downloader_unconfirmed.rs index 4b813df7f6..b56ffcae61 100644 --- a/stackslib/src/net/download/nakamoto/tenure_downloader_unconfirmed.rs +++ b/stackslib/src/net/download/nakamoto/tenure_downloader_unconfirmed.rs @@ -26,9 +26,7 @@ use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; use crate::chainstate::stacks::boot::RewardSet; use crate::chainstate::stacks::db::StacksChainState; use crate::net::api::gettenureinfo::RPCGetTenureInfo; -use crate::net::download::nakamoto::{ - downloader_block_height_to_reward_cycle, NakamotoTenureDownloader, -}; +use crate::net::download::nakamoto::NakamotoTenureDownloader; use crate::net::httpcore::{StacksHttpRequest, StacksHttpResponse}; use crate::net::neighbors::rpc::NeighborRPC; use crate::net::p2p::{CurrentRewardSet, DropReason, DropSource, PeerNetwork}; @@ -162,8 +160,11 @@ impl NakamotoUnconfirmedTenureDownloader { return Err(NetError::InvalidState); } - debug!("Got tenure info {:?}", remote_tenure_tip); - debug!("Local sortition tip is {}", &local_sort_tip.consensus_hash); + debug!( + "Got tenure info"; + "remote_tenure_tip" => ?remote_tenure_tip, + "local_sortition_tip" => %local_sort_tip.consensus_hash + ); // authenticate consensus hashes against canonical chain history let local_tenure_sn = SortitionDB::get_block_snapshot_consensus( @@ -286,19 +287,24 @@ impl NakamotoUnconfirmedTenureDownloader { return Ok(()); } + debug!( + "TenureDownloaderUnconfirmed not finished"; + "tenure_burn_ht" => local_tenure_sn.block_height, + "parent_tenure_burn_ht" => parent_local_tenure_sn.block_height + ); + // we're not finished - let tenure_rc = downloader_block_height_to_reward_cycle( - &sortdb.pox_constants, - sortdb.first_block_height, - local_tenure_sn.block_height, - ) - .expect("FATAL: sortition from before system start"); - let parent_tenure_rc = downloader_block_height_to_reward_cycle( - &sortdb.pox_constants, - sortdb.first_block_height, - parent_local_tenure_sn.block_height, - ) - .expect("FATAL: sortition from before system start"); + let tenure_rc = sortdb + .pox_constants + .block_height_to_reward_cycle(sortdb.first_block_height, local_tenure_sn.block_height) + .expect("FATAL: sortition from before system start"); + let parent_tenure_rc = sortdb + .pox_constants + .block_height_to_reward_cycle( + sortdb.first_block_height, + parent_local_tenure_sn.block_height, + ) + .expect("FATAL: sortition from before system start"); // get reward set info for the unconfirmed tenure and highest-complete tenure sortitions let Some(Some(confirmed_reward_set)) = current_reward_sets @@ -697,10 +703,12 @@ impl NakamotoUnconfirmedTenureDownloader { return Err(NetError::InvalidState); }; - debug!( - "Create downloader for highest complete tenure {} known by {}", - &tenure_tip.parent_consensus_hash, &self.naddr, + info!( + "Create highest confirmed downloader from unconfirmed"; + "confirmed_tenure" => %tenure_tip.parent_consensus_hash, + "neighbor" => %self.naddr, ); + let ntd = NakamotoTenureDownloader::new( tenure_tip.parent_consensus_hash.clone(), tenure_tip.consensus_hash.clone(), diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index b04a4144e0..89096800e6 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -555,6 +555,7 @@ impl MultipleMinerTest { node_1_config_modifier, node_2_config_modifier, |port| u8::try_from(port % 2).unwrap(), + None, ) } @@ -576,6 +577,7 @@ impl MultipleMinerTest { mut node_1_config_modifier: G, mut node_2_config_modifier: H, signer_distributor: S, + ports: Option>, ) -> MultipleMinerTest { let sender_sk = Secp256k1PrivateKey::random(); let sender_addr = tests::to_addr(&sender_sk); @@ -587,10 +589,16 @@ impl MultipleMinerTest { let btc_miner_1_pk = Keychain::default(btc_miner_1_seed.clone()).get_pub_key(); let btc_miner_2_pk = Keychain::default(btc_miner_2_seed.clone()).get_pub_key(); - let node_1_rpc = gen_random_port(); - let node_1_p2p = gen_random_port(); - let node_2_rpc = gen_random_port(); - let node_2_p2p = gen_random_port(); + let (node_1_rpc, node_1_p2p, node_2_rpc, node_2_p2p) = if let Some(ports) = ports { + (ports[0], ports[1], ports[2], ports[3]) + } else { + ( + gen_random_port(), + gen_random_port(), + gen_random_port(), + gen_random_port(), + ) + }; let localhost = "127.0.0.1"; let node_1_rpc_bind = format!("{localhost}:{node_1_rpc}"); @@ -662,7 +670,6 @@ impl MultipleMinerTest { conf_node_2.node.miner = true; conf_node_2.events_observers.clear(); conf_node_2.events_observers.extend(node_2_listeners); - assert!(!conf_node_2.events_observers.is_empty()); node_2_config_modifier(&mut conf_node_2); let node_1_sk = StacksPrivateKey::from_seed(&conf.node.local_peer_seed); @@ -8075,6 +8082,288 @@ fn duplicate_signers() { signer_test.shutdown(); } +#[test] +#[ignore] +fn signer_multinode_rollover() { + let num_signers = 5; + let new_num_signers = 4; + + let new_signer_sks: Vec<_> = (0..new_num_signers) + .map(|ix| StacksPrivateKey::from_seed(format!("new_signer_{ix}").as_bytes())) + .collect(); + let new_signer_pks: Vec<_> = new_signer_sks + .iter() + .map(|sk| Secp256k1PublicKey::from_private(sk).to_bytes_compressed()) + .collect(); + let new_signer_addrs: Vec<_> = new_signer_sks.iter().map(tests::to_addr).collect(); + let additional_initial_balances: Vec<_> = new_signer_addrs + .iter() + .map(|addr| (*addr, POX_4_DEFAULT_STACKER_BALANCE)) + .collect(); + let new_signers_port_start = 3000 + num_signers; + + let node_1_rpc = 40553; + let node_1_p2p = 40554; + let node_2_rpc = 50553; + let node_2_p2p = 50554; + let localhost = "127.0.0.1"; + let node_1_rpc_bind = format!("{localhost}:{node_1_rpc}"); + + let new_signer_configs = build_signer_config_tomls( + &new_signer_sks, + &node_1_rpc_bind, + Some(Duration::from_millis(128)), // Timeout defaults to 5 seconds. Let's override it to 128 milliseconds. + &Network::Testnet, + "12345", + rand::random(), + 3000 + num_signers, + Some(100_000), + None, + Some(9000 + num_signers), + None, + ); + + let new_signer_configs: Vec<_> = new_signer_configs + .iter() + .map(|conf_str| SignerConfig::load_from_str(conf_str).unwrap()) + .collect(); + + let new_spawned_signers: Vec<_> = new_signer_configs + .iter() + .map(|signer_config| { + info!("spawning signer"); + SpawnedSigner::new(signer_config.clone()) + }) + .collect(); + + let mut miners = MultipleMinerTest::new_with_signer_dist( + num_signers, + 60 * 5, + |_| {}, + |node_config| { + for (addr, balance) in additional_initial_balances.iter() { + node_config.add_initial_balance(addr.to_string(), *balance); + } + for (ix, _) in new_signer_sks.iter().enumerate() { + info!( + "---- Adding signer endpoint to naka conf ({}) ----", + new_signers_port_start + ix, + ); + + node_config.events_observers.insert(EventObserverConfig { + endpoint: format!("localhost:{}", new_signers_port_start + ix), + events_keys: vec![ + EventKeyType::StackerDBChunks, + EventKeyType::BlockProposal, + EventKeyType::BurnchainBlocks, + ], + timeout_ms: 1000, + disable_retries: false, + }); + } + }, + |node_2_conf| { + node_2_conf.connection_options.reject_blocks_pushed = true; + }, + |_| 0, + Some(vec![node_1_rpc, node_1_p2p, node_2_rpc, node_2_p2p]), + ); + + miners.signer_test.num_stacking_cycles = 1; + miners.pause_commits_miner_2(); + miners.boot_to_epoch_3(); + + // verify that the first reward cycle has the old signers in the reward set + let reward_cycle = miners.signer_test.get_current_reward_cycle(); + let signer_test_pks: Vec<_> = miners + .signer_test + .signer_stacks_private_keys + .iter() + .map(|sk| Secp256k1PublicKey::from_private(sk).to_bytes_compressed()) + .collect(); + + info!("---- Verifying that the current signers are the old signers ----"); + let current_signers = miners.signer_test.get_reward_set_signers(reward_cycle); + assert_eq!(current_signers.len(), num_signers); + // Verify that the current signers are the same as the old signers + for signer in current_signers.iter() { + assert!(signer_test_pks.contains(&signer.signing_key.to_vec())); + assert!(!new_signer_pks.contains(&signer.signing_key.to_vec())); + } + + let burnchain = miners.get_node_configs().0.get_burnchain(); + let sortdb = burnchain.open_sortition_db(true).unwrap(); + + miners + .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 120) + .unwrap(); + + let mined_block = test_observer::get_mined_nakamoto_blocks().pop().unwrap(); + let block_sighash = mined_block.signer_signature_hash; + let signer_signatures = mined_block.signer_signature; + + // verify the mined_block signatures against the OLD signer set + for signature in signer_signatures.iter() { + let pk = Secp256k1PublicKey::recover_to_pubkey(block_sighash.bits(), signature) + .expect("FATAL: Failed to recover pubkey from block sighash"); + assert!(signer_test_pks.contains(&pk.to_bytes_compressed())); + assert!(!new_signer_pks.contains(&pk.to_bytes_compressed())); + } + + // advance to the next reward cycle, stacking to the new signers beforehand + let reward_cycle = miners.signer_test.get_current_reward_cycle(); + + info!("---- Stacking new signers -----"); + + let burn_block_height = miners + .signer_test + .running_nodes + .btc_regtest_controller + .get_headers_height(); + let accounts_to_check = new_signer_addrs; + for stacker_sk in new_signer_sks.iter() { + let pox_addr = PoxAddress::from_legacy( + AddressHashMode::SerializeP2PKH, + tests::to_addr(stacker_sk).bytes().clone(), + ); + let pox_addr_tuple: clarity::vm::Value = + pox_addr.clone().as_clarity_tuple().unwrap().into(); + let signature = make_pox_4_signer_key_signature( + &pox_addr, + stacker_sk, + reward_cycle.into(), + &Pox4SignatureTopic::StackStx, + CHAIN_ID_TESTNET, + 1_u128, + u128::MAX, + 1, + ) + .unwrap() + .to_rsv(); + + let chain_id = miners.get_node_configs().0.burnchain.chain_id; + let signer_pk = Secp256k1PublicKey::from_private(stacker_sk); + let stacking_tx = make_contract_call( + stacker_sk, + 0, + 1000, + chain_id, + &StacksAddress::burn_address(false), + "pox-4", + "stack-stx", + &[ + clarity::vm::Value::UInt(POX_4_DEFAULT_STACKER_STX_AMT), + pox_addr_tuple.clone(), + clarity::vm::Value::UInt(burn_block_height as u128), + clarity::vm::Value::UInt(1), + clarity::vm::Value::some(clarity::vm::Value::buff_from(signature).unwrap()) + .unwrap(), + clarity::vm::Value::buff_from(signer_pk.to_bytes_compressed()).unwrap(), + clarity::vm::Value::UInt(u128::MAX), + clarity::vm::Value::UInt(1), + ], + ); + submit_tx(&miners.node_http(), &stacking_tx); + } + + wait_for(60, || { + Ok(accounts_to_check + .iter() + .all(|acct| get_account(&miners.node_http(), acct).nonce >= 1)) + }) + .expect("Timed out waiting for stacking txs to be mined"); + + let next_reward_cycle = reward_cycle.saturating_add(1); + + let next_cycle_height = miners + .btc_regtest_controller_mut() + .get_burnchain() + .nakamoto_first_block_of_cycle(next_reward_cycle) + .saturating_add(1); + + miners.signer_test.run_until_burnchain_height_nakamoto( + Duration::from_secs(60), + next_cycle_height.saturating_sub(3), + new_num_signers, + ); + + miners.wait_for_chains(120); + + // Verify that the new reward set is the new signers + let reward_set = miners.signer_test.get_reward_set_signers(next_reward_cycle); + for signer in reward_set.iter() { + assert!(!signer_test_pks.contains(&signer.signing_key.to_vec())); + assert!(new_signer_pks.contains(&signer.signing_key.to_vec())); + } + + info!("---- Mining to just before the next reward cycle (block {next_cycle_height}) -----",); + miners.signer_test.run_until_burnchain_height_nakamoto( + Duration::from_secs(60), + next_cycle_height.saturating_sub(1), + new_num_signers, + ); + + let (old_spawned_signers, _, _) = + miners + .signer_test + .replace_signers(new_spawned_signers, new_signer_sks, new_signer_configs); + + miners.wait_for_chains(120); + + info!("---- Mining into the next reward cycle (block {next_cycle_height}) -----",); + miners.signer_test.run_until_burnchain_height_nakamoto( + Duration::from_secs(60), + next_cycle_height, + new_num_signers, + ); + let new_reward_cycle = miners.signer_test.get_current_reward_cycle(); + assert_eq!(new_reward_cycle, reward_cycle.saturating_add(1)); + + // miners.wait_for_chains(120); + + miners + .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 120) + .unwrap(); + + //miners.wait_for_chains(120); + + miners.send_and_mine_transfer_tx(60).unwrap(); + //miners.wait_for_chains(120); + miners.send_and_mine_transfer_tx(60).unwrap(); + //miners.wait_for_chains(120); + miners.send_and_mine_transfer_tx(60).unwrap(); + miners.wait_for_chains(120); + + let mined_block = test_observer::get_mined_nakamoto_blocks().pop().unwrap(); + + info!("---- Verifying that the new signers signed the block -----"); + let signer_signatures = mined_block.signer_signature; + + // verify the mined_block signatures against the NEW signer set + for signature in signer_signatures.iter() { + let pk = Secp256k1PublicKey::recover_to_pubkey(block_sighash.bits(), signature) + .expect("FATAL: Failed to recover pubkey from block sighash"); + assert!(!signer_test_pks.contains(&pk.to_bytes_compressed())); + assert!(new_signer_pks.contains(&pk.to_bytes_compressed())); + } + + miners + .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 120) + .unwrap(); + miners.wait_for_chains(120); + miners.send_and_mine_transfer_tx(60).unwrap(); + miners.wait_for_chains(120); + miners.send_and_mine_transfer_tx(60).unwrap(); + miners.wait_for_chains(120); + miners.send_and_mine_transfer_tx(60).unwrap(); + miners.wait_for_chains(120); + + miners.shutdown(); + for signer in old_spawned_signers { + assert!(signer.stop().is_none()); + } +} + /// This test involves two miners, each mining tenures with 6 blocks each. Half /// of the signers are attached to each miner, so the test also verifies that /// the signers' messages successfully make their way to the active miner. @@ -17215,6 +17504,7 @@ fn bitcoin_reorg_extended_tenure() { 0 } }, + None, ); let (conf_1, _conf_2) = miners.get_node_configs(); From 99e4e4b075071ec252f4533e83d5bed4acd04cca Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Thu, 10 Jul 2025 16:15:50 -0500 Subject: [PATCH 2/3] chore: add changelog, delete commented out test lines --- CHANGELOG.md | 4 ++++ testnet/stacks-node/src/tests/signer/v0.rs | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a0911103..fba6527dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE - When a previous block commit is unable to be RBFed, the miner will now just wait for it to be confirmed instead of submitting a new block commit which breaks the miner's UTXO chain. +### Fixed + +- Fixed tenure downloader logic on reward cycle boundaries (#6234). + ## [3.1.0.0.13] ### Added diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index 9ba1433c4c..bafd036b87 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -8462,18 +8462,12 @@ fn signer_multinode_rollover() { let new_reward_cycle = miners.signer_test.get_current_reward_cycle(); assert_eq!(new_reward_cycle, reward_cycle.saturating_add(1)); - // miners.wait_for_chains(120); - miners .mine_bitcoin_block_and_tenure_change_tx(&sortdb, TenureChangeCause::BlockFound, 120) .unwrap(); - //miners.wait_for_chains(120); - miners.send_and_mine_transfer_tx(60).unwrap(); - //miners.wait_for_chains(120); miners.send_and_mine_transfer_tx(60).unwrap(); - //miners.wait_for_chains(120); miners.send_and_mine_transfer_tx(60).unwrap(); miners.wait_for_chains(120); From fde56b4dbd5224715e483d10827af7da0c3228b4 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Fri, 11 Jul 2025 14:25:22 -0500 Subject: [PATCH 3/3] chore: comment typos Co-authored-by: Francesco <2530388+Jiloc@users.noreply.github.com> --- stackslib/src/net/download/nakamoto/download_state_machine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stackslib/src/net/download/nakamoto/download_state_machine.rs b/stackslib/src/net/download/nakamoto/download_state_machine.rs index 3072b0eaed..566f17f66e 100644 --- a/stackslib/src/net/download/nakamoto/download_state_machine.rs +++ b/stackslib/src/net/download/nakamoto/download_state_machine.rs @@ -238,7 +238,7 @@ impl NakamotoDownloadStateMachine { // we want tenures that are *confirmable* from the current sortition tip. // any miner commitment chosen in the sortition tip confirms a tenure with a lower // block height, so only consider the reward cycle at height - 1. - // *Note: its possible that a the second or later sortition of a RC also confirms a tenure + // *Note: it's possible that the second or later sortition of a RC also confirms a tenure // in the previous RC, but for the purposes of the wanted tenures calculations, we only // are loading up wanted tenures in the current RC. let tip_rc = Self::get_confirmable_reward_cycle(