Skip to content

Commit e5ecf72

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feat/naka-miner-heuristics
2 parents 994ef3b + c87c0eb commit e5ecf72

File tree

10 files changed

+380
-369
lines changed

10 files changed

+380
-369
lines changed

stacks-signer/src/chainstate.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ impl SortitionsView {
187187
block: &NakamotoBlock,
188188
block_pk: &StacksPublicKey,
189189
reward_cycle: u64,
190+
reset_view_if_wrong_consensus_hash: bool,
190191
) -> Result<bool, SignerChainstateError> {
191192
if self
192193
.cur_sortition
@@ -236,6 +237,23 @@ impl SortitionsView {
236237
})
237238
})
238239
else {
240+
if reset_view_if_wrong_consensus_hash {
241+
info!(
242+
"Miner block proposal has consensus hash that is neither the current or last sortition. Resetting view.";
243+
"proposed_block_consensus_hash" => %block.header.consensus_hash,
244+
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
245+
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
246+
);
247+
self.reset_view(client)?;
248+
return self.check_proposal(
249+
client,
250+
signer_db,
251+
block,
252+
block_pk,
253+
reward_cycle,
254+
false,
255+
);
256+
}
239257
warn!(
240258
"Miner block proposal has consensus hash that is neither the current or last sortition. Considering invalid.";
241259
"proposed_block_consensus_hash" => %block.header.consensus_hash,
@@ -624,4 +642,23 @@ impl SortitionsView {
624642
config,
625643
})
626644
}
645+
646+
/// Reset the view to the current sortition and last sortition
647+
pub fn reset_view(&mut self, client: &StacksClient) -> Result<(), ClientError> {
648+
let CurrentAndLastSortition {
649+
current_sortition,
650+
last_sortition,
651+
} = client.get_current_and_last_sortition()?;
652+
653+
let cur_sortition = SortitionState::try_from(current_sortition)?;
654+
let last_sortition = last_sortition
655+
.map(SortitionState::try_from)
656+
.transpose()
657+
.ok()
658+
.flatten();
659+
660+
self.cur_sortition = cur_sortition;
661+
self.last_sortition = last_sortition;
662+
Ok(())
663+
}
627664
}

stacks-signer/src/client/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ pub enum ClientError {
8989
/// Invalid response from the stacks node
9090
#[error("Invalid response from the stacks node: {0}")]
9191
InvalidResponse(String),
92-
/// A successful sortition has not occurred yet
93-
#[error("The Stacks chain has not processed any successful sortitions yet")]
94-
NoSortitionOnChain,
9592
/// A successful sortition's info response should be parseable into a SortitionState
9693
#[error("A successful sortition's info response should be parseable into a SortitionState")]
9794
UnexpectedSortitionInfo,

stacks-signer/src/client/stacks_client.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use blockstack_lib::net::api::get_tenures_fork_info::{
3232
use blockstack_lib::net::api::getaccount::AccountEntryResponse;
3333
use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData;
3434
use blockstack_lib::net::api::getsortition::{SortitionInfo, RPC_SORTITION_INFO_PATH};
35-
use blockstack_lib::net::api::getstackers::{GetStackersErrors, GetStackersResponse};
35+
use blockstack_lib::net::api::getstackers::GetStackersResponse;
3636
use blockstack_lib::net::api::postblock::StacksBlockAcceptedData;
3737
use blockstack_lib::net::api::postblock_proposal::NakamotoBlockProposal;
3838
use blockstack_lib::net::api::postblock_v3;
@@ -80,7 +80,6 @@ pub struct StacksClient {
8080

8181
#[derive(Deserialize)]
8282
struct GetStackersErrorResp {
83-
err_type: String,
8483
err_msg: String,
8584
}
8685

@@ -485,14 +484,11 @@ impl StacksClient {
485484
warn!("Failed to parse the GetStackers error response: {e}");
486485
backoff::Error::permanent(e.into())
487486
})?;
488-
if error_data.err_type == GetStackersErrors::NOT_AVAILABLE_ERR_TYPE {
489-
Err(backoff::Error::permanent(ClientError::NoSortitionOnChain))
490-
} else {
491-
warn!("Got error response ({status}): {}", error_data.err_msg);
492-
Err(backoff::Error::permanent(ClientError::RequestFailure(
493-
status,
494-
)))
495-
}
487+
488+
warn!("Got error response ({status}): {}", error_data.err_msg);
489+
Err(backoff::Error::permanent(ClientError::RequestFailure(
490+
status,
491+
)))
496492
};
497493
let stackers_response =
498494
retry_with_exponential_backoff::<_, ClientError, GetStackersResponse>(send_request)?;
@@ -578,7 +574,9 @@ impl StacksClient {
578574
return block_push_result;
579575
}
580576
Err(e) => {
581-
if cfg!(test) && start_time.elapsed() > Duration::from_secs(30) {
577+
if cfg!(any(test, feature = "testing"))
578+
&& start_time.elapsed() > Duration::from_secs(30)
579+
{
582580
panic!(
583581
"{log_fmt}: Timed out in test while pushing block to stacks node: {e}"
584582
);

stacks-signer/src/tests/chainstate.rs

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use blockstack_lib::chainstate::stacks::{
2626
TransactionSpendingCondition, TransactionVersion,
2727
};
2828
use blockstack_lib::net::api::get_tenures_fork_info::TenureForkingInfo;
29+
use blockstack_lib::net::api::getsortition::SortitionInfo;
2930
use clarity::types::chainstate::{BurnchainHeaderHash, SortitionId};
3031
use clarity::util::vrf::VRFProof;
3132
use libsigner::BlockProposal;
@@ -128,13 +129,13 @@ fn check_proposal_units() {
128129
setup_test_environment("check_proposal_units");
129130

130131
assert!(!view
131-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
132+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
132133
.unwrap());
133134

134135
view.last_sortition = None;
135136

136137
assert!(!view
137-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
138+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
138139
.unwrap());
139140
}
140141

@@ -150,7 +151,8 @@ fn check_proposal_miner_pkh_mismatch() {
150151
&mut signer_db,
151152
&block,
152153
&different_block_pk,
153-
1
154+
1,
155+
false,
154156
)
155157
.unwrap());
156158

@@ -161,7 +163,8 @@ fn check_proposal_miner_pkh_mismatch() {
161163
&mut signer_db,
162164
&block,
163165
&different_block_pk,
164-
1
166+
1,
167+
false,
165168
)
166169
.unwrap());
167170
}
@@ -257,7 +260,7 @@ fn reorg_timing_testing(
257260
config,
258261
} = MockServerClient::new();
259262
let h = std::thread::spawn(move || {
260-
view.check_proposal(&client, &mut signer_db, &block, &block_pk, 1)
263+
view.check_proposal(&client, &mut signer_db, &block, &block_pk, 1, false)
261264
});
262265
header_clone.chain_length -= 1;
263266
let response = crate::client::tests::build_get_tenure_tip_response(
@@ -294,16 +297,16 @@ fn check_proposal_invalid_status() {
294297
setup_test_environment("invalid_status");
295298
block.header.consensus_hash = view.cur_sortition.consensus_hash;
296299
assert!(view
297-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
300+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
298301
.unwrap());
299302
view.cur_sortition.miner_status = SortitionMinerStatus::InvalidatedAfterFirstBlock;
300303
assert!(!view
301-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
304+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
302305
.unwrap());
303306

304307
block.header.consensus_hash = view.last_sortition.as_ref().unwrap().consensus_hash;
305308
assert!(!view
306-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
309+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
307310
.unwrap());
308311

309312
view.cur_sortition.miner_status = SortitionMinerStatus::InvalidatedBeforeFirstBlock;
@@ -314,7 +317,7 @@ fn check_proposal_invalid_status() {
314317
// parent blocks have been seen before, while the signer state checks are only reasoning about
315318
// stacks blocks seen by the signer, which may be a subset)
316319
assert!(view
317-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
320+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
318321
.unwrap());
319322
}
320323

@@ -363,7 +366,7 @@ fn check_proposal_tenure_extend_invalid_conditions() {
363366
let tx = make_tenure_change_tx(extend_payload);
364367
block.txs = vec![tx];
365368
assert!(!view
366-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
369+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
367370
.unwrap());
368371

369372
let mut extend_payload = make_tenure_change_payload();
@@ -373,7 +376,7 @@ fn check_proposal_tenure_extend_invalid_conditions() {
373376
let tx = make_tenure_change_tx(extend_payload);
374377
block.txs = vec![tx];
375378
assert!(view
376-
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1)
379+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
377380
.unwrap());
378381
}
379382

@@ -400,7 +403,8 @@ fn check_block_proposal_timeout() {
400403
&mut signer_db,
401404
&curr_sortition_block,
402405
&block_pk,
403-
1
406+
1,
407+
false,
404408
)
405409
.unwrap());
406410

@@ -410,7 +414,8 @@ fn check_block_proposal_timeout() {
410414
&mut signer_db,
411415
&last_sortition_block,
412416
&block_pk,
413-
1
417+
1,
418+
false,
414419
)
415420
.unwrap());
416421

@@ -422,7 +427,8 @@ fn check_block_proposal_timeout() {
422427
&mut signer_db,
423428
&curr_sortition_block,
424429
&block_pk,
425-
1
430+
1,
431+
false,
426432
)
427433
.unwrap());
428434

@@ -432,7 +438,8 @@ fn check_block_proposal_timeout() {
432438
&mut signer_db,
433439
&last_sortition_block,
434440
&block_pk,
435-
1
441+
1,
442+
false,
436443
)
437444
.unwrap());
438445
}
@@ -513,3 +520,64 @@ fn check_sortition_timeout() {
513520
.is_timed_out(Duration::from_secs(1), &signer_db)
514521
.unwrap());
515522
}
523+
524+
/// Test that the sortition info is refreshed once
525+
/// when `check_proposal` is called with a sortition view
526+
/// that doesn't match the block proposal
527+
#[test]
528+
fn check_proposal_refresh() {
529+
let (stacks_client, mut signer_db, block_pk, mut view, mut block) =
530+
setup_test_environment("check_proposal_refresh");
531+
block.header.consensus_hash = view.cur_sortition.consensus_hash;
532+
assert!(view
533+
.check_proposal(&stacks_client, &mut signer_db, &block, &block_pk, 1, false)
534+
.unwrap());
535+
536+
let MockServerClient {
537+
server,
538+
client,
539+
config: _,
540+
} = MockServerClient::new();
541+
542+
let last_sortition = view.last_sortition.as_ref().unwrap();
543+
544+
let expected_result = vec![
545+
SortitionInfo {
546+
burn_block_hash: last_sortition.burn_block_hash,
547+
burn_block_height: 2,
548+
sortition_id: SortitionId([2; 32]),
549+
parent_sortition_id: SortitionId([1; 32]),
550+
consensus_hash: block.header.consensus_hash,
551+
was_sortition: true,
552+
burn_header_timestamp: 2,
553+
miner_pk_hash160: Some(view.cur_sortition.miner_pkh),
554+
stacks_parent_ch: Some(view.cur_sortition.parent_tenure_id),
555+
last_sortition_ch: Some(view.cur_sortition.parent_tenure_id),
556+
committed_block_hash: None,
557+
},
558+
SortitionInfo {
559+
burn_block_hash: BurnchainHeaderHash([128; 32]),
560+
burn_block_height: 1,
561+
sortition_id: SortitionId([1; 32]),
562+
parent_sortition_id: SortitionId([0; 32]),
563+
consensus_hash: view.cur_sortition.parent_tenure_id,
564+
was_sortition: true,
565+
burn_header_timestamp: 1,
566+
miner_pk_hash160: Some(view.cur_sortition.miner_pkh),
567+
stacks_parent_ch: Some(view.cur_sortition.parent_tenure_id),
568+
last_sortition_ch: Some(view.cur_sortition.parent_tenure_id),
569+
committed_block_hash: None,
570+
},
571+
];
572+
573+
view.cur_sortition.consensus_hash = ConsensusHash([128; 20]);
574+
let h = std::thread::spawn(move || {
575+
view.check_proposal(&client, &mut signer_db, &block, &block_pk, 1, true)
576+
});
577+
crate::client::tests::write_response(
578+
server,
579+
format!("HTTP/1.1 200 Ok\n\n{}", serde_json::json!(expected_result)).as_bytes(),
580+
);
581+
let result = h.join().unwrap();
582+
assert!(result.unwrap());
583+
}

stacks-signer/src/v0/signer.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ impl Signer {
379379
&block_proposal.block,
380380
miner_pubkey,
381381
self.reward_cycle,
382+
true,
382383
) {
383384
// Error validating block
384385
Err(e) => {
@@ -512,7 +513,16 @@ impl Signer {
512513
.signer_db
513514
.block_lookup(self.reward_cycle, &signer_signature_hash)
514515
{
515-
Ok(Some(block_info)) => block_info,
516+
Ok(Some(block_info)) => {
517+
if block_info.state == BlockState::GloballyRejected
518+
|| block_info.state == BlockState::GloballyAccepted
519+
{
520+
debug!("{self}: Received block validation for a block that is already marked as {}. Ignoring...", block_info.state);
521+
return None;
522+
} else {
523+
block_info
524+
}
525+
}
516526
Ok(None) => {
517527
// We have not seen this block before. Why are we getting a response for it?
518528
debug!("{self}: Received a block validate response for a block we have not seen before. Ignoring...");

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,12 @@ fn microblocks_disabled() {
211211
);
212212
assert_eq!(account.nonce, 1);
213213

214-
info!(
215-
"Microblocks assembled: {}",
216-
test_observer::get_microblocks().len()
214+
let microblocks_assembled = test_observer::get_microblocks().len();
215+
info!("Microblocks assembled: {microblocks_assembled}",);
216+
assert!(
217+
microblocks_assembled > 0,
218+
"There should be at least 1 microblock assembled"
217219
);
218-
assert_eq!(test_observer::get_microblocks().len(), 1);
219220

220221
let miner_nonce_before_microblock_assembly = get_account(&http_origin, &miner_account).nonce;
221222

@@ -244,8 +245,8 @@ fn microblocks_disabled() {
244245
);
245246
assert_eq!(account.nonce, 1);
246247

247-
// but we should have assembled and announced at least 1 to the observer
248-
assert!(test_observer::get_microblocks().len() >= 2);
248+
// but we should have assembled and announced at least 1 more block to the observer
249+
assert!(test_observer::get_microblocks().len() > microblocks_assembled);
249250
info!(
250251
"Microblocks assembled: {}",
251252
test_observer::get_microblocks().len()

0 commit comments

Comments
 (0)