Skip to content

Commit bb9f17b

Browse files
authored
Merge branch 'develop' into chore/update-docs_1721318026
2 parents b1f813b + 30acb47 commit bb9f17b

File tree

35 files changed

+2544
-761
lines changed

35 files changed

+2544
-761
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ jobs:
3232
- tests::bitcoin_regtest::bitcoind_integration_test
3333
- tests::integrations::integration_test_get_info
3434
- tests::neon_integrations::antientropy_integration_test
35-
- tests::neon_integrations::bad_microblock_pubkey
3635
- tests::neon_integrations::bitcoind_forking_test
3736
- tests::neon_integrations::bitcoind_integration_test
3837
- tests::neon_integrations::block_large_tx_integration_test
@@ -43,21 +42,26 @@ jobs:
4342
- tests::neon_integrations::fuzzed_median_fee_rate_estimation_test_window10
4443
- tests::neon_integrations::fuzzed_median_fee_rate_estimation_test_window5
4544
- tests::neon_integrations::liquid_ustx_integration
46-
- tests::neon_integrations::microblock_fork_poison_integration_test
47-
- tests::neon_integrations::microblock_integration_test
45+
# Microblock tests that are no longer needed on every CI run
46+
# (microblocks are unsupported starting in Epoch 2.5)
47+
# - tests::neon_integrations::bad_microblock_pubkey
48+
# - tests::neon_integrations::microblock_fork_poison_integration_test
49+
# - tests::neon_integrations::microblock_integration_test
50+
# - tests::neon_integrations::microblock_limit_hit_integration_test
51+
# - tests::neon_integrations::test_problematic_microblocks_are_not_mined
52+
# - tests::neon_integrations::test_problematic_microblocks_are_not_relayed_or_stored
53+
# - tests::neon_integrations::size_overflow_unconfirmed_invalid_stream_microblocks_integration_test
54+
# - tests::neon_integrations::size_overflow_unconfirmed_microblocks_integration_test
55+
# - tests::neon_integrations::size_overflow_unconfirmed_stream_microblocks_integration_test
56+
# - tests::neon_integrations::runtime_overflow_unconfirmed_microblocks_integration_test
4857
# Disable this flaky test. Microblocks are no longer supported anyways.
4958
# - tests::neon_integrations::microblock_large_tx_integration_test_FLAKY
50-
- tests::neon_integrations::microblock_limit_hit_integration_test
5159
- tests::neon_integrations::miner_submit_twice
5260
- tests::neon_integrations::mining_events_integration_test
5361
- tests::neon_integrations::pox_integration_test
5462
- tests::neon_integrations::push_boot_receipts
55-
- tests::neon_integrations::runtime_overflow_unconfirmed_microblocks_integration_test
5663
- tests::neon_integrations::should_fix_2771
5764
- tests::neon_integrations::size_check_integration_test
58-
- tests::neon_integrations::size_overflow_unconfirmed_invalid_stream_microblocks_integration_test
59-
- tests::neon_integrations::size_overflow_unconfirmed_microblocks_integration_test
60-
- tests::neon_integrations::size_overflow_unconfirmed_stream_microblocks_integration_test
6165
- tests::neon_integrations::stx_delegate_btc_integration_test
6266
- tests::neon_integrations::stx_transfer_btc_integration_test
6367
- tests::neon_integrations::stack_stx_burn_op_test
@@ -66,8 +70,6 @@ jobs:
6670
- tests::neon_integrations::test_flash_block_skip_tenure
6771
- tests::neon_integrations::test_problematic_blocks_are_not_mined
6872
- tests::neon_integrations::test_problematic_blocks_are_not_relayed_or_stored
69-
- tests::neon_integrations::test_problematic_microblocks_are_not_mined
70-
- tests::neon_integrations::test_problematic_microblocks_are_not_relayed_or_stored
7173
- tests::neon_integrations::test_problematic_txs_are_not_stored
7274
- tests::neon_integrations::use_latest_tip_integration_test
7375
- tests::neon_integrations::confirm_unparsed_ongoing_ops
@@ -81,7 +83,7 @@ jobs:
8183
- tests::epoch_25::microblocks_disabled
8284
- tests::should_succeed_handling_malformed_and_valid_txs
8385
- tests::nakamoto_integrations::simple_neon_integration
84-
- tests::nakamoto_integrations::simple_neon_integration_with_flash_blocks_on_epoch_3
86+
- tests::nakamoto_integrations::flash_blocks_on_epoch_3
8587
- tests::nakamoto_integrations::mine_multiple_per_tenure_integration
8688
- tests::nakamoto_integrations::block_proposal_api_endpoint
8789
- tests::nakamoto_integrations::miner_writes_proposed_block_to_stackerdb
@@ -90,6 +92,7 @@ jobs:
9092
- tests::nakamoto_integrations::follower_bootup
9193
- tests::nakamoto_integrations::forked_tenure_is_ignored
9294
- tests::nakamoto_integrations::nakamoto_attempt_time
95+
- tests::nakamoto_integrations::skip_mining_long_tx
9396
- tests::signer::v0::block_proposal_rejection
9497
- tests::signer::v0::miner_gather_signatures
9598
- tests::signer::v0::end_of_tenure

stacks-signer/src/chainstate.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,15 @@ 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
193194
.is_timed_out(self.config.block_proposal_timeout, signer_db)?
194195
{
195196
info!(
196197
"Current miner timed out, marking as invalid.";
198+
"block_height" => block.header.chain_length,
197199
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
198200
);
199201
self.cur_sortition.miner_status = SortitionMinerStatus::InvalidatedBeforeFirstBlock;
@@ -202,6 +204,7 @@ impl SortitionsView {
202204
if last_sortition.is_timed_out(self.config.block_proposal_timeout, signer_db)? {
203205
info!(
204206
"Last miner timed out, marking as invalid.";
207+
"block_height" => block.header.chain_length,
205208
"last_sortition_consensus_hash" => ?last_sortition.consensus_hash,
206209
);
207210
last_sortition.miner_status = SortitionMinerStatus::InvalidatedBeforeFirstBlock;
@@ -236,6 +239,23 @@ impl SortitionsView {
236239
})
237240
})
238241
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+
}
239259
warn!(
240260
"Miner block proposal has consensus hash that is neither the current or last sortition. Considering invalid.";
241261
"proposed_block_consensus_hash" => %block.header.consensus_hash,
@@ -347,6 +367,7 @@ impl SortitionsView {
347367
"sortition_state.consensus_hash" => %sortition_state.consensus_hash,
348368
"sortition_state.prior_sortition" => %sortition_state.prior_sortition,
349369
"sortition_state.parent_tenure_id" => %sortition_state.parent_tenure_id,
370+
"block_height" => block.header.chain_length,
350371
);
351372

352373
let tenures_reorged = client.get_tenure_forking_info(
@@ -406,6 +427,7 @@ impl SortitionsView {
406427
"Miner is not building off of most recent tenure. A tenure they reorg has already mined blocks, but the block was poorly timed, allowing the reorg.";
407428
"proposed_block_consensus_hash" => %block.header.consensus_hash,
408429
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
430+
"proposed_block_height" => block.header.chain_length,
409431
"parent_tenure" => %sortition_state.parent_tenure_id,
410432
"last_sortition" => %sortition_state.prior_sortition,
411433
"violating_tenure_id" => %tenure.consensus_hash,
@@ -578,6 +600,7 @@ impl SortitionsView {
578600
"Have no accepted blocks in the tenure, assuming block confirmation is correct";
579601
"proposed_block_consensus_hash" => %block.header.consensus_hash,
580602
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
603+
"proposed_block_height" => block.header.chain_length,
581604
);
582605
return Ok(true);
583606
};
@@ -624,4 +647,23 @@ impl SortitionsView {
624647
config,
625648
})
626649
}
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+
}
627669
}

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: 38 additions & 13 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

@@ -375,10 +376,12 @@ impl StacksClient {
375376
"last_sortition" => %last_sortition,
376377
);
377378
let path = self.tenure_forking_info_path(chosen_parent, last_sortition);
378-
let timer = crate::monitoring::new_rpc_call_timer(
379-
"/v3/tenures/fork_info/:start/:stop",
380-
&self.http_origin,
379+
// Use a separate metrics path to allow the same metric for different start and stop hashes
380+
let metrics_path = format!(
381+
"{}{RPC_TENURE_FORKING_INFO_PATH}/:start/:stop",
382+
self.http_origin
381383
);
384+
let timer = crate::monitoring::new_rpc_call_timer(&metrics_path, &self.http_origin);
382385
let send_request = || {
383386
self.stacks_node_client
384387
.get(&path)
@@ -483,14 +486,11 @@ impl StacksClient {
483486
warn!("Failed to parse the GetStackers error response: {e}");
484487
backoff::Error::permanent(e.into())
485488
})?;
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-
}
489+
490+
warn!("Got error response ({status}): {}", error_data.err_msg);
491+
Err(backoff::Error::permanent(ClientError::RequestFailure(
492+
status,
493+
)))
494494
};
495495
let stackers_response =
496496
retry_with_exponential_backoff::<_, ClientError, GetStackersResponse>(send_request)?;
@@ -564,6 +564,31 @@ impl StacksClient {
564564
Ok(account_entry)
565565
}
566566

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

stacks-signer/src/monitoring/mod.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,22 @@ pub fn update_signer_nonce(nonce: u64) {
9292
prometheus::SIGNER_NONCE.set(nonce as i64);
9393
}
9494

95+
// Allow dead code because this is only used in the `monitoring_prom` feature
96+
// but we want to run it in a test
97+
#[allow(dead_code)]
98+
/// Remove the origin from the full path to avoid duplicate metrics for different origins
99+
fn remove_origin_from_path(full_path: &str, origin: &str) -> String {
100+
let path = full_path.replace(origin, "");
101+
path
102+
}
103+
95104
/// Start a new RPC call timer.
96105
/// The `origin` parameter is the base path of the RPC call, e.g. `http://node.com`.
97106
/// The `origin` parameter is removed from `full_path` when storing in prometheus.
98107
#[cfg(feature = "monitoring_prom")]
99108
pub fn new_rpc_call_timer(full_path: &str, origin: &str) -> HistogramTimer {
100-
let path = &full_path[origin.len()..];
101-
let histogram = prometheus::SIGNER_RPC_CALL_LATENCIES_HISTOGRAM.with_label_values(&[path]);
109+
let path = remove_origin_from_path(full_path, origin);
110+
let histogram = prometheus::SIGNER_RPC_CALL_LATENCIES_HISTOGRAM.with_label_values(&[&path]);
102111
histogram.start_timer()
103112
}
104113

@@ -140,3 +149,16 @@ pub fn start_serving_monitoring_metrics(config: GlobalConfig) -> Result<(), Stri
140149
}
141150
Ok(())
142151
}
152+
153+
#[test]
154+
fn test_remove_origin_from_path() {
155+
let full_path = "http://localhost:20443/v2/info";
156+
let origin = "http://localhost:20443";
157+
let path = remove_origin_from_path(full_path, origin);
158+
assert_eq!(path, "/v2/info");
159+
160+
let full_path = "/v2/info";
161+
let origin = "http://localhost:20443";
162+
let path = remove_origin_from_path(full_path, origin);
163+
assert_eq!(path, "/v2/info");
164+
}

0 commit comments

Comments
 (0)