Skip to content

Commit 0a4f5f1

Browse files
authored
Merge pull request #6229 from kantai/fix/bitcoin-reorg-extend
Fix: miner coordinator behavior in reorg/extend
2 parents 1e60445 + 377c7aa commit 0a4f5f1

File tree

7 files changed

+34
-52
lines changed

7 files changed

+34
-52
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
///

stackslib/src/net/relay.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,6 +2497,12 @@ impl Relayer {
24972497
let tx = self.stacker_dbs.tx_begin(config.clone())?;
24982498
for sync_result in sync_results.into_iter() {
24992499
for chunk in sync_result.chunks_to_store.into_iter() {
2500+
if let Some(event_list) = all_events.get_mut(&sync_result.contract_id) {
2501+
event_list.push(chunk.clone());
2502+
} else {
2503+
all_events.insert(sync_result.contract_id.clone(), vec![chunk.clone()]);
2504+
}
2505+
25002506
let md = chunk.get_slot_metadata();
25012507
if let Err(e) = tx.try_replace_chunk(&sc, &md, &chunk.data) {
25022508
if matches!(e, Error::StaleChunk { .. }) {
@@ -2525,11 +2531,6 @@ impl Relayer {
25252531
debug!("Stored chunk"; "stackerdb_contract_id" => %sync_result.contract_id, "slot_id" => md.slot_id, "slot_version" => md.slot_version);
25262532
}
25272533

2528-
if let Some(event_list) = all_events.get_mut(&sync_result.contract_id) {
2529-
event_list.push(chunk.clone());
2530-
} else {
2531-
all_events.insert(sync_result.contract_id.clone(), vec![chunk.clone()]);
2532-
}
25332534
let msg = StacksMessageType::StackerDBPushChunk(StackerDBPushChunkData {
25342535
contract_id: sc.clone(),
25352536
rc_consensus_hash: rc_consensus_hash.clone(),

stackslib/src/net/stackerdb/db.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ use stacks_common::util::get_epoch_time_secs;
2525
use stacks_common::util::hash::Sha512Trunc256Sum;
2626
use stacks_common::util::secp256k1::MessageSignature;
2727

28-
use super::StackerDBEventDispatcher;
2928
use crate::net::stackerdb::{StackerDBConfig, StackerDBTx, StackerDBs, STACKERDB_INV_MAX};
3029
use crate::net::{Error as net_error, StackerDBChunkData};
3130
use crate::util_lib::db::{
@@ -396,20 +395,6 @@ impl StackerDBTx<'_> {
396395
Ok(())
397396
}
398397

399-
/// Try to upload a chunk to the StackerDB instance, notifying
400-
/// and subscribed listeners via the `dispatcher`
401-
pub fn put_chunk<ED: StackerDBEventDispatcher>(
402-
self,
403-
contract: &QualifiedContractIdentifier,
404-
chunk: StackerDBChunkData,
405-
dispatcher: &ED,
406-
) -> Result<(), net_error> {
407-
self.try_replace_chunk(contract, &chunk.get_slot_metadata(), &chunk.data)?;
408-
self.commit()?;
409-
dispatcher.new_stackerdb_chunks(contract.clone(), vec![chunk]);
410-
Ok(())
411-
}
412-
413398
/// Add or replace a chunk for a given reward cycle, if it is valid
414399
/// Otherwise, this errors out with Error::StaleChunk
415400
pub fn try_replace_chunk(

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: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17349,6 +17349,13 @@ fn bitcoin_reorg_extended_tenure() {
1734917349
info!("Bitcoin fork triggered"; "ch" => %before_fork, "btc_height" => burn_block_height);
1735017350
info!("Chain info before fork: {:?}", get_chain_info(&conf_1));
1735117351

17352+
// Make sure signers don't perform block broadcast for the next bits:
17353+
// we want to ensure that the *miner* is the one broadcast blocks,
17354+
// because when we stall p2p broadcast, we don't want to accidentally
17355+
// stall the miner in the situation where they produce block A, signers broadcast it,
17356+
// we initiate the stall, and then the miner attempts to broadcast A.
17357+
stacks_signer::v0::tests::TEST_SKIP_BLOCK_BROADCAST.set(true);
17358+
1735217359
let mut after_fork = get_chain_info(&conf_1).pox_consensus;
1735317360
wait_for(60, || {
1735417361
after_fork = get_chain_info(&conf_1).pox_consensus;
@@ -17371,7 +17378,8 @@ fn bitcoin_reorg_extended_tenure() {
1737117378
// so that we can ensure all the signers approve the proposal
1737217379
// before it gets accepted by stacks-nodes
1737317380
TEST_P2P_BROADCAST_STALL.set(true);
17374-
stacks_signer::v0::tests::TEST_SKIP_BLOCK_BROADCAST.set(true);
17381+
17382+
info!("Stalled broadcast, submitting a contract call!");
1737517383

1737617384
// the signer signature hash is the same as the block header hash.
1737717385
// we use the latest_signer_sighash to make sure we're getting block responses for the
@@ -17392,11 +17400,18 @@ fn bitcoin_reorg_extended_tenure() {
1739217400
let rc = miners.signer_test.get_current_reward_cycle();
1739317401
let slot_ids = miners.signer_test.get_signer_indices(rc);
1739417402
let mut block_responses: Vec<_> = vec![];
17403+
1739517404
wait_for(60, || {
1739617405
block_responses = slot_ids
1739717406
.iter()
1739817407
.filter_map(|slot_id| {
1739917408
let latest_br = miners.signer_test.get_latest_block_response(slot_id.0);
17409+
info!(
17410+
"[{}] Checking response for {}. accepted = {}",
17411+
slot_id.0,
17412+
latest_br.get_signer_signature_hash(),
17413+
latest_br.as_block_accepted().is_some()
17414+
);
1740017415
if latest_br.get_signer_signature_hash() != latest_signer_sighash {
1740117416
Some(latest_br)
1740217417
} else {

0 commit comments

Comments
 (0)