Skip to content

Commit f95f21f

Browse files
committed
feat: Allow stacks-inspect try-mine to mine multiple blocks
1 parent a1d0024 commit f95f21f

File tree

1 file changed

+165
-115
lines changed

1 file changed

+165
-115
lines changed

stackslib/src/cli.rs

Lines changed: 165 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
use std::any::type_name;
2020
use std::cell::LazyCell;
2121
use std::path::{Path, PathBuf};
22-
use std::time::Instant;
22+
use std::time::{Duration, Instant};
2323
use std::{env, fs, io, process, thread};
2424

2525
use clarity::types::chainstate::SortitionId;
26+
use clarity::vm::costs::ExecutionCost;
2627
use db::blocks::DummyEventDispatcher;
2728
use db::ChainstateTx;
2829
use regex::Regex;
@@ -484,11 +485,9 @@ pub fn command_try_mine(mut argv: Vec<String>, opts: &StacksInspectOpts) {
484485

485486
let min_fee = try_mine_opts.min_fee.unwrap_or(0);
486487
let max_time = try_mine_opts.max_time.unwrap_or(u64::MAX);
487-
let _max_blocks = try_mine_opts.max_blocks.unwrap_or(1);
488+
let max_blocks = try_mine_opts.max_blocks.unwrap_or(1);
488489
let reset_tenure = try_mine_opts.reset_tenure.unwrap_or(false);
489490

490-
let start = Instant::now();
491-
492491
let conf = opts.config.as_ref().unwrap_or(&DEFAULT_MAINNET_CONFIG);
493492

494493
let burnchain_path = format!("{db_path}/burnchain");
@@ -539,129 +538,180 @@ pub fn command_try_mine(mut argv: Vec<String>, opts: &StacksInspectOpts) {
539538
let miner_pubkey_hash = Hash160::from_node_public_key(&miner_pubkey);
540539
let miner_nonce = 0;
541540

542-
let result = match &parent_stacks_header.anchored_header {
543-
StacksBlockHeaderTypes::Epoch2(..) => {
544-
let mut tx_auth = TransactionAuth::from_p2pkh(&miner_privk).unwrap();
545-
tx_auth.set_origin_nonce(miner_nonce);
546-
547-
let mut coinbase_tx = StacksTransaction::new(
548-
TransactionVersion::Mainnet,
549-
tx_auth,
550-
TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None),
551-
);
541+
// Keep important stats for each block
542+
struct BlockStats {
543+
block_hash: BlockHeaderHash,
544+
time_elapsed: Duration,
545+
num_txs: usize,
546+
fees: u64,
547+
cost: ExecutionCost,
548+
size: u64,
549+
}
550+
let mut block_stats = vec![];
552551

553-
coinbase_tx.chain_id = conf.burnchain.chain_id;
554-
coinbase_tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
555-
let mut tx_signer = StacksTransactionSigner::new(&coinbase_tx);
556-
tx_signer.sign_origin(&miner_privk).unwrap();
557-
let coinbase_tx = tx_signer.get_tx().unwrap();
558-
559-
StacksBlockBuilder::build_anchored_block(
560-
&chainstate,
561-
&burn_dbconn,
562-
&mut mempool_db,
563-
&parent_stacks_header,
564-
chain_tip.total_burn,
565-
VRFProof::empty(),
566-
Hash160([0; 20]),
567-
&coinbase_tx,
568-
settings,
569-
None,
570-
&Burnchain::new(
571-
&burnchain_path,
572-
&burnchain.chain_name,
573-
&burnchain.network_name,
574-
)
575-
.unwrap_or_else(|e| panic!("Failed to instantiate burnchain: {e}")),
576-
)
577-
.map(|(block, cost, size)| (block.block_hash(), block.txs, cost, size))
578-
}
579-
StacksBlockHeaderTypes::Nakamoto(..) => {
580-
let tenure_info = if reset_tenure {
581-
let num_blocks_so_far = NakamotoChainState::get_nakamoto_tenure_length(
582-
chainstate.db(),
583-
&parent_block_id,
584-
)
585-
.unwrap_or_else(|e| panic!("Error getting tenure length: {e}"));
586-
let payload = TenureChangePayload {
587-
tenure_consensus_hash: parent_consensus_hash,
588-
prev_tenure_consensus_hash: parent_consensus_hash,
589-
burn_view_consensus_hash: parent_consensus_hash,
590-
previous_tenure_end: parent_block_id,
591-
previous_tenure_blocks: num_blocks_so_far,
592-
cause: TenureChangeCause::Extended,
593-
pubkey_hash: miner_pubkey_hash,
594-
};
595-
let tenure_change_tx_payload = TransactionPayload::TenureChange(payload);
552+
for _ in 0..max_blocks {
553+
// Timer for THIS block
554+
let start = Instant::now();
596555

556+
let result = match &parent_stacks_header.anchored_header {
557+
StacksBlockHeaderTypes::Epoch2(..) => {
597558
let mut tx_auth = TransactionAuth::from_p2pkh(&miner_privk).unwrap();
598559
tx_auth.set_origin_nonce(miner_nonce);
599560

600-
let version = if conf.is_mainnet() {
601-
TransactionVersion::Mainnet
561+
let mut coinbase_tx = StacksTransaction::new(
562+
TransactionVersion::Mainnet,
563+
tx_auth,
564+
TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None),
565+
);
566+
567+
coinbase_tx.chain_id = conf.burnchain.chain_id;
568+
coinbase_tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
569+
let mut tx_signer = StacksTransactionSigner::new(&coinbase_tx);
570+
tx_signer.sign_origin(&miner_privk).unwrap();
571+
let coinbase_tx = tx_signer.get_tx().unwrap();
572+
573+
StacksBlockBuilder::build_anchored_block(
574+
&chainstate,
575+
&burn_dbconn,
576+
&mut mempool_db,
577+
&parent_stacks_header,
578+
chain_tip.total_burn,
579+
VRFProof::empty(),
580+
Hash160([0; 20]),
581+
&coinbase_tx,
582+
settings.clone(),
583+
None,
584+
&Burnchain::new(
585+
&burnchain_path,
586+
&burnchain.chain_name,
587+
&burnchain.network_name,
588+
)
589+
.unwrap_or_else(|e| panic!("Failed to instantiate burnchain: {e}")),
590+
)
591+
.map(|(block, cost, size)| (block.block_hash(), block.txs, cost, size))
592+
}
593+
StacksBlockHeaderTypes::Nakamoto(..) => {
594+
let tenure_info = if reset_tenure {
595+
let num_blocks_so_far = NakamotoChainState::get_nakamoto_tenure_length(
596+
chainstate.db(),
597+
&parent_block_id,
598+
)
599+
.unwrap_or_else(|e| panic!("Error getting tenure length: {e}"));
600+
let payload = TenureChangePayload {
601+
tenure_consensus_hash: parent_consensus_hash,
602+
prev_tenure_consensus_hash: parent_consensus_hash,
603+
burn_view_consensus_hash: parent_consensus_hash,
604+
previous_tenure_end: parent_block_id,
605+
previous_tenure_blocks: num_blocks_so_far,
606+
cause: TenureChangeCause::Extended,
607+
pubkey_hash: miner_pubkey_hash,
608+
};
609+
let tenure_change_tx_payload = TransactionPayload::TenureChange(payload);
610+
611+
let mut tx_auth = TransactionAuth::from_p2pkh(&miner_privk).unwrap();
612+
tx_auth.set_origin_nonce(miner_nonce);
613+
614+
let version = if conf.is_mainnet() {
615+
TransactionVersion::Mainnet
616+
} else {
617+
TransactionVersion::Testnet
618+
};
619+
620+
let mut tx = StacksTransaction::new(version, tx_auth, tenure_change_tx_payload);
621+
622+
tx.chain_id = conf.burnchain.chain_id;
623+
tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
624+
let mut tx_signer = StacksTransactionSigner::new(&tx);
625+
tx_signer
626+
.sign_origin(&miner_privk)
627+
.unwrap_or_else(|e| panic!("Failed to sign transaction: {e}"));
628+
629+
let tenure_change_tx = Some(tx_signer.get_tx().expect("Failed to get tx"));
630+
NakamotoTenureInfo {
631+
coinbase_tx: None,
632+
tenure_change_tx,
633+
}
602634
} else {
603-
TransactionVersion::Testnet
635+
NakamotoTenureInfo::default()
604636
};
637+
NakamotoBlockBuilder::build_nakamoto_block(
638+
&chainstate,
639+
&burn_dbconn,
640+
&mut mempool_db,
641+
&parent_stacks_header,
642+
// tenure ID consensus hash of this block
643+
&parent_stacks_header.consensus_hash,
644+
// the burn so far on the burnchain (i.e. from the last burnchain block)
645+
chain_tip.total_burn,
646+
tenure_info,
647+
settings.clone(),
648+
None,
649+
0,
650+
)
651+
.map(|(block, cost, size, _)| (block.header.block_hash(), block.txs, cost, size))
652+
}
653+
};
605654

606-
let mut tx = StacksTransaction::new(version, tx_auth, tenure_change_tx_payload);
607-
608-
tx.chain_id = conf.burnchain.chain_id;
609-
tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
610-
let mut tx_signer = StacksTransactionSigner::new(&tx);
611-
tx_signer
612-
.sign_origin(&miner_privk)
613-
.unwrap_or_else(|e| panic!("Failed to sign transaction: {e}"));
614-
615-
let tenure_change_tx = Some(tx_signer.get_tx().expect("Failed to get tx"));
616-
NakamotoTenureInfo {
617-
coinbase_tx: None,
618-
tenure_change_tx,
619-
}
620-
} else {
621-
NakamotoTenureInfo::default()
622-
};
623-
NakamotoBlockBuilder::build_nakamoto_block(
624-
&chainstate,
625-
&burn_dbconn,
626-
&mut mempool_db,
627-
&parent_stacks_header,
628-
// tenure ID consensus hash of this block
629-
&parent_stacks_header.consensus_hash,
630-
// the burn so far on the burnchain (i.e. from the last burnchain block)
631-
chain_tip.total_burn,
632-
tenure_info,
633-
settings,
634-
None,
635-
0,
636-
)
637-
.map(|(block, cost, size, _)| (block.header.block_hash(), block.txs, cost, size))
638-
}
639-
};
655+
let time_elapsed = start.elapsed();
656+
let summary = format!(
657+
"block @ height = {h} off of {parent_block_id} ({parent_consensus_hash}/{pbh}) in {t}ms. Min-fee: {min_fee}, Max-time: {max_time}",
658+
h=parent_stacks_header.stacks_block_height + 1,
659+
pbh=&parent_stacks_header.anchored_header.block_hash(),
660+
t=time_elapsed.as_millis(),
661+
);
640662

641-
let elapsed = start.elapsed();
642-
let summary = format!(
643-
"block @ height = {h} off of {parent_block_id} ({parent_consensus_hash}/{pbh}) in {t}ms. Min-fee: {min_fee}, Max-time: {max_time}",
644-
h=parent_stacks_header.stacks_block_height + 1,
645-
pbh=&parent_stacks_header.anchored_header.block_hash(),
646-
t=elapsed.as_millis(),
647-
);
663+
match result {
664+
Ok((block_hash, txs, cost, size)) => {
665+
let num_txs: usize = txs.len();
666+
let fees: u64 = txs.into_iter().map(|tx| tx.get_tx_fee()).sum();
667+
668+
println!("Successfully mined {summary}");
669+
println!(
670+
"Block {block_hash}: {num_txs} txs, {fees} uSTX, {size} bytes, cost {cost:?}"
671+
);
672+
block_stats.push(BlockStats {
673+
block_hash,
674+
time_elapsed,
675+
num_txs,
676+
fees,
677+
cost,
678+
size,
679+
})
680+
}
681+
Err(e) => {
682+
println!("Failed to mine {summary}");
683+
println!("Error: {e}");
684+
process::exit(1);
685+
}
686+
};
687+
}
648688

649-
let code = match result {
650-
Ok((block_hash, txs, cost, size)) => {
651-
let total_fees: u64 = txs.iter().map(|tx| tx.get_tx_fee()).sum();
689+
// Print summary
690+
let blocks_mined = block_stats.len();
691+
let (time_elapsed, num_txs, fees, cost, size) = block_stats.into_iter().fold(
692+
(Duration::ZERO, 0usize, 0u64, ExecutionCost::ZERO, 0u64),
693+
|acc, bs| {
694+
let (time_elapsed, num_txs, total_fees, mut cost, size) = acc;
695+
cost.add(&bs.cost).expect("Cost overflow");
696+
(
697+
time_elapsed.saturating_add(bs.time_elapsed),
698+
num_txs.saturating_add(bs.num_txs),
699+
total_fees.saturating_add(bs.fees),
700+
cost,
701+
size.saturating_add(bs.size),
702+
)
703+
},
704+
);
652705

653-
println!("Successfully mined {summary}");
654-
println!("Block {block_hash}: {total_fees} uSTX, {size} bytes, cost {cost:?}");
655-
0
656-
}
657-
Err(e) => {
658-
println!("Failed to mine {summary}");
659-
println!("Error: {e}");
660-
1
661-
}
662-
};
706+
println!("Successfully mined {blocks_mined} blocks from mempool");
707+
println!("Totals:");
708+
println!(" Time Elapsed: {} ms", time_elapsed.as_millis());
709+
println!(" Transactions: {num_txs}");
710+
println!(" Fees: {fees}");
711+
println!(" Size: {size}");
712+
println!(" Cost: {cost:?}");
663713

664-
process::exit(code);
714+
process::exit(0);
665715
}
666716

667717
/// Fetch and process a `StagingBlock` from database and call `replay_block()` to validate

0 commit comments

Comments
 (0)