Skip to content

Commit c3b55f0

Browse files
committed
CRC: ensure that the tenure change transaction and contract call get mined in the same stacks block
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 7c073fa commit c3b55f0

File tree

2 files changed

+68
-7
lines changed

2 files changed

+68
-7
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ use crate::neon_node;
6060
use crate::run_loop::nakamoto::Globals;
6161
use crate::run_loop::RegisteredKey;
6262

63+
#[cfg(test)]
64+
pub static TEST_MINE_STALL: std::sync::Mutex<Option<bool>> = std::sync::Mutex::new(None);
6365
#[cfg(test)]
6466
pub static TEST_BROADCAST_STALL: std::sync::Mutex<Option<bool>> = std::sync::Mutex::new(None);
6567
#[cfg(test)]
@@ -291,6 +293,15 @@ impl BlockMinerThread {
291293
let mut attempts = 0;
292294
// now, actually run this tenure
293295
loop {
296+
#[cfg(test)]
297+
if *TEST_MINE_STALL.lock().unwrap() == Some(true) {
298+
// Do an extra check just so we don't log EVERY time.
299+
warn!("Mining is stalled due to testing directive");
300+
while *TEST_MINE_STALL.lock().unwrap() == Some(true) {
301+
std::thread::sleep(std::time::Duration::from_millis(10));
302+
}
303+
warn!("Mining is no longer stalled due to testing directive. Continuing...");
304+
}
294305
let new_block = loop {
295306
// If we're mock mining, we may not have processed the block that the
296307
// actual tenure winner committed to yet. So, before attempting to

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

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ use wsts::net::Message;
9494

9595
use super::bitcoin_regtest::BitcoinCoreController;
9696
use crate::config::{EventKeyType, EventObserverConfig, InitialBalance};
97-
use crate::nakamoto_node::miner::{TEST_BLOCK_ANNOUNCE_STALL, TEST_BROADCAST_STALL};
97+
use crate::nakamoto_node::miner::{
98+
TEST_BLOCK_ANNOUNCE_STALL, TEST_BROADCAST_STALL, TEST_MINE_STALL,
99+
};
98100
use crate::neon::{Counters, RunLoopCounter};
99101
use crate::operations::BurnchainOpSigner;
100102
use crate::run_loop::boot_nakamoto;
@@ -5074,6 +5076,7 @@ fn clarity_burn_state() {
50745076
let mut signers = TestSigners::default();
50755077
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
50765078
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
5079+
// Increase the wait on interim blocks to prevent a stacks block being mined before we force it to be
50775080
naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
50785081
let sender_sk = Secp256k1PrivateKey::new();
50795082
let sender_signer_sk = Secp256k1PrivateKey::new();
@@ -5185,21 +5188,68 @@ fn clarity_burn_state() {
51855188
vec![&Value::UInt(burn_block_height)],
51865189
);
51875190
result.expect_result_ok().expect("Read-only call failed");
5191+
5192+
// Pause mining to prevent the stacks block from being mined before the tenure change is processed
5193+
TEST_MINE_STALL.lock().unwrap().replace(true);
5194+
// Submit a tx for the next block (the next block will be a new tenure, so the burn block height will increment)
5195+
let call_tx = tests::make_contract_call(
5196+
&sender_sk,
5197+
sender_nonce,
5198+
tx_fee,
5199+
&sender_addr,
5200+
contract_name,
5201+
"bar",
5202+
&[Value::UInt(burn_block_height + 1)],
5203+
);
5204+
sender_nonce += 1;
5205+
submit_tx(&http_origin, &call_tx);
51885206
}
51895207

51905208
let commits_before = commits_submitted.load(Ordering::SeqCst);
5191-
next_block_and_mine_commit(
5192-
&mut btc_regtest_controller,
5193-
60,
5194-
&coord_channel,
5195-
&commits_submitted,
5196-
)
5209+
let blocks_processed_before = coord_channel
5210+
.lock()
5211+
.expect("Mutex poisoned")
5212+
.get_stacks_blocks_processed();
5213+
next_block_and(&mut btc_regtest_controller, 60, || {
5214+
Ok(commits_submitted.load(Ordering::SeqCst) > commits_before)
5215+
})
5216+
.unwrap();
5217+
TEST_MINE_STALL.lock().unwrap().replace(false);
5218+
wait_for(20, || {
5219+
Ok(coord_channel
5220+
.lock()
5221+
.expect("Mutex poisoned")
5222+
.get_stacks_blocks_processed()
5223+
> blocks_processed_before)
5224+
})
51975225
.unwrap();
51985226

51995227
let info = get_chain_info(&naka_conf);
52005228
burn_block_height = info.burn_block_height as u128;
52015229
info!("Expecting burn block height to be {}", burn_block_height);
52025230

5231+
// Assert that the contract call was successful
5232+
test_observer::get_mined_nakamoto_blocks()
5233+
.last()
5234+
.unwrap()
5235+
.tx_events
5236+
.iter()
5237+
.for_each(|event| match event {
5238+
TransactionEvent::Success(TransactionSuccessEvent { result, fee, .. }) => {
5239+
// Ignore coinbase and tenure transactions
5240+
if *fee == 0 {
5241+
return;
5242+
}
5243+
5244+
info!("Contract call result: {}", result);
5245+
result.clone().expect_result_ok().expect("Ok result");
5246+
}
5247+
_ => {
5248+
info!("Unsuccessful event: {:?}", event);
5249+
panic!("Expected a successful transaction");
5250+
}
5251+
});
5252+
52035253
// mine the interim blocks
52045254
for interim_block_ix in 0..inter_blocks_per_tenure {
52055255
info!("Mining interim block {interim_block_ix}");

0 commit comments

Comments
 (0)