Skip to content

Commit 2fd4e78

Browse files
committed
feat: test for handling pending block proposal at tenure change
1 parent 8a072d1 commit 2fd4e78

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl SignerCoordinator {
107107

108108
// Spawn the signer DB listener thread
109109
let listener_thread = std::thread::Builder::new()
110-
.name("stackerdb_listener".to_string())
110+
.name(format!("stackerdb_listener_{}", burn_tip.block_height))
111111
.spawn(move || {
112112
if let Err(e) = listener.run() {
113113
error!("StackerDBListener: exited with error: {e:?}");

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7892,6 +7892,177 @@ fn block_validation_pending_table() {
78927892
signer_test.shutdown();
78937893
}
78947894

7895+
/// Test scenario:
7896+
///
7897+
/// - Miner A proposes a block in tenure A
7898+
/// - While that block is pending validation,
7899+
/// Miner B proposes a new block in tenure B
7900+
/// - After A's block is validated, Miner B's block is
7901+
/// rejected (because it's a sister block)
7902+
/// - Miner B retries and successfully mines a block
7903+
#[test]
7904+
#[ignore]
7905+
fn new_tenure_while_validating_previous_scenario() {
7906+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
7907+
return;
7908+
}
7909+
7910+
tracing_subscriber::registry()
7911+
.with(fmt::layer())
7912+
.with(EnvFilter::from_default_env())
7913+
.init();
7914+
7915+
info!("------------------------- Test Setup -------------------------");
7916+
let num_signers = 5;
7917+
let timeout = Duration::from_secs(30);
7918+
let sender_sk = Secp256k1PrivateKey::new();
7919+
let sender_addr = tests::to_addr(&sender_sk);
7920+
let send_amt = 100;
7921+
let send_fee = 180;
7922+
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
7923+
7924+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
7925+
num_signers,
7926+
vec![(sender_addr, send_amt + send_fee)],
7927+
|_| {},
7928+
|_| {},
7929+
None,
7930+
None,
7931+
);
7932+
let db_path = signer_test.signer_configs[0].db_path.clone();
7933+
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
7934+
signer_test.boot_to_epoch_3();
7935+
7936+
info!("----- Starting test -----";
7937+
"db_path" => db_path.clone().to_str(),
7938+
);
7939+
signer_test.mine_and_verify_confirmed_naka_block(timeout, num_signers, true);
7940+
TEST_VALIDATE_DELAY_DURATION_SECS.set(30);
7941+
7942+
let proposals_before = signer_test.get_miner_proposal_messages().len();
7943+
7944+
let peer_info_before_stall = signer_test.get_peer_info();
7945+
let burn_height_before_stall = peer_info_before_stall.burn_block_height;
7946+
let stacks_height_before_stall = peer_info_before_stall.stacks_tip_height;
7947+
7948+
// STEP 1: Miner A proposes a block in tenure A
7949+
7950+
// submit a tx so that the miner will attempt to mine an extra block
7951+
let sender_nonce = 0;
7952+
let transfer_tx = make_stacks_transfer(
7953+
&sender_sk,
7954+
sender_nonce,
7955+
send_fee,
7956+
signer_test.running_nodes.conf.burnchain.chain_id,
7957+
&recipient,
7958+
send_amt,
7959+
);
7960+
submit_tx(&http_origin, &transfer_tx);
7961+
7962+
info!("----- Waiting for miner to propose a block -----");
7963+
7964+
// Wait for the miner to propose a block
7965+
wait_for(30, || {
7966+
Ok(signer_test.get_miner_proposal_messages().len() > proposals_before)
7967+
})
7968+
.expect("Timed out waiting for miner to propose a block");
7969+
7970+
let proposals_before = signer_test.get_miner_proposal_messages().len();
7971+
let info_before = signer_test.get_peer_info();
7972+
7973+
// STEP 2: Miner B proposes a block in tenure B, while A's block is pending validation
7974+
7975+
info!("----- Mining a new BTC block -----");
7976+
signer_test
7977+
.running_nodes
7978+
.btc_regtest_controller
7979+
.build_next_block(1);
7980+
7981+
let mut last_log = Instant::now();
7982+
last_log -= Duration::from_secs(5);
7983+
let mut new_block_hash = None;
7984+
wait_for(120, || {
7985+
let proposals = signer_test.get_miner_proposal_messages();
7986+
let new_proposal = proposals.iter().find(|p| {
7987+
p.burn_height > burn_height_before_stall
7988+
&& p.block.header.chain_length == info_before.stacks_tip_height + 1
7989+
});
7990+
7991+
let has_new_proposal = new_proposal.is_some() && proposals.len() > proposals_before;
7992+
if last_log.elapsed() > Duration::from_secs(5) && !has_new_proposal {
7993+
info!(
7994+
"----- Waiting for a new proposal -----";
7995+
"proposals_len" => proposals.len(),
7996+
"burn_height_before" => info_before.burn_block_height,
7997+
);
7998+
last_log = Instant::now();
7999+
}
8000+
if let Some(proposal) = new_proposal {
8001+
new_block_hash = Some(proposal.block.header.signer_signature_hash());
8002+
}
8003+
Ok(has_new_proposal)
8004+
})
8005+
.expect("Timed out waiting for pending block proposal");
8006+
8007+
info!("----- Waiting for pending block validation to be submitted -----");
8008+
let new_block_hash = new_block_hash.unwrap();
8009+
8010+
// Set the delay to 0 so that the block validation finishes quickly
8011+
TEST_VALIDATE_DELAY_DURATION_SECS.set(0);
8012+
8013+
wait_for(30, || {
8014+
let proposal_responses = test_observer::get_proposal_responses();
8015+
let found_proposal = proposal_responses
8016+
.iter()
8017+
.any(|p| p.signer_signature_hash() == new_block_hash);
8018+
Ok(found_proposal)
8019+
})
8020+
.expect("Timed out waiting for pending block validation to be submitted");
8021+
8022+
// STEP 3: Miner B is rejected, retries, and mines a block
8023+
8024+
// Now, wait for miner B to propose a new block
8025+
let mut last_log = Instant::now();
8026+
last_log -= Duration::from_secs(5);
8027+
wait_for(30, || {
8028+
let proposals = signer_test.get_miner_proposal_messages();
8029+
let new_proposal = proposals.iter().find(|p| {
8030+
p.burn_height > burn_height_before_stall
8031+
&& p.block.header.chain_length == stacks_height_before_stall + 2
8032+
});
8033+
if last_log.elapsed() > Duration::from_secs(5) && !new_proposal.is_some() {
8034+
let last_proposal = proposals.last().unwrap();
8035+
info!(
8036+
"----- Waiting for a new proposal -----";
8037+
"proposals_len" => proposals.len(),
8038+
"burn_height_before" => burn_height_before_stall,
8039+
"stacks_height_before" => stacks_height_before_stall,
8040+
"last_proposal_burn_height" => last_proposal.burn_height,
8041+
"last_proposal_stacks_height" => last_proposal.block.header.chain_length,
8042+
);
8043+
last_log = Instant::now();
8044+
}
8045+
Ok(new_proposal.is_some())
8046+
})
8047+
.expect("Timed out waiting for miner to try a new block proposal");
8048+
8049+
// Wait for the new block to be mined
8050+
wait_for(30, || {
8051+
let peer_info = signer_test.get_peer_info();
8052+
Ok(
8053+
peer_info.stacks_tip_height == stacks_height_before_stall + 2
8054+
&& peer_info.burn_block_height == burn_height_before_stall + 1,
8055+
)
8056+
})
8057+
.expect("Timed out waiting for new block to be mined");
8058+
8059+
// Ensure that we didn't tenure extend
8060+
verify_last_block_contains_tenure_change_tx(TenureChangeCause::BlockFound);
8061+
8062+
info!("------------------------- Shutdown -------------------------");
8063+
signer_test.shutdown();
8064+
}
8065+
78958066
#[test]
78968067
#[ignore]
78978068
/// Test that a miner will extend its tenure after the succeding miner fails to mine a block.

0 commit comments

Comments
 (0)