Skip to content

Commit da16af9

Browse files
committed
feat: allow other transactions with tenure extends
We want the heuristic to be such that the miner mines block found tenure changes quickly, only including the tenure change and the coinbase, but tenure extensions do not require this quick response, so they should include other transactions. Fixes #5577
1 parent 36d49b0 commit da16af9

File tree

4 files changed

+120
-10
lines changed

4 files changed

+120
-10
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ jobs:
124124
- tests::signer::v0::signing_in_0th_tenure_of_reward_cycle
125125
- tests::signer::v0::continue_after_tenure_extend
126126
- tests::signer::v0::tenure_extend_after_idle_signers
127+
- tests::signer::v0::tenure_extend_with_other_transactions
127128
- tests::signer::v0::tenure_extend_after_idle_miner
128129
- tests::signer::v0::tenure_extend_after_failed_miner
129130
- tests::signer::v0::tenure_extend_succeeds_after_rejected_attempt

stackslib/src/chainstate/stacks/miner.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2258,7 +2258,13 @@ impl StacksBlockBuilder {
22582258
// nakamoto miner tenure start heuristic:
22592259
// mine an empty block so you can start your tenure quickly!
22602260
if let Some(tx) = initial_txs.first() {
2261-
if matches!(&tx.payload, TransactionPayload::TenureChange(_)) {
2261+
if matches!(
2262+
&tx.payload,
2263+
TransactionPayload::TenureChange(TenureChangePayload {
2264+
cause: TenureChangeCause::BlockFound,
2265+
..
2266+
})
2267+
) {
22622268
info!("Nakamoto miner heuristic: during tenure change blocks, produce a fast short block to begin tenure");
22632269
return Ok((false, tx_events));
22642270
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,15 @@ pub fn check_nakamoto_empty_block_heuristics() {
247247
continue;
248248
}
249249
let txs = test_observer::parse_transactions(block);
250-
let has_tenure_change = txs
251-
.iter()
252-
.any(|tx| matches!(tx.payload, TransactionPayload::TenureChange(_)));
250+
let has_tenure_change = txs.iter().any(|tx| {
251+
matches!(
252+
tx.payload,
253+
TransactionPayload::TenureChange(TenureChangePayload {
254+
cause: TenureChangeCause::BlockFound,
255+
..
256+
})
257+
)
258+
});
253259
if has_tenure_change {
254260
let only_coinbase_and_tenure_change = txs.iter().all(|tx| {
255261
matches!(

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

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,6 +2616,51 @@ fn tenure_extend_after_idle_signers() {
26162616
return;
26172617
}
26182618

2619+
tracing_subscriber::registry()
2620+
.with(fmt::layer())
2621+
.with(EnvFilter::from_default_env())
2622+
.init();
2623+
2624+
info!("------------------------- Test Setup -------------------------");
2625+
let num_signers = 5;
2626+
let idle_timeout = Duration::from_secs(30);
2627+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
2628+
num_signers,
2629+
vec![],
2630+
|config| {
2631+
config.tenure_idle_timeout = idle_timeout;
2632+
},
2633+
|_| {},
2634+
None,
2635+
None,
2636+
);
2637+
2638+
signer_test.boot_to_epoch_3();
2639+
2640+
info!("---- Nakamoto booted, starting test ----");
2641+
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
2642+
2643+
info!("---- Waiting for a tenure extend ----");
2644+
2645+
// Now, wait for a block with a tenure extend
2646+
wait_for(idle_timeout.as_secs() + 10, || {
2647+
Ok(last_block_contains_tenure_change_tx(
2648+
TenureChangeCause::Extended,
2649+
))
2650+
})
2651+
.expect("Timed out waiting for a block with a tenure extend");
2652+
2653+
signer_test.shutdown();
2654+
}
2655+
2656+
#[test]
2657+
#[ignore]
2658+
/// This test verifies that a miner will include other transactions with a TenureExtend transaction.
2659+
fn tenure_extend_with_other_transactions() {
2660+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
2661+
return;
2662+
}
2663+
26192664
tracing_subscriber::registry()
26202665
.with(fmt::layer())
26212666
.with(EnvFilter::from_default_env())
@@ -2627,7 +2672,7 @@ fn tenure_extend_after_idle_signers() {
26272672
let sender_addr = tests::to_addr(&sender_sk);
26282673
let send_amt = 100;
26292674
let send_fee = 180;
2630-
let _recipient = PrincipalData::from(StacksAddress::burn_address(false));
2675+
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
26312676
let idle_timeout = Duration::from_secs(30);
26322677
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
26332678
num_signers,
@@ -2639,20 +2684,72 @@ fn tenure_extend_after_idle_signers() {
26392684
None,
26402685
None,
26412686
);
2642-
let _http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
2687+
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
26432688

26442689
signer_test.boot_to_epoch_3();
26452690

26462691
info!("---- Nakamoto booted, starting test ----");
26472692
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
26482693

2649-
info!("---- Waiting for a tenure extend ----");
2694+
info!("Pause miner so it doesn't propose a block before the tenure extend");
2695+
TEST_MINE_STALL.set(true);
2696+
2697+
// Submit a transaction to be included with the tenure extend
2698+
let transfer_tx = make_stacks_transfer(
2699+
&sender_sk,
2700+
0,
2701+
send_fee,
2702+
signer_test.running_nodes.conf.burnchain.chain_id,
2703+
&recipient,
2704+
send_amt,
2705+
);
2706+
let _tx = submit_tx(&http_origin, &transfer_tx);
2707+
2708+
info!("---- Wait for tenure extend timeout ----");
2709+
2710+
sleep_ms(idle_timeout.as_millis() as u64 + 1000);
2711+
2712+
info!("---- Resume miner to propose a block with the tenure extend ----");
2713+
TEST_MINE_STALL.set(false);
26502714

26512715
// Now, wait for a block with a tenure extend
26522716
wait_for(idle_timeout.as_secs() + 10, || {
2653-
Ok(last_block_contains_tenure_change_tx(
2654-
TenureChangeCause::Extended,
2655-
))
2717+
let blocks = test_observer::get_blocks();
2718+
let last_block = &blocks.last().unwrap();
2719+
let transactions = last_block["transactions"].as_array().unwrap();
2720+
let (first_tx, other_txs) = transactions.split_first().unwrap();
2721+
let raw_tx = first_tx["raw_tx"].as_str().unwrap();
2722+
let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
2723+
let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
2724+
let found_tenure_extend = match &parsed.payload {
2725+
TransactionPayload::TenureChange(payload)
2726+
if payload.cause == TenureChangeCause::Extended =>
2727+
{
2728+
info!("Found tenure extend transaction: {parsed:?}");
2729+
true
2730+
}
2731+
_ => false,
2732+
};
2733+
if found_tenure_extend {
2734+
let found_transfer = other_txs.iter().any(|tx| {
2735+
let raw_tx = tx["raw_tx"].as_str().unwrap();
2736+
let tx_bytes = hex_bytes(&raw_tx[2..]).unwrap();
2737+
let parsed = StacksTransaction::consensus_deserialize(&mut &tx_bytes[..]).unwrap();
2738+
match &parsed.payload {
2739+
TransactionPayload::TokenTransfer(..) => true,
2740+
_ => false,
2741+
}
2742+
});
2743+
if found_transfer {
2744+
info!("Found transfer transaction");
2745+
Ok(true)
2746+
} else {
2747+
Err("No transfer transaction found together with the tenure extend".to_string())
2748+
}
2749+
} else {
2750+
info!("No tenure change transaction found");
2751+
Ok(false)
2752+
}
26562753
})
26572754
.expect("Timed out waiting for a block with a tenure extend");
26582755

0 commit comments

Comments
 (0)