Skip to content

Commit 8f2bd84

Browse files
committed
fix: miner coordinator behavior in reorg/extend
* fix the miner's sign coordinator so that it does not register reorged blocks as tip changes * fix signer runloop handling of test_skip_block_broadcast flag. prior test behavior would cause signer to intermittently drop miner messages
1 parent ffc1b2e commit 8f2bd84

File tree

5 files changed

+32
-34
lines changed

5 files changed

+32
-34
lines changed

stacks-signer/src/v0/signer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ impl Signer {
482482
);
483483
#[cfg(any(test, feature = "testing"))]
484484
if self.test_skip_block_broadcast(b) {
485-
return;
485+
continue;
486486
}
487487
self.handle_post_block(stacks_client, b);
488488
}
@@ -491,7 +491,7 @@ impl Signer {
491491
Ok(epoch) => epoch,
492492
Err(e) => {
493493
warn!("{self}: Failed to determine node epoch. Cannot mock sign: {e}");
494-
return;
494+
continue;
495495
}
496496
};
497497
info!("{self}: received a mock block proposal.";

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2851,29 +2851,6 @@ impl NakamotoChainState {
28512851
Self::get_block_header_nakamoto(chainstate_conn.sqlite(), &block_id)
28522852
}
28532853

2854-
/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
2855-
/// tenure.
2856-
///
2857-
/// Get the highest block in a given tenure (identified by its consensus hash).
2858-
/// Ties will be broken by timestamp.
2859-
///
2860-
/// Used to verify that a signer-submitted block proposal builds atop the highest known block
2861-
/// in the given tenure, regardless of which fork it's on.
2862-
pub fn get_highest_known_block_header_in_tenure(
2863-
db: &Connection,
2864-
consensus_hash: &ConsensusHash,
2865-
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
2866-
// see if we have a nakamoto block in this tenure
2867-
let qry = "SELECT * FROM nakamoto_block_headers WHERE consensus_hash = ?1 ORDER BY block_height DESC, timestamp DESC LIMIT 1";
2868-
let args = params![consensus_hash];
2869-
if let Some(header) = query_row(db, qry, args)? {
2870-
return Ok(Some(header));
2871-
}
2872-
2873-
// see if this is an epoch2 header. If it exists, then there will only be one.
2874-
Ok(StacksChainState::get_stacks_block_header_info_by_consensus_hash(db, consensus_hash)?)
2875-
}
2876-
28772854
/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
28782855
/// tenure.
28792856
///

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,9 @@ impl SignerCoordinator {
420420

421421
// Check if a new Stacks block has arrived in the parent tenure
422422
let highest_in_tenure =
423-
NakamotoChainState::get_highest_known_block_header_in_tenure(
424-
&mut chain_state.index_conn(),
423+
NakamotoChainState::find_highest_known_block_header_in_tenure(
424+
&chain_state,
425+
&sortdb,
425426
&parent_tenure_header.consensus_hash,
426427
)?
427428
.ok_or(NakamotoNodeError::UnexpectedChainState)?;
@@ -436,7 +437,10 @@ impl SignerCoordinator {
436437
};
437438
return Ok(stored_block.signer_signature);
438439
} else if highest_stacks_block_id != parent_block_id {
439-
info!("SignCoordinator: Exiting due to new stacks tip");
440+
info!("SignCoordinator: Exiting due to new stacks tip";
441+
"new_block_hash" => %highest_in_tenure.anchored_header.block_hash(),
442+
"new_block_height" => %highest_in_tenure.anchored_header.height(),
443+
);
440444
return Err(NakamotoNodeError::StacksTipChanged);
441445
}
442446

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,18 +1388,18 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
13881388
SignerSlotID(0), // We are just reading so again, don't care about index.
13891389
SignerDb::new(":memory:").unwrap(),
13901390
);
1391-
let latest_msgs = StackerDB::get_messages(
1391+
let mut latest_msgs = StackerDB::get_messages(
13921392
stackerdb
13931393
.get_session_mut(&MessageSlotID::BlockResponse)
13941394
.expect("Failed to get BlockResponse stackerdb session"),
13951395
&[slot_id],
13961396
)
13971397
.expect("Failed to get message from stackerdb");
1398-
let latest_msg = latest_msgs.last().unwrap();
1398+
let latest_msg = latest_msgs.pop().unwrap();
13991399
let SignerMessage::BlockResponse(block_response) = latest_msg else {
14001400
panic!("Latest message from slot #{slot_id} isn't a block acceptance");
14011401
};
1402-
block_response.clone()
1402+
block_response
14031403
}
14041404

14051405
/// Get the latest block acceptance from the given slot

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ use crate::event_dispatcher::{
106106
EventObserver, MinedNakamotoBlockEvent, TEST_SKIP_BLOCK_ANNOUNCEMENT,
107107
};
108108
use crate::nakamoto_node::miner::{
109-
TEST_BLOCK_ANNOUNCE_STALL, TEST_BROADCAST_PROPOSAL_STALL, TEST_MINE_STALL,
110-
TEST_P2P_BROADCAST_STALL,
109+
TEST_BLOCK_ANNOUNCE_STALL, TEST_BLOCK_PUSH_SKIP, TEST_BROADCAST_PROPOSAL_STALL,
110+
TEST_MINE_STALL, TEST_P2P_BROADCAST_STALL,
111111
};
112112
use crate::nakamoto_node::stackerdb_listener::TEST_IGNORE_SIGNERS;
113113
use crate::neon::{Counters, RunLoopCounter};
@@ -17350,6 +17350,13 @@ fn bitcoin_reorg_extended_tenure() {
1735017350
info!("Bitcoin fork triggered"; "ch" => %before_fork, "btc_height" => burn_block_height);
1735117351
info!("Chain info before fork: {:?}", get_chain_info(&conf_1));
1735217352

17353+
// Make sure signers don't perform block broadcast for the next bits:
17354+
// we want to ensure that the *miner* is the one broadcast blocks,
17355+
// because when we stall p2p broadcast, we don't want to accidentally
17356+
// stall the miner in the situation where they produce block A, signers broadcast it,
17357+
// we initiate the stall, and then the miner attempts to broadcast A.
17358+
stacks_signer::v0::tests::TEST_SKIP_BLOCK_BROADCAST.set(true);
17359+
1735317360
let mut after_fork = get_chain_info(&conf_1).pox_consensus;
1735417361
wait_for(60, || {
1735517362
after_fork = get_chain_info(&conf_1).pox_consensus;
@@ -17368,11 +17375,14 @@ fn bitcoin_reorg_extended_tenure() {
1736817375

1736917376
miners.wait_for_chains(60);
1737017377

17378+
thread::sleep(Duration::from_secs(5));
17379+
1737117380
// stall p2p broadcast and signer block announcements
1737217381
// so that we can ensure all the signers approve the proposal
1737317382
// before it gets accepted by stacks-nodes
1737417383
TEST_P2P_BROADCAST_STALL.set(true);
17375-
stacks_signer::v0::tests::TEST_SKIP_BLOCK_BROADCAST.set(true);
17384+
17385+
info!("Stalled broadcast, submitting a contract call!");
1737617386

1737717387
// the signer signature hash is the same as the block header hash.
1737817388
// we use the latest_signer_sighash to make sure we're getting block responses for the
@@ -17393,11 +17403,18 @@ fn bitcoin_reorg_extended_tenure() {
1739317403
let rc = miners.signer_test.get_current_reward_cycle();
1739417404
let slot_ids = miners.signer_test.get_signer_indices(rc);
1739517405
let mut block_responses: Vec<_> = vec![];
17406+
1739617407
wait_for(60, || {
1739717408
block_responses = slot_ids
1739817409
.iter()
1739917410
.filter_map(|slot_id| {
1740017411
let latest_br = miners.signer_test.get_latest_block_response(slot_id.0);
17412+
info!(
17413+
"[{}] Checking response for {}. accepted = {}",
17414+
slot_id.0,
17415+
latest_br.get_signer_signature_hash(),
17416+
latest_br.as_block_accepted().is_some()
17417+
);
1740117418
if latest_br.get_signer_signature_hash() != latest_signer_sighash {
1740217419
Some(latest_br)
1740317420
} else {

0 commit comments

Comments
 (0)