Skip to content

Commit 8b70c14

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into feat/signer-track-validation-submission-with-config-timeout
2 parents 38bd6c7 + 9c5d822 commit 8b70c14

Some content is hidden

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

82 files changed

+5150
-3873
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ jobs:
9999
- tests::signer::v0::forked_tenure_okay
100100
- tests::signer::v0::forked_tenure_invalid
101101
- tests::signer::v0::empty_sortition
102+
- tests::signer::v0::empty_sortition_before_approval
103+
- tests::signer::v0::empty_sortition_before_proposal
102104
- tests::signer::v0::bitcoind_forking_test
103105
- tests::signer::v0::multiple_miners
104106
- tests::signer::v0::mock_sign_epoch_25
@@ -112,6 +114,7 @@ jobs:
112114
- tests::signer::v0::locally_accepted_blocks_overriden_by_global_rejection
113115
- tests::signer::v0::locally_rejected_blocks_overriden_by_global_acceptance
114116
- tests::signer::v0::reorg_locally_accepted_blocks_across_tenures_succeeds
117+
- tests::signer::v0::reorg_locally_accepted_blocks_across_tenures_fails
115118
- tests::signer::v0::miner_recovers_when_broadcast_block_delay_across_tenures_occurs
116119
- tests::signer::v0::multiple_miners_with_nakamoto_blocks
117120
- tests::signer::v0::partial_tenure_fork
@@ -134,6 +137,7 @@ jobs:
134137
- tests::nakamoto_integrations::utxo_check_on_startup_panic
135138
- tests::nakamoto_integrations::utxo_check_on_startup_recover
136139
- tests::nakamoto_integrations::v3_signer_api_endpoint
140+
- tests::nakamoto_integrations::signer_chainstate
137141
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
138142
# - tests::signer::v1::dkg
139143
# - tests::signer::v1::sign_request_rejected

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Changed
11+
- Add index for StacksBlockId to nakamoto block headers table (improves node performance)
12+
- Remove the panic for reporting DB deadlocks (just error and continue waiting)
13+
14+
## [3.0.0.0.1]
15+
16+
### Changed
17+
- Add index for StacksBlockId to nakamoto block headers table (improves node performance)
18+
- Remove the panic for reporting DB deadlocks (just error and continue waiting)
19+
- Various test fixes for CI (5353, 5368, 5372, 5371, 5380, 5378, 5387, 5396, 5390, 5394)
20+
- Various log fixes:
21+
- don't say proceeding to mine blocks if not a miner
22+
- misc. warns downgraded to debugs
23+
- 5391: Update default block proposal timeout to 10 minutes
24+
- 5406: After block rejection, miner pauses
25+
- Docs fixes
26+
- Fix signer docs link
27+
- Specify burn block in clarity docs
28+
1029
## [3.0.0.0.0]
1130

1231
### Added

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ rand = "0.8"
2121
rand_chacha = "0.3.1"
2222
tikv-jemallocator = "0.5.4"
2323
rusqlite = { version = "0.31.0", features = ["blob", "serde_json", "i128_blob", "bundled", "trace"] }
24+
thiserror = { version = "1.0.65" }
2425

2526
# Use a bit more than default optimization for
2627
# dev builds to speed up test execution

libsigner/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ slog-term = "2.6.0"
3030
slog-json = { version = "2.3.0", optional = true }
3131
stacks-common = { path = "../stacks-common" }
3232
stackslib = { path = "../stackslib"}
33-
thiserror = "1.0"
33+
thiserror = { workspace = true }
3434
tiny_http = "0.12"
3535

3636
[dev-dependencies]

stacks-common/src/util/db.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,25 @@ pub fn update_lock_table(conn: &Connection) {
5151
/// Called by `rusqlite` if we are waiting too long on a database lock
5252
/// If called too many times, will assume a deadlock and panic
5353
pub fn tx_busy_handler(run_count: i32) -> bool {
54-
const TIMEOUT: Duration = Duration::from_secs(300);
5554
const AVG_SLEEP_TIME_MS: u64 = 100;
5655

56+
// Every ~5min, report an error with a backtrace
57+
// 5min * 60s/min * 1_000ms/s / 100ms
58+
const ERROR_COUNT: u32 = 3_000;
59+
5760
// First, check if this is taking unreasonably long. If so, it's probably a deadlock
5861
let run_count = run_count.unsigned_abs();
59-
let approx_time_elapsed =
60-
Duration::from_millis(AVG_SLEEP_TIME_MS.saturating_mul(u64::from(run_count)));
61-
if approx_time_elapsed > TIMEOUT {
62-
error!("Deadlock detected. Waited {} seconds (estimated) for database lock. Giving up", approx_time_elapsed.as_secs();
62+
if run_count > 0 && run_count % ERROR_COUNT == 0 {
63+
error!("Deadlock detected. Waited 5 minutes (estimated) for database lock.";
6364
"run_count" => run_count,
6465
"backtrace" => ?Backtrace::capture()
6566
);
6667
for (k, v) in LOCK_TABLE.lock().unwrap().iter() {
6768
error!("Database '{k}' last locked by {v}");
6869
}
69-
panic!("Deadlock in thread {:?}", thread::current().name());
7070
}
7171

7272
let mut sleep_time_ms = 2u64.saturating_pow(run_count);
73-
7473
sleep_time_ms = sleep_time_ms.saturating_add(thread_rng().gen_range(0..sleep_time_ms));
7574

7675
if sleep_time_ms > AVG_SLEEP_TIME_MS {

stacks-common/src/util/log.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,13 @@ fn make_json_logger() -> Logger {
215215
panic!("Tried to construct JSON logger, but stacks-blockchain built without slog_json feature enabled.")
216216
}
217217

218-
#[cfg(not(any(test, feature = "testing")))]
219218
fn make_logger() -> Logger {
220219
if env::var("STACKS_LOG_JSON") == Ok("1".into()) {
221220
make_json_logger()
222221
} else {
223222
let debug = env::var("STACKS_LOG_DEBUG") == Ok("1".into());
224223
let pretty_print = env::var("STACKS_LOG_PP") == Ok("1".into());
225-
let decorator = slog_term::PlainSyncDecorator::new(std::io::stderr());
224+
let decorator = get_decorator();
226225
let atty = isatty(Stream::Stderr);
227226
let drain = TermFormat::new(decorator, pretty_print, debug, atty);
228227
let logger = Logger::root(drain.ignore_res(), o!());
@@ -231,17 +230,13 @@ fn make_logger() -> Logger {
231230
}
232231

233232
#[cfg(any(test, feature = "testing"))]
234-
fn make_logger() -> Logger {
235-
if env::var("STACKS_LOG_JSON") == Ok("1".into()) {
236-
make_json_logger()
237-
} else {
238-
let debug = env::var("STACKS_LOG_DEBUG") == Ok("1".into());
239-
let plain = slog_term::PlainSyncDecorator::new(slog_term::TestStdoutWriter);
240-
let isatty = isatty(Stream::Stdout);
241-
let drain = TermFormat::new(plain, false, debug, isatty);
242-
let logger = Logger::root(drain.ignore_res(), o!());
243-
logger
244-
}
233+
fn get_decorator() -> slog_term::PlainSyncDecorator<slog_term::TestStdoutWriter> {
234+
slog_term::PlainSyncDecorator::new(slog_term::TestStdoutWriter)
235+
}
236+
237+
#[cfg(not(any(test, feature = "testing")))]
238+
fn get_decorator() -> slog_term::PlainSyncDecorator<std::io::Stderr> {
239+
slog_term::PlainSyncDecorator::new(std::io::stderr())
245240
}
246241

247242
fn inner_get_loglevel() -> slog::Level {

stacks-signer/CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,21 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1111

1212
### Changed
1313

14-
## [3.0.0.0.0]
14+
## [3.0.0.0.1.0]
15+
16+
### Changed
17+
18+
- Change block rejection message to generic block response
19+
20+
## [3.0.0.0.0.1]
21+
22+
### Added
23+
24+
### Changed
25+
- Update block proposal timeout default to 10 minutes (#5391)
26+
- Updated documentation link in output (#5363)
27+
28+
## [3.0.0.0.0.0]
1529

1630
### Added
1731

stacks-signer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ slog-json = { version = "2.3.0", optional = true }
3838
slog-term = "2.6.0"
3939
stacks-common = { path = "../stacks-common" }
4040
stackslib = { path = "../stackslib" }
41-
thiserror = "1.0"
41+
thiserror = { workspace = true }
4242
tiny_http = { version = "0.12", optional = true }
4343
toml = "0.5.6"
4444
tracing = "0.1.37"

stacks-signer/src/chainstate.rs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
1919
use blockstack_lib::chainstate::stacks::TenureChangePayload;
2020
use blockstack_lib::net::api::getsortition::SortitionInfo;
2121
use blockstack_lib::util_lib::db::Error as DBError;
22-
use clarity::types::chainstate::BurnchainHeaderHash;
2322
use slog::{slog_info, slog_warn};
24-
use stacks_common::types::chainstate::{ConsensusHash, StacksPublicKey};
23+
use stacks_common::types::chainstate::{BurnchainHeaderHash, ConsensusHash, StacksPublicKey};
24+
use stacks_common::util::get_epoch_time_secs;
2525
use stacks_common::util::hash::Hash160;
2626
use stacks_common::{info, warn};
2727

2828
use crate::client::{ClientError, CurrentAndLastSortition, StacksClient};
2929
use crate::config::SignerConfig;
30-
use crate::signerdb::{BlockState, SignerDb};
30+
use crate::signerdb::{BlockInfo, BlockState, SignerDb};
3131

3232
#[derive(thiserror::Error, Debug)]
3333
/// Error type for the signer chainstate module
@@ -119,13 +119,17 @@ pub struct ProposalEvalConfig {
119119
pub first_proposal_burn_block_timing: Duration,
120120
/// Time between processing a sortition and proposing a block before the block is considered invalid
121121
pub block_proposal_timeout: Duration,
122+
/// Time to wait for the last block of a tenure to be globally accepted or rejected before considering
123+
/// a new miner's block at the same height as valid.
124+
pub tenure_last_block_proposal_timeout: Duration,
122125
}
123126

124127
impl From<&SignerConfig> for ProposalEvalConfig {
125128
fn from(value: &SignerConfig) -> Self {
126129
Self {
127130
first_proposal_burn_block_timing: value.first_proposal_burn_block_timing,
128131
block_proposal_timeout: value.block_proposal_timeout,
132+
tenure_last_block_proposal_timeout: value.tenure_last_block_proposal_timeout,
129133
}
130134
}
131135
}
@@ -460,7 +464,36 @@ impl SortitionsView {
460464
Ok(true)
461465
}
462466

463-
/// Check if the tenure change block confirms the expected parent block (i.e., the last globally accepted block in the parent tenure)
467+
/// Get the last block from the given tenure
468+
/// Returns the last locally accepted block if it is not timed out, otherwise it will return the last globally accepted block.
469+
fn get_tenure_last_block_info(
470+
consensus_hash: &ConsensusHash,
471+
signer_db: &SignerDb,
472+
tenure_last_block_proposal_timeout: Duration,
473+
) -> Result<Option<BlockInfo>, ClientError> {
474+
// Get the last known block in the previous tenure
475+
let last_locally_accepted_block = signer_db
476+
.get_last_accepted_block(consensus_hash)
477+
.map_err(|e| ClientError::InvalidResponse(e.to_string()))?;
478+
479+
if let Some(local_info) = last_locally_accepted_block {
480+
if let Some(signed_over_time) = local_info.signed_self {
481+
if signed_over_time + tenure_last_block_proposal_timeout.as_secs()
482+
> get_epoch_time_secs()
483+
{
484+
// The last locally accepted block is not timed out, return it
485+
return Ok(Some(local_info));
486+
}
487+
}
488+
}
489+
// The last locally accepted block is timed out, get the last globally accepted block
490+
signer_db
491+
.get_last_globally_accepted_block(consensus_hash)
492+
.map_err(|e| ClientError::InvalidResponse(e.to_string()))
493+
}
494+
495+
/// Check if the tenure change block confirms the expected parent block
496+
/// (i.e., the last locally accepted block in the parent tenure, or if that block is timed out, the last globally accepted block in the parent tenure)
464497
/// It checks the local DB first, and if the block is not present in the local DB, it asks the
465498
/// Stacks node for the highest processed block header in the given tenure (and then caches it
466499
/// in the DB).
@@ -473,24 +506,27 @@ impl SortitionsView {
473506
reward_cycle: u64,
474507
signer_db: &mut SignerDb,
475508
client: &StacksClient,
509+
tenure_last_block_proposal_timeout: Duration,
476510
) -> Result<bool, ClientError> {
477-
// If the tenure change block confirms the expected parent block, it should confirm at least one more block than the last globally accepted block in the parent tenure.
478-
let last_globally_accepted_block = signer_db
479-
.get_last_globally_accepted_block(&tenure_change.prev_tenure_consensus_hash)
480-
.map_err(|e| ClientError::InvalidResponse(e.to_string()))?;
511+
// If the tenure change block confirms the expected parent block, it should confirm at least one more block than the last accepted block in the parent tenure.
512+
let last_block_info = Self::get_tenure_last_block_info(
513+
&tenure_change.prev_tenure_consensus_hash,
514+
signer_db,
515+
tenure_last_block_proposal_timeout,
516+
)?;
481517

482-
if let Some(global_info) = last_globally_accepted_block {
518+
if let Some(info) = last_block_info {
483519
// N.B. this block might not be the last globally accepted block across the network;
484520
// it's just the highest one in this tenure that we know about. If this given block is
485521
// no higher than it, then it's definitely no higher than the last globally accepted
486522
// block across the network, so we can do an early rejection here.
487-
if block.header.chain_length <= global_info.block.header.chain_length {
523+
if block.header.chain_length <= info.block.header.chain_length {
488524
warn!(
489525
"Miner's block proposal does not confirm as many blocks as we expect";
490526
"proposed_block_consensus_hash" => %block.header.consensus_hash,
491527
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
492528
"proposed_chain_length" => block.header.chain_length,
493-
"expected_at_least" => global_info.block.header.chain_length + 1,
529+
"expected_at_least" => info.block.header.chain_length + 1,
494530
);
495531
return Ok(false);
496532
}
@@ -558,6 +594,7 @@ impl SortitionsView {
558594
reward_cycle,
559595
signer_db,
560596
client,
597+
self.config.tenure_last_block_proposal_timeout,
561598
)?;
562599
if !confirms_expected_parent {
563600
return Ok(false);
@@ -573,15 +610,15 @@ impl SortitionsView {
573610
if !is_valid_parent_tenure {
574611
return Ok(false);
575612
}
576-
let last_in_tenure = signer_db
613+
let last_in_current_tenure = signer_db
577614
.get_last_globally_accepted_block(&block.header.consensus_hash)
578615
.map_err(|e| ClientError::InvalidResponse(e.to_string()))?;
579-
if let Some(last_in_tenure) = last_in_tenure {
616+
if let Some(last_in_current_tenure) = last_in_current_tenure {
580617
warn!(
581618
"Miner block proposal contains a tenure change, but we've already signed a block in this tenure. Considering proposal invalid.";
582619
"proposed_block_consensus_hash" => %block.header.consensus_hash,
583620
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
584-
"last_in_tenure_signer_sighash" => %last_in_tenure.block.header.signer_signature_hash(),
621+
"last_in_tenure_signer_sighash" => %last_in_current_tenure.block.header.signer_signature_hash(),
585622
);
586623
return Ok(false);
587624
}

0 commit comments

Comments
 (0)