Skip to content

Commit 063284e

Browse files
committed
Merge branch 'develop' into test/replay-block-naka
2 parents eac2e5e + 20d5137 commit 063284e

File tree

102 files changed

+9102
-6727
lines changed

Some content is hidden

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

102 files changed

+9102
-6727
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 7 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
@@ -120,6 +123,9 @@ jobs:
120123
- tests::signer::v0::signing_in_0th_tenure_of_reward_cycle
121124
- tests::signer::v0::continue_after_tenure_extend
122125
- tests::signer::v0::multiple_miners_with_custom_chain_id
126+
- tests::signer::v0::block_commit_delay
127+
- tests::signer::v0::continue_after_fast_block_no_sortition
128+
- tests::signer::v0::block_validation_response_timeout
123129
- tests::nakamoto_integrations::burn_ops_integration_test
124130
- tests::nakamoto_integrations::check_block_heights
125131
- tests::nakamoto_integrations::clarity_burn_state
@@ -133,6 +139,7 @@ jobs:
133139
- tests::nakamoto_integrations::utxo_check_on_startup_panic
134140
- tests::nakamoto_integrations::utxo_check_on_startup_recover
135141
- tests::nakamoto_integrations::v3_signer_api_endpoint
142+
- tests::nakamoto_integrations::signer_chainstate
136143
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
137144
# - tests::signer::v1::dkg
138145
# - tests::signer::v1::sign_request_rejected

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ 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+
- Add index to `metadata_table` in Clarity DB on `blockhash`
14+
- Add `block_commit_delay_ms` to the config file to control the time to wait after seeing a new burn block, before submitting a block commit, to allow time for the first Nakamoto block of the new tenure to be mined, allowing this miner to avoid the need to RBF the block commit.
15+
16+
## [3.0.0.0.1]
17+
18+
### Changed
19+
- Add index for StacksBlockId to nakamoto block headers table (improves node performance)
20+
- Remove the panic for reporting DB deadlocks (just error and continue waiting)
21+
- Various test fixes for CI (5353, 5368, 5372, 5371, 5380, 5378, 5387, 5396, 5390, 5394)
22+
- Various log fixes:
23+
- don't say proceeding to mine blocks if not a miner
24+
- misc. warns downgraded to debugs
25+
- 5391: Update default block proposal timeout to 10 minutes
26+
- 5406: After block rejection, miner pauses
27+
- Docs fixes
28+
- Fix signer docs link
29+
- Specify burn block in clarity docs
30+
1031
## [3.0.0.0.0]
1132

1233
### 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/database/sqlite.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ impl SqliteConnection {
248248
)
249249
.map_err(|x| InterpreterError::SqliteError(IncomparableError { err: x }))?;
250250

251+
conn.execute(
252+
"CREATE INDEX IF NOT EXISTS md_blockhashes ON metadata_table(blockhash)",
253+
NO_PARAMS,
254+
)
255+
.map_err(|x| InterpreterError::SqliteError(IncomparableError { err: x }))?;
256+
251257
Self::check_schema(conn)?;
252258

253259
Ok(())

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

docs/mining.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,26 @@ nakamoto_attempt_time_ms = 20000
1919
[burnchain]
2020
# Maximum amount (in sats) of "burn commitment" to broadcast for the next block's leader election
2121
burn_fee_cap = 20000
22-
# Amount (in sats) per byte - Used to calculate the transaction fees
23-
satoshis_per_byte = 25
24-
# Amount of sats to add when RBF'ing bitcoin tx (default: 5)
22+
# Amount in sats per byte used to calculate the Bitcoin transaction fee (default: 50)
23+
satoshis_per_byte = 50
24+
# Amount of sats per byte to add when RBF'ing a Bitcoin tx (default: 5)
2525
rbf_fee_increment = 5
26-
# Maximum percentage to RBF bitcoin tx (default: 150% of satsv/B)
26+
# Maximum percentage of satoshis_per_byte to allow in RBF fee (default: 150)
2727
max_rbf = 150
2828
```
2929

30+
NOTE: Ensuring that your miner can successfully use RBF (Replace-by-Fee) is
31+
critical for reliable block production. If a miner fails to replace an outdated
32+
block commit with a higher-fee transaction, it risks committing to an incorrect
33+
tenure. This would prevent the miner from producing valid blocks during its
34+
tenure, as it would be building on an invalid chain tip, causing the signers to
35+
reject its blocks.
36+
37+
To avoid this, configure satoshis_per_byte, rbf_fee_increment, and max_rbf to
38+
allow for at least three fee increments within the max_rbf limit. This helps
39+
ensure that your miner can adjust its fees sufficiently to stay on the canonical
40+
chain.
41+
3042
You can verify that your node is operating as a miner by checking its log output
3143
to verify that it was able to find its Bitcoin UTXOs:
3244

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/types/mod.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cmp::Ordering;
22
use std::fmt;
3+
use std::ops::{Deref, DerefMut, Index, IndexMut};
34

45
#[cfg(feature = "canonical")]
56
pub mod sqlite;
@@ -460,3 +461,83 @@ impl<L: PartialEq + Eq> Ord for StacksEpoch<L> {
460461
self.epoch_id.cmp(&other.epoch_id)
461462
}
462463
}
464+
465+
/// A wrapper for holding a list of Epochs, indexable by StacksEpochId
466+
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
467+
pub struct EpochList<L: Clone>(Vec<StacksEpoch<L>>);
468+
469+
impl<L: Clone> EpochList<L> {
470+
pub fn new(epochs: &[StacksEpoch<L>]) -> EpochList<L> {
471+
EpochList(epochs.to_vec())
472+
}
473+
474+
pub fn get(&self, index: StacksEpochId) -> Option<&StacksEpoch<L>> {
475+
self.0.get(StacksEpoch::find_epoch_by_id(&self.0, index)?)
476+
}
477+
478+
pub fn get_mut(&mut self, index: StacksEpochId) -> Option<&mut StacksEpoch<L>> {
479+
let index = StacksEpoch::find_epoch_by_id(&self.0, index)?;
480+
self.0.get_mut(index)
481+
}
482+
483+
/// Truncates the list after the given epoch id
484+
pub fn truncate_after(&mut self, epoch_id: StacksEpochId) {
485+
if let Some(index) = StacksEpoch::find_epoch_by_id(&self.0, epoch_id) {
486+
self.0.truncate(index + 1);
487+
}
488+
}
489+
490+
/// Determine which epoch, if any, a given burnchain height falls into.
491+
pub fn epoch_id_at_height(&self, height: u64) -> Option<StacksEpochId> {
492+
StacksEpoch::find_epoch(self, height).map(|idx| self.0[idx].epoch_id)
493+
}
494+
495+
/// Determine which epoch, if any, a given burnchain height falls into.
496+
pub fn epoch_at_height(&self, height: u64) -> Option<StacksEpoch<L>> {
497+
StacksEpoch::find_epoch(self, height).map(|idx| self.0[idx].clone())
498+
}
499+
500+
/// Pushes a new `StacksEpoch` to the end of the list
501+
pub fn push(&mut self, epoch: StacksEpoch<L>) {
502+
if let Some(last) = self.0.last() {
503+
assert!(
504+
epoch.start_height == last.end_height && epoch.epoch_id > last.epoch_id,
505+
"Epochs must be pushed in order"
506+
);
507+
}
508+
self.0.push(epoch);
509+
}
510+
511+
pub fn to_vec(&self) -> Vec<StacksEpoch<L>> {
512+
self.0.clone()
513+
}
514+
}
515+
516+
impl<L: Clone> Index<StacksEpochId> for EpochList<L> {
517+
type Output = StacksEpoch<L>;
518+
fn index(&self, index: StacksEpochId) -> &StacksEpoch<L> {
519+
self.get(index)
520+
.expect("Invalid StacksEpochId: could not find corresponding epoch")
521+
}
522+
}
523+
524+
impl<L: Clone> IndexMut<StacksEpochId> for EpochList<L> {
525+
fn index_mut(&mut self, index: StacksEpochId) -> &mut StacksEpoch<L> {
526+
self.get_mut(index)
527+
.expect("Invalid StacksEpochId: could not find corresponding epoch")
528+
}
529+
}
530+
531+
impl<L: Clone> Deref for EpochList<L> {
532+
type Target = [StacksEpoch<L>];
533+
534+
fn deref(&self) -> &Self::Target {
535+
&self.0
536+
}
537+
}
538+
539+
impl<L: Clone> DerefMut for EpochList<L> {
540+
fn deref_mut(&mut self) -> &mut Self::Target {
541+
&mut self.0
542+
}
543+
}

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 {

0 commit comments

Comments
 (0)