diff --git a/stacks-node/src/tests/nakamoto_integrations.rs b/stacks-node/src/tests/nakamoto_integrations.rs index da1de8a559b..9addbceac49 100644 --- a/stacks-node/src/tests/nakamoto_integrations.rs +++ b/stacks-node/src/tests/nakamoto_integrations.rs @@ -142,6 +142,7 @@ pub static POX_4_DEFAULT_STACKER_STX_AMT: u128 = 99_000_000_000_000; use clarity::vm::database::STXBalance; use stacks::chainstate::stacks::boot::SIP_031_NAME; use stacks::clarity_vm::clarity::SIP_031_INITIAL_MINT; +use stacks::config::DEFAULT_MAX_TENURE_BYTES; use crate::clarity::vm::clarity::ClarityConnection; @@ -3177,6 +3178,7 @@ fn block_proposal_api_endpoint() { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .expect("Failed to build Nakamoto block"); @@ -17695,3 +17697,582 @@ fn check_as_contract_rollback() { run_loop_thread.join().unwrap(); } + +#[test] +#[ignore] +/// Tests that the tenure size limit is correctly accounted. +/// Deploys 10 (big) contracts (each 512K) +/// The block limit is 2MB, the tenure limit is 3MB +/// One block will contain 3 of the deployed contracts (the block size will be reached at it) +/// The following one will contain 2 of the deployed contract (tenure size limit will be reached) +fn smaller_tenure_size_for_miner() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + + let http_origin = format!("http://{}", &naka_conf.node.rpc_bind); + + let mut senders: Vec<(Secp256k1PrivateKey, StacksAddress)> = vec![]; + + // number of deploys to submit in the test + let num_deploys = 10; + + for _ in 0..num_deploys { + let sender_sk = Secp256k1PrivateKey::random(); + let sender_addr = tests::to_addr(&sender_sk); + naka_conf.add_initial_balance( + PrincipalData::from(sender_addr.clone()).to_string(), + 10000000000000, + ); + + senders.push((sender_sk, sender_addr)); + } + + let signer_sk = Secp256k1PrivateKey::random(); + let signer_addr = tests::to_addr(&signer_sk); + + naka_conf.miner.max_tenure_bytes = 3 * 1024 * 1024; // 3MB + naka_conf.miner.log_skipped_transactions = true; + + naka_conf.add_initial_balance( + PrincipalData::from(signer_addr.clone()).to_string(), + 10000000000000, + ); + let mut signers = TestSigners::new(vec![signer_sk.clone()]); + + let stacker_sk = setup_stacker(&mut naka_conf); + + test_observer::spawn(); + test_observer::register( + &mut naka_conf, + &[EventKeyType::AnyEvent, EventKeyType::MinedBlocks], + ); + + let mut btcd_controller = BitcoinCoreController::from_stx_config(&naka_conf); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, .. + } = run_loop.counters(); + let counters = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::Builder::new() + .name("run_loop".into()) + .spawn(move || run_loop.start(None, 0)) + .unwrap(); + wait_for_runloop(&blocks_processed); + + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk.clone()], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + info!("Nakamoto miner started..."); + blind_signer(&naka_conf, &signers, &counters); + + let mut long_comment = String::from(";; "); + long_comment.extend(std::iter::repeat('x').take(524_288 - long_comment.len())); + let contract = format!( + r#" + {long_comment} + (define-public (test-fn) + (ok "Hello, world!") + ) + "# + ); + + let deploy_fee = 524504; + + test_observer::clear(); + + for deploy in 0..num_deploys { + info!("Submitting deploy {deploy}"); + let contract_name = format!("test-{deploy}"); + + let contract_tx = make_contract_publish( + &senders[deploy].0, + 0, + deploy_fee, + naka_conf.burnchain.chain_id, + &contract_name, + &contract, + ); + + submit_tx(&http_origin, &contract_tx); + } + + next_block_and(&mut btc_regtest_controller, 60, || { + let nakamoto_block_events = test_observer::get_mined_nakamoto_blocks(); + if !nakamoto_block_events.is_empty() { + let nakamoto_block_event = nakamoto_block_events.last().unwrap(); + let mut skipped_transactions = 0; + for tx_event in &nakamoto_block_event.tx_events { + match tx_event { + TransactionEvent::Skipped(reason) => { + if reason.error == "Too much data in tenure" { + skipped_transactions += 1; + } + } + _ => (), + } + } + // assume 2 blocks, the first one with 3 transactions the second with 2 + // that means we will have 5 skipped transactions at the end + if skipped_transactions == 5 { + return Ok(true); + } + } + Ok(false) + }) + .unwrap(); + + // wait for signers + wait_for(30, || Ok(test_observer::get_blocks().len() >= 3)) + .expect("Timed out waiting for signers"); + + let blocks = test_observer::get_blocks(); + + assert_eq!( + blocks.len(), + 3, + "Should have successfully mined three blocks, but got {}", + blocks.len() + ); + + let mut deployed_contracts = 0; + for deploy in 0..num_deploys { + if get_account(&http_origin, &senders[deploy].1).nonce == 1 { + deployed_contracts += 1; + } + } + + assert_eq!( + deployed_contracts, 5, + "Should have successfully deployed 5 contracts, but got {}", + deployed_contracts + ); + + // ensure no tenure extend + for block in &blocks { + let txs = test_observer::parse_transactions(block); + let has_tenure_extend = txs.iter().any(|tx| match &tx.payload { + TransactionPayload::TenureChange(tenure_change) => tenure_change.cause.is_extended(), + _ => false, + }); + + assert!(!has_tenure_extend, "Unexpected tenure extend transaction"); + } + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + +#[test] +#[ignore] +/// Tests that the tenure size limit is correctly accounted. +/// Deploys 10 (big) contracts (each 512K) +/// The block limit is 2MB, the tenure limit is 3MB +/// One block will contain 3 of the deployed contracts (the block size will be reached at it) +/// The following one will contain 2 of the deployed contract (tenure size limit will be reached) +/// Start a new tenure to process the remaining transactions. +fn smaller_tenure_size_for_miner_on_two_tenures() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + + let http_origin = format!("http://{}", &naka_conf.node.rpc_bind); + + let mut senders: Vec<(Secp256k1PrivateKey, StacksAddress)> = vec![]; + + // number of deploys to submit in the test + let num_deploys = 10; + + for _ in 0..num_deploys { + let sender_sk = Secp256k1PrivateKey::random(); + let sender_addr = tests::to_addr(&sender_sk); + naka_conf.add_initial_balance( + PrincipalData::from(sender_addr.clone()).to_string(), + 10000000000000, + ); + + senders.push((sender_sk, sender_addr)); + } + + let signer_sk = Secp256k1PrivateKey::random(); + let signer_addr = tests::to_addr(&signer_sk); + + naka_conf.miner.max_tenure_bytes = 3 * 1024 * 1024; // 3MB + naka_conf.miner.log_skipped_transactions = true; + + naka_conf.add_initial_balance( + PrincipalData::from(signer_addr.clone()).to_string(), + 10000000000000, + ); + let mut signers = TestSigners::new(vec![signer_sk.clone()]); + + let stacker_sk = setup_stacker(&mut naka_conf); + + test_observer::spawn(); + test_observer::register( + &mut naka_conf, + &[ + EventKeyType::AnyEvent, + EventKeyType::MinedBlocks, + EventKeyType::MemPoolTransactions, + ], + ); + + let mut btcd_controller = BitcoinCoreController::from_stx_config(&naka_conf); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, .. + } = run_loop.counters(); + let counters = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::Builder::new() + .name("run_loop".into()) + .spawn(move || run_loop.start(None, 0)) + .unwrap(); + wait_for_runloop(&blocks_processed); + + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk.clone()], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + info!("Nakamoto miner started..."); + blind_signer(&naka_conf, &signers, &counters); + + let mut long_comment = String::from(";; "); + long_comment.extend(std::iter::repeat('x').take(524_288 - long_comment.len())); + let contract = format!( + r#" + {long_comment} + (define-public (test-fn) + (ok "Hello, world!") + ) + "# + ); + + let deploy_fee = 524504; + + test_observer::clear(); + + for deploy in 0..num_deploys { + info!("Submitting deploy {deploy}"); + let contract_name = format!("test-{deploy}"); + + let contract_tx = make_contract_publish( + &senders[deploy].0, + 0, + deploy_fee, + naka_conf.burnchain.chain_id, + &contract_name, + &contract, + ); + + submit_tx(&http_origin, &contract_tx); + } + + next_block_and(&mut btc_regtest_controller, 60, || { + let nakamoto_block_events = test_observer::get_mined_nakamoto_blocks(); + if !nakamoto_block_events.is_empty() { + let nakamoto_block_event = nakamoto_block_events.last().unwrap(); + let mut skipped_transactions = 0; + for tx_event in &nakamoto_block_event.tx_events { + match tx_event { + TransactionEvent::Skipped(reason) => { + if reason.error == "Too much data in tenure" { + skipped_transactions += 1; + } + } + _ => (), + } + } + // assume 2 blocks, the first one with 3 transactions the second with 2 + // that means we will have 5 skipped transactions at the end + if skipped_transactions == 5 { + return Ok(true); + } + } + Ok(false) + }) + .unwrap(); + + // wait for signers + wait_for(30, || Ok(test_observer::get_blocks().len() >= 3)) + .expect("Timed out waiting for signers"); + + // start the second tenure and wait till no more transactions are skipped + next_block_and(&mut btc_regtest_controller, 60, || { + let nakamoto_block_events = test_observer::get_mined_nakamoto_blocks(); + if !nakamoto_block_events.is_empty() { + let nakamoto_block_event = nakamoto_block_events.last().unwrap(); + let mut skipped_transactions = 0; + for tx_event in &nakamoto_block_event.tx_events { + match tx_event { + TransactionEvent::Skipped(reason) => { + if reason.error == "Too much data in tenure" { + skipped_transactions += 1; + } + } + _ => (), + } + } + if skipped_transactions == 0 { + return Ok(true); + } + } + Ok(false) + }) + .unwrap(); + + // wait for signers + wait_for(30, || Ok(test_observer::get_blocks().len() >= 6)) + .expect("Timed out waiting for signers"); + + let blocks = test_observer::get_blocks(); + + assert_eq!( + blocks.len(), + 6, + "Should have successfully mined six blocks, but got {}", + blocks.len() + ); + + let mut deployed_contracts = 0; + for deploy in 0..num_deploys { + if get_account(&http_origin, &senders[deploy].1).nonce == 1 { + deployed_contracts += 1; + } + } + + assert_eq!( + deployed_contracts, 10, + "Should have successfully deployes 10 contracts, but got {}", + deployed_contracts + ); + + // ensure no tenure extend + for block in &blocks { + let txs = test_observer::parse_transactions(block); + let has_tenure_extend = txs.iter().any(|tx| match &tx.payload { + TransactionPayload::TenureChange(tenure_change) => tenure_change.cause.is_extended(), + _ => false, + }); + + assert!(!has_tenure_extend, "Unexpected tenure extend transaction"); + } + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} + +#[test] +#[ignore] +/// Tests that the tenure size limit is correctly reset on tenure extend. +/// Deploys 10 (big) contracts (each 512K) +/// The block limit is 2MB, the tenure limit is 3MB +/// One block will contain 3 of the deployed contracts (the block size will be reached at it) +/// The following ones will contain the others as tenure extend is constantly triggered +fn smaller_tenure_size_for_miner_with_tenure_extend() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None); + + let http_origin = format!("http://{}", &naka_conf.node.rpc_bind); + + let mut senders: Vec<(Secp256k1PrivateKey, StacksAddress)> = vec![]; + + // number of deploys to submit in the test + let num_deploys = 10; + + for _ in 0..num_deploys { + let sender_sk = Secp256k1PrivateKey::random(); + let sender_addr = tests::to_addr(&sender_sk); + naka_conf.add_initial_balance( + PrincipalData::from(sender_addr.clone()).to_string(), + 10000000000000, + ); + + senders.push((sender_sk, sender_addr)); + } + + let signer_sk = Secp256k1PrivateKey::random(); + let signer_addr = tests::to_addr(&signer_sk); + + naka_conf.miner.max_tenure_bytes = 3 * 1024 * 1024; // 3MB + naka_conf.miner.log_skipped_transactions = true; + // quickly tenure extend + naka_conf.miner.tenure_timeout = Duration::from_secs(1); + naka_conf.miner.tenure_extend_cost_threshold = 2; + + naka_conf.add_initial_balance( + PrincipalData::from(signer_addr.clone()).to_string(), + 10000000000000, + ); + let mut signers = TestSigners::new(vec![signer_sk.clone()]); + + let stacker_sk = setup_stacker(&mut naka_conf); + + test_observer::spawn(); + test_observer::register( + &mut naka_conf, + &[EventKeyType::AnyEvent, EventKeyType::MinedBlocks], + ); + + let mut btcd_controller = BitcoinCoreController::from_stx_config(&naka_conf); + btcd_controller + .start_bitcoind() + .expect("Failed starting bitcoind"); + let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None); + btc_regtest_controller.bootstrap_chain(201); + + let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap(); + let run_loop_stopper = run_loop.get_termination_switch(); + let Counters { + blocks_processed, .. + } = run_loop.counters(); + let counters = run_loop.counters(); + + let coord_channel = run_loop.coordinator_channels(); + + let run_loop_thread = thread::Builder::new() + .name("run_loop".into()) + .spawn(move || run_loop.start(None, 0)) + .unwrap(); + wait_for_runloop(&blocks_processed); + + boot_to_epoch_3( + &naka_conf, + &blocks_processed, + &[stacker_sk.clone()], + &[signer_sk], + &mut Some(&mut signers), + &mut btc_regtest_controller, + ); + + info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner"); + + info!("Nakamoto miner started..."); + blind_signer(&naka_conf, &signers, &counters); + + let mut long_comment = String::from(";; "); + long_comment.extend(std::iter::repeat('x').take(524_288 - long_comment.len())); + let contract = format!( + r#" + {long_comment} + (define-public (test-fn) + (ok "Hello, world!") + ) + "# + ); + + let deploy_fee = 524504; + + test_observer::clear(); + + for deploy in 0..num_deploys { + info!("Submitting deploy {deploy}"); + let contract_name = format!("test-{deploy}"); + + let contract_tx = make_contract_publish( + &senders[deploy].0, + 0, + deploy_fee, + naka_conf.burnchain.chain_id, + &contract_name, + &contract, + ); + + submit_tx(&http_origin, &contract_tx); + } + + next_block_and(&mut btc_regtest_controller, 60, || { + let mut deployed_contracts = 0; + for deploy in 0..num_deploys { + if get_account(&http_origin, &senders[deploy].1).nonce == 1 { + deployed_contracts += 1; + } + } + Ok(deployed_contracts == 10) + }) + .unwrap(); + + let blocks = test_observer::get_blocks(); + + assert_eq!( + blocks.len(), + 5, + "Should have successfully mined five blocks, but got {}", + blocks.len() + ); + + // ensure tenure extend is present in the last 3 blocks + for (block_index, block) in blocks.iter().enumerate() { + let txs = test_observer::parse_transactions(block); + let has_tenure_extend = txs.iter().any(|tx| match &tx.payload { + TransactionPayload::TenureChange(tenure_change) => tenure_change.cause.is_extended(), + _ => false, + }); + + if block_index > 1 { + assert!(has_tenure_extend, "Expected tenure extend transaction"); + } else { + assert!(!has_tenure_extend, "Unexpected tenure extend transaction"); + } + } + + coord_channel + .lock() + .expect("Mutex poisoned") + .stop_chains_coordinator(); + run_loop_stopper.store(false, Ordering::SeqCst); + + run_loop_thread.join().unwrap(); +} diff --git a/stacks-node/src/tests/signer/v0.rs b/stacks-node/src/tests/signer/v0.rs index 60a516994db..58edff19cc1 100644 --- a/stacks-node/src/tests/signer/v0.rs +++ b/stacks-node/src/tests/signer/v0.rs @@ -56,7 +56,9 @@ use stacks::chainstate::stacks::{ StacksTransaction, TenureChangeCause, TenureChangePayload, TransactionPayload, }; use stacks::codec::StacksMessageCodec; -use stacks::config::{Config as NeonConfig, EventKeyType, EventObserverConfig}; +use stacks::config::{ + Config as NeonConfig, EventKeyType, EventObserverConfig, DEFAULT_MAX_TENURE_BYTES, +}; use stacks::core::mempool::MemPoolWalkStrategy; use stacks::core::test_util::{ insert_tx_in_mempool, make_big_read_count_contract, make_contract_call, make_contract_publish, @@ -2048,6 +2050,7 @@ fn sip034_tenure_extend_proposal(allow: bool) { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .expect("Failed to build Nakamoto block"); @@ -3119,6 +3122,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(); @@ -17840,6 +17844,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 diff --git a/stackslib/src/chainstate/nakamoto/miner.rs b/stackslib/src/chainstate/nakamoto/miner.rs index fbf7cba6fd3..d9abf5a2f40 100644 --- a/stackslib/src/chainstate/nakamoto/miner.rs +++ b/stackslib/src/chainstate/nakamoto/miner.rs @@ -37,7 +37,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::{ @@ -96,6 +96,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, + /// Maximum size of the whole tenure + pub max_tenure_bytes: u64, } /// NB: No PartialEq implementation is deliberate in order to ensure that we use the appropriate @@ -235,6 +237,7 @@ impl NakamotoBlockBuilder { header: NakamotoBlockHeader::genesis(), soft_limit: None, contract_limit_percentage: None, + max_tenure_bytes: u64::from(DEFAULT_MAX_TENURE_BYTES), } } @@ -266,6 +269,7 @@ impl NakamotoBlockBuilder { soft_limit: Option, contract_limit_percentage: Option, timestamp: Option, + max_tenure_bytes: u64, ) -> Result { let next_height = parent_stacks_header .anchored_header @@ -308,6 +312,7 @@ impl NakamotoBlockBuilder { ), soft_limit, contract_limit_percentage, + max_tenure_bytes, }) } @@ -671,6 +676,7 @@ impl NakamotoBlockBuilder { None, settings.mempool_settings.contract_cost_limit_percentage, None, + settings.max_tenure_bytes, )?; let ts_start = get_epoch_time_ms(); @@ -806,6 +812,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, diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index c32313ae6ca..ae51611678c 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -288,6 +288,24 @@ 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#" + -- total_tenure_size cannot be consensus critical as existing nodes which migrate will report a 0 size while + -- nodes booting from genesis sync will get the true tenure size + 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 = std::sync::Mutex::new(false); @@ -2649,6 +2667,19 @@ impl NakamotoChainState { Ok(result) } + /// Load the total_tenure_size for a Nakamoto header + pub fn get_block_header_nakamoto_total_tenure_size( + chainstate_conn: &Connection, + index_block_hash: &StacksBlockId, + ) -> Result, ChainstateError> { + let sql = + "SELECT total_tenure_size FROM nakamoto_block_headers WHERE index_block_hash = ?1"; + let result = query_row_panic(chainstate_conn, sql, &[&index_block_hash], || { + "FATAL: multiple rows for the same block hash".to_string() + })?; + Ok(result) + } + /// Load an epoch2 header pub fn get_block_header_epoch2( chainstate_conn: &Connection, @@ -3245,6 +3276,7 @@ impl NakamotoChainState { stacks_block_height, burn_header_height, burn_header_timestamp, + total_tenure_size, .. } = tip_info; @@ -3301,6 +3333,7 @@ impl NakamotoChainState { "Nakamoto block StacksHeaderInfo did not set burnchain view".into(), )) })?, + total_tenure_size ]; chainstate_tx.execute( @@ -3333,8 +3366,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 )?; @@ -3400,6 +3434,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( @@ -3463,6 +3500,28 @@ impl NakamotoChainState { marf_keys.push(nakamoto_keys::ongoing_tenure_id().to_string()); marf_values.push(nakamoto_keys::make_tenure_id_value(&tenure_id)); + } else { + // if we are here (no new tenure or tenure_extend) we need to accumulate the parent total tenure size + if let Some(current_total_tenure_size) = + NakamotoChainState::get_block_header_nakamoto_total_tenure_size( + &headers_tx, + &new_tip.parent_block_id, + )? + { + total_tenure_size = match total_tenure_size.checked_add(current_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); + } + }; + } else { + warn!( + "Unable to retrieve total tenure size"; + "consensus_hash" => %new_tip.consensus_hash, + "parent_block_id" => %new_tip.parent_block_id, + ); + } } // record the highest block in this tenure @@ -3494,6 +3553,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 diff --git a/stackslib/src/chainstate/nakamoto/shadow.rs b/stackslib/src/chainstate/nakamoto/shadow.rs index 8f3d3aeba69..3c40cca72bb 100644 --- a/stackslib/src/chainstate/nakamoto/shadow.rs +++ b/stackslib/src/chainstate/nakamoto/shadow.rs @@ -70,6 +70,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}; @@ -726,6 +727,7 @@ impl NakamotoBlockBuilder { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), )?; let mut block_txs = vec![tenure_change_tx, coinbase_tx]; diff --git a/stackslib/src/chainstate/nakamoto/tests/mod.rs b/stackslib/src/chainstate/nakamoto/tests/mod.rs index 575e777d983..3c58a33f793 100644 --- a/stackslib/src/chainstate/nakamoto/tests/mod.rs +++ b/stackslib/src/chainstate/nakamoto/tests/mod.rs @@ -668,6 +668,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 { @@ -801,6 +802,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 { @@ -847,6 +849,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 { @@ -888,6 +891,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 { @@ -921,6 +925,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 { @@ -954,6 +959,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 { diff --git a/stackslib/src/chainstate/nakamoto/tests/node.rs b/stackslib/src/chainstate/nakamoto/tests/node.rs index 02969cf7c4b..9bb0aef5de9 100644 --- a/stackslib/src/chainstate/nakamoto/tests/node.rs +++ b/stackslib/src/chainstate/nakamoto/tests/node.rs @@ -48,6 +48,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, *}; @@ -796,6 +797,7 @@ impl TestStacksNode { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), )? } else { NakamotoBlockBuilder::new_first_block( diff --git a/stackslib/src/chainstate/stacks/db/mod.rs b/stackslib/src/chainstate/stacks/db/mod.rs index a72f3f9edc9..83310c0d4e9 100644 --- a/stackslib/src/chainstate/stacks/db/mod.rs +++ b/stackslib/src/chainstate/stacks/db/mod.rs @@ -50,6 +50,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::*; @@ -184,6 +185,9 @@ 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, + /// Total tenure size (reset at every tenure extend) in bytes + /// Not consensus-critical (may differ between nodes) + pub total_tenure_size: u64, } #[derive(Debug, Clone, PartialEq)] @@ -282,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..=CHAINSTATE_VERSION_NUMBER).contains(&version_u32), + StacksEpochId::Epoch2_05 => (2..=CHAINSTATE_VERSION_NUMBER).contains(&version_u32), StacksEpochId::Epoch21 | StacksEpochId::Epoch22 | StacksEpochId::Epoch23 @@ -292,7 +296,7 @@ impl DBConfig { | StacksEpochId::Epoch30 | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 - | StacksEpochId::Epoch33 => (3..=11).contains(&version_u32), + | StacksEpochId::Epoch33 => (3..=CHAINSTATE_VERSION_NUMBER).contains(&version_u32), } } } @@ -361,6 +365,7 @@ impl StacksHeaderInfo { burn_header_timestamp: 0, anchored_block_size: 0, burn_view: None, + total_tenure_size: 0, } } @@ -381,6 +386,7 @@ impl StacksHeaderInfo { burn_header_timestamp: first_burnchain_block_timestamp, anchored_block_size: 0, burn_view: None, + total_tenure_size: 0, } } @@ -453,6 +459,13 @@ impl FromRow 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, @@ -464,6 +477,7 @@ impl FromRow for StacksHeaderInfo { burn_header_timestamp, anchored_block_size, burn_view, + total_tenure_size, }) } } @@ -651,7 +665,8 @@ impl<'a> DerefMut for ChainstateTx<'a> { } } -pub const CHAINSTATE_VERSION: &str = "11"; +pub const CHAINSTATE_VERSION: &str = "12"; +pub const CHAINSTATE_VERSION_NUMBER: u32 = 12; const CHAINSTATE_INITIAL_SCHEMA: &[&str] = &[ "PRAGMA foreign_keys = ON;", @@ -1144,6 +1159,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 {}", @@ -2768,6 +2791,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( diff --git a/stackslib/src/chainstate/stacks/miner.rs b/stackslib/src/chainstate/stacks/miner.rs index 32dcc496fd3..5aa663f72c5 100644 --- a/stackslib/src/chainstate/stacks/miner.rs +++ b/stackslib/src/chainstate/stacks/miner.rs @@ -51,6 +51,7 @@ use crate::chainstate::stacks::db::{ChainstateTx, ClarityTx, StacksChainState}; use crate::chainstate::stacks::events::StacksTransactionReceipt; use crate::chainstate::stacks::{Error, StacksBlockHeader, StacksMicroblockHeader, *}; use crate::clarity_vm::clarity::{ClarityInstance, Error as clarity_error}; +use crate::config::DEFAULT_MAX_TENURE_BYTES; use crate::core::mempool::*; use crate::core::*; use crate::monitoring::{ @@ -242,6 +243,7 @@ pub struct BlockBuilderSettings { /// Should the builder attempt to confirm any parent microblocks pub confirm_microblocks: bool, pub max_execution_time: Option, + pub max_tenure_bytes: u64, } impl BlockBuilderSettings { @@ -254,6 +256,7 @@ impl BlockBuilderSettings { miner_status: Arc::new(Mutex::new(MinerStatus::make_ready(0))), confirm_microblocks: true, max_execution_time: None, + max_tenure_bytes: u64::from(DEFAULT_MAX_TENURE_BYTES), } } @@ -266,6 +269,7 @@ impl BlockBuilderSettings { miner_status: Arc::new(Mutex::new(MinerStatus::make_ready(0))), confirm_microblocks: true, max_execution_time: None, + max_tenure_bytes: u64::from(DEFAULT_MAX_TENURE_BYTES), } } } @@ -1536,6 +1540,7 @@ impl StacksBlockBuilder { burn_header_height: genesis_burn_header_height, anchored_block_size: 0, burn_view: None, + total_tenure_size: 0, }; let mut builder = StacksBlockBuilder::from_parent_pubkey_hash( diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index f5072f36d50..b186f8142ec 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -120,6 +120,7 @@ pub enum Error { /// This error indicates a Epoch2 block attempted to build off of a Nakamoto block. InvalidChildOfNakomotoBlock, NoRegisteredSigners(u64), + TenureTooBigError, } impl From for Error { @@ -222,6 +223,7 @@ impl fmt::Display for Error { Error::NotInSameFork => { write!(f, "The supplied block identifiers are not in the same fork") } + Error::TenureTooBigError => write!(f, "Too much data in tenure"), } } } @@ -268,6 +270,7 @@ impl error::Error for Error { Error::ExpectedTenureChange => None, Error::NoRegisteredSigners(_) => None, Error::NotInSameFork => None, + Error::TenureTooBigError => None, } } } @@ -314,6 +317,7 @@ impl Error { Error::ExpectedTenureChange => "ExpectedTenureChange", Error::NoRegisteredSigners(_) => "NoRegisteredSigners", Error::NotInSameFork => "NotInSameFork", + Error::TenureTooBigError => "TenureTooBigError", } } @@ -770,6 +774,18 @@ impl TenureChangeCause { (_, _) => false, } } + + pub fn is_extended(&self) -> bool { + match self { + TenureChangeCause::BlockFound => false, + TenureChangeCause::Extended => true, + TenureChangeCause::ExtendedRuntime => true, + TenureChangeCause::ExtendedReadCount => true, + TenureChangeCause::ExtendedReadLength => true, + TenureChangeCause::ExtendedWriteCount => true, + TenureChangeCause::ExtendedWriteLength => true, + } + } } /// Reasons why a `TenureChange` transaction can be bad diff --git a/stackslib/src/clarity_vm/tests/ephemeral.rs b/stackslib/src/clarity_vm/tests/ephemeral.rs index 9972bb80d12..7f7db25423a 100644 --- a/stackslib/src/clarity_vm/tests/ephemeral.rs +++ b/stackslib/src/clarity_vm/tests/ephemeral.rs @@ -43,6 +43,7 @@ use crate::chainstate::stacks::{ use crate::clarity::vm::database::ClarityBackingStore; use crate::clarity_vm::clarity::ClarityMarfStoreTransaction; use crate::clarity_vm::database::marf::MarfedKV; +use crate::config::DEFAULT_MAX_TENURE_BYTES; use crate::net::test::TestEventObserver; use crate::net::tests::inv::nakamoto::make_nakamoto_peer_from_invs; use crate::net::tests::{NakamotoBootPlan, NakamotoBootStep, NakamotoBootTenure}; @@ -346,6 +347,7 @@ fn replay_block( None, Some(100), Some(original_block.header.timestamp), + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .unwrap(); diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 407744f57db..3b79f72ba4c 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -134,6 +134,8 @@ const DEFAULT_EMPTY_MEMPOOL_SLEEP_MS: u64 = 2_500; const DEFAULT_MAX_EXECUTION_TIME_SECS: u64 = 30; /// Default number of seconds that a miner should wait before timing out an HTTP request to StackerDB. const DEFAULT_STACKERDB_TIMEOUT_SECS: u64 = 120; +/// Default maximum size for a tenure (note: the counter is reset on tenure extend). +pub const DEFAULT_MAX_TENURE_BYTES: u64 = 10 * 1024 * 1024; // 10 MB static HELIUM_DEFAULT_CONNECTION_OPTIONS: LazyLock = LazyLock::new(|| ConnectionOptions { @@ -1150,12 +1152,14 @@ impl Config { tenure_cost_limit_per_block_percentage: miner_config .tenure_cost_limit_per_block_percentage, contract_cost_limit_percentage: miner_config.contract_cost_limit_percentage, + log_skipped_transactions: miner_config.log_skipped_transactions, }, miner_status, confirm_microblocks: false, max_execution_time: miner_config .max_execution_time_secs .map(Duration::from_secs), + max_tenure_bytes: miner_config.max_tenure_bytes, } } @@ -1197,12 +1201,14 @@ impl Config { tenure_cost_limit_per_block_percentage: miner_config .tenure_cost_limit_per_block_percentage, contract_cost_limit_percentage: miner_config.contract_cost_limit_percentage, + log_skipped_transactions: miner_config.log_skipped_transactions, }, miner_status, confirm_microblocks: true, max_execution_time: miner_config .max_execution_time_secs .map(Duration::from_secs), + max_tenure_bytes: miner_config.max_tenure_bytes, } } @@ -3061,6 +3067,18 @@ pub struct MinerConfig { /// @default: [`DEFAULT_STACKERDB_TIMEOUT_SECS`] /// @units: seconds. pub stackerdb_timeout: Duration, + /// Defines them maximum numnber of bytes to allow in a tenure. + /// The miner will stop mining if the limit is reached. + /// --- + /// @default: [`DEFAULT_MAX_TENURE_BYTES`] + /// @units: bytes. + pub max_tenure_bytes: u64, + /// Enable logging of skipped transactions (generally used for tests) + /// --- + /// @default: `false` + /// @notes: + /// - Primarily intended for testing purposes. + pub log_skipped_transactions: bool, } impl Default for MinerConfig { @@ -3116,6 +3134,8 @@ impl Default for MinerConfig { max_execution_time_secs: Some(DEFAULT_MAX_EXECUTION_TIME_SECS), replay_transactions: false, stackerdb_timeout: Duration::from_secs(DEFAULT_STACKERDB_TIMEOUT_SECS), + max_tenure_bytes: DEFAULT_MAX_TENURE_BYTES, + log_skipped_transactions: false, } } } @@ -4061,6 +4081,8 @@ pub struct MinerConfigFile { /// TODO: remove this config option once its no longer a testing feature pub replay_transactions: Option, pub stackerdb_timeout_secs: Option, + pub max_tenure_bytes: Option, + pub log_skipped_transactions: Option, } impl MinerConfigFile { @@ -4253,6 +4275,8 @@ impl MinerConfigFile { max_execution_time_secs: self.max_execution_time_secs, replay_transactions: self.replay_transactions.unwrap_or_default(), stackerdb_timeout: self.stackerdb_timeout_secs.map(Duration::from_secs).unwrap_or(miner_default_config.stackerdb_timeout), + max_tenure_bytes: self.max_tenure_bytes.unwrap_or(miner_default_config.max_tenure_bytes), + log_skipped_transactions: self.log_skipped_transactions.unwrap_or(miner_default_config.log_skipped_transactions), }) } } diff --git a/stackslib/src/core/mempool.rs b/stackslib/src/core/mempool.rs index 099e24b7713..a2e2445e312 100644 --- a/stackslib/src/core/mempool.rs +++ b/stackslib/src/core/mempool.rs @@ -569,6 +569,8 @@ pub struct MemPoolWalkSettings { /// further non-boot contract calls and instead consider only boot contract calls /// and STX transfers. pub contract_cost_limit_percentage: Option, + /// Enable logging of skipped transactions (disabled by default, generally used for tests) + pub log_skipped_transactions: bool, } impl Default for MemPoolWalkSettings { @@ -583,6 +585,7 @@ impl Default for MemPoolWalkSettings { filter_origins: HashSet::new(), tenure_cost_limit_per_block_percentage: None, contract_cost_limit_percentage: None, + log_skipped_transactions: false, } } } @@ -598,6 +601,7 @@ impl MemPoolWalkSettings { filter_origins: HashSet::new(), tenure_cost_limit_per_block_percentage: None, contract_cost_limit_percentage: None, + log_skipped_transactions: false, } } } @@ -1905,7 +1909,10 @@ impl MemPoolDB { output_events.push(tx_event); } TransactionEvent::Skipped(_) => { - // don't push `Skipped` events to the observer + // don't push `Skipped` events to the observer by default + if settings.log_skipped_transactions { + output_events.push(tx_event); + } } _ => { output_events.push(tx_event); diff --git a/stackslib/src/core/tests/mod.rs b/stackslib/src/core/tests/mod.rs index 3e0a5224c30..4977c2e85c3 100644 --- a/stackslib/src/core/tests/mod.rs +++ b/stackslib/src/core/tests/mod.rs @@ -123,6 +123,7 @@ pub fn make_block( burn_header_timestamp: 0, anchored_block_size: 1, burn_view: None, + total_tenure_size: 0, }; c_tx.commit_block(); diff --git a/stackslib/src/cost_estimates/tests/common.rs b/stackslib/src/cost_estimates/tests/common.rs index 56e7269f365..7f169393d3c 100644 --- a/stackslib/src/cost_estimates/tests/common.rs +++ b/stackslib/src/cost_estimates/tests/common.rs @@ -37,6 +37,7 @@ pub fn make_block_receipt(tx_receipts: Vec) -> StacksE burn_header_timestamp: 2, anchored_block_size: 1, burn_view: None, + total_tenure_size: 0, }, tx_receipts, matured_rewards: vec![], diff --git a/stackslib/src/net/api/blockreplay.rs b/stackslib/src/net/api/blockreplay.rs index 44ca42e1c74..8fb48541713 100644 --- a/stackslib/src/net/api/blockreplay.rs +++ b/stackslib/src/net/api/blockreplay.rs @@ -30,6 +30,7 @@ use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::events::{StacksTransactionReceipt, TransactionOrigin}; use crate::chainstate::stacks::miner::{BlockBuilder, BlockLimitFunction, TransactionResult}; use crate::chainstate::stacks::{Error as ChainError, StacksTransaction, TransactionPayload}; +use crate::config::DEFAULT_MAX_TENURE_BYTES; use crate::net::http::{ parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble, HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble, HttpServerError, @@ -134,6 +135,7 @@ impl RPCNakamotoBlockReplayRequestHandler { None, None, Some(block.header.timestamp), + u64::from(DEFAULT_MAX_TENURE_BYTES), ) { Ok(builder) => builder, Err(e) => return Err(e), diff --git a/stackslib/src/net/api/postblock_proposal.rs b/stackslib/src/net/api/postblock_proposal.rs index 9894454ab61..e41eaeee07e 100644 --- a/stackslib/src/net/api/postblock_proposal.rs +++ b/stackslib/src/net/api/postblock_proposal.rs @@ -42,6 +42,7 @@ use crate::chainstate::stacks::miner::{ }; use crate::chainstate::stacks::{Error as ChainError, StacksTransaction, TransactionPayload}; use crate::clarity_vm::clarity::Error as ClarityError; +use crate::config::DEFAULT_MAX_TENURE_BYTES; use crate::core::mempool::ProposalCallbackReceiver; use crate::net::http::{ http_reason, parse_json, Error, HttpContentType, HttpRequest, HttpRequestContents, @@ -580,6 +581,7 @@ impl NakamotoBlockProposal { None, None, Some(self.block.header.timestamp), + u64::from(DEFAULT_MAX_TENURE_BYTES), )?; let mut miner_tenure_info = @@ -735,6 +737,7 @@ impl NakamotoBlockProposal { None, None, Some(self.block.header.timestamp), + u64::from(DEFAULT_MAX_TENURE_BYTES), )?; let (mut replay_chainstate, _) = StacksChainState::open(mainnet, chain_id, chainstate_path, None)?; diff --git a/stackslib/src/net/api/tests/postblock_proposal.rs b/stackslib/src/net/api/tests/postblock_proposal.rs index 0ffb2f582e2..f4fba43e3dd 100644 --- a/stackslib/src/net/api/tests/postblock_proposal.rs +++ b/stackslib/src/net/api/tests/postblock_proposal.rs @@ -39,6 +39,7 @@ use crate::chainstate::stacks::db::StacksChainState; use crate::chainstate::stacks::miner::{BlockBuilder, BlockLimitFunction, TransactionResult}; use crate::chainstate::stacks::test::make_codec_test_nakamoto_block; use crate::chainstate::stacks::{StacksMicroblock, StacksTransaction}; +use crate::config::DEFAULT_MAX_TENURE_BYTES; use crate::core::mempool::{MemPoolDropReason, MemPoolEventDispatcher, ProposalCallbackReceiver}; use crate::core::test_util::{ make_big_read_count_contract, make_contract_call, make_contract_publish, @@ -275,6 +276,7 @@ fn test_try_make_response() { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .unwrap(); @@ -577,6 +579,7 @@ fn test_block_proposal_validation_timeout() { None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .unwrap(); @@ -724,6 +727,7 @@ fn replay_validation_test( None, None, None, + u64::from(DEFAULT_MAX_TENURE_BYTES), ) .unwrap();