Skip to content

Commit 90c2596

Browse files
committed
test: add assertions for empty block heuristics
1 parent 91f429a commit 90c2596

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,32 @@ impl TestSigningChannel {
230230
}
231231
}
232232

233+
/// Assert that the block events captured by the test observer
234+
/// all match the miner heuristic of *exclusively* including the
235+
/// tenure change transaction in tenure changing blocks.
236+
pub fn check_nakamoto_empty_block_heuristics() {
237+
let blocks = test_observer::get_blocks();
238+
for block in blocks.iter() {
239+
// if its not a nakamoto block, don't check anything
240+
if block.get("miner_signature").is_none() {
241+
continue;
242+
}
243+
let txs = test_observer::parse_transactions(block);
244+
let has_tenure_change = txs
245+
.iter()
246+
.any(|tx| matches!(tx.payload, TransactionPayload::TenureChange(_)));
247+
if has_tenure_change {
248+
let only_coinbase_and_tenure_change = txs.iter().all(|tx| {
249+
matches!(
250+
tx.payload,
251+
TransactionPayload::TenureChange(_) | TransactionPayload::Coinbase(..)
252+
)
253+
});
254+
assert!(only_coinbase_and_tenure_change, "Nakamoto blocks with a tenure change in them should only have coinbase or tenure changes");
255+
}
256+
}
257+
}
258+
233259
pub fn get_stacker_set(http_origin: &str, cycle: u64) -> GetStackersResponse {
234260
let client = reqwest::blocking::Client::new();
235261
let path = format!("{http_origin}/v3/stacker_set/{cycle}");
@@ -1683,6 +1709,8 @@ fn simple_neon_integration() {
16831709
assert!(res.contains(&expected_result));
16841710
}
16851711

1712+
check_nakamoto_empty_block_heuristics();
1713+
16861714
coord_channel
16871715
.lock()
16881716
.expect("Mutex poisoned")
@@ -1960,6 +1988,7 @@ fn flash_blocks_on_epoch_3() {
19601988
// Verify blocks before and after the gap
19611989
test_observer::contains_burn_block_range(220..=(gap_start - 1)).unwrap();
19621990
test_observer::contains_burn_block_range((gap_end + 1)..=bhh).unwrap();
1991+
check_nakamoto_empty_block_heuristics();
19631992

19641993
info!("Verified burn block ranges, including expected gap for flash blocks");
19651994
info!("Confirmed that the gap includes the Epoch 3.0 activation height (Bitcoin block height): {}", epoch_3_start_height);
@@ -2141,6 +2170,8 @@ fn mine_multiple_per_tenure_integration() {
21412170
"Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks"
21422171
);
21432172

2173+
check_nakamoto_empty_block_heuristics();
2174+
21442175
coord_channel
21452176
.lock()
21462177
.expect("Mutex poisoned")
@@ -2394,6 +2425,8 @@ fn multiple_miners() {
23942425
"Should have mined (1 + interim_blocks_per_tenure) * tenure_count nakamoto blocks"
23952426
);
23962427

2428+
check_nakamoto_empty_block_heuristics();
2429+
23972430
coord_channel
23982431
.lock()
23992432
.expect("Mutex poisoned")
@@ -2761,6 +2794,8 @@ fn correct_burn_outs() {
27612794
assert_eq!(signer_weight, 1, "The signer should have a weight of 1, indicating they stacked the minimum stacking amount");
27622795
}
27632796

2797+
check_nakamoto_empty_block_heuristics();
2798+
27642799
run_loop_thread.join().unwrap();
27652800
}
27662801

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ pub mod test_observer {
197197

198198
use stacks::chainstate::stacks::boot::RewardSet;
199199
use stacks::chainstate::stacks::events::StackerDBChunksEvent;
200+
use stacks::chainstate::stacks::StacksTransaction;
201+
use stacks::codec::StacksMessageCodec;
200202
use stacks::net::api::postblock_proposal::BlockValidateResponse;
203+
use stacks::util::hash::hex_bytes;
201204
use stacks_common::types::chainstate::StacksBlockId;
202205
use warp::Filter;
203206
use {tokio, warp};
@@ -572,6 +575,30 @@ pub mod test_observer {
572575
PROPOSAL_RESPONSES.lock().unwrap().clear();
573576
}
574577

578+
/// Parse the StacksTransactions from a block (does not include burn ops)
579+
/// panics on any failures to parse
580+
pub fn parse_transactions(block: &serde_json::Value) -> Vec<StacksTransaction> {
581+
block
582+
.get("transactions")
583+
.unwrap()
584+
.as_array()
585+
.unwrap()
586+
.iter()
587+
.filter_map(|tx_json| {
588+
if let Some(burnchain_op_val) = tx_json.get("burnchain_op") {
589+
if !burnchain_op_val.is_null() {
590+
return None;
591+
}
592+
}
593+
let tx_hex = tx_json.get("raw_tx").unwrap().as_str().unwrap();
594+
let tx_bytes = hex_bytes(tx_hex).unwrap();
595+
let tx =
596+
StacksTransaction::consensus_deserialize(&mut tx_bytes.as_slice()).unwrap();
597+
Some(tx)
598+
})
599+
.collect()
600+
}
601+
575602
pub fn contains_burn_block_range(range: impl RangeBounds<u64>) -> Result<(), String> {
576603
// Get set of all burn block heights
577604
let burn_block_heights = get_blocks()

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerCon
5959
use stacks_signer::runloop::{SignerResult, State, StateInfo};
6060
use stacks_signer::{Signer, SpawnedSigner};
6161

62-
use super::nakamoto_integrations::wait_for;
62+
use super::nakamoto_integrations::{check_nakamoto_empty_block_heuristics, wait_for};
6363
use crate::config::{Config as NeonConfig, EventKeyType, EventObserverConfig, InitialBalance};
6464
use crate::event_dispatcher::MinedNakamotoBlockEvent;
6565
use crate::neon::{Counters, TestFlag};
@@ -549,6 +549,8 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
549549
}
550550

551551
pub fn shutdown(self) {
552+
check_nakamoto_empty_block_heuristics();
553+
552554
self.running_nodes
553555
.coord_channel
554556
.lock()

0 commit comments

Comments
 (0)