Skip to content

Commit ad01a76

Browse files
committed
Merge branch 'fix/5285' into feat/shadow-block-tooling
2 parents 5f83847 + 6eaf1ed commit ad01a76

File tree

7 files changed

+881
-239
lines changed

7 files changed

+881
-239
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::continue_after_tenure_extend
125125
- tests::signer::v0::multiple_miners_with_custom_chain_id
126126
- tests::signer::v0::block_commit_delay
127+
- tests::signer::v0::continue_after_fast_block_no_sortition
127128
- tests::nakamoto_integrations::burn_ops_integration_test
128129
- tests::nakamoto_integrations::check_block_heights
129130
- tests::nakamoto_integrations::clarity_burn_state

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub enum MinerReason {
109109
/// sortition.
110110
burn_view_consensus_hash: ConsensusHash,
111111
},
112+
/// The miner thread was spawned to initialize a prior empty tenure
113+
EmptyTenure,
112114
}
113115

114116
impl std::fmt::Display for MinerReason {
@@ -121,6 +123,7 @@ impl std::fmt::Display for MinerReason {
121123
f,
122124
"Extended: burn_view_consensus_hash = {burn_view_consensus_hash:?}",
123125
),
126+
MinerReason::EmptyTenure => write!(f, "EmptyTenure"),
124127
}
125128
}
126129
}
@@ -921,19 +924,19 @@ impl BlockMinerThread {
921924
let vrf_proof = if self.config.get_node_config(false).mock_mining {
922925
self.keychain.generate_proof(
923926
VRF_MOCK_MINER_KEY,
924-
self.burn_block.sortition_hash.as_bytes(),
927+
self.burn_election_block.sortition_hash.as_bytes(),
925928
)
926929
} else {
927930
self.keychain.generate_proof(
928931
self.registered_key.target_block_height,
929-
self.burn_block.sortition_hash.as_bytes(),
932+
self.burn_election_block.sortition_hash.as_bytes(),
930933
)
931934
};
932935

933936
debug!(
934937
"Generated VRF Proof: {} over {} ({},{}) with key {}",
935938
vrf_proof.to_hex(),
936-
&self.burn_block.sortition_hash,
939+
&self.burn_election_block.sortition_hash,
937940
&self.burn_block.block_height,
938941
&self.burn_block.burn_header_hash,
939942
&self.registered_key.vrf_public_key.to_hex()
@@ -1154,7 +1157,7 @@ impl BlockMinerThread {
11541157
};
11551158

11561159
let (tenure_change_tx, coinbase_tx) = match &self.reason {
1157-
MinerReason::BlockFound => {
1160+
MinerReason::BlockFound | MinerReason::EmptyTenure => {
11581161
let tenure_change_tx =
11591162
self.generate_tenure_change_tx(current_miner_nonce, payload)?;
11601163
let coinbase_tx =

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

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -898,23 +898,14 @@ impl RelayerThread {
898898
SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortdb.conn()).unwrap();
899899
let canonical_stacks_tip =
900900
StacksBlockId::new(&canonical_stacks_tip_ch, &canonical_stacks_tip_bh);
901-
let block_election_snapshot =
902-
SortitionDB::get_block_snapshot_consensus(self.sortdb.conn(), &canonical_stacks_tip_ch)
903-
.map_err(|e| {
904-
error!("Relayer: failed to get block snapshot for canonical tip: {e:?}");
905-
NakamotoNodeError::SnapshotNotFoundForChainTip
906-
})?
907-
.ok_or_else(|| {
908-
error!("Relayer: failed to get block snapshot for canonical tip");
909-
NakamotoNodeError::SnapshotNotFoundForChainTip
910-
})?;
911901

912902
let Some(ref mining_key) = self.config.miner.mining_key else {
913903
return Ok(());
914904
};
915905
let mining_pkh = Hash160::from_node_public_key(&StacksPublicKey::from_private(mining_key));
916906

917-
let last_winner_snapshot = {
907+
// If we won the last sortition, then we should start a new tenure off of it.
908+
let last_block_election_snapshot = {
918909
let ih = self.sortdb.index_handle(&burn_tip.sortition_id);
919910
ih.get_last_snapshot_with_sortition(burn_tip.block_height)
920911
.map_err(|e| {
@@ -923,29 +914,59 @@ impl RelayerThread {
923914
})?
924915
};
925916

926-
let won_last_sortition = last_winner_snapshot.miner_pk_hash == Some(mining_pkh);
917+
let won_last_sortition = last_block_election_snapshot.miner_pk_hash == Some(mining_pkh);
927918
debug!(
928919
"Relayer: Current burn block had no sortition. Checking for tenure continuation.";
929920
"won_last_sortition" => won_last_sortition,
930921
"current_mining_pkh" => %mining_pkh,
931-
"last_winner_snapshot.miner_pk_hash" => ?last_winner_snapshot.miner_pk_hash,
922+
"last_block_election_snapshot.consensus_hash" => %last_block_election_snapshot.consensus_hash,
923+
"last_block_election_snapshot.miner_pk_hash" => ?last_block_election_snapshot.miner_pk_hash,
932924
"canonical_stacks_tip_id" => %canonical_stacks_tip,
933925
"canonical_stacks_tip_ch" => %canonical_stacks_tip_ch,
934-
"block_election_ch" => %block_election_snapshot.consensus_hash,
935926
"burn_view_ch" => %new_burn_view,
936927
);
937928

938929
if !won_last_sortition {
939930
return Ok(());
940931
}
941932

933+
let canonical_block_snapshot =
934+
SortitionDB::get_block_snapshot_consensus(self.sortdb.conn(), &canonical_stacks_tip_ch)
935+
.map_err(|e| {
936+
error!("Relayer: failed to get block snapshot for canonical tip: {e:?}");
937+
NakamotoNodeError::SnapshotNotFoundForChainTip
938+
})?
939+
.ok_or_else(|| {
940+
error!("Relayer: failed to get block snapshot for canonical tip");
941+
NakamotoNodeError::SnapshotNotFoundForChainTip
942+
})?;
943+
944+
let won_canonical_block_snapshot =
945+
canonical_block_snapshot.miner_pk_hash == Some(mining_pkh);
946+
947+
let (parent_tenure_start, block_election_snapshot, reason) =
948+
if !won_canonical_block_snapshot {
949+
debug!("Relayer: Failed to issue a tenure change payload in our last tenure. Issue a new tenure change payload.");
950+
(
951+
StacksBlockId(last_block_election_snapshot.winning_stacks_block_hash.0),
952+
last_block_election_snapshot,
953+
MinerReason::EmptyTenure,
954+
)
955+
} else {
956+
debug!("Relayer: Successfully issued a tenure change payload in its tenure. Issue a continue extend from the chain tip.");
957+
(
958+
canonical_stacks_tip, //For tenure extend, we should be extending off the canonical tip
959+
canonical_block_snapshot,
960+
MinerReason::Extended {
961+
burn_view_consensus_hash: new_burn_view,
962+
},
963+
)
964+
};
942965
match self.start_new_tenure(
943-
canonical_stacks_tip, // For tenure extend, we should be extending off the canonical tip
966+
parent_tenure_start,
944967
block_election_snapshot,
945968
burn_tip,
946-
MinerReason::Extended {
947-
burn_view_consensus_hash: new_burn_view,
948-
},
969+
reason,
949970
) {
950971
Ok(()) => {
951972
debug!("Relayer: successfully started new tenure.");
@@ -1018,13 +1039,7 @@ impl RelayerThread {
10181039

10191040
#[cfg(test)]
10201041
fn fault_injection_skip_block_commit(&self) -> bool {
1021-
self.globals
1022-
.counters
1023-
.naka_skip_commit_op
1024-
.0
1025-
.lock()
1026-
.unwrap()
1027-
.unwrap_or(false)
1042+
self.globals.counters.naka_skip_commit_op.get()
10281043
}
10291044

10301045
#[cfg(not(test))]

testnet/stacks-node/src/run_loop/neon.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ impl Default for TestFlag {
9393
}
9494
}
9595

96+
#[cfg(test)]
97+
impl TestFlag {
98+
/// Set the test flag to the given value
99+
pub fn set(&self, value: bool) {
100+
*self.0.lock().unwrap() = Some(value);
101+
}
102+
103+
/// Get the test flag value. Defaults to false if the flag is not set.
104+
pub fn get(&self) -> bool {
105+
self.0.lock().unwrap().unwrap_or(false)
106+
}
107+
}
108+
96109
#[derive(Clone, Default)]
97110
pub struct Counters {
98111
pub blocks_processed: RunLoopCounter,
@@ -1022,7 +1035,7 @@ impl RunLoop {
10221035
/// This function will block by looping infinitely.
10231036
/// It will start the burnchain (separate thread), set-up a channel in
10241037
/// charge of coordinating the new blocks coming from the burnchain and
1025-
/// the nodes, taking turns on tenures.
1038+
/// the nodes, taking turns on tenures.
10261039
///
10271040
/// Returns `Option<NeonGlobals>` so that data can be passed to `NakamotoNode`
10281041
pub fn start(

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

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use stacks_common::types::chainstate::StacksPrivateKey;
2323

2424
use crate::config::InitialBalance;
2525
use crate::tests::bitcoin_regtest::BitcoinCoreController;
26-
use crate::tests::nakamoto_integrations::wait_for;
26+
use crate::tests::nakamoto_integrations::{next_block_and, wait_for};
2727
use crate::tests::neon_integrations::{
2828
get_account, get_chain_info, neon_integration_test_conf, next_block_and_wait, submit_tx,
2929
test_observer, wait_for_runloop,
@@ -162,6 +162,9 @@ fn microblocks_disabled() {
162162
// push us to block 205
163163
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
164164

165+
// Ensure we start off with 0 microblocks
166+
assert!(test_observer::get_microblocks().is_empty());
167+
165168
let tx = make_stacks_transfer_mblock_only(
166169
&spender_1_sk,
167170
0,
@@ -172,7 +175,11 @@ fn microblocks_disabled() {
172175
);
173176
submit_tx(&http_origin, &tx);
174177

175-
// wait until just before epoch 2.5
178+
// Wait for a microblock to be assembled
179+
wait_for(60, || Ok(test_observer::get_microblocks().len() == 1))
180+
.expect("Failed to wait for microblocks to be assembled");
181+
182+
// mine Bitcoin blocks up until just before epoch 2.5
176183
wait_for(120, || {
177184
let tip_info = get_chain_info(&conf);
178185
if tip_info.burn_block_height >= epoch_2_5 - 2 {
@@ -183,6 +190,14 @@ fn microblocks_disabled() {
183190
})
184191
.expect("Failed to wait until just before epoch 2.5");
185192

193+
// Verify that the microblock was processed
194+
let account = get_account(&http_origin, &spender_1_addr);
195+
assert_eq!(
196+
u64::try_from(account.balance).unwrap(),
197+
spender_1_bal - 1_000
198+
);
199+
assert_eq!(account.nonce, 1);
200+
186201
let old_tip_info = get_chain_info(&conf);
187202
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
188203
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
@@ -194,13 +209,8 @@ fn microblocks_disabled() {
194209
.expect("Failed to process block");
195210

196211
info!("Test passed processing 2.5");
197-
let account = get_account(&http_origin, &spender_1_addr);
198-
assert_eq!(
199-
u64::try_from(account.balance).unwrap(),
200-
spender_1_bal - 1_000
201-
);
202-
assert_eq!(account.nonce, 1);
203212

213+
// Submit another microblock only transaction
204214
let tx = make_stacks_transfer_mblock_only(
205215
&spender_1_sk,
206216
1,
@@ -211,19 +221,12 @@ fn microblocks_disabled() {
211221
);
212222
submit_tx(&http_origin, &tx);
213223

214-
let mut last_block_height = get_chain_info(&conf).burn_block_height;
215-
for _i in 0..5 {
216-
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
217-
wait_for(30, || {
218-
let tip_info = get_chain_info(&conf);
219-
if tip_info.burn_block_height > last_block_height {
220-
last_block_height = tip_info.burn_block_height;
221-
return Ok(true);
222-
}
223-
Ok(false)
224-
})
225-
.expect("Failed to mine");
226-
}
224+
// Wait for a microblock to be assembled, but expect none to be assembled
225+
wait_for(30, || Ok(test_observer::get_microblocks().len() > 1))
226+
.expect_err("Microblocks should not have been assembled");
227+
228+
// Mine a block to see if the microblock gets processed
229+
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
227230

228231
// second transaction should not have been processed!
229232
let account = get_account(&http_origin, &spender_1_addr);
@@ -233,31 +236,18 @@ fn microblocks_disabled() {
233236
);
234237
assert_eq!(account.nonce, 1);
235238

236-
let microblocks_assembled = test_observer::get_microblocks().len();
237-
info!("Microblocks assembled: {microblocks_assembled}",);
238-
assert!(
239-
microblocks_assembled > 0,
240-
"There should be at least 1 microblock assembled"
241-
);
242-
243239
let miner_nonce_before_microblock_assembly = get_account(&http_origin, &miner_account).nonce;
244240

245241
// Now, lets tell the miner to try to mine microblocks, but don't try to confirm them!
242+
info!("Setting STACKS_TEST_FORCE_MICROBLOCKS_POST_25");
246243
env::set_var("STACKS_TEST_FORCE_MICROBLOCKS_POST_25", "1");
247244

248-
let mut last_block_height = get_chain_info(&conf).burn_block_height;
249-
for _i in 0..2 {
250-
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
251-
wait_for(30, || {
252-
let tip_info = get_chain_info(&conf);
253-
if tip_info.burn_block_height > last_block_height {
254-
last_block_height = tip_info.burn_block_height;
255-
return Ok(true);
256-
}
257-
Ok(false)
258-
})
259-
.expect("Failed to mine");
260-
}
245+
// Wait for a second microblock to be assembled
246+
wait_for(60, || Ok(test_observer::get_microblocks().len() == 2))
247+
.expect("Failed to wait for microblocks to be assembled");
248+
249+
// Mine a block to see if the microblock gets processed
250+
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
261251

262252
let miner_nonce_after_microblock_assembly = get_account(&http_origin, &miner_account).nonce;
263253

@@ -270,44 +260,35 @@ fn microblocks_disabled() {
270260
);
271261
assert_eq!(account.nonce, 1);
272262

273-
// but we should have assembled and announced at least 1 more block to the observer
274-
assert!(test_observer::get_microblocks().len() > microblocks_assembled);
275263
info!(
276264
"Microblocks assembled: {}",
277265
test_observer::get_microblocks().len()
278266
);
279267

280268
// and our miner should have gotten some blocks accepted
281-
assert!(
282-
miner_nonce_after_microblock_assembly > miner_nonce_before_microblock_assembly,
269+
assert_eq!(
270+
miner_nonce_after_microblock_assembly, miner_nonce_before_microblock_assembly + 1,
283271
"Mined before started microblock assembly: {miner_nonce_before_microblock_assembly}, Mined after started microblock assembly: {miner_nonce_after_microblock_assembly}"
284272
);
285273

286274
// Now, tell the miner to try to confirm microblocks as well.
287275
// This should test that the block gets rejected by append block
276+
info!("Setting STACKS_TEST_CONFIRM_MICROBLOCKS_POST_25");
288277
env::set_var("STACKS_TEST_CONFIRM_MICROBLOCKS_POST_25", "1");
289278

290-
let mut last_block_height = get_chain_info(&conf).burn_block_height;
291-
for _i in 0..2 {
292-
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
293-
wait_for(30, || {
294-
let tip_info = get_chain_info(&conf);
295-
if tip_info.burn_block_height > last_block_height {
296-
last_block_height = tip_info.burn_block_height;
297-
return Ok(true);
298-
}
299-
Ok(false)
300-
})
301-
.expect("Failed to mine");
302-
}
279+
// Wait for a third microblock to be assembled
280+
wait_for(60, || Ok(test_observer::get_microblocks().len() == 3))
281+
.expect("Failed to wait for microblocks to be assembled");
282+
283+
// Mine a block to see if the microblock gets processed
284+
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
303285

304286
let miner_nonce_after_microblock_confirmation = get_account(&http_origin, &miner_account).nonce;
305287

306-
// and our miner should have gotten at most one more block accepted
307-
// (because they may have had 1 block confirmation in the bitcoin mempool which didn't confirm a microblock
308-
// before we flipped the flag)
309-
assert!(
310-
miner_nonce_after_microblock_confirmation <= miner_nonce_after_microblock_assembly + 1,
288+
// our miner should not have gotten any more blocks accepted
289+
assert_eq!(
290+
miner_nonce_after_microblock_confirmation,
291+
miner_nonce_after_microblock_assembly + 1,
311292
"Mined after started microblock confimration: {miner_nonce_after_microblock_confirmation}",
312293
);
313294

0 commit comments

Comments
 (0)