Skip to content

Commit 90cc224

Browse files
authored
Merge branch 'develop' into logs/info-neighbor-add-and-remove
2 parents b14dcd5 + 819440b commit 90cc224

Some content is hidden

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

59 files changed

+2189
-662
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ jobs:
140140
- tests::nakamoto_integrations::utxo_check_on_startup_recover
141141
- tests::nakamoto_integrations::v3_signer_api_endpoint
142142
- tests::nakamoto_integrations::signer_chainstate
143+
- tests::nakamoto_integrations::clarity_cost_spend_down
144+
- tests::nakamoto_integrations::v3_blockbyheight_api_endpoint
143145
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
144146
# - tests::signer::v1::dkg
145147
# - 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1212
- Remove the panic for reporting DB deadlocks (just error and continue waiting)
1313
- Add index to `metadata_table` in Clarity DB on `blockhash`
1414
- 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
1517

1618
## [3.0.0.0.1]
1719

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 {

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/signerdb.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,22 @@ impl BlockInfo {
258258
self.state = state;
259259
Ok(())
260260
}
261+
262+
/// Check if the block is globally accepted or rejected
263+
pub fn has_reached_consensus(&self) -> bool {
264+
matches!(
265+
self.state,
266+
BlockState::GloballyAccepted | BlockState::GloballyRejected
267+
)
268+
}
269+
270+
/// Check if the block is locally accepted or rejected
271+
pub fn is_locally_finalized(&self) -> bool {
272+
matches!(
273+
self.state,
274+
BlockState::LocallyAccepted | BlockState::LocallyRejected
275+
)
276+
}
261277
}
262278

263279
/// This struct manages a SQLite database connection

stacks-signer/src/v0/signer.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader};
2121
use blockstack_lib::net::api::postblock_proposal::{
2222
BlockValidateOk, BlockValidateReject, BlockValidateResponse,
2323
};
24+
use blockstack_lib::util_lib::db::Error as DBError;
2425
use clarity::types::chainstate::StacksPrivateKey;
2526
use clarity::types::{PrivateKey, StacksEpochId};
2627
use clarity::util::hash::MerkleHashFunc;
@@ -496,7 +497,7 @@ impl Signer {
496497
// Do not store KNOWN invalid blocks as this could DOS the signer. We only store blocks that are valid or unknown.
497498
self.signer_db
498499
.insert_block(&block_info)
499-
.unwrap_or_else(|_| panic!("{self}: Failed to insert block in DB"));
500+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
500501
}
501502
}
502503

@@ -539,9 +540,7 @@ impl Signer {
539540
.block_lookup(self.reward_cycle, &signer_signature_hash)
540541
{
541542
Ok(Some(block_info)) => {
542-
if block_info.state == BlockState::GloballyRejected
543-
|| block_info.state == BlockState::GloballyAccepted
544-
{
543+
if block_info.is_locally_finalized() {
545544
debug!("{self}: Received block validation for a block that is already marked as {}. Ignoring...", block_info.state);
546545
return None;
547546
}
@@ -558,8 +557,11 @@ impl Signer {
558557
}
559558
};
560559
if let Err(e) = block_info.mark_locally_accepted(false) {
561-
warn!("{self}: Failed to mark block as locally accepted: {e:?}",);
562-
return None;
560+
if !block_info.has_reached_consensus() {
561+
warn!("{self}: Failed to mark block as locally accepted: {e:?}",);
562+
return None;
563+
}
564+
block_info.signed_self.get_or_insert(get_epoch_time_secs());
563565
}
564566
let signature = self
565567
.private_key
@@ -568,7 +570,7 @@ impl Signer {
568570

569571
self.signer_db
570572
.insert_block(&block_info)
571-
.unwrap_or_else(|_| panic!("{self}: Failed to insert block in DB"));
573+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
572574
let accepted = BlockAccepted::new(block_info.signer_signature_hash(), signature);
573575
// have to save the signature _after_ the block info
574576
self.handle_block_signature(stacks_client, &accepted);
@@ -597,9 +599,7 @@ impl Signer {
597599
.block_lookup(self.reward_cycle, &signer_signature_hash)
598600
{
599601
Ok(Some(block_info)) => {
600-
if block_info.state == BlockState::GloballyRejected
601-
|| block_info.state == BlockState::GloballyAccepted
602-
{
602+
if block_info.is_locally_finalized() {
603603
debug!("{self}: Received block validation for a block that is already marked as {}. Ignoring...", block_info.state);
604604
return None;
605605
}
@@ -616,8 +616,10 @@ impl Signer {
616616
}
617617
};
618618
if let Err(e) = block_info.mark_locally_rejected() {
619-
warn!("{self}: Failed to mark block as locally rejected: {e:?}",);
620-
return None;
619+
if !block_info.has_reached_consensus() {
620+
warn!("{self}: Failed to mark block as locally rejected: {e:?}",);
621+
return None;
622+
}
621623
}
622624
let block_rejection = BlockRejection::from_validate_rejection(
623625
block_validate_reject.clone(),
@@ -626,7 +628,7 @@ impl Signer {
626628
);
627629
self.signer_db
628630
.insert_block(&block_info)
629-
.unwrap_or_else(|_| panic!("{self}: Failed to insert block in DB"));
631+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
630632
self.handle_block_rejection(&block_rejection);
631633
Some(BlockResponse::Rejected(block_rejection))
632634
}
@@ -739,7 +741,7 @@ impl Signer {
739741
}
740742
self.signer_db
741743
.insert_block(&block_info)
742-
.unwrap_or_else(|_| panic!("{self}: Failed to insert block in DB"));
744+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
743745
}
744746

745747
/// Compute the signing weight, given a list of signatures
@@ -1095,7 +1097,7 @@ impl Signer {
10951097
// in case this is the first time we saw this block. Safe to do since this is testing case only.
10961098
self.signer_db
10971099
.insert_block(block_info)
1098-
.unwrap_or_else(|_| panic!("{self}: Failed to insert block in DB"));
1100+
.unwrap_or_else(|e| self.handle_insert_block_error(e));
10991101
Some(BlockResponse::rejected(
11001102
block_proposal.block.header.signer_signature_hash(),
11011103
RejectCode::TestingDirective,
@@ -1119,4 +1121,10 @@ impl Signer {
11191121
warn!("{self}: Failed to send mock signature to stacker-db: {e:?}",);
11201122
}
11211123
}
1124+
1125+
/// Helper for logging insert_block error
1126+
fn handle_insert_block_error(&self, e: DBError) {
1127+
error!("{self}: Failed to insert block into signer-db: {e:?}");
1128+
panic!("{self} Failed to write block to signerdb: {e}");
1129+
}
11221130
}

stackslib/src/burnchains/bitcoin/indexer.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use crate::burnchains::{
4545
Burnchain, BurnchainBlockHeader, Error as burnchain_error, MagicBytes, BLOCKSTACK_MAGIC_MAINNET,
4646
};
4747
use crate::core::{
48-
StacksEpoch, StacksEpochExtension, STACKS_EPOCHS_MAINNET, STACKS_EPOCHS_REGTEST,
48+
EpochList, StacksEpoch, StacksEpochExtension, STACKS_EPOCHS_MAINNET, STACKS_EPOCHS_REGTEST,
4949
STACKS_EPOCHS_TESTNET,
5050
};
5151
use crate::util_lib::db::Error as DBError;
@@ -91,11 +91,11 @@ impl TryFrom<u32> for BitcoinNetworkType {
9191
/// Get the default epochs definitions for the given BitcoinNetworkType.
9292
/// Should *not* be used except by the BitcoinIndexer when no epochs vector
9393
/// was specified.
94-
pub fn get_bitcoin_stacks_epochs(network_id: BitcoinNetworkType) -> Vec<StacksEpoch> {
94+
pub fn get_bitcoin_stacks_epochs(network_id: BitcoinNetworkType) -> EpochList {
9595
match network_id {
96-
BitcoinNetworkType::Mainnet => STACKS_EPOCHS_MAINNET.to_vec(),
97-
BitcoinNetworkType::Testnet => STACKS_EPOCHS_TESTNET.to_vec(),
98-
BitcoinNetworkType::Regtest => STACKS_EPOCHS_REGTEST.to_vec(),
96+
BitcoinNetworkType::Mainnet => (*STACKS_EPOCHS_MAINNET).clone(),
97+
BitcoinNetworkType::Testnet => (*STACKS_EPOCHS_TESTNET).clone(),
98+
BitcoinNetworkType::Regtest => (*STACKS_EPOCHS_REGTEST).clone(),
9999
}
100100
}
101101

@@ -112,7 +112,7 @@ pub struct BitcoinIndexerConfig {
112112
pub spv_headers_path: String,
113113
pub first_block: u64,
114114
pub magic_bytes: MagicBytes,
115-
pub epochs: Option<Vec<StacksEpoch>>,
115+
pub epochs: Option<EpochList>,
116116
}
117117

118118
#[derive(Debug)]
@@ -1041,7 +1041,7 @@ impl BurnchainIndexer for BitcoinIndexer {
10411041
/// 2) Use hard-coded static values, otherwise.
10421042
///
10431043
/// It is an error (panic) to set custom epochs if running on `Mainnet`.
1044-
fn get_stacks_epochs(&self) -> Vec<StacksEpoch> {
1044+
fn get_stacks_epochs(&self) -> EpochList {
10451045
StacksEpoch::get_epochs(self.runtime.network_id, self.config.epochs.as_ref())
10461046
}
10471047

0 commit comments

Comments
 (0)