Skip to content

Commit 3a41360

Browse files
authored
Merge pull request #5075 from stacks-network/test/partial-tenure-fork
Test partial tenure fork
2 parents c37babd + a8e4b40 commit 3a41360

File tree

8 files changed

+685
-80
lines changed

8 files changed

+685
-80
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ jobs:
112112
- tests::nakamoto_integrations::multiple_miners
113113
- tests::nakamoto_integrations::utxo_check_on_startup_panic
114114
- tests::nakamoto_integrations::utxo_check_on_startup_recover
115+
- tests::signer::v0::multiple_miners_with_nakamoto_blocks
116+
- tests::signer::v0::partial_tenure_fork
115117
# Do not run this one until we figure out why it fails in CI
116118
# - tests::neon_integrations::bitcoin_reorg_flap
117119
# - tests::neon_integrations::bitcoin_reorg_flap_with_follower

stackslib/src/chainstate/nakamoto/coordinator/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ use crate::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandle};
4141
use crate::chainstate::burn::operations::{BlockstackOperationType, LeaderBlockCommitOp};
4242
use crate::chainstate::coordinator::tests::{p2pkh_from, pox_addr_from};
4343
use crate::chainstate::nakamoto::coordinator::load_nakamoto_reward_set;
44+
use crate::chainstate::nakamoto::fault_injection::*;
4445
use crate::chainstate::nakamoto::miner::NakamotoBlockBuilder;
4546
use crate::chainstate::nakamoto::signer_set::NakamotoSigners;
4647
use crate::chainstate::nakamoto::test_signers::TestSigners;
47-
use crate::chainstate::nakamoto::test_stall::*;
4848
use crate::chainstate::nakamoto::tests::get_account;
4949
use crate::chainstate::nakamoto::tests::node::TestStacker;
5050
use crate::chainstate::nakamoto::{

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,27 +271,26 @@ lazy_static! {
271271
}
272272

273273
#[cfg(test)]
274-
mod test_stall {
275-
pub static TEST_PROCESS_BLOCK_STALL: std::sync::Mutex<Option<bool>> =
276-
std::sync::Mutex::new(None);
274+
mod fault_injection {
275+
static PROCESS_BLOCK_STALL: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
277276

278277
pub fn stall_block_processing() {
279-
if *TEST_PROCESS_BLOCK_STALL.lock().unwrap() == Some(true) {
278+
if *PROCESS_BLOCK_STALL.lock().unwrap() {
280279
// Do an extra check just so we don't log EVERY time.
281280
warn!("Block processing is stalled due to testing directive.");
282-
while *TEST_PROCESS_BLOCK_STALL.lock().unwrap() == Some(true) {
281+
while *PROCESS_BLOCK_STALL.lock().unwrap() {
283282
std::thread::sleep(std::time::Duration::from_millis(10));
284283
}
285284
info!("Block processing is no longer stalled due to testing directive.");
286285
}
287286
}
288287

289288
pub fn enable_process_block_stall() {
290-
TEST_PROCESS_BLOCK_STALL.lock().unwrap().replace(true);
289+
*PROCESS_BLOCK_STALL.lock().unwrap() = true;
291290
}
292291

293292
pub fn disable_process_block_stall() {
294-
TEST_PROCESS_BLOCK_STALL.lock().unwrap().replace(false);
293+
*PROCESS_BLOCK_STALL.lock().unwrap() = false;
295294
}
296295
}
297296

@@ -1758,7 +1757,7 @@ impl NakamotoChainState {
17581757
dispatcher_opt: Option<&'a T>,
17591758
) -> Result<Option<StacksEpochReceipt>, ChainstateError> {
17601759
#[cfg(test)]
1761-
test_stall::stall_block_processing();
1760+
fault_injection::stall_block_processing();
17621761

17631762
let nakamoto_blocks_db = stacks_chain_state.nakamoto_blocks_db();
17641763
let Some((next_ready_block, block_size)) =

stackslib/src/net/relay.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,51 @@ pub const MAX_RECENT_MESSAGES: usize = 256;
7070
pub const MAX_RECENT_MESSAGE_AGE: usize = 600; // seconds; equal to the expected epoch length
7171
pub const RELAY_DUPLICATE_INFERENCE_WARMUP: usize = 128;
7272

73+
#[cfg(any(test, feature = "testing"))]
74+
pub mod fault_injection {
75+
use std::path::Path;
76+
77+
static IGNORE_BLOCK: std::sync::Mutex<Option<(u64, String)>> = std::sync::Mutex::new(None);
78+
79+
pub fn ignore_block(height: u64, working_dir: &str) -> bool {
80+
if let Some((ignore_height, ignore_dir)) = &*IGNORE_BLOCK.lock().unwrap() {
81+
let working_dir_path = Path::new(working_dir);
82+
let ignore_dir_path = Path::new(ignore_dir);
83+
84+
let ignore = *ignore_height == height && working_dir_path.starts_with(ignore_dir_path);
85+
if ignore {
86+
warn!("Fault injection: ignore block at height {}", height);
87+
}
88+
return ignore;
89+
}
90+
false
91+
}
92+
93+
pub fn set_ignore_block(height: u64, working_dir: &str) {
94+
warn!(
95+
"Fault injection: set ignore block at height {} for working directory {}",
96+
height, working_dir
97+
);
98+
*IGNORE_BLOCK.lock().unwrap() = Some((height, working_dir.to_string()));
99+
}
100+
101+
pub fn clear_ignore_block() {
102+
warn!("Fault injection: clear ignore block");
103+
*IGNORE_BLOCK.lock().unwrap() = None;
104+
}
105+
}
106+
107+
#[cfg(not(any(test, feature = "testing")))]
108+
pub mod fault_injection {
109+
pub fn ignore_block(_height: u64, _working_dir: &str) -> bool {
110+
false
111+
}
112+
113+
pub fn set_ignore_block(_height: u64, _working_dir: &str) {}
114+
115+
pub fn clear_ignore_block() {}
116+
}
117+
73118
pub struct Relayer {
74119
/// Connection to the p2p thread
75120
p2p: NetworkHandle,
@@ -845,6 +890,10 @@ impl Relayer {
845890
&obtained_method,
846891
);
847892

893+
if fault_injection::ignore_block(block.header.chain_length, &burnchain.working_dir) {
894+
return Ok(false);
895+
}
896+
848897
// do we have this block? don't lock the DB needlessly if so.
849898
if chainstate
850899
.nakamoto_blocks_db()

testnet/stacks-node/src/nakamoto_node/miner.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ use stacks::chainstate::stacks::{
4545
use stacks::net::p2p::NetworkHandle;
4646
use stacks::net::stackerdb::StackerDBs;
4747
use stacks::net::{NakamotoBlocksData, StacksMessageType};
48-
use stacks::util::get_epoch_time_secs;
4948
use stacks::util::secp256k1::MessageSignature;
5049
use stacks_common::codec::read_next;
5150
use stacks_common::types::chainstate::{StacksAddress, StacksBlockId};
@@ -1068,9 +1067,12 @@ impl BlockMinerThread {
10681067
);
10691068
NakamotoNodeError::ParentNotFound
10701069
})?;
1071-
let current_timestamp = get_epoch_time_secs();
1072-
let time_since_parent_ms =
1073-
current_timestamp.saturating_sub(stacks_parent_header.burn_header_timestamp) * 1000;
1070+
let current_timestamp = x.header.timestamp;
1071+
let parent_timestamp = match stacks_parent_header.anchored_header.as_stacks_nakamoto() {
1072+
Some(naka_header) => naka_header.timestamp,
1073+
None => stacks_parent_header.burn_header_timestamp,
1074+
};
1075+
let time_since_parent_ms = current_timestamp.saturating_sub(parent_timestamp) * 1000;
10741076
if time_since_parent_ms < self.config.miner.min_time_between_blocks_ms {
10751077
debug!("Parent block mined {time_since_parent_ms} ms ago. Required minimum gap between blocks is {} ms", self.config.miner.min_time_between_blocks_ms;
10761078
"current_timestamp" => current_timestamp,

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ pub fn next_block_and_wait_for_commits(
711711
(0..commits_before.len()).map(|_| None).collect();
712712
let mut commit_sent_time: Vec<Option<Instant>> =
713713
(0..commits_before.len()).map(|_| None).collect();
714+
sleep_ms(2000); // Make sure that the proposed stacks block has a different timestamp than its parent
714715
next_block_and(btc_controller, timeout_secs, || {
715716
for i in 0..commits_submitted.len() {
716717
let commits_sent = commits_submitted[i].load(Ordering::SeqCst);

testnet/stacks-node/src/tests/neon_integrations.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,8 @@ pub fn wait_for_microblocks(microblocks_processed: &Arc<AtomicU64>, timeout: u64
749749
return true;
750750
}
751751

752-
/// returns Txid string
753-
pub fn submit_tx(http_origin: &str, tx: &Vec<u8>) -> String {
752+
/// returns Txid string upon success
753+
pub fn submit_tx_fallible(http_origin: &str, tx: &Vec<u8>) -> Result<String, String> {
754754
let client = reqwest::blocking::Client::new();
755755
let path = format!("{}/v2/transactions", http_origin);
756756
let res = client
@@ -768,13 +768,20 @@ pub fn submit_tx(http_origin: &str, tx: &Vec<u8>) -> String {
768768
.txid()
769769
.to_string()
770770
);
771-
return res;
771+
Ok(res)
772772
} else {
773-
eprintln!("Submit tx error: {}", res.text().unwrap());
774-
panic!("");
773+
Err(res.text().unwrap())
775774
}
776775
}
777776

777+
/// returns Txid string
778+
pub fn submit_tx(http_origin: &str, tx: &Vec<u8>) -> String {
779+
submit_tx_fallible(http_origin, tx).unwrap_or_else(|e| {
780+
eprintln!("Submit tx error: {}", e);
781+
panic!("");
782+
})
783+
}
784+
778785
pub fn get_unconfirmed_tx(http_origin: &str, txid: &Txid) -> Option<String> {
779786
let client = reqwest::blocking::Client::new();
780787
let path = format!("{}/v2/transactions/unconfirmed/{}", http_origin, txid);

0 commit comments

Comments
 (0)