Skip to content

Commit 2ef3ef3

Browse files
committed
Merge branch 'fix/tenure-extend-burnchain-tip-check' of https://github.com/stacks-network/stacks-core into fix/tenure-extend-burnchain-tip-check
2 parents 446ff01 + ff8f55d commit 2ef3ef3

File tree

5 files changed

+172
-11
lines changed

5 files changed

+172
-11
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ jobs:
118118
- tests::signer::v0::mine_2_nakamoto_reward_cycles
119119
- tests::signer::v0::signer_set_rollover
120120
- tests::signer::v0::signing_in_0th_tenure_of_reward_cycle
121+
- tests::signer::v0::continue_after_tenure_extend
121122
- tests::nakamoto_integrations::burn_ops_integration_test
122123
- tests::nakamoto_integrations::check_block_heights
123124
- tests::nakamoto_integrations::clarity_burn_state

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ impl BlockMinerThread {
383383
"block_height" => new_block.header.chain_length,
384384
"consensus_hash" => %new_block.header.consensus_hash,
385385
);
386-
return Err(e);
386+
continue;
387387
}
388388
_ => {
389389
error!("Error while gathering signatures: {e:?}. Will try mining again.";
@@ -1247,7 +1247,9 @@ impl ParentStacksBlockInfo {
12471247
let burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn())
12481248
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
12491249

1250-
if burn_chain_tip.consensus_hash != check_burn_block.consensus_hash {
1250+
if burn_chain_tip.consensus_hash != check_burn_block.consensus_hash
1251+
&& burn_chain_tip.sortition_id != check_burn_block.sortition_id
1252+
{
12511253
info!(
12521254
"New canonical burn chain tip detected. Will not try to mine.";
12531255
"new_consensus_hash" => %burn_chain_tip.consensus_hash,

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,13 @@ impl SignCoordinator {
251251
}
252252

253253
/// Check if the tenure needs to change
254-
fn check_burn_tip_changed(sortdb: &SortitionDB, consensus_hash: &ConsensusHash) -> bool {
254+
fn check_burn_tip_changed(sortdb: &SortitionDB, burn_block: &BlockSnapshot) -> bool {
255255
let cur_burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
256256
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
257257

258-
if cur_burn_chain_tip.consensus_hash != *consensus_hash {
258+
if cur_burn_chain_tip.consensus_hash != burn_block.consensus_hash
259+
&& cur_burn_chain_tip.sortition_id != burn_block.sortition_id
260+
{
259261
info!("SignCoordinator: Cancel signature aggregation; burnchain tip has changed");
260262
true
261263
} else {
@@ -365,7 +367,7 @@ impl SignCoordinator {
365367
return Ok(stored_block.header.signer_signature);
366368
}
367369

368-
if Self::check_burn_tip_changed(&sortdb, &burn_tip.consensus_hash) {
370+
if Self::check_burn_tip_changed(&sortdb, &burn_tip) {
369371
debug!("SignCoordinator: Exiting due to new burnchain tip");
370372
return Err(NakamotoNodeError::BurnchainTipChanged);
371373
}

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

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6920,10 +6920,10 @@ fn continue_tenure_extend() {
69206920
// setup sender + recipient for a test stx transfer
69216921
let sender_addr = tests::to_addr(&sender_sk);
69226922
let send_amt = 1000;
6923-
let send_fee = 100;
6923+
let send_fee = 200;
69246924
naka_conf.add_initial_balance(
69256925
PrincipalData::from(sender_addr.clone()).to_string(),
6926-
send_amt * 2 + send_fee,
6926+
(send_amt + send_fee) * 20,
69276927
);
69286928
let sender_signer_sk = Secp256k1PrivateKey::new();
69296929
let sender_signer_addr = tests::to_addr(&sender_signer_sk);
@@ -6933,6 +6933,7 @@ fn continue_tenure_extend() {
69336933
);
69346934
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
69356935
let stacker_sk = setup_stacker(&mut naka_conf);
6936+
let mut transfer_nonce = 0;
69366937

69376938
test_observer::spawn();
69386939
test_observer::register_any(&mut naka_conf);
@@ -7053,7 +7054,7 @@ fn continue_tenure_extend() {
70537054
// Submit a TX
70547055
let transfer_tx = make_stacks_transfer(
70557056
&sender_sk,
7056-
0,
7057+
transfer_nonce,
70577058
send_fee,
70587059
naka_conf.burnchain.chain_id,
70597060
&recipient,
@@ -7115,6 +7116,26 @@ fn continue_tenure_extend() {
71157116
})
71167117
.unwrap();
71177118

7119+
// Mine 3 nakamoto tenures
7120+
for i in 0..3 {
7121+
info!("Triggering Nakamoto blocks after extend ({})", i + 1);
7122+
transfer_nonce += 1;
7123+
let transfer_tx = make_stacks_transfer(
7124+
&sender_sk,
7125+
transfer_nonce,
7126+
send_fee,
7127+
naka_conf.burnchain.chain_id,
7128+
&recipient,
7129+
send_amt,
7130+
);
7131+
submit_tx(&http_origin, &transfer_tx);
7132+
wait_for(10, || {
7133+
let sender_nonce = get_account(&http_origin, &to_addr(&sender_sk)).nonce;
7134+
Ok(sender_nonce >= transfer_nonce)
7135+
})
7136+
.expect("Timed out waiting for transfer TX to confirm");
7137+
}
7138+
71187139
info!("Resuming commit ops to mine regular tenures.");
71197140
test_skip_commit_op.0.lock().unwrap().replace(false);
71207141

@@ -7152,7 +7173,9 @@ fn continue_tenure_extend() {
71527173
let mut tenure_extends = vec![];
71537174
let mut tenure_block_founds = vec![];
71547175
let mut transfer_tx_included = false;
7176+
let mut last_block_had_extend = false;
71557177
for block in test_observer::get_blocks() {
7178+
let mut has_extend = false;
71567179
for tx in block["transactions"].as_array().unwrap() {
71577180
let raw_tx = tx["raw_tx"].as_str().unwrap();
71587181
if raw_tx == &transfer_tx_hex {
@@ -7166,12 +7189,21 @@ fn continue_tenure_extend() {
71667189
let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
71677190
match &parsed.payload {
71687191
TransactionPayload::TenureChange(payload) => match payload.cause {
7169-
TenureChangeCause::Extended => tenure_extends.push(parsed),
7170-
TenureChangeCause::BlockFound => tenure_block_founds.push(parsed),
7192+
TenureChangeCause::Extended => {
7193+
has_extend = true;
7194+
tenure_extends.push(parsed);
7195+
}
7196+
TenureChangeCause::BlockFound => {
7197+
if last_block_had_extend {
7198+
panic!("Expected a Nakamoto block to happen after tenure extend block");
7199+
}
7200+
tenure_block_founds.push(parsed);
7201+
}
71717202
},
71727203
_ => {}
71737204
};
71747205
}
7206+
last_block_had_extend = has_extend;
71757207
}
71767208
assert!(
71777209
!tenure_extends.is_empty(),

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

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader, NakamotoC
3434
use stacks::chainstate::stacks::address::PoxAddress;
3535
use stacks::chainstate::stacks::boot::MINERS_NAME;
3636
use stacks::chainstate::stacks::db::{StacksBlockHeaderTypes, StacksChainState, StacksHeaderInfo};
37+
use stacks::chainstate::stacks::{StacksTransaction, TenureChangeCause, TransactionPayload};
3738
use stacks::codec::StacksMessageCodec;
3839
use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET};
3940
use stacks::libstackerdb::StackerDBChunkData;
@@ -42,7 +43,7 @@ use stacks::net::api::postblock_proposal::{ValidateRejectCode, TEST_VALIDATE_STA
4243
use stacks::net::relay::fault_injection::set_ignore_block;
4344
use stacks::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey};
4445
use stacks::types::PublicKey;
45-
use stacks::util::hash::MerkleHashFunc;
46+
use stacks::util::hash::{hex_bytes, MerkleHashFunc};
4647
use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
4748
use stacks::util_lib::boot::boot_code_id;
4849
use stacks::util_lib::signed_structured_data::pox4::{
@@ -5076,6 +5077,129 @@ fn miner_recovers_when_broadcast_block_delay_across_tenures_occurs() {
50765077
assert_ne!(block_n_2, block_n);
50775078
}
50785079

5080+
#[test]
5081+
#[ignore]
5082+
/// Test that we can mine a tenure extend and then continue mining afterwards.
5083+
fn continue_after_tenure_extend() {
5084+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
5085+
return;
5086+
}
5087+
5088+
tracing_subscriber::registry()
5089+
.with(fmt::layer())
5090+
.with(EnvFilter::from_default_env())
5091+
.init();
5092+
5093+
info!("------------------------- Test Setup -------------------------");
5094+
let num_signers = 5;
5095+
let sender_sk = Secp256k1PrivateKey::new();
5096+
let sender_addr = tests::to_addr(&sender_sk);
5097+
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
5098+
let send_amt = 100;
5099+
let send_fee = 180;
5100+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new(
5101+
num_signers,
5102+
vec![(sender_addr.clone(), (send_amt + send_fee) * 5)],
5103+
);
5104+
let timeout = Duration::from_secs(200);
5105+
let coord_channel = signer_test.running_nodes.coord_channel.clone();
5106+
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
5107+
5108+
signer_test.boot_to_epoch_3();
5109+
5110+
info!("------------------------- Mine Normal Tenure -------------------------");
5111+
signer_test.mine_and_verify_confirmed_naka_block(timeout, num_signers);
5112+
5113+
info!("------------------------- Extend Tenure -------------------------");
5114+
signer_test
5115+
.running_nodes
5116+
.nakamoto_test_skip_commit_op
5117+
.0
5118+
.lock()
5119+
.unwrap()
5120+
.replace(true);
5121+
5122+
// It's possible that we have a pending block commit already.
5123+
// Mine two BTC blocks to "flush" this commit.
5124+
5125+
for i in 0..2 {
5126+
info!(
5127+
"------------- After pausing commits, triggering 2 BTC blocks: ({} of 2) -----------",
5128+
i + 1
5129+
);
5130+
5131+
let blocks_processed_before = coord_channel
5132+
.lock()
5133+
.expect("Mutex poisoned")
5134+
.get_stacks_blocks_processed();
5135+
signer_test
5136+
.running_nodes
5137+
.btc_regtest_controller
5138+
.build_next_block(1);
5139+
5140+
wait_for(60, || {
5141+
let blocks_processed_after = coord_channel
5142+
.lock()
5143+
.expect("Mutex poisoned")
5144+
.get_stacks_blocks_processed();
5145+
Ok(blocks_processed_after > blocks_processed_before)
5146+
})
5147+
.expect("Timed out waiting for tenure extend block");
5148+
}
5149+
5150+
// The last block should have a single instruction in it, the tenure extend
5151+
let blocks = test_observer::get_blocks();
5152+
let last_block = blocks.last().unwrap();
5153+
let transactions = last_block["transactions"].as_array().unwrap();
5154+
let tx = transactions.first().expect("No transactions in block");
5155+
let raw_tx = tx["raw_tx"].as_str().unwrap();
5156+
let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
5157+
let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
5158+
match &parsed.payload {
5159+
TransactionPayload::TenureChange(payload)
5160+
if payload.cause == TenureChangeCause::Extended => {}
5161+
_ => panic!("Expected tenure extend transaction, got {:?}", parsed),
5162+
};
5163+
5164+
// Verify that the miner can continue mining in the tenure with the tenure extend
5165+
info!("------------------------- Mine After Tenure Extend -------------------------");
5166+
let mut sender_nonce = 0;
5167+
let mut blocks_processed_before = coord_channel
5168+
.lock()
5169+
.expect("Mutex poisoned")
5170+
.get_stacks_blocks_processed();
5171+
for _ in 0..5 {
5172+
// submit a tx so that the miner will mine an extra block
5173+
let transfer_tx = make_stacks_transfer(
5174+
&sender_sk,
5175+
sender_nonce,
5176+
send_fee,
5177+
signer_test.running_nodes.conf.burnchain.chain_id,
5178+
&recipient,
5179+
send_amt,
5180+
);
5181+
sender_nonce += 1;
5182+
submit_tx(&http_origin, &transfer_tx);
5183+
5184+
info!("Submitted transfer tx and waiting for block proposal");
5185+
wait_for(30, || {
5186+
let blocks_processed_after = coord_channel
5187+
.lock()
5188+
.expect("Mutex poisoned")
5189+
.get_stacks_blocks_processed();
5190+
Ok(blocks_processed_after > blocks_processed_before)
5191+
})
5192+
.expect("Timed out waiting for block proposal");
5193+
blocks_processed_before = coord_channel
5194+
.lock()
5195+
.expect("Mutex poisoned")
5196+
.get_stacks_blocks_processed();
5197+
info!("Block {blocks_processed_before} processed, continuing");
5198+
}
5199+
5200+
signer_test.shutdown();
5201+
}
5202+
50795203
#[test]
50805204
#[ignore]
50815205
/// Test that signers can successfully sign a block proposal in the 0th tenure of a reward cycle

0 commit comments

Comments
 (0)