|
14 | 14 | // You should have received a copy of the GNU General Public License
|
15 | 15 | // along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 | 16 |
|
17 |
| -use std::collections::HashMap; |
| 17 | +use std::collections::{HashMap, HashSet}; |
18 | 18 | use std::fs;
|
19 | 19 | use std::ops::{Deref, DerefMut, Range};
|
20 | 20 | use std::path::PathBuf;
|
@@ -104,7 +104,9 @@ use crate::clarity_vm::clarity::{
|
104 | 104 | ClarityInstance, ClarityTransactionConnection, Error as ClarityError, PreCommitClarityBlock,
|
105 | 105 | };
|
106 | 106 | use crate::clarity_vm::database::SortitionDBRef;
|
107 |
| -use crate::core::{BOOT_BLOCK_HASH, NAKAMOTO_SIGNER_BLOCK_APPROVAL_THRESHOLD}; |
| 107 | +use crate::core::{ |
| 108 | + BOOT_BLOCK_HASH, BURNCHAIN_TX_SEARCH_WINDOW, NAKAMOTO_SIGNER_BLOCK_APPROVAL_THRESHOLD, |
| 109 | +}; |
108 | 110 | use crate::net::stackerdb::{StackerDBConfig, MINER_SLOT_COUNT};
|
109 | 111 | use crate::net::Error as net_error;
|
110 | 112 | use crate::util_lib::boot;
|
@@ -3251,14 +3253,18 @@ impl NakamotoChainState {
|
3251 | 3253 | if let Some(block_reward) = block_reward {
|
3252 | 3254 | StacksChainState::insert_miner_payment_schedule(headers_tx.deref_mut(), block_reward)?;
|
3253 | 3255 | }
|
3254 |
| - StacksChainState::store_burnchain_txids( |
3255 |
| - headers_tx.deref(), |
3256 |
| - &index_block_hash, |
3257 |
| - burn_stack_stx_ops, |
3258 |
| - burn_transfer_stx_ops, |
3259 |
| - burn_delegate_stx_ops, |
3260 |
| - burn_vote_for_aggregate_key_ops, |
3261 |
| - )?; |
| 3256 | + |
| 3257 | + // NOTE: this is a no-op if the block isn't a tenure-start block |
| 3258 | + if new_tenure { |
| 3259 | + StacksChainState::store_burnchain_txids( |
| 3260 | + headers_tx.deref(), |
| 3261 | + &index_block_hash, |
| 3262 | + burn_stack_stx_ops, |
| 3263 | + burn_transfer_stx_ops, |
| 3264 | + burn_delegate_stx_ops, |
| 3265 | + burn_vote_for_aggregate_key_ops, |
| 3266 | + )?; |
| 3267 | + } |
3262 | 3268 |
|
3263 | 3269 | if let Some(matured_miner_payouts) = mature_miner_payouts_opt {
|
3264 | 3270 | let rewarded_miner_block_id = StacksBlockId::new(
|
@@ -3360,6 +3366,145 @@ impl NakamotoChainState {
|
3360 | 3366 | .map_err(ChainstateError::from)
|
3361 | 3367 | }
|
3362 | 3368 |
|
| 3369 | + /// Find all of the TXIDs of Stacks-on-burnchain operations processed in the given Stacks fork. |
| 3370 | + /// In Nakamoto, we index these TXIDs by the tenure-start block ID |
| 3371 | + pub(crate) fn get_burnchain_txids_in_ancestor_tenures<SDBI: StacksDBIndexed>( |
| 3372 | + conn: &mut SDBI, |
| 3373 | + tip_consensus_hash: &ConsensusHash, |
| 3374 | + tip_block_hash: &BlockHeaderHash, |
| 3375 | + search_window: u64, |
| 3376 | + ) -> Result<HashSet<Txid>, ChainstateError> { |
| 3377 | + let tip = StacksBlockId::new(tip_consensus_hash, tip_block_hash); |
| 3378 | + let mut cursor = tip_consensus_hash.clone(); |
| 3379 | + let mut ret = HashSet::new(); |
| 3380 | + for _ in 0..search_window { |
| 3381 | + let Some(tenure_start_block_id) = conn.get_tenure_start_block_id(&tip, &cursor)? else { |
| 3382 | + break; |
| 3383 | + }; |
| 3384 | + let txids = StacksChainState::get_burnchain_txids_for_block( |
| 3385 | + conn.sqlite(), |
| 3386 | + &tenure_start_block_id, |
| 3387 | + )?; |
| 3388 | + ret.extend(txids.into_iter()); |
| 3389 | + |
| 3390 | + let Some(parent_tenure_id) = conn.get_parent_tenure_consensus_hash(&tip, &cursor)? |
| 3391 | + else { |
| 3392 | + break; |
| 3393 | + }; |
| 3394 | + |
| 3395 | + cursor = parent_tenure_id; |
| 3396 | + } |
| 3397 | + Ok(ret) |
| 3398 | + } |
| 3399 | + |
| 3400 | + /// Get all Stacks-on-burnchain operations that we haven't processed yet |
| 3401 | + pub(crate) fn get_stacks_on_burnchain_operations<SDBI: StacksDBIndexed>( |
| 3402 | + conn: &mut SDBI, |
| 3403 | + parent_consensus_hash: &ConsensusHash, |
| 3404 | + parent_block_hash: &BlockHeaderHash, |
| 3405 | + sortdb_conn: &Connection, |
| 3406 | + burn_tip: &BurnchainHeaderHash, |
| 3407 | + burn_tip_height: u64, |
| 3408 | + ) -> Result< |
| 3409 | + ( |
| 3410 | + Vec<StackStxOp>, |
| 3411 | + Vec<TransferStxOp>, |
| 3412 | + Vec<DelegateStxOp>, |
| 3413 | + Vec<VoteForAggregateKeyOp>, |
| 3414 | + ), |
| 3415 | + ChainstateError, |
| 3416 | + > { |
| 3417 | + let cur_epoch = SortitionDB::get_stacks_epoch(sortdb_conn, burn_tip_height)? |
| 3418 | + .expect("FATAL: no epoch defined for current burnchain tip height"); |
| 3419 | + |
| 3420 | + // only consider transactions in Stacks 3.0 |
| 3421 | + if cur_epoch.epoch_id < StacksEpochId::Epoch30 { |
| 3422 | + return Ok((vec![], vec![], vec![], vec![])); |
| 3423 | + } |
| 3424 | + |
| 3425 | + let epoch_start_height = cur_epoch.start_height; |
| 3426 | + |
| 3427 | + let search_window: u8 = |
| 3428 | + if epoch_start_height + u64::from(BURNCHAIN_TX_SEARCH_WINDOW) > burn_tip_height { |
| 3429 | + burn_tip_height |
| 3430 | + .saturating_sub(epoch_start_height) |
| 3431 | + .try_into() |
| 3432 | + .expect("FATAL: search window exceeds u8") |
| 3433 | + } else { |
| 3434 | + BURNCHAIN_TX_SEARCH_WINDOW |
| 3435 | + }; |
| 3436 | + |
| 3437 | + debug!( |
| 3438 | + "Search the last {} sortitions for burnchain-hosted stacks operations before {} ({})", |
| 3439 | + search_window, burn_tip, burn_tip_height |
| 3440 | + ); |
| 3441 | + let ancestor_burnchain_header_hashes = SortitionDB::get_ancestor_burnchain_header_hashes( |
| 3442 | + sortdb_conn, |
| 3443 | + burn_tip, |
| 3444 | + search_window.into(), |
| 3445 | + )?; |
| 3446 | + let processed_burnchain_txids = |
| 3447 | + NakamotoChainState::get_burnchain_txids_in_ancestor_tenures( |
| 3448 | + conn, |
| 3449 | + parent_consensus_hash, |
| 3450 | + parent_block_hash, |
| 3451 | + search_window.into(), |
| 3452 | + )?; |
| 3453 | + |
| 3454 | + // Find the *new* transactions -- the ones that we *haven't* seen in this Stacks |
| 3455 | + // fork yet. Note that we search for the ones that we have seen by searching back |
| 3456 | + // `BURNCHAIN_TX_SEARCH_WINDOW` tenures, whose sortitions may span more |
| 3457 | + // than `BURNCHAIN_TX_SEARCH_WINDOW` burnchain blocks. The inclusion of txids for |
| 3458 | + // burnchain transactions in the latter query is not a problem, because these txids |
| 3459 | + // are used to *exclude* transactions from the last `BURNCHAIN_TX_SEARCH_WINDOW` |
| 3460 | + // burnchain blocks. These excluded txids, if they were mined outside of this |
| 3461 | + // window, are *already* excluded. |
| 3462 | + |
| 3463 | + let mut all_stacking_burn_ops = vec![]; |
| 3464 | + let mut all_transfer_burn_ops = vec![]; |
| 3465 | + let mut all_delegate_burn_ops = vec![]; |
| 3466 | + let mut all_vote_for_aggregate_key_ops = vec![]; |
| 3467 | + |
| 3468 | + // go from oldest burn header hash to newest |
| 3469 | + for ancestor_bhh in ancestor_burnchain_header_hashes.iter().rev() { |
| 3470 | + let stacking_ops = SortitionDB::get_stack_stx_ops(sortdb_conn, ancestor_bhh)?; |
| 3471 | + let transfer_ops = SortitionDB::get_transfer_stx_ops(sortdb_conn, ancestor_bhh)?; |
| 3472 | + let delegate_ops = SortitionDB::get_delegate_stx_ops(sortdb_conn, ancestor_bhh)?; |
| 3473 | + let vote_for_aggregate_key_ops = |
| 3474 | + SortitionDB::get_vote_for_aggregate_key_ops(sortdb_conn, ancestor_bhh)?; |
| 3475 | + |
| 3476 | + for stacking_op in stacking_ops.into_iter() { |
| 3477 | + if !processed_burnchain_txids.contains(&stacking_op.txid) { |
| 3478 | + all_stacking_burn_ops.push(stacking_op); |
| 3479 | + } |
| 3480 | + } |
| 3481 | + |
| 3482 | + for transfer_op in transfer_ops.into_iter() { |
| 3483 | + if !processed_burnchain_txids.contains(&transfer_op.txid) { |
| 3484 | + all_transfer_burn_ops.push(transfer_op); |
| 3485 | + } |
| 3486 | + } |
| 3487 | + |
| 3488 | + for delegate_op in delegate_ops.into_iter() { |
| 3489 | + if !processed_burnchain_txids.contains(&delegate_op.txid) { |
| 3490 | + all_delegate_burn_ops.push(delegate_op); |
| 3491 | + } |
| 3492 | + } |
| 3493 | + |
| 3494 | + for vote_op in vote_for_aggregate_key_ops.into_iter() { |
| 3495 | + if !processed_burnchain_txids.contains(&vote_op.txid) { |
| 3496 | + all_vote_for_aggregate_key_ops.push(vote_op); |
| 3497 | + } |
| 3498 | + } |
| 3499 | + } |
| 3500 | + Ok(( |
| 3501 | + all_stacking_burn_ops, |
| 3502 | + all_transfer_burn_ops, |
| 3503 | + all_delegate_burn_ops, |
| 3504 | + all_vote_for_aggregate_key_ops, |
| 3505 | + )) |
| 3506 | + } |
| 3507 | + |
3363 | 3508 | /// Begin block-processing and return all of the pre-processed state within a
|
3364 | 3509 | /// `SetupBlockResult`.
|
3365 | 3510 | ///
|
@@ -3432,10 +3577,11 @@ impl NakamotoChainState {
|
3432 | 3577 | };
|
3433 | 3578 |
|
3434 | 3579 | let (stacking_burn_ops, transfer_burn_ops, delegate_burn_ops, vote_for_agg_key_ops) =
|
3435 |
| - if new_tenure || tenure_extend { |
3436 |
| - StacksChainState::get_stacking_and_transfer_and_delegate_burn_ops( |
3437 |
| - chainstate_tx, |
3438 |
| - &parent_index_hash, |
| 3580 | + if new_tenure { |
| 3581 | + NakamotoChainState::get_stacks_on_burnchain_operations( |
| 3582 | + chainstate_tx.as_tx(), |
| 3583 | + &parent_consensus_hash, |
| 3584 | + &parent_header_hash, |
3439 | 3585 | sortition_dbconn.sqlite_conn(),
|
3440 | 3586 | &burn_header_hash,
|
3441 | 3587 | burn_header_height.into(),
|
|
0 commit comments