Skip to content

Commit 8f8fe8d

Browse files
authored
Merge pull request #5036 from stacks-network/fix/deadlock
Fix deadlock
2 parents 8adec6d + 571dfee commit 8f8fe8d

File tree

2 files changed

+136
-21
lines changed

2 files changed

+136
-21
lines changed

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use crate::chainstate::nakamoto::coordinator::load_nakamoto_reward_set;
4444
use crate::chainstate::nakamoto::miner::NakamotoBlockBuilder;
4545
use crate::chainstate::nakamoto::signer_set::NakamotoSigners;
4646
use crate::chainstate::nakamoto::test_signers::TestSigners;
47+
use crate::chainstate::nakamoto::test_stall::*;
4748
use crate::chainstate::nakamoto::tests::get_account;
4849
use crate::chainstate::nakamoto::tests::node::TestStacker;
4950
use crate::chainstate::nakamoto::{
@@ -2453,3 +2454,89 @@ pub fn simple_nakamoto_coordinator_10_extended_tenures_10_sortitions() -> TestPe
24532454
fn test_nakamoto_coordinator_10_tenures_and_extensions_10_blocks() {
24542455
simple_nakamoto_coordinator_10_extended_tenures_10_sortitions();
24552456
}
2457+
2458+
#[test]
2459+
fn process_next_nakamoto_block_deadlock() {
2460+
let private_key = StacksPrivateKey::from_seed(&[2]);
2461+
let addr = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(&private_key));
2462+
2463+
let num_stackers: u32 = 4;
2464+
let mut signing_key_seed = num_stackers.to_be_bytes().to_vec();
2465+
signing_key_seed.extend_from_slice(&[1, 1, 1, 1]);
2466+
let signing_key = StacksPrivateKey::from_seed(signing_key_seed.as_slice());
2467+
let test_stackers = (0..num_stackers)
2468+
.map(|index| TestStacker {
2469+
signer_private_key: signing_key.clone(),
2470+
stacker_private_key: StacksPrivateKey::from_seed(&index.to_be_bytes()),
2471+
amount: u64::MAX as u128 - 10000,
2472+
pox_addr: Some(PoxAddress::Standard(
2473+
StacksAddress::new(
2474+
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
2475+
Hash160::from_data(&index.to_be_bytes()),
2476+
),
2477+
Some(AddressHashMode::SerializeP2PKH),
2478+
)),
2479+
max_amount: None,
2480+
})
2481+
.collect::<Vec<_>>();
2482+
let test_signers = TestSigners::new(vec![signing_key]);
2483+
let mut pox_constants = TestPeerConfig::default().burnchain.pox_constants;
2484+
pox_constants.reward_cycle_length = 10;
2485+
pox_constants.v2_unlock_height = 21;
2486+
pox_constants.pox_3_activation_height = 26;
2487+
pox_constants.v3_unlock_height = 27;
2488+
pox_constants.pox_4_activation_height = 28;
2489+
2490+
let mut boot_plan = NakamotoBootPlan::new(function_name!())
2491+
.with_test_stackers(test_stackers.clone())
2492+
.with_test_signers(test_signers.clone())
2493+
.with_private_key(private_key);
2494+
boot_plan.pox_constants = pox_constants;
2495+
2496+
info!("Creating peer");
2497+
2498+
let mut peer = boot_plan.boot_into_nakamoto_peer(vec![], None);
2499+
let mut sortition_db = peer.sortdb().reopen().unwrap();
2500+
let (chainstate, _) = &mut peer
2501+
.stacks_node
2502+
.as_mut()
2503+
.unwrap()
2504+
.chainstate
2505+
.reopen()
2506+
.unwrap();
2507+
2508+
enable_process_block_stall();
2509+
2510+
let miner_thread = std::thread::spawn(move || {
2511+
info!(" ------------------------------- MINING TENURE");
2512+
let (block, burn_height, ..) =
2513+
peer.single_block_tenure(&private_key, |_| {}, |_| {}, |_| true);
2514+
info!(" ------------------------------- TENURE MINED");
2515+
});
2516+
2517+
// Wait a bit, to ensure the miner has reached the stall
2518+
std::thread::sleep(std::time::Duration::from_secs(10));
2519+
2520+
// Lock the sortdb
2521+
info!(" ------------------------------- TRYING TO LOCK THE SORTDB");
2522+
let sort_tx = sortition_db.tx_begin().unwrap();
2523+
info!(" ------------------------------- SORTDB LOCKED");
2524+
2525+
// Un-stall the block processing
2526+
disable_process_block_stall();
2527+
2528+
// Wait a bit, to ensure the tenure will have grabbed any locks it needs
2529+
std::thread::sleep(std::time::Duration::from_secs(10));
2530+
2531+
// Lock the chainstate db
2532+
info!(" ------------------------------- TRYING TO LOCK THE CHAINSTATE");
2533+
let chainstate_tx = chainstate.chainstate_tx_begin().unwrap();
2534+
2535+
info!(" ------------------------------- SORTDB AND CHAINSTATE LOCKED");
2536+
drop(chainstate_tx);
2537+
drop(sort_tx);
2538+
info!(" ------------------------------- MAIN THREAD FINISHED");
2539+
2540+
// Wait for the blocker and miner threads to finish
2541+
miner_thread.join().unwrap();
2542+
}

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,31 @@ lazy_static! {
270270
];
271271
}
272272

273+
#[cfg(test)]
274+
mod test_stall {
275+
pub static TEST_PROCESS_BLOCK_STALL: std::sync::Mutex<Option<bool>> =
276+
std::sync::Mutex::new(None);
277+
278+
pub fn stall_block_processing() {
279+
if *TEST_PROCESS_BLOCK_STALL.lock().unwrap() == Some(true) {
280+
// Do an extra check just so we don't log EVERY time.
281+
warn!("Block processing is stalled due to testing directive.");
282+
while *TEST_PROCESS_BLOCK_STALL.lock().unwrap() == Some(true) {
283+
std::thread::sleep(std::time::Duration::from_millis(10));
284+
}
285+
info!("Block processing is no longer stalled due to testing directive.");
286+
}
287+
}
288+
289+
pub fn enable_process_block_stall() {
290+
TEST_PROCESS_BLOCK_STALL.lock().unwrap().replace(true);
291+
}
292+
293+
pub fn disable_process_block_stall() {
294+
TEST_PROCESS_BLOCK_STALL.lock().unwrap().replace(false);
295+
}
296+
}
297+
273298
/// Trait for common MARF getters between StacksDBConn and StacksDBTx
274299
pub trait StacksDBIndexed {
275300
fn get(&mut self, tip: &StacksBlockId, key: &str) -> Result<Option<String>, DBError>;
@@ -1722,6 +1747,9 @@ impl NakamotoChainState {
17221747
canonical_sortition_tip: &SortitionId,
17231748
dispatcher_opt: Option<&'a T>,
17241749
) -> Result<Option<StacksEpochReceipt>, ChainstateError> {
1750+
#[cfg(test)]
1751+
test_stall::stall_block_processing();
1752+
17251753
let nakamoto_blocks_db = stacks_chain_state.nakamoto_blocks_db();
17261754
let Some((next_ready_block, block_size)) =
17271755
nakamoto_blocks_db.next_ready_nakamoto_block(stacks_chain_state.db())?
@@ -1992,22 +2020,14 @@ impl NakamotoChainState {
19922020
next_ready_block.header.consensus_hash
19932021
);
19942022

1995-
// set stacks block accepted
1996-
let mut sort_tx = sort_db.tx_handle_begin(canonical_sortition_tip)?;
1997-
sort_tx.set_stacks_block_accepted(
1998-
&next_ready_block.header.consensus_hash,
1999-
&next_ready_block.header.block_hash(),
2000-
next_ready_block.header.chain_length,
2001-
)?;
2002-
20032023
// this will panic if the Clarity commit fails.
20042024
clarity_commit.commit();
20052025
chainstate_tx.commit()
2006-
.unwrap_or_else(|e| {
2007-
error!("Failed to commit chainstate transaction after committing Clarity block. The chainstate database is now corrupted.";
2008-
"error" => ?e);
2009-
panic!()
2010-
});
2026+
.unwrap_or_else(|e| {
2027+
error!("Failed to commit chainstate transaction after committing Clarity block. The chainstate database is now corrupted.";
2028+
"error" => ?e);
2029+
panic!()
2030+
});
20112031

20122032
// as a separate transaction, mark this block as processed.
20132033
// This is done separately so that the staging blocks DB, which receives writes
@@ -2019,6 +2039,22 @@ impl NakamotoChainState {
20192039

20202040
let signer_bitvec = (&next_ready_block).header.pox_treatment.clone();
20212041

2042+
// set stacks block accepted
2043+
let mut sort_tx = sort_db.tx_handle_begin(canonical_sortition_tip)?;
2044+
sort_tx.set_stacks_block_accepted(
2045+
&next_ready_block.header.consensus_hash,
2046+
&next_ready_block.header.block_hash(),
2047+
next_ready_block.header.chain_length,
2048+
)?;
2049+
2050+
sort_tx
2051+
.commit()
2052+
.unwrap_or_else(|e| {
2053+
error!("Failed to commit sortition db transaction after committing chainstate and clarity block. The chainstate database is now corrupted.";
2054+
"error" => ?e);
2055+
panic!()
2056+
});
2057+
20222058
// announce the block, if we're connected to an event dispatcher
20232059
if let Some(dispatcher) = dispatcher_opt {
20242060
let block_event = (
@@ -2045,14 +2081,6 @@ impl NakamotoChainState {
20452081
);
20462082
}
20472083

2048-
sort_tx
2049-
.commit()
2050-
.unwrap_or_else(|e| {
2051-
error!("Failed to commit sortition db transaction after committing chainstate and clarity block. The chainstate database is now corrupted.";
2052-
"error" => ?e);
2053-
panic!()
2054-
});
2055-
20562084
Ok(Some(receipt))
20572085
}
20582086

0 commit comments

Comments
 (0)