Skip to content

Commit 1f44a69

Browse files
authored
Merge pull request #5093 from stacks-network/feat/more-multi-miner-fixes-jude
Feat/more multi miner fixes jude
2 parents 3a41360 + 80c069f commit 1f44a69

File tree

42 files changed

+2076
-1026
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2076
-1026
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ jobs:
110110
- tests::nakamoto_integrations::continue_tenure_extend
111111
- tests::nakamoto_integrations::mock_mining
112112
- tests::nakamoto_integrations::multiple_miners
113+
- tests::nakamoto_integrations::follower_bootup_across_multiple_cycles
113114
- tests::nakamoto_integrations::utxo_check_on_startup_panic
114115
- tests::nakamoto_integrations::utxo_check_on_startup_recover
115116
- tests::signer::v0::multiple_miners_with_nakamoto_blocks

libstackerdb/src/libstackerdb.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub struct SlotMetadata {
8282
}
8383

8484
/// Stacker DB chunk (i.e. as a reply to a chunk request)
85-
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85+
#[derive(Clone, PartialEq, Serialize, Deserialize)]
8686
pub struct StackerDBChunkData {
8787
/// slot ID
8888
pub slot_id: u32,
@@ -98,6 +98,31 @@ pub struct StackerDBChunkData {
9898
pub data: Vec<u8>,
9999
}
100100

101+
impl fmt::Debug for StackerDBChunkData {
102+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103+
if self.data.len() < 128 {
104+
write!(
105+
f,
106+
"StackerDBChunkData({},{},{},{})",
107+
self.slot_id,
108+
self.slot_version,
109+
&self.sig,
110+
&to_hex(&self.data)
111+
)
112+
} else {
113+
write!(
114+
f,
115+
"StackerDBChunkData({},{},{},{}...({}))",
116+
self.slot_id,
117+
self.slot_version,
118+
&self.sig,
119+
&to_hex(&self.data[..128]),
120+
self.data.len()
121+
)
122+
}
123+
}
124+
}
125+
101126
/// StackerDB post chunk acknowledgement
102127
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103128
pub struct StackerDBChunkAckData {

stackslib/src/burnchains/db.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,8 @@ impl BurnchainDB {
14111411
Ok(())
14121412
}
14131413

1414+
/// Stores a newly-parsed burnchain block's relevant data into the DB.
1415+
/// The given block's operations will be validated.
14141416
pub fn store_new_burnchain_block<B: BurnchainHeaderReader>(
14151417
&mut self,
14161418
burnchain: &Burnchain,

stackslib/src/chainstate/burn/db/processing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ impl<'a> SortitionHandleTx<'a> {
211211
"SORTITION-HASH({}): {}",
212212
this_block_height, &snapshot.sortition_hash
213213
);
214-
debug!(
214+
info!(
215215
"CONSENSUS({}): {}",
216216
this_block_height, &snapshot.consensus_hash
217217
);

stackslib/src/chainstate/burn/db/sortdb.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3456,6 +3456,14 @@ impl SortitionDB {
34563456
SortitionDB::apply_schema_9(&tx.deref(), epochs)?;
34573457
tx.commit()?;
34583458
} else if version == expected_version {
3459+
// this transaction is almost never needed
3460+
let validated_epochs = StacksEpoch::validate_epochs(epochs);
3461+
let existing_epochs = Self::get_stacks_epochs(self.conn())?;
3462+
if existing_epochs == validated_epochs {
3463+
return Ok(());
3464+
}
3465+
3466+
// epochs are out of date
34593467
let tx = self.tx_begin()?;
34603468
SortitionDB::validate_and_replace_epochs(&tx, epochs)?;
34613469
tx.commit()?;

stackslib/src/chainstate/coordinator/mod.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -543,20 +543,24 @@ impl<
543543
in_nakamoto_epoch: false,
544544
};
545545

546-
let mut nakamoto_available = false;
547546
loop {
548-
if nakamoto_available
549-
|| inst
550-
.can_process_nakamoto()
551-
.expect("FATAL: could not determine if Nakamoto is available")
552-
{
553-
// short-circuit to avoid gratuitous I/O
554-
nakamoto_available = true;
555-
if !inst.handle_comms_nakamoto(&comms, miner_status.clone()) {
547+
let bits = comms.wait_on();
548+
if inst.in_subsequent_nakamoto_reward_cycle() {
549+
debug!("Coordinator: in subsequent Nakamoto reward cycle");
550+
if !inst.handle_comms_nakamoto(bits, miner_status.clone()) {
551+
return;
552+
}
553+
} else if inst.in_first_nakamoto_reward_cycle() {
554+
debug!("Coordinator: in first Nakamoto reward cycle");
555+
if !inst.handle_comms_nakamoto(bits, miner_status.clone()) {
556+
return;
557+
}
558+
if !inst.handle_comms_epoch2(bits, miner_status.clone()) {
556559
return;
557560
}
558561
} else {
559-
if !inst.handle_comms_epoch2(&comms, miner_status.clone()) {
562+
debug!("Coordinator: in epoch2 reward cycle");
563+
if !inst.handle_comms_epoch2(bits, miner_status.clone()) {
560564
return;
561565
}
562566
}
@@ -566,13 +570,8 @@ impl<
566570
/// This is the Stacks 2.x coordinator loop body, which handles communications
567571
/// from the given `comms`. It returns `true` if the coordinator is still running, and `false`
568572
/// if not.
569-
pub fn handle_comms_epoch2(
570-
&mut self,
571-
comms: &CoordinatorReceivers,
572-
miner_status: Arc<Mutex<MinerStatus>>,
573-
) -> bool {
573+
pub fn handle_comms_epoch2(&mut self, bits: u8, miner_status: Arc<Mutex<MinerStatus>>) -> bool {
574574
// timeout so that we handle Ctrl-C a little gracefully
575-
let bits = comms.wait_on();
576575
if (bits & (CoordinatorEvents::NEW_STACKS_BLOCK as u8)) != 0 {
577576
signal_mining_blocked(miner_status.clone());
578577
debug!("Received new stacks block notice");

stackslib/src/chainstate/nakamoto/coordinator/mod.rs

Lines changed: 44 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -614,85 +614,68 @@ impl<
614614
B: BurnchainHeaderReader,
615615
> ChainsCoordinator<'a, T, N, U, CE, FE, B>
616616
{
617-
/// Check to see if we're in the last of the 2.x epochs, and we have the first PoX anchor block
618-
/// for epoch 3.
619-
/// NOTE: the first block in epoch3 must be after the first block in the reward phase, so as
620-
/// to ensure that the PoX stackers have been selected for this cycle. This means that we
621-
/// don't proceed to process Nakamoto blocks until the reward cycle has begun. Also, the last
622-
/// reward cycle of epoch2 _must_ be PoX so we have stackers who can sign.
623-
pub fn can_process_nakamoto(&mut self) -> Result<bool, Error> {
624-
let canonical_sortition_tip = self
625-
.canonical_sortition_tip
626-
.clone()
627-
.expect("FAIL: checking epoch status, but we don't have a canonical sortition tip");
628-
629-
let canonical_sn =
630-
SortitionDB::get_block_snapshot(self.sortition_db.conn(), &canonical_sortition_tip)?
631-
.expect("FATAL: canonical sortition tip has no sortition");
632-
633-
// what epoch are we in?
634-
let cur_epoch =
635-
SortitionDB::get_stacks_epoch(self.sortition_db.conn(), canonical_sn.block_height)?
636-
.unwrap_or_else(|| {
637-
panic!(
638-
"BUG: no epoch defined at height {}",
639-
canonical_sn.block_height
640-
)
641-
});
642-
643-
if cur_epoch.epoch_id < StacksEpochId::Epoch30 {
644-
return Ok(false);
645-
}
617+
/// Get the first nakamoto reward cycle
618+
fn get_first_nakamoto_reward_cycle(&self) -> u64 {
619+
let all_epochs = SortitionDB::get_stacks_epochs(self.sortition_db.conn())
620+
.unwrap_or_else(|e| panic!("FATAL: failed to query sortition DB for epochs: {:?}", &e));
646621

647-
// in epoch3
648-
let all_epochs = SortitionDB::get_stacks_epochs(self.sortition_db.conn())?;
649-
let epoch_3_idx = StacksEpoch::find_epoch_by_id(&all_epochs, StacksEpochId::Epoch30)
650-
.expect("FATAL: epoch3 not defined");
622+
let Some(epoch_3_idx) = StacksEpoch::find_epoch_by_id(&all_epochs, StacksEpochId::Epoch30)
623+
else {
624+
// this is only reachable in tests
625+
if cfg!(any(test, feature = "testing")) {
626+
return u64::MAX;
627+
} else {
628+
panic!("FATAL: epoch3 not defined");
629+
}
630+
};
651631

652632
let epoch3 = &all_epochs[epoch_3_idx];
653633
let first_epoch3_reward_cycle = self
654634
.burnchain
655635
.block_height_to_reward_cycle(epoch3.start_height)
656636
.expect("FATAL: epoch3 block height has no reward cycle");
657637

658-
// NOTE(safety): this is not guaranteed to be the canonical best Stacks tip.
659-
// However, it's safe to use here because we're only interested in loading up the first
660-
// Nakamoto reward set, which uses the epoch2 anchor block selection algorithm. There will
661-
// only be one such reward set in epoch2 rules, since it's tied to a specific block-commit
662-
// (note that this is not true for reward sets generated in Nakamoto prepare phases).
663-
let (local_best_stacks_ch, local_best_stacks_bhh) =
664-
SortitionDB::get_canonical_stacks_chain_tip_hash(self.sortition_db.conn())?;
665-
let local_best_stacks_tip =
666-
StacksBlockId::new(&local_best_stacks_ch, &local_best_stacks_bhh);
667-
668-
// only proceed if we have processed the _anchor block_ for this reward cycle.
669-
let Some((rc_info, _)) = load_nakamoto_reward_set(
670-
self.burnchain
671-
.block_height_to_reward_cycle(canonical_sn.block_height)
672-
.expect("FATAL: snapshot has no reward cycle"),
673-
&canonical_sn.sortition_id,
674-
&self.burnchain,
675-
&mut self.chain_state_db,
676-
&local_best_stacks_tip,
677-
&self.sortition_db,
678-
&OnChainRewardSetProvider::new(),
679-
)?
680-
else {
681-
return Ok(false);
682-
};
683-
Ok(rc_info.reward_cycle >= first_epoch3_reward_cycle)
638+
first_epoch3_reward_cycle
639+
}
640+
641+
/// Get the current reward cycle
642+
fn get_current_reward_cycle(&self) -> u64 {
643+
let canonical_sortition_tip = self.canonical_sortition_tip.clone().unwrap_or_else(|| {
644+
panic!("FAIL: checking epoch status, but we don't have a canonical sortition tip")
645+
});
646+
647+
let canonical_sn =
648+
SortitionDB::get_block_snapshot(self.sortition_db.conn(), &canonical_sortition_tip)
649+
.unwrap_or_else(|e| panic!("FATAL: failed to query sortition DB: {:?}", &e))
650+
.unwrap_or_else(|| panic!("FATAL: canonical sortition tip has no sortition"));
651+
652+
let cur_reward_cycle = self
653+
.burnchain
654+
.block_height_to_reward_cycle(canonical_sn.block_height)
655+
.expect("FATAL: snapshot has no reward cycle");
656+
657+
cur_reward_cycle
658+
}
659+
660+
/// Are we in the first-ever Nakamoto reward cycle?
661+
pub fn in_first_nakamoto_reward_cycle(&self) -> bool {
662+
self.get_current_reward_cycle() == self.get_first_nakamoto_reward_cycle()
663+
}
664+
665+
/// Are we in the second or later Nakamoto reward cycle?
666+
pub fn in_subsequent_nakamoto_reward_cycle(&self) -> bool {
667+
self.get_current_reward_cycle() > self.get_first_nakamoto_reward_cycle()
684668
}
685669

686670
/// This is the main loop body for the coordinator in epoch 3.
687671
/// Returns true if the coordinator is still running.
688672
/// Returns false otherwise.
689673
pub fn handle_comms_nakamoto(
690674
&mut self,
691-
comms: &CoordinatorReceivers,
675+
bits: u8,
692676
miner_status: Arc<Mutex<MinerStatus>>,
693677
) -> bool {
694678
// timeout so that we handle Ctrl-C a little gracefully
695-
let bits = comms.wait_on();
696679
if (bits & (CoordinatorEvents::NEW_STACKS_BLOCK as u8)) != 0 {
697680
signal_mining_blocked(miner_status.clone());
698681
debug!("Received new Nakamoto stacks block notice");

stackslib/src/chainstate/nakamoto/coordinator/tests.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ use crate::chainstate::stacks::{
6565
};
6666
use crate::clarity::vm::types::StacksAddressExtensions;
6767
use crate::core::StacksEpochExtension;
68-
use crate::net::relay::Relayer;
68+
use crate::net::relay::{BlockAcceptResponse, Relayer};
6969
use crate::net::stackerdb::StackerDBConfig;
7070
use crate::net::test::{TestEventObserver, TestPeer, TestPeerConfig};
7171
use crate::net::tests::NakamotoBootPlan;
@@ -338,8 +338,10 @@ fn replay_reward_cycle(
338338
None,
339339
NakamotoBlockObtainMethod::Pushed,
340340
)
341-
.unwrap_or(false);
342-
if accepted {
341+
.unwrap_or(BlockAcceptResponse::Rejected(
342+
"encountered error on acceptance".into(),
343+
));
344+
if accepted.is_accepted() {
343345
test_debug!("Accepted Nakamoto block {block_id}");
344346
peer.coord.handle_new_nakamoto_stacks_block().unwrap();
345347
} else {

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,12 @@ impl NakamotoChainState {
20642064
panic!()
20652065
});
20662066

2067+
info!(
2068+
"Advanced to new tip! {}/{}",
2069+
&receipt.header.consensus_hash,
2070+
&receipt.header.anchored_header.block_hash()
2071+
);
2072+
20672073
// announce the block, if we're connected to an event dispatcher
20682074
if let Some(dispatcher) = dispatcher_opt {
20692075
let block_event = (
@@ -2296,7 +2302,14 @@ impl NakamotoChainState {
22962302
"signing_weight" => signing_weight);
22972303
true
22982304
} else {
2299-
debug!("Will not store alternative copy of block {} ({}) with block hash {}, since it has less signing power", &block_id, &block.header.consensus_hash, &block_hash);
2305+
if existing_signing_weight > signing_weight {
2306+
debug!("Will not store alternative copy of block {} ({}) with block hash {}, since it has less signing power", &block_id, &block.header.consensus_hash, &block_hash);
2307+
} else {
2308+
debug!(
2309+
"Will not store duplicate copy of block {} ({}) with block hash {}",
2310+
&block_id, &block.header.consensus_hash, &block_hash
2311+
);
2312+
}
23002313
false
23012314
};
23022315

@@ -3022,7 +3035,6 @@ impl NakamotoChainState {
30223035
);
30233036

30243037
let parent_hash = new_tip.parent_block_id.clone();
3025-
let new_block_hash = new_tip.block_hash();
30263038
let index_block_hash = new_tip.block_id();
30273039

30283040
let mut marf_keys = vec![];
@@ -3214,10 +3226,6 @@ impl NakamotoChainState {
32143226
headers_tx.deref_mut().execute(sql, args)?;
32153227
}
32163228

3217-
debug!(
3218-
"Advanced to new tip! {}/{}",
3219-
&new_tip.consensus_hash, new_block_hash,
3220-
);
32213229
Ok(new_tip_info)
32223230
}
32233231

stackslib/src/chainstate/nakamoto/tests/node.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ use crate::chainstate::stacks::{
7070
use crate::core::{BOOT_BLOCK_HASH, STACKS_EPOCH_3_0_MARKER};
7171
use crate::cost_estimates::metrics::UnitMetric;
7272
use crate::cost_estimates::UnitEstimator;
73-
use crate::net::relay::Relayer;
73+
use crate::net::relay::{BlockAcceptResponse, Relayer};
7474
use crate::net::test::{TestPeer, TestPeerConfig, *};
7575
use crate::util_lib::boot::boot_code_addr;
7676
use crate::util_lib::db::Error as db_error;
@@ -822,9 +822,9 @@ impl TestStacksNode {
822822
}
823823
}
824824
} else {
825-
false
825+
BlockAcceptResponse::Rejected("try_to_process is false".into())
826826
};
827-
if accepted {
827+
if accepted.is_accepted() {
828828
test_debug!("Accepted Nakamoto block {}", &block_to_store.block_id());
829829
coord.handle_new_nakamoto_stacks_block().unwrap();
830830
processed_blocks.push(block_to_store.clone());
@@ -1247,7 +1247,7 @@ impl<'a> TestPeer<'a> {
12471247
None,
12481248
NakamotoBlockObtainMethod::Pushed,
12491249
)?;
1250-
if !accepted {
1250+
if !accepted.is_accepted() {
12511251
return Ok(false);
12521252
}
12531253
let sort_tip = SortitionDB::get_canonical_sortition_tip(self.sortdb().conn()).unwrap();
@@ -1491,7 +1491,7 @@ impl<'a> TestPeer<'a> {
14911491
NakamotoBlockObtainMethod::Pushed,
14921492
)
14931493
.unwrap();
1494-
if accepted {
1494+
if accepted.is_accepted() {
14951495
test_debug!("Accepted Nakamoto block {}", &block_id);
14961496
self.coord.handle_new_nakamoto_stacks_block().unwrap();
14971497

0 commit comments

Comments
 (0)