Skip to content

Commit ce9fb34

Browse files
authored
Merge branch 'develop' into feat/replacement_tx_id
2 parents 92627cf + 9c5d822 commit ce9fb34

Some content is hidden

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

84 files changed

+5645
-4301
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
@@ -133,6 +136,7 @@ jobs:
133136
- tests::nakamoto_integrations::utxo_check_on_startup_panic
134137
- tests::nakamoto_integrations::utxo_check_on_startup_recover
135138
- tests::nakamoto_integrations::v3_signer_api_endpoint
139+
- tests::nakamoto_integrations::signer_chainstate
136140
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
137141
# - tests::signer::v1::dkg
138142
# - 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

clarity/src/vm/docs/mod.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,17 +1775,17 @@ this value is less than or equal to the value for `miner-spend-total` at the sam
17751775
const GET_BURN_BLOCK_INFO_API: SpecialAPI = SpecialAPI {
17761776
input_type: "BurnBlockInfoPropertyName, uint",
17771777
output_type: "(optional buff) | (optional (tuple (addrs (list 2 (tuple (hashbytes (buff 32)) (version (buff 1))))) (payout uint)))",
1778-
snippet: "get-burn-block-info? ${1:prop} ${2:block-height}",
1779-
signature: "(get-burn-block-info? prop-name block-height)",
1778+
snippet: "get-burn-block-info? ${1:prop} ${2:burn-block-height}",
1779+
signature: "(get-burn-block-info? prop-name burn-block-height)",
17801780
description: "The `get-burn-block-info?` function fetches data for a block of the given *burnchain* block height. The
1781-
value and type returned are determined by the specified `BlockInfoPropertyName`. Valid values for `block-height` only
1781+
value and type returned are determined by the specified `BlockInfoPropertyName`. Valid values for `burn-block-height` only
17821782
include heights between the burnchain height at the time the Stacks chain was launched, and the last-processed burnchain
1783-
block. If the `block-height` argument falls outside of this range, then `none` shall be returned.
1783+
block. If the `burn-block-height` argument falls outside of this range, then `none` shall be returned.
17841784
17851785
The following `BlockInfoPropertyName` values are defined:
17861786
17871787
* The `header-hash` property returns a 32-byte buffer representing the header hash of the burnchain block at
1788-
burnchain height `block-height`.
1788+
burnchain height `burn-block-height`.
17891789
17901790
* The `pox-addrs` property returns a tuple with two items: a list of up to two PoX addresses that received a PoX payout at that block height, and the amount of burnchain
17911791
tokens paid to each address (note that per the blockchain consensus rules, each PoX payout will be the same for each address in the block-commit transaction).
@@ -1811,11 +1811,11 @@ The `addrs` list contains the same PoX address values passed into the PoX smart
18111811

18121812
const GET_STACKS_BLOCK_INFO_API: SpecialAPI = SpecialAPI {
18131813
input_type: "StacksBlockInfoPropertyName, uint",
1814-
snippet: "get-stacks-block-info? ${1:prop} ${2:block-height}",
1814+
snippet: "get-stacks-block-info? ${1:prop} ${2:stacks-block-height}",
18151815
output_type: "(optional buff) | (optional uint)",
1816-
signature: "(get-stacks-block-info? prop-name block-height)",
1816+
signature: "(get-stacks-block-info? prop-name stacks-block-height)",
18171817
description: "The `get-stacks-block-info?` function fetches data for a block of the given *Stacks* block height. The
1818-
value and type returned are determined by the specified `StacksBlockInfoPropertyName`. If the provided `block-height` does
1818+
value and type returned are determined by the specified `StacksBlockInfoPropertyName`. If the provided `stacks-block-height` does
18191819
not correspond to an existing block prior to the current block, the function returns `none`. The currently available property names
18201820
are as follows:
18211821
@@ -1840,11 +1840,11 @@ the mining of this block started, but is not guaranteed to be accurate. This tim
18401840

18411841
const GET_TENURE_INFO_API: SpecialAPI = SpecialAPI {
18421842
input_type: "TenureInfoPropertyName, uint",
1843-
snippet: "get-tenure-info? ${1:prop} ${2:block-height}",
1843+
snippet: "get-tenure-info? ${1:prop} ${2:stacks-block-height}",
18441844
output_type: "(optional buff) | (optional uint)",
1845-
signature: "(get-tenure-info? prop-name block-height)",
1845+
signature: "(get-tenure-info? prop-name stacks-block-height)",
18461846
description: "The `get-tenure-info?` function fetches data for the tenure at the given block height. The
1847-
value and type returned are determined by the specified `TenureInfoPropertyName`. If the provided `block-height` does
1847+
value and type returned are determined by the specified `TenureInfoPropertyName`. If the provided `stacks-block-height` does
18481848
not correspond to an existing block prior to the current block, the function returns `none`. The currently available property names
18491849
are as follows:
18501850

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)