Skip to content

Commit 10eb397

Browse files
authored
Merge branch 'develop' into feat/add-block-height-logs
2 parents c9a1581 + c87c0eb commit 10eb397

File tree

15 files changed

+539
-409
lines changed

15 files changed

+539
-409
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
@@ -238,6 +239,23 @@ impl SortitionsView {
238239
})
239240
})
240241
else {
242+
if reset_view_if_wrong_consensus_hash {
243+
info!(
244+
"Miner block proposal has consensus hash that is neither the current or last sortition. Resetting view.";
245+
"proposed_block_consensus_hash" => %block.header.consensus_hash,
246+
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
247+
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
248+
);
249+
self.reset_view(client)?;
250+
return self.check_proposal(
251+
client,
252+
signer_db,
253+
block,
254+
block_pk,
255+
reward_cycle,
256+
false,
257+
);
258+
}
241259
warn!(
242260
"Miner block proposal has consensus hash that is neither the current or last sortition. Considering invalid.";
243261
"proposed_block_consensus_hash" => %block.header.consensus_hash,
@@ -629,4 +647,23 @@ impl SortitionsView {
629647
config,
630648
})
631649
}
650+
651+
/// Reset the view to the current sortition and last sortition
652+
pub fn reset_view(&mut self, client: &StacksClient) -> Result<(), ClientError> {
653+
let CurrentAndLastSortition {
654+
current_sortition,
655+
last_sortition,
656+
} = client.get_current_and_last_sortition()?;
657+
658+
let cur_sortition = SortitionState::try_from(current_sortition)?;
659+
let last_sortition = last_sortition
660+
.map(SortitionState::try_from)
661+
.transpose()
662+
.ok()
663+
.flatten();
664+
665+
self.cur_sortition = cur_sortition;
666+
self.last_sortition = last_sortition;
667+
Ok(())
668+
}
632669
}

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: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
use std::collections::{HashMap, VecDeque};
17+
use std::fmt::Display;
18+
use std::time::{Duration, Instant};
1719

1820
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
1921
use blockstack_lib::chainstate::stacks::boot::{NakamotoSignerEntry, SIGNERS_NAME};
@@ -30,7 +32,7 @@ use blockstack_lib::net::api::get_tenures_fork_info::{
3032
use blockstack_lib::net::api::getaccount::AccountEntryResponse;
3133
use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData;
3234
use blockstack_lib::net::api::getsortition::{SortitionInfo, RPC_SORTITION_INFO_PATH};
33-
use blockstack_lib::net::api::getstackers::{GetStackersErrors, GetStackersResponse};
35+
use blockstack_lib::net::api::getstackers::GetStackersResponse;
3436
use blockstack_lib::net::api::postblock::StacksBlockAcceptedData;
3537
use blockstack_lib::net::api::postblock_proposal::NakamotoBlockProposal;
3638
use blockstack_lib::net::api::postblock_v3;
@@ -78,7 +80,6 @@ pub struct StacksClient {
7880

7981
#[derive(Deserialize)]
8082
struct GetStackersErrorResp {
81-
err_type: String,
8283
err_msg: String,
8384
}
8485

@@ -483,14 +484,11 @@ impl StacksClient {
483484
warn!("Failed to parse the GetStackers error response: {e}");
484485
backoff::Error::permanent(e.into())
485486
})?;
486-
if error_data.err_type == GetStackersErrors::NOT_AVAILABLE_ERR_TYPE {
487-
Err(backoff::Error::permanent(ClientError::NoSortitionOnChain))
488-
} else {
489-
warn!("Got error response ({status}): {}", error_data.err_msg);
490-
Err(backoff::Error::permanent(ClientError::RequestFailure(
491-
status,
492-
)))
493-
}
487+
488+
warn!("Got error response ({status}): {}", error_data.err_msg);
489+
Err(backoff::Error::permanent(ClientError::RequestFailure(
490+
status,
491+
)))
494492
};
495493
let stackers_response =
496494
retry_with_exponential_backoff::<_, ClientError, GetStackersResponse>(send_request)?;
@@ -564,6 +562,31 @@ impl StacksClient {
564562
Ok(account_entry)
565563
}
566564

565+
/// Post a block to the stacks-node, retry forever on errors.
566+
///
567+
/// In tests, this panics if the retry takes longer than 30 seconds.
568+
pub fn post_block_until_ok<F: Display>(&self, log_fmt: &F, block: &NakamotoBlock) -> bool {
569+
let start_time = Instant::now();
570+
loop {
571+
match self.post_block(block) {
572+
Ok(block_push_result) => {
573+
debug!("{log_fmt}: Block pushed to stacks node: {block_push_result:?}");
574+
return block_push_result;
575+
}
576+
Err(e) => {
577+
if cfg!(any(test, feature = "testing"))
578+
&& start_time.elapsed() > Duration::from_secs(30)
579+
{
580+
panic!(
581+
"{log_fmt}: Timed out in test while pushing block to stacks node: {e}"
582+
);
583+
}
584+
warn!("{log_fmt}: Failed to push block to stacks node: {e}. Retrying...");
585+
}
586+
};
587+
}
588+
}
589+
567590
/// Try to post a completed nakamoto block to our connected stacks-node
568591
/// Returns `true` if the block was accepted or `false` if the block
569592
/// was rejected.

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+
}

0 commit comments

Comments
 (0)