Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
58b8231
Merge pull request #5881 from stacks-network/release/3.1.0.0.7
obycode Mar 3, 2025
91c18cb
Merge pull request #6012 from stacks-network/release/3.1.0.0.8
wileyj Apr 16, 2025
da3ea6f
Merge pull request #6091 from stacks-network/release/3.1.0.0.9
wileyj May 9, 2025
233927f
Merge pull request #6131 from wileyj/fix/clippy_errors
obycode May 23, 2025
ce4ff94
Merge branch 'master' into release/3.1.0.0.11
wileyj Jun 2, 2025
7fee690
Merge pull request #6154 from stacks-network/release/3.1.0.0.11
wileyj Jun 2, 2025
7d8b675
Merge pull request #6179 from stacks-network/release/3.1.0.0.12
wileyj Jun 11, 2025
a5587d1
Merge pull request #6246 from stacks-network/release/3.1.0.0.13
wileyj Jul 3, 2025
4349d1e
feat: add support for producing ephemeral blocks
jcnelson Aug 11, 2025
708d279
feat: add support for beginning ephemeral blocks
jcnelson Aug 11, 2025
7b0e79c
chore: API sync
jcnelson Aug 11, 2025
4fd470d
feat: support for instantiating a ClarityTx for an ephemeral block
jcnelson Aug 11, 2025
2f629b8
chore: public function to get the sqlite db path
jcnelson Aug 11, 2025
909e976
feat: new function for instantiating an ephemeral ClarityBlockConnection
jcnelson Aug 11, 2025
fa631ea
feat: redefine WritableMarfStore as an enum of a persistent MARF stor…
jcnelson Aug 11, 2025
60e1725
chore: WIP unit test coverage for ephemeral MARF store
jcnelson Aug 11, 2025
8be1135
fix: correct ephemeral open-tip height
moodmosaic Aug 11, 2025
1eb29af
Merge pull request #1 from moodmosaic/test/shadow-marf
jcnelson Aug 12, 2025
7f6b723
chore: cargo fmt
jcnelson Aug 14, 2025
a07469a
Merge remote-tracking branch 'stacks-network/develop' into feat/shado…
jcnelson Aug 14, 2025
da5f19c
Merge remote-tracking branch 'origin' into feat/shadow-marf
jcnelson Aug 21, 2025
e42ee4c
fix: revert debug verbosity and fix off-by-one error for getting a bl…
jcnelson Aug 21, 2025
22c0196
feat: add test with Nakamoto block replay
jcnelson Aug 21, 2025
b124fd4
feat: if a transaction has an unexpected vm_error, report it when pan…
jcnelson Aug 21, 2025
ae90917
Merge branch 'develop' into feat/shadow-marf
jcnelson Aug 21, 2025
2665922
Merge remote-tracking branch 'origin/develop' into feat/shadow-marf
jcnelson Aug 27, 2025
c6b8d92
feat: add ClarityMarfStore and ClarityMarfStoreTransaction traits, wh…
jcnelson Aug 29, 2025
c84a016
Merge branch 'feat/shadow-marf' of https://github.com/jcnelson/stacks…
jcnelson Aug 29, 2025
5e3df0b
Merge branch 'develop' into feat/shadow-marf
jcnelson Aug 29, 2025
93e8935
clippy: don't hide elided lifetime
jcnelson Aug 29, 2025
4d2ea3f
Merge branch 'feat/shadow-marf' of https://github.com/jcnelson/stacks…
jcnelson Aug 29, 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
84 changes: 61 additions & 23 deletions stackslib/src/chainstate/nakamoto/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub struct MinerTenureInfo<'a> {
pub cause: Option<TenureChangeCause>,
pub active_reward_set: boot::RewardSet,
pub tenure_block_commit_opt: Option<LeaderBlockCommitOp>,
pub ephemeral: bool,
}

/// Structure returned from `NakamotoBlockBuilder::build_nakamoto_block` with
Expand Down Expand Up @@ -216,14 +217,27 @@ impl NakamotoBlockBuilder {
/// This function should be called before `tenure_begin`.
/// It creates a MinerTenureInfo struct which owns connections to the chainstate and sortition
/// DBs, so that block-processing is guaranteed to terminate before the lives of these handles
/// expire.
/// expire. This is used for normal blocks.
pub fn load_tenure_info<'a>(
&self,
chainstate: &'a mut StacksChainState,
burn_dbconn: &'a SortitionHandleConn,
cause: Option<TenureChangeCause>,
) -> Result<MinerTenureInfo<'a>, Error> {
self.inner_load_tenure_info(chainstate, burn_dbconn, cause, false)
self.inner_load_tenure_info(chainstate, burn_dbconn, cause, false, false)
}

/// This function should be called before `tenure_begin`.
/// It creates a MinerTenureInfo struct which owns connections to the chainstate and sortition
/// DBs, so that block-processing is guaranteed to terminate before the lives of these handles
/// expire. This is used for ephemeral blocks
pub fn load_ephemeral_tenure_info<'a>(
&self,
chainstate: &'a mut StacksChainState,
burn_dbconn: &'a SortitionHandleConn,
cause: Option<TenureChangeCause>,
) -> Result<MinerTenureInfo<'a>, Error> {
self.inner_load_tenure_info(chainstate, burn_dbconn, cause, false, true)
}

/// This function should be called before `tenure_begin`.
Expand All @@ -236,8 +250,9 @@ impl NakamotoBlockBuilder {
burn_dbconn: &'a SortitionHandleConn,
cause: Option<TenureChangeCause>,
shadow_block: bool,
ephemeral: bool,
) -> Result<MinerTenureInfo<'a>, Error> {
debug!("Nakamoto miner tenure begin"; "shadow" => shadow_block, "tenure_change" => ?cause);
debug!("Nakamoto miner tenure begin"; "shadow" => shadow_block, "tenure_change" => ?cause, "ephemeral" => ephemeral);

let Some(tenure_election_sn) =
SortitionDB::get_block_snapshot_consensus(burn_dbconn, &self.header.consensus_hash)?
Expand Down Expand Up @@ -372,6 +387,7 @@ impl NakamotoBlockBuilder {
coinbase_height,
active_reward_set,
tenure_block_commit_opt,
ephemeral,
})
}

Expand All @@ -395,24 +411,45 @@ impl NakamotoBlockBuilder {
clarity_tx,
matured_miner_rewards_opt,
..
} = NakamotoChainState::setup_block(
&mut info.chainstate_tx,
info.clarity_instance,
burn_dbconn,
burn_dbconn.context.first_block_height,
&burn_dbconn.context.pox_constants,
info.parent_consensus_hash,
info.parent_header_hash,
info.parent_burn_block_height,
info.burn_tip,
info.burn_tip_height,
info.cause == Some(TenureChangeCause::BlockFound),
info.coinbase_height,
info.cause == Some(TenureChangeCause::Extended),
&self.header.pox_treatment,
block_commit,
&info.active_reward_set,
)?;
} = if info.ephemeral {
NakamotoChainState::setup_ephemeral_block(
&mut info.chainstate_tx,
info.clarity_instance,
burn_dbconn,
burn_dbconn.context.first_block_height,
&burn_dbconn.context.pox_constants,
info.parent_consensus_hash,
info.parent_header_hash,
info.parent_burn_block_height,
info.burn_tip,
info.burn_tip_height,
info.cause == Some(TenureChangeCause::BlockFound),
info.coinbase_height,
info.cause == Some(TenureChangeCause::Extended),
&self.header.pox_treatment,
block_commit,
&info.active_reward_set,
)
} else {
NakamotoChainState::setup_block(
&mut info.chainstate_tx,
info.clarity_instance,
burn_dbconn,
burn_dbconn.context.first_block_height,
&burn_dbconn.context.pox_constants,
info.parent_consensus_hash,
info.parent_header_hash,
info.parent_burn_block_height,
info.burn_tip,
info.burn_tip_height,
info.cause == Some(TenureChangeCause::BlockFound),
info.coinbase_height,
info.cause == Some(TenureChangeCause::Extended),
&self.header.pox_treatment,
block_commit,
&info.active_reward_set,
)
}?;
self.matured_miner_rewards_opt = matured_miner_rewards_opt;
Ok(clarity_tx)
}
Expand Down Expand Up @@ -458,10 +495,11 @@ impl NakamotoBlockBuilder {
};

test_debug!(
"\n\nMined Nakamoto block {}, {} transactions, state root is {}\n",
"\n\nMined Nakamoto block {}, {} transactions, state root is {}\nBlock: {:?}",
block.header.block_hash(),
block.txs.len(),
state_root_hash
state_root_hash,
&block
);

debug!(
Expand Down
76 changes: 66 additions & 10 deletions stackslib/src/chainstate/nakamoto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3782,7 +3782,8 @@ impl NakamotoChainState {
}

/// Begin block-processing for a normal block and return all of the pre-processed state within a
/// `SetupBlockResult`. Used by the Nakamoto miner, and called by Self::setup_normal_block()
/// `SetupBlockResult`. Used by the Nakamoto miner, and called by
/// Self::setup_normal_block_processing()
pub fn setup_block<'a, 'b>(
chainstate_tx: &'b mut ChainstateTx,
clarity_instance: &'a mut ClarityInstance,
Expand Down Expand Up @@ -3817,6 +3818,47 @@ impl NakamotoChainState {
new_tenure,
coinbase_height,
tenure_extend,
false,
)
}

/// Begin block-processing for a replay of a normal block and return all of the pre-processed state within a
/// `SetupBlockResult`. Used by the block replay logic, and called by Self::setup_normal_block_processing()
pub fn setup_ephemeral_block<'a, 'b>(
chainstate_tx: &'b mut ChainstateTx,
clarity_instance: &'a mut ClarityInstance,
sortition_dbconn: &'b dyn SortitionDBRef,
first_block_height: u64,
pox_constants: &PoxConstants,
parent_consensus_hash: ConsensusHash,
parent_header_hash: BlockHeaderHash,
parent_burn_height: u32,
burn_header_hash: BurnchainHeaderHash,
burn_header_height: u32,
new_tenure: bool,
coinbase_height: u64,
tenure_extend: bool,
block_bitvec: &BitVec<4000>,
tenure_block_commit: &LeaderBlockCommitOp,
active_reward_set: &RewardSet,
) -> Result<SetupBlockResult<'a, 'b>, ChainstateError> {
// this block's bitvec header must match the miner's block commit punishments
Self::check_pox_bitvector(block_bitvec, tenure_block_commit, active_reward_set)?;
Self::inner_setup_block(
chainstate_tx,
clarity_instance,
sortition_dbconn,
first_block_height,
pox_constants,
parent_consensus_hash,
parent_header_hash,
parent_burn_height,
burn_header_hash,
burn_header_height,
new_tenure,
coinbase_height,
tenure_extend,
true,
)
}

Expand Down Expand Up @@ -3942,6 +3984,7 @@ impl NakamotoChainState {
/// * coinbase_height: the number of tenures that this block confirms (including epoch2 blocks)
/// (this is equivalent to the number of coinbases)
/// * tenure_extend: whether or not to reset the tenure's ongoing execution cost
/// * ephemeral: whether or not to begin an ephemeral block (i.e. which won't hit disk)
///
/// Returns clarity_tx, list of receipts, microblock execution cost,
/// microblock fees, microblock burns, list of microblock tx receipts,
Expand All @@ -3961,6 +4004,7 @@ impl NakamotoChainState {
new_tenure: bool,
coinbase_height: u64,
tenure_extend: bool,
ephemeral: bool,
) -> Result<SetupBlockResult<'a, 'b>, ChainstateError> {
let parent_index_hash = StacksBlockId::new(&parent_consensus_hash, &parent_header_hash);
let parent_sortition_id = sortition_dbconn
Expand Down Expand Up @@ -4010,15 +4054,27 @@ impl NakamotoChainState {
parent_cost_total
};

let mut clarity_tx = StacksChainState::chainstate_block_begin(
chainstate_tx,
clarity_instance,
sortition_dbconn.as_burn_state_db(),
&parent_consensus_hash,
&parent_header_hash,
&MINER_BLOCK_CONSENSUS_HASH,
&MINER_BLOCK_HEADER_HASH,
);
let mut clarity_tx = if ephemeral {
StacksChainState::chainstate_ephemeral_block_begin(
chainstate_tx,
clarity_instance,
sortition_dbconn.as_burn_state_db(),
&parent_consensus_hash,
&parent_header_hash,
&MINER_BLOCK_CONSENSUS_HASH,
&MINER_BLOCK_HEADER_HASH,
)
} else {
StacksChainState::chainstate_block_begin(
chainstate_tx,
clarity_instance,
sortition_dbconn.as_burn_state_db(),
&parent_consensus_hash,
&parent_header_hash,
&MINER_BLOCK_CONSENSUS_HASH,
&MINER_BLOCK_HEADER_HASH,
)
};

// now that we have access to the ClarityVM, we can account for reward deductions from
// PoisonMicroblocks if we have new rewards scheduled
Expand Down
3 changes: 2 additions & 1 deletion stackslib/src/chainstate/nakamoto/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ impl NakamotoChainState {
new_tenure,
coinbase_height,
tenure_extend,
false,
)
}
}
Expand All @@ -431,7 +432,7 @@ impl NakamotoBlockBuilder {
burn_dbconn: &'a SortitionHandleConn,
cause: Option<TenureChangeCause>,
) -> Result<MinerTenureInfo<'a>, Error> {
self.inner_load_tenure_info(chainstate, burn_dbconn, cause, true)
self.inner_load_tenure_info(chainstate, burn_dbconn, cause, true, false)
}

/// Begin/resume mining a shadow tenure's transactions.
Expand Down
20 changes: 11 additions & 9 deletions stackslib/src/chainstate/stacks/boot/contract_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ use crate::chainstate::stacks::boot::{
use crate::chainstate::stacks::index::ClarityMarfTrieId;
use crate::chainstate::stacks::{C32_ADDRESS_VERSION_TESTNET_SINGLESIG, *};
use crate::clarity_vm::clarity::{ClarityBlockConnection, Error as ClarityError};
use crate::clarity_vm::database::marf::{MarfedKV, WritableMarfStore};
use crate::clarity_vm::database::marf::{
ClarityMarfStore, ClarityMarfStoreTransaction, MarfedKV, WritableMarfStore,
};
use crate::core::{
StacksEpoch, StacksEpochId, BITCOIN_REGTEST_FIRST_BLOCK_HASH,
BITCOIN_REGTEST_FIRST_BLOCK_HEIGHT, BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP,
Expand Down Expand Up @@ -141,10 +143,10 @@ impl ClarityTestSim {
F: FnOnce(&mut ClarityBlockConnection) -> R,
{
let r = {
let mut store = self.marf.begin(
let mut store: Box<dyn WritableMarfStore> = Box::new(self.marf.begin(
&StacksBlockId(test_sim_height_to_hash(self.block_height, self.fork)),
&StacksBlockId(test_sim_height_to_hash(self.block_height + 1, self.fork)),
);
));

self.block_height += 1;
if new_tenure {
Expand Down Expand Up @@ -193,10 +195,10 @@ impl ClarityTestSim {
where
F: FnOnce(&mut OwnedEnvironment) -> R,
{
let mut store = self.marf.begin(
let mut store: Box<dyn WritableMarfStore> = Box::new(self.marf.begin(
&StacksBlockId(test_sim_height_to_hash(self.block_height, self.fork)),
&StacksBlockId(test_sim_height_to_hash(self.block_height + 1, self.fork)),
);
));

self.block_height += 1;
if new_tenure {
Expand Down Expand Up @@ -240,8 +242,8 @@ impl ClarityTestSim {
self.execute_next_block_with_tenure(true, f)
}

fn check_and_bump_epoch(
store: &mut WritableMarfStore,
fn check_and_bump_epoch<'a>(
store: &mut Box<dyn WritableMarfStore + 'a>,
headers_db: &TestSimHeadersDB,
burn_db: &dyn BurnStateDB,
) -> StacksEpochId {
Expand All @@ -268,10 +270,10 @@ impl ClarityTestSim {
where
F: FnOnce(&mut OwnedEnvironment) -> R,
{
let mut store = self.marf.begin(
let mut store: Box<dyn WritableMarfStore> = Box::new(self.marf.begin(
&StacksBlockId(test_sim_height_to_hash(parent_height, self.fork)),
&StacksBlockId(test_sim_height_to_hash(parent_height + 1, self.fork + 1)),
);
));

let r = {
let headers_db = TestSimHeadersDB {
Expand Down
Loading