Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dd2090d
added total_tenure_size tracking and limiting
rdeioris Sep 12, 2025
669a9cc
prepare for better test
rdeioris Sep 12, 2025
0438a27
Merge branch 'develop' into feat/max_tenure_bytes
rdeioris Sep 15, 2025
29c484e
improve integration test
rdeioris Sep 15, 2025
fbc31c1
added integration tests
rdeioris Sep 16, 2025
209b9c1
fixed typo, enforce max_tenure_bytes to 0 for shadow blocks
rdeioris Sep 18, 2025
d292f8a
added CHAINSTATE_VERSION_NUMBER
rdeioris Sep 18, 2025
1c25cb8
refactored total_tenure_size query
rdeioris Sep 24, 2025
9b841d9
added tenure extend if total tenure size is > 50% [prototype]
rdeioris Sep 24, 2025
01affd3
merged with develop
rdeioris Sep 24, 2025
fa75b94
fixed tests
rdeioris Sep 24, 2025
fd3193e
Merge branch 'develop' into feat/max_tenure_bytes
rdeioris Sep 25, 2025
7461739
merged with develop
rdeioris Sep 25, 2025
bffb2d8
report max_tenure_bytes
rdeioris Sep 25, 2025
c569ba7
take into account tenure size limit when tenure extending
rdeioris Sep 26, 2025
2e5576d
check if signers still fail
rdeioris Sep 26, 2025
b2c29fe
fix cargo check
rdeioris Sep 28, 2025
2d9ca07
another check for CI
rdeioris Sep 28, 2025
5aceb9e
Merge branch 'develop' into feat/max_tenure_bytes
rdeioris Sep 29, 2025
2390efc
fixed postblock_proposal
rdeioris Sep 29, 2025
55b7c5f
re-added tenure extend management
rdeioris Sep 29, 2025
d00bd11
fixed tenure extend support
rdeioris Sep 29, 2025
f0759f5
added config option for log_skipped_transactions
rdeioris Sep 30, 2025
84cb785
Merge branch 'develop' into feat/max_tenure_bytes
rdeioris Oct 2, 2025
5b12dce
Merge branch 'develop' into feat/max_tenure_bytes
rdeioris Oct 7, 2025
d6a0e87
better error management for get_block_header_nakamoto_total_tenure_size
rdeioris Oct 7, 2025
6c1e699
fixed typo
rdeioris Oct 7, 2025
23f3477
added sql comment about total_tenure_size
rdeioris Oct 7, 2025
432252a
added tenure extend check
rdeioris Oct 7, 2025
686d36d
ensure no tenure extend in tests
rdeioris Oct 7, 2025
ef4a9dc
added comment about total_tenure_size not being consensus critical
rdeioris Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
393 changes: 393 additions & 0 deletions stacks-node/src/tests/nakamoto_integrations.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions stacks-node/src/tests/signer/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2841,6 +2841,7 @@ fn forked_tenure_testing(
burn_header_timestamp: tip_sn.burn_header_timestamp,
anchored_block_size: tip_b_block.serialize_to_vec().len() as u64,
burn_view: Some(tip_b_block.header.consensus_hash),
total_tenure_size: 0,
};

let blocks = test_observer::get_mined_nakamoto_blocks();
Expand Down Expand Up @@ -17586,6 +17587,7 @@ fn reorging_signers_capitulate_to_nonreorging_signers_during_tenure_fork() {
burn_header_timestamp: tip_sn.burn_header_timestamp,
anchored_block_size: tenure_b_block.serialize_to_vec().len() as u64,
burn_view: Some(tenure_b_block.header.consensus_hash),
total_tenure_size: 0,
};

// Block B was built atop block A
Expand Down
22 changes: 21 additions & 1 deletion stackslib/src/chainstate/nakamoto/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::chainstate::stacks::miner::{
};
use crate::chainstate::stacks::{Error, StacksBlockHeader, *};
use crate::clarity_vm::clarity::ClarityInstance;
use crate::config::DEFAULT_CONTRACT_COST_LIMIT_PERCENTAGE;
use crate::config::{DEFAULT_CONTRACT_COST_LIMIT_PERCENTAGE, DEFAULT_MAX_TENURE_BYTES};
use crate::core::mempool::*;
use crate::core::*;
use crate::monitoring::{
Expand Down Expand Up @@ -94,6 +94,8 @@ pub struct NakamotoBlockBuilder {
/// Percentage of a block's budget that may be consumed by
/// contract calls before reverting to stx transfers/boot contract calls only
contract_limit_percentage: Option<u8>,
/// Maximum size of the whole tenure
pub max_tenure_bytes: u64,
}

pub struct MinerTenureInfo<'a> {
Expand Down Expand Up @@ -146,6 +148,7 @@ impl NakamotoBlockBuilder {
header: NakamotoBlockHeader::genesis(),
soft_limit: None,
contract_limit_percentage: None,
max_tenure_bytes: u64::from(DEFAULT_MAX_TENURE_BYTES),
}
}

Expand Down Expand Up @@ -176,6 +179,7 @@ impl NakamotoBlockBuilder {
bitvec_len: u16,
soft_limit: Option<ExecutionCost>,
contract_limit_percentage: Option<u8>,
max_tenure_bytes: u64,
) -> Result<NakamotoBlockBuilder, Error> {
let next_height = parent_stacks_header
.anchored_header
Expand Down Expand Up @@ -217,6 +221,7 @@ impl NakamotoBlockBuilder {
),
soft_limit,
contract_limit_percentage,
max_tenure_bytes,
})
}

Expand Down Expand Up @@ -542,6 +547,7 @@ impl NakamotoBlockBuilder {
signer_bitvec_len,
None,
settings.mempool_settings.contract_cost_limit_percentage,
settings.max_tenure_bytes,
)?;

let ts_start = get_epoch_time_ms();
Expand Down Expand Up @@ -679,6 +685,20 @@ impl BlockBuilder for NakamotoBlockBuilder {
return TransactionResult::skipped_due_to_error(tx, Error::BlockTooBigError);
}

if let Some(parent_header) = &self.parent_header {
let mut total_tenure_size = self.bytes_so_far + tx_len;

// if we are in the same tenure of the parent, accumulate the parent total_tenure_size
// note that total_tenure_size is reset whenever a new tenure extend happens
if parent_header.consensus_hash == self.header.consensus_hash {
total_tenure_size += parent_header.total_tenure_size;
}

if total_tenure_size >= self.max_tenure_bytes {
return TransactionResult::skipped_due_to_error(tx, Error::TenureTooBigError);
}
}

let non_boot_code_contract_call = match &tx.payload {
TransactionPayload::ContractCall(cc) => !cc.address.is_boot_code_addr(),
TransactionPayload::SmartContract(..) => true,
Expand Down
41 changes: 39 additions & 2 deletions stackslib/src/chainstate/nakamoto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ pub static NAKAMOTO_CHAINSTATE_SCHEMA_6: &[&str] = &[
"CREATE INDEX IF NOT EXISTS nakamoto_block_headers_by_ch_bv ON nakamoto_block_headers(consensus_hash, burn_view);"
];

pub static NAKAMOTO_CHAINSTATE_SCHEMA_7: &[&str] = &[
r#"
UPDATE db_config SET version = "12";
"#,
// Add a `total_tenure_size` field to the block header row, so we can keep track
// of the whole tenure size (and eventually limit it)
//
//
//
// Default to 0.
r#"
ALTER TABLE nakamoto_block_headers
ADD COLUMN total_tenure_size NOT NULL DEFAULT 0;
"#,
];

#[cfg(test)]
mod fault_injection {
static PROCESS_BLOCK_STALL: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
Expand Down Expand Up @@ -3196,6 +3212,7 @@ impl NakamotoChainState {
stacks_block_height,
burn_header_height,
burn_header_timestamp,
total_tenure_size,
..
} = tip_info;

Expand Down Expand Up @@ -3252,6 +3269,7 @@ impl NakamotoChainState {
"Nakamoto block StacksHeaderInfo did not set burnchain view".into(),
))
})?,
total_tenure_size
];

chainstate_tx.execute(
Expand Down Expand Up @@ -3284,8 +3302,9 @@ impl NakamotoChainState {
vrf_proof,
signer_bitvec,
height_in_tenure,
burn_view)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27)",
burn_view,
total_tenure_size)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28)",
args
)?;

Expand Down Expand Up @@ -3351,6 +3370,9 @@ impl NakamotoChainState {
let mut marf_keys = vec![];
let mut marf_values = vec![];

// assume a new tenure (we will eventually add the parent accumulated size later)
let mut total_tenure_size = block_size;

if new_tenure {
// make the coinbase height point to this tenure-start block
marf_keys.push(nakamoto_keys::ongoing_tenure_coinbase_height(
Expand Down Expand Up @@ -3403,6 +3425,20 @@ impl NakamotoChainState {
&tenure_change_tx.tenure_consensus_hash,
));
marf_values.push(nakamoto_keys::make_tenure_id_value(&block_found_tenure_id));
} else {
// if we are here we need to accumulate the parent total tenure size
if let Some(parent_header_info) =
NakamotoChainState::get_block_header(&headers_tx, &new_tip.parent_block_id)?
{
total_tenure_size =
match total_tenure_size.checked_add(parent_header_info.total_tenure_size) {
Some(total_tenure_size) => total_tenure_size,
// in the extremely improbable case of overflow, just throw the tenure too big error
None => {
return Err(ChainstateError::TenureTooBigError);
}
};
}
}

if let Some(tenure_tx) = new_block.get_tenure_tx_payload() {
Expand Down Expand Up @@ -3445,6 +3481,7 @@ impl NakamotoChainState {
burn_header_timestamp: new_burnchain_timestamp,
anchored_block_size: block_size,
burn_view: Some(burn_view.clone()),
total_tenure_size,
};

let tenure_fees = block_fees
Expand Down
2 changes: 2 additions & 0 deletions stackslib/src/chainstate/nakamoto/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use crate::chainstate::stacks::{
use crate::clarity::vm::types::StacksAddressExtensions;
use crate::clarity_vm::clarity::ClarityInstance;
use crate::clarity_vm::database::SortitionDBRef;
use crate::config::DEFAULT_MAX_TENURE_BYTES;
use crate::net::Error as NetError;
use crate::util_lib::db::{query_row, u64_to_sql, Error as DBError};

Expand Down Expand Up @@ -728,6 +729,7 @@ impl NakamotoBlockBuilder {
1,
None,
None,
u64::from(DEFAULT_MAX_TENURE_BYTES),
)?;

let mut block_txs = vec![tenure_change_tx, coinbase_tx];
Expand Down
6 changes: 6 additions & 0 deletions stackslib/src/chainstate/nakamoto/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1000,
anchored_block_size: 12345,
burn_view: None,
total_tenure_size: 0,
};

let epoch2_execution_cost = ExecutionCost {
Expand Down Expand Up @@ -723,6 +724,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1001,
anchored_block_size: 123,
burn_view: Some(nakamoto_header.consensus_hash.clone()),
total_tenure_size: 0,
};

let epoch2_block = StacksBlock {
Expand Down Expand Up @@ -769,6 +771,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1001,
anchored_block_size: 123,
burn_view: Some(nakamoto_header_2.consensus_hash.clone()),
total_tenure_size: 0,
};

let nakamoto_block_2 = NakamotoBlock {
Expand Down Expand Up @@ -810,6 +813,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1001,
anchored_block_size: 123,
burn_view: Some(nakamoto_header_3.consensus_hash.clone()),
total_tenure_size: 0,
};

let nakamoto_block_3 = NakamotoBlock {
Expand Down Expand Up @@ -843,6 +847,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1001,
anchored_block_size: 123,
burn_view: Some(nakamoto_header_3.consensus_hash.clone()),
total_tenure_size: 0,
};

let nakamoto_block_3_weight_2 = NakamotoBlock {
Expand Down Expand Up @@ -876,6 +881,7 @@ pub fn test_load_store_update_nakamoto_blocks() {
burn_header_timestamp: 1001,
anchored_block_size: 123,
burn_view: Some(nakamoto_header_4.consensus_hash.clone()),
total_tenure_size: 0,
};

let nakamoto_block_4 = NakamotoBlock {
Expand Down
2 changes: 2 additions & 0 deletions stackslib/src/chainstate/nakamoto/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use crate::chainstate::stacks::db::*;
use crate::chainstate::stacks::miner::*;
use crate::chainstate::stacks::tests::TestStacksNode;
use crate::chainstate::stacks::{Error as ChainstateError, StacksBlock, *};
use crate::config::DEFAULT_MAX_TENURE_BYTES;
use crate::core::{BOOT_BLOCK_HASH, STACKS_EPOCH_3_0_MARKER};
use crate::net::relay::{BlockAcceptResponse, Relayer};
use crate::net::test::{TestPeer, *};
Expand Down Expand Up @@ -797,6 +798,7 @@ impl TestStacksNode {
1,
None,
None,
u64::from(DEFAULT_MAX_TENURE_BYTES),
)?
} else {
NakamotoBlockBuilder::new_first_block(
Expand Down
30 changes: 26 additions & 4 deletions stackslib/src/chainstate/stacks/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use crate::chainstate::nakamoto::{
HeaderTypeNames, NakamotoBlockHeader, NakamotoChainState, NakamotoStagingBlocksConn,
NAKAMOTO_CHAINSTATE_SCHEMA_1, NAKAMOTO_CHAINSTATE_SCHEMA_2, NAKAMOTO_CHAINSTATE_SCHEMA_3,
NAKAMOTO_CHAINSTATE_SCHEMA_4, NAKAMOTO_CHAINSTATE_SCHEMA_5, NAKAMOTO_CHAINSTATE_SCHEMA_6,
NAKAMOTO_CHAINSTATE_SCHEMA_7,
};
use crate::chainstate::stacks::address::StacksAddressExtensions;
use crate::chainstate::stacks::boot::*;
Expand Down Expand Up @@ -185,6 +186,8 @@ pub struct StacksHeaderInfo {
/// The burnchain tip that is passed to Clarity while processing this block.
/// This should always be `Some()` for Nakamoto blocks and `None` for 2.x blocks
pub burn_view: Option<ConsensusHash>,
/// Total tenure size (reset at every tenure extend) in bytes
pub total_tenure_size: u64,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -283,8 +286,8 @@ impl DBConfig {
});
match epoch_id {
StacksEpochId::Epoch10 => true,
StacksEpochId::Epoch20 => (1..=11).contains(&version_u32),
StacksEpochId::Epoch2_05 => (2..=11).contains(&version_u32),
StacksEpochId::Epoch20 => (1..=12).contains(&version_u32),
StacksEpochId::Epoch2_05 => (2..=12).contains(&version_u32),
StacksEpochId::Epoch21
| StacksEpochId::Epoch22
| StacksEpochId::Epoch23
Expand All @@ -293,7 +296,7 @@ impl DBConfig {
| StacksEpochId::Epoch30
| StacksEpochId::Epoch31
| StacksEpochId::Epoch32
| StacksEpochId::Epoch33 => (3..=11).contains(&version_u32),
| StacksEpochId::Epoch33 => (3..=12).contains(&version_u32),
}
}
}
Expand Down Expand Up @@ -362,6 +365,7 @@ impl StacksHeaderInfo {
burn_header_timestamp: 0,
anchored_block_size: 0,
burn_view: None,
total_tenure_size: 0,
}
}

Expand All @@ -382,6 +386,7 @@ impl StacksHeaderInfo {
burn_header_timestamp: first_burnchain_block_timestamp,
anchored_block_size: 0,
burn_view: None,
total_tenure_size: 0,
}
}

Expand Down Expand Up @@ -454,6 +459,13 @@ impl FromRow<StacksHeaderInfo> for StacksHeaderInfo {
return Err(db_error::ParseError);
}

let total_tenure_size = {
match header_type {
HeaderTypeNames::Epoch2 => 0,
HeaderTypeNames::Nakamoto => u64::from_column(row, "total_tenure_size")?,
}
};

Ok(StacksHeaderInfo {
anchored_header: stacks_header,
microblock_tail: None,
Expand All @@ -465,6 +477,7 @@ impl FromRow<StacksHeaderInfo> for StacksHeaderInfo {
burn_header_timestamp,
anchored_block_size,
burn_view,
total_tenure_size,
})
}
}
Expand Down Expand Up @@ -652,7 +665,7 @@ impl<'a> DerefMut for ChainstateTx<'a> {
}
}

pub const CHAINSTATE_VERSION: &str = "11";
pub const CHAINSTATE_VERSION: &str = "12";

const CHAINSTATE_INITIAL_SCHEMA: &[&str] = &[
"PRAGMA foreign_keys = ON;",
Expand Down Expand Up @@ -1145,6 +1158,14 @@ impl StacksChainState {
tx.execute_batch(cmd)?;
}
}
"11" => {
info!(
"Migrating chainstate schema from version 11 to 12: add total_tenure_size field"
);
for cmd in NAKAMOTO_CHAINSTATE_SCHEMA_7.iter() {
tx.execute_batch(cmd)?;
}
}
_ => {
error!(
"Invalid chain state database: expected version = {}, got {}",
Expand Down Expand Up @@ -2671,6 +2692,7 @@ impl StacksChainState {
burn_header_timestamp: new_burnchain_timestamp,
anchored_block_size: anchor_block_size,
burn_view: None,
total_tenure_size: 0,
};

StacksChainState::insert_stacks_block_header(
Expand Down
Loading