diff --git a/stackslib/src/cli.rs b/stackslib/src/cli.rs index 49560ed7d2..b151c185f8 100644 --- a/stackslib/src/cli.rs +++ b/stackslib/src/cli.rs @@ -53,25 +53,34 @@ use crate::cost_estimates::metrics::UnitMetric; use crate::cost_estimates::UnitEstimator; use crate::util_lib::db::IndexDBTx; -/// Options common to many `stacks-inspect` subcommands -/// Returned by `process_common_opts()` +/// Options for `stacks-inspect` command and used by many subcommands #[derive(Debug, Default)] -pub struct CommonOpts { +pub struct StacksInspectOpts { pub config: Option, } -/// Process arguments common to many `stacks-inspect` subcommands and drain them from `argv` +impl StacksInspectOpts { + #[inline(always)] + pub fn new_with_config(config: Config) -> Self { + Self { + config: Some(config), + ..Self::default() + } + } +} + +/// Process `stacks-inspect` options and drain them from `argv` /// /// Args: -/// - `argv`: Full CLI args `Vec` +/// - `argv`: Full or partial CLI args `Vec` /// - `start_at`: Position in args vec where to look for common options. /// For example, if `start_at` is `1`, then look for these options **before** the subcommand: /// ```console /// stacks-inspect --config testnet.toml replay-block path/to/chainstate /// ``` -pub fn drain_common_opts(argv: &mut Vec, start_at: usize) -> CommonOpts { +pub fn drain_stacks_inspect_opts(argv: &mut Vec, start_at: usize) -> StacksInspectOpts { let mut i = start_at; - let mut opts = CommonOpts::default(); + let mut opts = StacksInspectOpts::default(); while let Some(arg) = argv.get(i) { let (prefix, opt) = arg.split_at(2); if prefix != "--" { @@ -118,12 +127,74 @@ pub fn drain_common_opts(argv: &mut Vec, start_at: usize) -> CommonOpts opts } +/// Options for `try-mine` subcommand +#[derive(Debug, Default)] +pub struct TryMineOpts { + pub min_fee: Option, + pub max_time: Option, + pub max_blocks: Option, + pub reset_tenure: Option, +} + +/// Process `try-mine` options and drain them from `argv` +/// +/// Args: +/// - `argv`: Full or partial CLI args `Vec` +/// - `start_at`: Position in args vec where to look for common options. +/// For example, if `start_at` is `1`, then look for these options after the `try-mine` command: +/// ```console +/// try-mine --min-fee 10000 path/to/chainstate +/// ``` +pub fn drain_try_mine_opts(argv: &mut Vec, start_at: usize) -> TryMineOpts { + let mut i = start_at; + let mut opts = TryMineOpts::default(); + while let Some(arg) = argv.get(i) { + let (prefix, opt) = arg.split_at(2); + if prefix != "--" { + // No args left to take + break; + } + // "Take" arg + i += 1; + match opt { + "min-fee" => { + let fee = argv[i] + .parse() + .unwrap_or_else(|e| panic!("Failed to parse `{opt}` as `u64`: {e}")); + opts.min_fee.replace(fee); + i += 1; + } + "max-time" => { + let time = argv[i] + .parse() + .unwrap_or_else(|e| panic!("Failed to parse `{opt}` as `u64`: {e}")); + opts.max_time.replace(time); + i += 1; + } + "max-blocks" => { + let blocks = argv[i] + .parse() + .unwrap_or_else(|e| panic!("Failed to parse `{opt}` as `u64`: {e}")); + opts.max_blocks.replace(blocks); + i += 1; + } + "reset-tenure" => { + opts.reset_tenure.replace(true); + } + _ => panic!("Unrecognized option: {opt}"), + } + } + // Remove options processed + argv.drain(start_at..i); + opts +} + /// Replay blocks from chainstate database /// Terminates on error using `process::exit()` /// /// Arguments: /// - `argv`: Args in CLI format: ` [args...]` -pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { +pub fn command_replay_block(argv: &[String], opts: &StacksInspectOpts) { let print_help_and_exit = || -> ! { let n = &argv[0]; eprintln!("Usage:"); @@ -186,6 +257,7 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { } let total = index_block_hashes.len(); + let conf = opts.config.as_ref(); println!("Will check {total} blocks"); for (i, index_block_hash) in index_block_hashes.iter().enumerate() { if i % 100 == 0 { @@ -201,7 +273,7 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { /// /// Arguments: /// - `argv`: Args in CLI format: ` [args...]` -pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { +pub fn command_replay_block_nakamoto(argv: &[String], opts: &StacksInspectOpts) { let print_help_and_exit = || -> ! { let n = &argv[0]; eprintln!("Usage:"); @@ -218,7 +290,7 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { let chain_state_path = format!("{db_path}/chainstate/"); - let conf = conf.unwrap_or(&DEFAULT_MAINNET_CONFIG); + let conf = opts.config.as_ref().unwrap_or(&DEFAULT_MAINNET_CONFIG); let (chainstate, _) = StacksChainState::open( conf.is_mainnet(), @@ -290,7 +362,7 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { /// Arguments: /// - `argv`: Args in CLI format: ` [args...]` /// - `conf`: Optional config for running on non-mainnet chainstate -pub fn command_replay_mock_mining(argv: &[String], conf: Option<&Config>) { +pub fn command_replay_mock_mining(argv: &[String], opts: &StacksInspectOpts) { let print_help_and_exit = || -> ! { let n = &argv[0]; eprintln!("Usage:"); @@ -366,6 +438,7 @@ pub fn command_replay_mock_mining(argv: &[String], conf: Option<&Config>) { indexed_files[0].0 ); + let conf = opts.config.as_ref(); for (bh, filename) in indexed_files { let filepath = blocks_path.join(filename); let block = AssembledAnchorBlock::deserialize_from_file(&filepath) @@ -384,32 +457,39 @@ pub fn command_replay_mock_mining(argv: &[String], conf: Option<&Config>) { /// Arguments: /// - `argv`: Args in CLI format: ` [args...]` /// - `conf`: Optional config for running on non-mainnet chainstate -pub fn command_try_mine(argv: &[String], conf: Option<&Config>) { +pub fn command_try_mine(mut argv: Vec, opts: &StacksInspectOpts) { + // Parse subcommand-specific flags + let try_mine_opts = drain_try_mine_opts(&mut argv, 1); + let print_help_and_exit = || { let n = &argv[0]; - eprintln!("Usage: {n} [min-fee [max-time]]"); + eprintln!("Usage: {n} [options...] "); + eprintln!(""); + eprintln!("Options:"); + eprintln!(" --min-fee : Minimum fee for miner to include transaction"); + eprintln!(" --max-time : Max time to spend mining a block, in ms"); + eprintln!(" --max-blocks : Max blocks to mine"); + eprintln!(" --reset-tenure: Force a tenure reset (only matters post-Nakamoto)"); eprintln!(""); - eprintln!("Given a , try to ''mine'' an anchored block. This invokes the miner block"); - eprintln!("assembly, but does not attempt to broadcast a block commit. This is useful for determining"); - eprintln!("what transactions a given chain state would include in an anchor block,"); - eprintln!("or otherwise simulating a miner."); + eprintln!("Given a , try to \"mine\" an anchored block."); + eprintln!("This invokes the miner block assembly, but does not attempt to"); + eprintln!("broadcast a block commit. This is useful for determining"); + eprintln!("which transactions a given chain state would include in an"); + eprintln!("anchor block, or otherwise simulating a miner."); process::exit(1); }; - // Parse subcommand-specific args + // Parse subcommand-specific positional args let db_path = argv.get(1).unwrap_or_else(print_help_and_exit); - let min_fee = argv - .get(2) - .map(|arg| arg.parse().expect("Could not parse min_fee")) - .unwrap_or(u64::MAX); - let max_time = argv - .get(3) - .map(|arg| arg.parse().expect("Could not parse max_time")) - .unwrap_or(u64::MAX); + + let min_fee = try_mine_opts.min_fee.unwrap_or(0); + let max_time = try_mine_opts.max_time.unwrap_or(u64::MAX); + let _max_blocks = try_mine_opts.max_blocks.unwrap_or(1); + let _reset_tenure = try_mine_opts.reset_tenure.unwrap_or(false); let start = Instant::now(); - let conf = conf.unwrap_or(&DEFAULT_MAINNET_CONFIG); + let conf = opts.config.as_ref().unwrap_or(&DEFAULT_MAINNET_CONFIG); let burnchain_path = format!("{db_path}/burnchain"); let sort_db_path = format!("{db_path}/burnchain/sortition"); @@ -1131,8 +1211,8 @@ pub mod test { "stacks-inspect try-mine --config my_config.toml /tmp/chainstate/mainnet", ); let argv_init = argv.clone(); - let opts = drain_common_opts(&mut argv, 0); - let opts = drain_common_opts(&mut argv, 1); + let opts = drain_stacks_inspect_opts(&mut argv, 0); + let opts = drain_stacks_inspect_opts(&mut argv, 1); assert_eq!(argv, argv_init); assert!(opts.config.is_none()); @@ -1141,7 +1221,7 @@ pub mod test { let mut argv = parse_cli_command( "stacks-inspect --network mocknet --network mainnet try-mine /tmp/chainstate/mainnet", ); - let opts = drain_common_opts(&mut argv, 1); + let opts = drain_stacks_inspect_opts(&mut argv, 1); let argv_expected = parse_cli_command("stacks-inspect try-mine /tmp/chainstate/mainnet"); assert_eq!(argv, argv_expected); diff --git a/stackslib/src/main.rs b/stackslib/src/main.rs index 76a9d63c21..05f4018f29 100644 --- a/stackslib/src/main.rs +++ b/stackslib/src/main.rs @@ -309,7 +309,7 @@ fn main() { process::exit(1); } - let common_opts = cli::drain_common_opts(&mut argv, 1); + let opts = cli::drain_stacks_inspect_opts(&mut argv, 1); if argv[1] == "--version" { println!( @@ -798,7 +798,7 @@ check if the associated microblocks can be downloaded } if argv[1] == "try-mine" { - cli::command_try_mine(&argv[1..], common_opts.config.as_ref()); + cli::command_try_mine(argv[1..].to_vec(), &opts); process::exit(0); } @@ -1600,17 +1600,17 @@ check if the associated microblocks can be downloaded } if argv[1] == "replay-block" { - cli::command_replay_block(&argv[1..], common_opts.config.as_ref()); + cli::command_replay_block(&argv[1..], &opts); process::exit(0); } if argv[1] == "replay-naka-block" { - cli::command_replay_block_nakamoto(&argv[1..], common_opts.config.as_ref()); + cli::command_replay_block_nakamoto(&argv[1..], &opts); process::exit(0); } if argv[1] == "replay-mock-mining" { - cli::command_replay_mock_mining(&argv[1..], common_opts.config.as_ref()); + cli::command_replay_mock_mining(&argv[1..], &opts); process::exit(0); } diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index fc363d3db8..e17fb3d3a1 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -38,7 +38,7 @@ use stacks::chainstate::stacks::{ StacksPublicKey, StacksTransaction, TransactionContractCall, TransactionPayload, }; use stacks::clarity_cli::vm_execute as execute; -use stacks::cli; +use stacks::cli::{self, StacksInspectOpts}; use stacks::codec::StacksMessageCodec; use stacks::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; use stacks::core::mempool::MemPoolWalkTxTypes; @@ -12689,10 +12689,11 @@ fn mock_miner_replay() { // Run `mock_miner_replay()` let blocks_dir = blocks_dir.into_os_string().into_string().unwrap(); let db_path = format!("{}/neon", conf.node.working_dir); - let args: Vec = vec!["replay-mock-mining".into(), db_path, blocks_dir]; info!("Replaying mock mined blocks..."); - cli::command_replay_mock_mining(&args, Some(&conf)); + let argv: Vec = vec!["replay-mock-mining".into(), db_path, blocks_dir]; + let opts = StacksInspectOpts::new_with_config(conf); + cli::command_replay_mock_mining(&argv, &opts); // ---------- Test finished, clean up ----------