Skip to content

POC/WIP block simulate endpoint #6346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion stacks-node/src/tests/nakamoto_integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3135,7 +3135,7 @@ fn block_proposal_api_endpoint() {
.unwrap();
let burn_chain_height = miner_tenure_info.burn_tip_height;
let mut tenure_tx = builder
.tenure_begin(&burn_dbconn, &mut miner_tenure_info)
.tenure_begin(&burn_dbconn, &mut miner_tenure_info, false)
.unwrap();

let tx = make_stacks_transfer_serialized(
Expand Down
4 changes: 3 additions & 1 deletion stackslib/src/chainstate/nakamoto/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ impl NakamotoBlockBuilder {
&mut self,
burn_dbconn: &'a SortitionHandleConn,
info: &'b mut MinerTenureInfo<'a>,
simulated: bool,
) -> Result<ClarityTx<'b, 'b>, Error> {
let Some(block_commit) = info.tenure_block_commit_opt.as_ref() else {
return Err(Error::InvalidStacksBlock(
Expand Down Expand Up @@ -412,6 +413,7 @@ impl NakamotoBlockBuilder {
&self.header.pox_treatment,
block_commit,
&info.active_reward_set,
simulated,
)?;
self.matured_miner_rewards_opt = matured_miner_rewards_opt;
Ok(clarity_tx)
Expand Down Expand Up @@ -541,7 +543,7 @@ impl NakamotoBlockBuilder {
let mut miner_tenure_info =
builder.load_tenure_info(&mut chainstate, burn_dbconn, tenure_info.cause())?;
let burn_chain_height = miner_tenure_info.burn_tip_height;
let mut tenure_tx = builder.tenure_begin(burn_dbconn, &mut miner_tenure_info)?;
let mut tenure_tx = builder.tenure_begin(burn_dbconn, &mut miner_tenure_info, false)?;

let tenure_budget = tenure_tx
.block_limit()
Expand Down
149 changes: 149 additions & 0 deletions stackslib/src/chainstate/nakamoto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@
};
use super::stacks::events::StacksTransactionReceipt;
use super::stacks::{
Error as ChainstateError, StacksBlock, StacksTransaction, TenureChangePayload,

Check failure on line 65 in stackslib/src/chainstate/nakamoto/mod.rs

View workflow job for this annotation

GitHub Actions / Clippy Check

unused import: `crate::burnchains::db::BurnchainHeaderReader`
TokenTransferMemo, TransactionPayload, TransactionVersion,
};
use crate::burnchains::db::BurnchainHeaderReader;
use crate::burnchains::{PoxConstants, Txid};
use crate::chainstate::burn::db::sortdb::SortitionDB;
use crate::chainstate::burn::operations::LeaderBlockCommitOp;
Expand Down Expand Up @@ -2989,7 +2990,7 @@
///
/// `tip_block_id` is the chain tip from which to perform the query.
pub fn get_tenure_start_block_header<SDBI: StacksDBIndexed>(
chainstate_conn: &mut SDBI,

Check warning on line 2993 in stackslib/src/chainstate/nakamoto/mod.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/stackslib/src/chainstate/nakamoto/mod.rs
tip_block_id: &StacksBlockId,
consensus_hash: &ConsensusHash,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
Expand Down Expand Up @@ -3109,6 +3110,149 @@
Ok(Vec::from_iter(epoch2_x))
}

pub fn get_block_headers_in_tenure_at_burnview(
db: &Connection,
tenure_id: &ConsensusHash,
burn_view: &ConsensusHash,
) -> Result<Vec<StacksHeaderInfo>, ChainstateError> {
// see if we have a nakamoto block in this tenure
let qry = "
SELECT *
FROM nakamoto_block_headers
WHERE consensus_hash = ?1
AND burn_view = ?2
ORDER BY block_height
";
let args = params![tenure_id, burn_view];
let out = query_rows(db, qry, args)?;
if !out.is_empty() {
return Ok(out);
}
Err(ChainstateError::NoSuchBlockError)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest block in a given tenure (identified by its consensus hash) with a canonical
/// burn_view (i.e., burn_view on the canonical sortition fork)
pub fn find_highest_known_block_header_in_tenure_by_height(
chainstate: &StacksChainState,
sort_db: &SortitionDB,
tenure_height: u64,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
let chainstate_db_conn = chainstate.db();

let candidates = Self::get_highest_known_block_header_in_tenure_by_height_at_each_burnview(
chainstate_db_conn,
tenure_height,
)?;

let canonical_sortition_handle = sort_db.index_handle_at_tip();
for candidate in candidates.into_iter() {
let Some(ref candidate_ch) = candidate.burn_view else {
// this is an epoch 2.x header, no burn view to check
return Ok(Some(candidate));
};
let in_canonical_fork = canonical_sortition_handle.processed_block(&candidate_ch)?;
if in_canonical_fork {
return Ok(Some(candidate));
}
}

// did not find any blocks in the tenure
Ok(None)
}

pub fn find_highest_known_block_header_in_tenure_by_block_hash(
chainstate: &StacksChainState,
sort_db: &SortitionDB,
tenure_block_hash: &BurnchainHeaderHash,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
let chainstate_db_conn = chainstate.db();

let candidates = Self::get_highest_known_block_header_in_tenure_by_block_hash_at_each_burnview(
chainstate_db_conn,
tenure_block_hash,
)?;

let canonical_sortition_handle = sort_db.index_handle_at_tip();
for candidate in candidates.into_iter() {
let Some(ref candidate_ch) = candidate.burn_view else {
// this is an epoch 2.x header, no burn view to check
return Ok(Some(candidate));
};
let in_canonical_fork = canonical_sortition_handle.processed_block(&candidate_ch)?;
if in_canonical_fork {
return Ok(Some(candidate));
}
}

// did not find any blocks in the tenure
Ok(None)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest blocks in a given tenure (identified by its consensus hash) at each burn view
/// active in that tenure. If there are ties at a given burn view, they will both be returned
fn get_highest_known_block_header_in_tenure_by_height_at_each_burnview(
db: &Connection,
tenure_height: u64,
) -> Result<Vec<StacksHeaderInfo>, ChainstateError> {
// see if we have a nakamoto block in this tenure
let qry = "
SELECT h.*
FROM nakamoto_block_headers h
JOIN (
SELECT burn_view, MAX(block_height) AS max_height
FROM nakamoto_block_headers
WHERE burn_header_height = ?1
GROUP BY burn_view
) maxed
ON h.burn_view = maxed.burn_view
AND h.block_height = maxed.max_height
WHERE h.burn_header_height = ?1
ORDER BY h.block_height DESC, h.timestamp
";
let args = params![tenure_height];
let out = query_rows(db, qry, args)?;
if !out.is_empty() {
return Ok(out);
}

Err(ChainstateError::NoSuchBlockError)
}

fn get_highest_known_block_header_in_tenure_by_block_hash_at_each_burnview(
db: &Connection,
tenure_block_hash: &BurnchainHeaderHash,
) -> Result<Vec<StacksHeaderInfo>, ChainstateError> {
// see if we have a nakamoto block in this tenure
let qry = "
SELECT h.*
FROM nakamoto_block_headers h
JOIN (
SELECT burn_view, MAX(block_height) AS max_height
FROM nakamoto_block_headers
WHERE burn_header_hash = ?1
GROUP BY burn_view
) maxed
ON h.burn_view = maxed.burn_view
AND h.block_height = maxed.max_height
WHERE h.burn_header_hash = ?1
ORDER BY h.block_height DESC, h.timestamp
";
let args = params![tenure_block_hash];
let out = query_rows(db, qry, args)?;
if !out.is_empty() {
return Ok(out);
}

Err(ChainstateError::NoSuchBlockError)
}

/// Get the VRF proof for a Stacks block.
/// For Nakamoto blocks, this is the VRF proof contained in the coinbase of the tenure-start
/// block of the given tenure identified by the consensus hash.
Expand Down Expand Up @@ -3978,6 +4122,7 @@
block_bitvec: &BitVec<4000>,
tenure_block_commit: &LeaderBlockCommitOp,
active_reward_set: &RewardSet,
simulated: bool,
) -> 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)?;
Expand All @@ -3995,6 +4140,7 @@
new_tenure,
coinbase_height,
tenure_extend,
simulated,
)
}

Expand Down Expand Up @@ -4093,6 +4239,7 @@
block_bitvec,
&tenure_block_commit,
active_reward_set,
false,
)
}

Expand Down Expand Up @@ -4139,6 +4286,7 @@
new_tenure: bool,
coinbase_height: u64,
tenure_extend: bool,
simulated: 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 @@ -4196,6 +4344,7 @@
&parent_header_hash,
&MINER_BLOCK_CONSENSUS_HASH,
&MINER_BLOCK_HEADER_HASH,
simulated,
);

// now that we have access to the ClarityVM, we can account for reward deductions from
Expand Down
1 change: 1 addition & 0 deletions 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 Down
2 changes: 1 addition & 1 deletion stackslib/src/chainstate/nakamoto/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ impl TestStacksNode {
let mut miner_tenure_info =
builder.load_tenure_info(&mut chainstate, burn_dbconn, tenure_cause)?;
let burn_chain_height = miner_tenure_info.burn_tip_height;
let mut tenure_tx = builder.tenure_begin(burn_dbconn, &mut miner_tenure_info)?;
let mut tenure_tx = builder.tenure_begin(burn_dbconn, &mut miner_tenure_info, false)?;
for tx in txs.into_iter() {
let tx_len = tx.tx_len();
match builder.try_mine_tx_with_len(
Expand Down
1 change: 1 addition & 0 deletions stackslib/src/chainstate/stacks/db/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5128,6 +5128,7 @@ impl StacksChainState {
&parent_header_hash,
&MINER_BLOCK_CONSENSUS_HASH,
&MINER_BLOCK_HEADER_HASH,
false,
);

clarity_tx.reset_cost(parent_block_cost.clone());
Expand Down
66 changes: 59 additions & 7 deletions stackslib/src/chainstate/stacks/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,10 @@
pub fn block_hash(&self) -> BlockHeaderHash {
match &self {
StacksBlockHeaderTypes::Epoch2(x) => x.block_hash(),
StacksBlockHeaderTypes::Nakamoto(x) => x.block_hash(),

Check warning on line 304 in stackslib/src/chainstate/stacks/db/mod.rs

View workflow job for this annotation

GitHub Actions / Rust Format

Diff in /home/runner/work/stacks-core/stacks-core/stackslib/src/chainstate/stacks/db/mod.rs
}
}

pub fn is_first_mined(&self) -> bool {
match self {
StacksBlockHeaderTypes::Epoch2(x) => x.is_first_mined(),
Expand Down Expand Up @@ -2023,6 +2023,7 @@
parent_block: &BlockHeaderHash,
new_consensus_hash: &ConsensusHash,
new_block: &BlockHeaderHash,
simulated: bool,
) -> ClarityTx<'a, 'b> {
let conf = chainstate_tx.config.clone();
StacksChainState::inner_clarity_tx_begin(
Expand All @@ -2034,6 +2035,7 @@
parent_block,
new_consensus_hash,
new_block,
simulated,
)
}

Expand All @@ -2057,6 +2059,7 @@
parent_block,
new_consensus_hash,
new_block,
false,
)
}

Expand Down Expand Up @@ -2122,6 +2125,45 @@
self.clarity_state.with_marf(f)
}

pub fn with_simulated_clarity_tx<F, R>(
&mut self,
burn_dbconn: &dyn BurnStateDB,
parent_block_id: &StacksBlockId,
new_block_id: &StacksBlockId,
to_do: F,
) -> Option<R>
where
F: FnOnce(&mut ClarityTx) -> R,
{
match NakamotoChainState::get_block_header(self.db(), parent_block_id) {
Ok(Some(_)) => {}
Ok(None) => {
return None;
}
Err(e) => {
warn!("Failed to query for {}: {:?}", parent_block_id, &e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this doesn't work for Stacks 2.x replay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently yes, but I would like to cover pre-nakamoto too

return None;
}
}

let dbconfig = self.config();

let conn = self.clarity_state.begin_simulated_block(
parent_block_id,
new_block_id,
&self.state_index,
burn_dbconn,
);

let mut clarity_tx = ClarityTx {
block: conn,
config: dbconfig,
};
let result = to_do(&mut clarity_tx);
clarity_tx.rollback_block();
Some(result)
}

/// Run to_do on the state of the Clarity VM at the given chain tip.
/// Returns Some(x: R) if the given parent_tip exists.
/// Returns None if not
Expand Down Expand Up @@ -2287,6 +2329,7 @@
parent_block: &BlockHeaderHash,
new_consensus_hash: &ConsensusHash,
new_block: &BlockHeaderHash,
simulated: bool,
) -> ClarityTx<'a, 'b> {
// mix consensus hash and stacks block header hash together, since the stacks block hash
// it not guaranteed to be globally unique (but the pair is)
Expand Down Expand Up @@ -2314,12 +2357,21 @@
parent_block
);

let inner_clarity_tx = clarity_instance.begin_block(
&parent_index_block,
&new_index_block,
headers_db,
burn_dbconn,
);
let inner_clarity_tx = if simulated {
clarity_instance.begin_simulated_block(
&parent_index_block,
&new_index_block,
headers_db,
burn_dbconn,
)
} else {
clarity_instance.begin_block(
&parent_index_block,
&new_index_block,
headers_db,
burn_dbconn,
)
};

test_debug!("Got clarity TX!");
ClarityTx {
Expand Down
Loading
Loading