Skip to content

Commit 0af0023

Browse files
authored
Merge branch 'develop' into feat/replacement_tx_id
2 parents 82c8701 + 819440b commit 0af0023

Some content is hidden

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

87 files changed

+4427
-2021
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ jobs:
123123
- tests::signer::v0::signing_in_0th_tenure_of_reward_cycle
124124
- tests::signer::v0::continue_after_tenure_extend
125125
- 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
126129
- tests::nakamoto_integrations::burn_ops_integration_test
127130
- tests::nakamoto_integrations::check_block_heights
128131
- tests::nakamoto_integrations::clarity_burn_state
@@ -137,6 +140,8 @@ jobs:
137140
- tests::nakamoto_integrations::utxo_check_on_startup_recover
138141
- tests::nakamoto_integrations::v3_signer_api_endpoint
139142
- tests::nakamoto_integrations::signer_chainstate
143+
- tests::nakamoto_integrations::clarity_cost_spend_down
144+
- tests::nakamoto_integrations::v3_blockbyheight_api_endpoint
140145
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
141146
# - tests::signer::v1::dkg
142147
# - tests::signer::v1::sign_request_rejected

.github/workflows/p2p-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
- net::tests::convergence::test_walk_star_15_pingback
4343
- net::tests::convergence::test_walk_star_15_org_biased
4444
- net::tests::convergence::test_walk_inbound_line_15
45+
- net::api::tests::postblock_proposal::test_try_make_response
4546
steps:
4647
## Setup test environment
4748
- name: Setup Test Environment

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1010
### Changed
1111
- Add index for StacksBlockId to nakamoto block headers table (improves node performance)
1212
- 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+
- Add `tenure_cost_limit_per_block_percentage` to the miner config file to control the percentage remaining tenure cost limit to consume per nakamoto block.
16+
- Add `/v3/blocks/height/:block_height` rpc endpoint
1317

1418
## [3.0.0.0.1]
1519

1620
### Changed
1721
- Add index for StacksBlockId to nakamoto block headers table (improves node performance)
1822
- Remove the panic for reporting DB deadlocks (just error and continue waiting)
1923
- Various test fixes for CI (5353, 5368, 5372, 5371, 5380, 5378, 5387, 5396, 5390, 5394)
20-
- Various log fixes:
24+
- Various log fixes:
2125
- don't say proceeding to mine blocks if not a miner
2226
- misc. warns downgraded to debugs
2327
- 5391: Update default block proposal timeout to 10 minutes

clarity/src/vm/costs/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ impl LimitedCostTracker {
896896
Self::Free => ExecutionCost::max_value(),
897897
}
898898
}
899+
899900
pub fn get_memory(&self) -> u64 {
900901
match self {
901902
Self::Limited(TrackerData { memory, .. }) => *memory,
@@ -1170,6 +1171,7 @@ pub trait CostOverflowingMath<T> {
11701171
fn cost_overflow_mul(self, other: T) -> Result<T>;
11711172
fn cost_overflow_add(self, other: T) -> Result<T>;
11721173
fn cost_overflow_sub(self, other: T) -> Result<T>;
1174+
fn cost_overflow_div(self, other: T) -> Result<T>;
11731175
}
11741176

11751177
impl CostOverflowingMath<u64> for u64 {
@@ -1185,6 +1187,10 @@ impl CostOverflowingMath<u64> for u64 {
11851187
self.checked_sub(other)
11861188
.ok_or_else(|| CostErrors::CostOverflow)
11871189
}
1190+
fn cost_overflow_div(self, other: u64) -> Result<u64> {
1191+
self.checked_div(other)
1192+
.ok_or_else(|| CostErrors::CostOverflow)
1193+
}
11881194
}
11891195

11901196
impl ExecutionCost {
@@ -1293,6 +1299,15 @@ impl ExecutionCost {
12931299
Ok(())
12941300
}
12951301

1302+
pub fn divide(&mut self, divisor: u64) -> Result<()> {
1303+
self.runtime = self.runtime.cost_overflow_div(divisor)?;
1304+
self.read_count = self.read_count.cost_overflow_div(divisor)?;
1305+
self.read_length = self.read_length.cost_overflow_div(divisor)?;
1306+
self.write_length = self.write_length.cost_overflow_div(divisor)?;
1307+
self.write_count = self.write_count.cost_overflow_div(divisor)?;
1308+
Ok(())
1309+
}
1310+
12961311
/// Returns whether or not this cost exceeds any dimension of the
12971312
/// other cost.
12981313
pub fn exceeds(&self, other: &ExecutionCost) -> bool {

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(())

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

docs/rpc-endpoints.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,17 @@ data.
503503

504504
This will return 404 if the block does not exist.
505505

506+
### GET /v3/blocks/height/[Block Height]
507+
508+
Fetch a Nakamoto block given its block height. This returns the raw block
509+
data.
510+
511+
This will return 404 if the block does not exist.
512+
513+
This endpoint also accepts a querystring parameter `?tip=` which when supplied
514+
will return the block relative to the specified tip allowing the querying of
515+
sibling blocks (same height, different tip) too.
516+
506517
### GET /v3/tenures/[Block ID]
507518

508519
Fetch a Nakamoto block and all of its ancestors in the same tenure, given its

docs/rpc/openapi.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,40 @@ paths:
627627
content:
628628
application/text-plain: {}
629629

630+
/v3/blocks/height/{block_height}:
631+
get:
632+
summary: Fetch a Nakamoto block by its height and optional tip
633+
tags:
634+
- Blocks
635+
operationId: get_block_v3_by_height
636+
description:
637+
Fetch a Nakamoto block by its height and optional tip.
638+
parameters:
639+
- name: block_height
640+
in: path
641+
description: The block's height
642+
required: true
643+
schema:
644+
type: integer
645+
- name: tip
646+
in: query
647+
schema:
648+
type: string
649+
description: The Stacks chain tip to query from. If tip == latest or empty, the query will be run
650+
from the latest known tip.
651+
responses:
652+
"200":
653+
description: The raw SIP-003-encoded block will be returned.
654+
content:
655+
application/octet-stream:
656+
schema:
657+
type: string
658+
format: binary
659+
"404":
660+
description: The block could not be found
661+
content:
662+
application/text-plain: {}
663+
630664
/v3/tenures/info:
631665
get:
632666
summary: Fetch metadata about the ongoing Nakamoto tenure

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-signer/src/client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ pub(crate) mod tests {
412412
first_proposal_burn_block_timing: config.first_proposal_burn_block_timing,
413413
block_proposal_timeout: config.block_proposal_timeout,
414414
tenure_last_block_proposal_timeout: config.tenure_last_block_proposal_timeout,
415+
block_proposal_validation_timeout: config.block_proposal_validation_timeout,
415416
}
416417
}
417418

0 commit comments

Comments
 (0)