Skip to content

Commit 9d0cebd

Browse files
authored
Merge branch 'develop' into feat/rpc-endpoints-to-fetch-data-from-key
2 parents b2bfb5e + ea7f7cd commit 9d0cebd

Some content is hidden

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

54 files changed

+6606
-927
lines changed

.github/actions/dockerfiles/Dockerfile.debian-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ RUN --mount=type=tmpfs,target=${BUILD_DIR} cp -R /src/. ${BUILD_DIR}/ \
2424
&& cp -R ${BUILD_DIR}/target/${TARGET}/release/. /out
2525

2626
FROM --platform=${TARGETPLATFORM} debian:bookworm
27-
COPY --from=build /out/stacks-node /out/stacks-signer /bin/
27+
COPY --from=build /out/stacks-node /out/stacks-signer /out/stacks-inspect /bin/
2828
CMD ["stacks-node", "mainnet"]

.github/workflows/bitcoin-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ jobs:
126126
- tests::signer::v0::block_commit_delay
127127
- tests::signer::v0::continue_after_fast_block_no_sortition
128128
- tests::signer::v0::block_validation_response_timeout
129+
- tests::signer::v0::tenure_extend_after_bad_commit
129130
- tests::nakamoto_integrations::burn_ops_integration_test
130131
- tests::nakamoto_integrations::check_block_heights
131132
- tests::nakamoto_integrations::clarity_burn_state
@@ -139,6 +140,7 @@ jobs:
139140
- tests::nakamoto_integrations::utxo_check_on_startup_panic
140141
- tests::nakamoto_integrations::utxo_check_on_startup_recover
141142
- tests::nakamoto_integrations::v3_signer_api_endpoint
143+
- tests::nakamoto_integrations::test_shadow_recovery
142144
- tests::nakamoto_integrations::signer_chainstate
143145
- tests::nakamoto_integrations::clarity_cost_spend_down
144146
- tests::nakamoto_integrations::v3_blockbyheight_api_endpoint

.github/workflows/p2p-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ jobs:
4343
- net::tests::convergence::test_walk_star_15_org_biased
4444
- net::tests::convergence::test_walk_inbound_line_15
4545
- net::api::tests::postblock_proposal::test_try_make_response
46+
- net::server::tests::test_http_10_threads_getinfo
47+
- net::server::tests::test_http_10_threads_getblock
48+
- net::server::tests::test_http_too_many_clients
49+
- net::server::tests::test_http_slow_client
4650
steps:
4751
## Setup test environment
4852
- name: Setup Test Environment

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1414
- Add `block_commit_delay_ms` to the config file to control the time to wait after seeing a new burn block, before submitting a block commit, to allow time for the first Nakamoto block of the new tenure to be mined, allowing this miner to avoid the need to RBF the block commit.
1515
- Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block.
1616
- Add `/v3/blocks/height/:block_height` rpc endpoint
17+
- If the winning miner of a sortition is committed to the wrong parent tenure, the previous miner can immediately tenure extend and continue mining since the winning miner would never be able to propose a valid block. (#5361)
1718

1819
## [3.0.0.0.1]
1920

stacks-common/src/types/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ impl StacksEpochId {
170170
}
171171
}
172172

173+
/// Whether or not this epoch supports shadow blocks
174+
pub fn supports_shadow_blocks(&self) -> bool {
175+
match self {
176+
StacksEpochId::Epoch10
177+
| StacksEpochId::Epoch20
178+
| StacksEpochId::Epoch2_05
179+
| StacksEpochId::Epoch21
180+
| StacksEpochId::Epoch22
181+
| StacksEpochId::Epoch23
182+
| StacksEpochId::Epoch24
183+
| StacksEpochId::Epoch25 => false,
184+
StacksEpochId::Epoch30 => true,
185+
}
186+
}
187+
173188
/// Does this epoch support unlocking PoX contributors that miss a slot?
174189
///
175190
/// Epoch 2.0 - 2.05 didn't support this feature, but they weren't epoch-guarded on it. Instead,

stacks-signer/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1111

1212
### Changed
1313

14+
- Allow a miner to extend their tenure immediately if the winner of the next tenure has committed to the wrong parent tenure (#5361)
15+
1416
## [3.0.0.0.1.0]
1517

1618
### Changed

stacks-signer/src/chainstate.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,40 @@ impl SortitionsView {
203203
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
204204
);
205205
self.cur_sortition.miner_status = SortitionMinerStatus::InvalidatedBeforeFirstBlock;
206+
} else if let Some(tip) = signer_db.get_canonical_tip()? {
207+
// Check if the current sortition is aligned with the expected tenure:
208+
// - If the tip is in the current tenure, we are in the process of mining this tenure.
209+
// - If the tip is not in the current tenure, then we’re starting a new tenure,
210+
// and the current sortition's parent tenure must match the tenure of the tip.
211+
// - If the tip is not building off of the current sortition's parent tenure, then
212+
// check to see if the tip's parent is within the first proposal burn block timeout,
213+
// which allows for forks when a burn block arrives quickly.
214+
// - Else the miner of the current sortition has committed to an incorrect parent tenure.
215+
let consensus_hash_match =
216+
self.cur_sortition.consensus_hash == tip.block.header.consensus_hash;
217+
let parent_tenure_id_match =
218+
self.cur_sortition.parent_tenure_id == tip.block.header.consensus_hash;
219+
if !consensus_hash_match && !parent_tenure_id_match {
220+
// More expensive check, so do it only if we need to.
221+
let is_valid_parent_tenure = Self::check_parent_tenure_choice(
222+
&self.cur_sortition,
223+
block,
224+
signer_db,
225+
client,
226+
&self.config.first_proposal_burn_block_timing,
227+
)?;
228+
if !is_valid_parent_tenure {
229+
warn!(
230+
"Current sortition does not build off of canonical tip tenure, marking as invalid";
231+
"current_sortition_parent" => ?self.cur_sortition.parent_tenure_id,
232+
"tip_consensus_hash" => ?tip.block.header.consensus_hash,
233+
);
234+
self.cur_sortition.miner_status =
235+
SortitionMinerStatus::InvalidatedBeforeFirstBlock;
236+
}
237+
}
206238
}
239+
207240
if let Some(last_sortition) = self.last_sortition.as_mut() {
208241
if last_sortition.is_timed_out(self.config.block_proposal_timeout, signer_db)? {
209242
info!(
@@ -304,6 +337,7 @@ impl SortitionsView {
304337
"Miner block proposal is from last sortition winner, when the new sortition winner is still valid. Considering proposal invalid.";
305338
"proposed_block_consensus_hash" => %block.header.consensus_hash,
306339
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
340+
"current_sortition_miner_status" => ?self.cur_sortition.miner_status,
307341
);
308342
return Ok(false);
309343
}
@@ -439,6 +473,8 @@ impl SortitionsView {
439473
"violating_tenure_proposed_time" => local_block_info.proposed_time,
440474
"new_tenure_received_time" => sortition_state_received_time,
441475
"new_tenure_burn_timestamp" => sortition_state.burn_header_timestamp,
476+
"first_proposal_burn_block_timing_secs" => first_proposal_burn_block_timing.as_secs(),
477+
"proposal_to_sortition" => proposal_to_sortition,
442478
);
443479
continue;
444480
}

stacks-signer/src/client/stacks_client.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ impl StacksClient {
173173
&self,
174174
consensus_hash: &ConsensusHash,
175175
) -> Result<StacksBlockHeaderTypes, ClientError> {
176+
debug!("StacksClient: Getting tenure tip";
177+
"consensus_hash" => %consensus_hash,
178+
);
176179
let send_request = || {
177180
self.stacks_node_client
178181
.get(self.tenure_tip_path(consensus_hash))
@@ -192,6 +195,7 @@ impl StacksClient {
192195

193196
/// Get the last set reward cycle stored within the stackerdb contract
194197
pub fn get_last_set_cycle(&self) -> Result<u128, ClientError> {
198+
debug!("StacksClient: Getting last set cycle");
195199
let signer_stackerdb_contract_id = boot_code_id(SIGNERS_NAME, self.mainnet);
196200
let function_name_str = "get-last-set-cycle";
197201
let function_name = ClarityName::from(function_name_str);
@@ -210,6 +214,10 @@ impl StacksClient {
210214
stackerdb_contract: &QualifiedContractIdentifier,
211215
page: u32,
212216
) -> Result<Vec<(StacksAddress, u128)>, ClientError> {
217+
debug!("StacksClient: Getting signer slots";
218+
"stackerdb_contract" => %stackerdb_contract,
219+
"page" => page,
220+
);
213221
let function_name_str = "stackerdb-get-signer-slots-page";
214222
let function_name = ClarityName::from(function_name_str);
215223
let function_args = &[ClarityValue::UInt(page.into())];
@@ -250,6 +258,9 @@ impl StacksClient {
250258
&self,
251259
reward_cycle: u64,
252260
) -> Result<HashMap<StacksAddress, SignerSlotID>, ClientError> {
261+
debug!("StacksClient: Getting parsed signer slots";
262+
"reward_cycle" => reward_cycle,
263+
);
253264
let signer_set =
254265
u32::try_from(reward_cycle % 2).expect("FATAL: reward_cycle % 2 exceeds u32::MAX");
255266
let signer_stackerdb_contract_id = boot_code_id(SIGNERS_NAME, self.mainnet);
@@ -272,6 +283,7 @@ impl StacksClient {
272283

273284
/// Determine the stacks node current epoch
274285
pub fn get_node_epoch(&self) -> Result<StacksEpochId, ClientError> {
286+
debug!("StacksClient: Getting node epoch");
275287
let pox_info = self.get_pox_data()?;
276288
let burn_block_height = self.get_burn_block_height()?;
277289

@@ -302,7 +314,7 @@ impl StacksClient {
302314

303315
/// Submit the block proposal to the stacks node. The block will be validated and returned via the HTTP endpoint for Block events.
304316
pub fn submit_block_for_validation(&self, block: NakamotoBlock) -> Result<(), ClientError> {
305-
debug!("stacks_node_client: Submitting block for validation...";
317+
debug!("StacksClient: Submitting block for validation";
306318
"signer_sighash" => %block.header.signer_signature_hash(),
307319
"block_id" => %block.header.block_id(),
308320
"block_height" => %block.header.chain_length,
@@ -337,6 +349,10 @@ impl StacksClient {
337349
chosen_parent: &ConsensusHash,
338350
last_sortition: &ConsensusHash,
339351
) -> Result<Vec<TenureForkingInfo>, ClientError> {
352+
debug!("StacksClient: Getting tenure forking info";
353+
"chosen_parent" => %chosen_parent,
354+
"last_sortition" => %last_sortition,
355+
);
340356
let mut tenures: VecDeque<TenureForkingInfo> =
341357
self.get_tenure_forking_info_step(chosen_parent, last_sortition)?;
342358
if tenures.is_empty() {
@@ -373,7 +389,7 @@ impl StacksClient {
373389
chosen_parent: &ConsensusHash,
374390
last_sortition: &ConsensusHash,
375391
) -> Result<VecDeque<TenureForkingInfo>, ClientError> {
376-
debug!("stacks_node_client: Getting tenure forking info...";
392+
debug!("StacksClient: Getting tenure forking info";
377393
"chosen_parent" => %chosen_parent,
378394
"last_sortition" => %last_sortition,
379395
);
@@ -402,7 +418,7 @@ impl StacksClient {
402418

403419
/// Get the current winning sortition and the last winning sortition
404420
pub fn get_current_and_last_sortition(&self) -> Result<CurrentAndLastSortition, ClientError> {
405-
debug!("stacks_node_client: Getting current and prior sortition...");
421+
debug!("StacksClient: Getting current and prior sortition");
406422
let path = format!("{}/latest_and_last", self.sortition_info_path());
407423
let timer = crate::monitoring::new_rpc_call_timer(&path, &self.http_origin);
408424
let send_request = || {
@@ -443,7 +459,7 @@ impl StacksClient {
443459

444460
/// Get the current peer info data from the stacks node
445461
pub fn get_peer_info(&self) -> Result<PeerInfo, ClientError> {
446-
debug!("stacks_node_client: Getting peer info...");
462+
debug!("StacksClient: Getting peer info");
447463
let timer =
448464
crate::monitoring::new_rpc_call_timer(&self.core_info_path(), &self.http_origin);
449465
let send_request = || {
@@ -466,7 +482,9 @@ impl StacksClient {
466482
&self,
467483
reward_cycle: u64,
468484
) -> Result<Option<Vec<NakamotoSignerEntry>>, ClientError> {
469-
debug!("stacks_node_client: Getting reward set signers for reward cycle {reward_cycle}...");
485+
debug!("StacksClient: Getting reward set signers";
486+
"reward_cycle" => reward_cycle,
487+
);
470488
let timer = crate::monitoring::new_rpc_call_timer(
471489
&format!("{}/v3/stacker_set/:reward_cycle", self.http_origin),
472490
&self.http_origin,
@@ -502,7 +520,7 @@ impl StacksClient {
502520

503521
/// Retrieve the current pox data from the stacks node
504522
pub fn get_pox_data(&self) -> Result<RPCPoxInfoData, ClientError> {
505-
debug!("stacks_node_client: Getting pox data...");
523+
debug!("StacksClient: Getting pox data");
506524
let timer = crate::monitoring::new_rpc_call_timer(&self.pox_path(), &self.http_origin);
507525
let send_request = || {
508526
self.stacks_node_client
@@ -521,11 +539,13 @@ impl StacksClient {
521539

522540
/// Helper function to retrieve the burn tip height from the stacks node
523541
fn get_burn_block_height(&self) -> Result<u64, ClientError> {
542+
debug!("StacksClient: Getting burn block height");
524543
self.get_peer_info().map(|info| info.burn_block_height)
525544
}
526545

527546
/// Get the current reward cycle info from the stacks node
528547
pub fn get_current_reward_cycle_info(&self) -> Result<RewardCycleInfo, ClientError> {
548+
debug!("StacksClient: Getting current reward cycle info");
529549
let pox_data = self.get_pox_data()?;
530550
let blocks_mined = pox_data
531551
.current_burnchain_block_height
@@ -548,7 +568,9 @@ impl StacksClient {
548568
&self,
549569
address: &StacksAddress,
550570
) -> Result<AccountEntryResponse, ClientError> {
551-
debug!("stacks_node_client: Getting account info...");
571+
debug!("StacksClient: Getting account info";
572+
"address" => %address,
573+
);
552574
let timer_label = format!("{}/v2/accounts/:principal", self.http_origin);
553575
let timer = crate::monitoring::new_rpc_call_timer(&timer_label, &self.http_origin);
554576
let send_request = || {
@@ -570,6 +592,11 @@ impl StacksClient {
570592
///
571593
/// In tests, this panics if the retry takes longer than 30 seconds.
572594
pub fn post_block_until_ok<F: Display>(&self, log_fmt: &F, block: &NakamotoBlock) -> bool {
595+
debug!("StacksClient: Posting block to stacks node";
596+
"signer_sighash" => %block.header.signer_signature_hash(),
597+
"block_id" => %block.header.block_id(),
598+
"block_height" => %block.header.chain_length,
599+
);
573600
let start_time = Instant::now();
574601
loop {
575602
match self.post_block(block) {
@@ -595,7 +622,8 @@ impl StacksClient {
595622
/// Returns `true` if the block was accepted or `false` if the block
596623
/// was rejected.
597624
pub fn post_block(&self, block: &NakamotoBlock) -> Result<bool, ClientError> {
598-
debug!("stacks_node_client: Posting block to the stacks node...";
625+
debug!("StacksClient: Posting block to the stacks node";
626+
"signer_sighash" => %block.header.signer_signature_hash(),
599627
"block_id" => %block.header.block_id(),
600628
"block_height" => %block.header.chain_length,
601629
);
@@ -630,7 +658,9 @@ impl StacksClient {
630658
function_name: &ClarityName,
631659
function_args: &[ClarityValue],
632660
) -> Result<ClarityValue, ClientError> {
633-
debug!("stacks_node_client: Calling read-only function {function_name} with args {function_args:?}...");
661+
debug!(
662+
"StacksClient: Calling read-only function {function_name} with args {function_args:?}"
663+
);
634664
let args = function_args
635665
.iter()
636666
.filter_map(|arg| arg.serialize_to_hex().ok())

0 commit comments

Comments
 (0)