Skip to content

Commit

Permalink
refactor: Add more options to try-mine and change how CLI options a…
Browse files Browse the repository at this point in the history
…re parsed/passed
  • Loading branch information
jbencin committed Dec 20, 2024
1 parent ce539b1 commit f3d6c96
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 38 deletions.
140 changes: 110 additions & 30 deletions stackslib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Config>,
}

/// 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<String>, start_at: usize) -> CommonOpts {
pub fn drain_stacks_inspect_opts(argv: &mut Vec<String>, 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 != "--" {
Expand Down Expand Up @@ -118,12 +127,74 @@ pub fn drain_common_opts(argv: &mut Vec<String>, start_at: usize) -> CommonOpts
opts
}

/// Options for `try-mine` subcommand
#[derive(Debug, Default)]
pub struct TryMineOpts {
pub min_fee: Option<u64>,
pub max_time: Option<u64>,
pub max_blocks: Option<u64>,
pub reset_tenure: Option<bool>,
}

/// 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<String>, 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: `<command-name> [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:");
Expand Down Expand Up @@ -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 {
Expand All @@ -201,7 +273,7 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) {
///
/// Arguments:
/// - `argv`: Args in CLI format: `<command-name> [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:");
Expand All @@ -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(),
Expand Down Expand Up @@ -290,7 +362,7 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) {
/// Arguments:
/// - `argv`: Args in CLI format: `<command-name> [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:");
Expand Down Expand Up @@ -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)
Expand All @@ -384,32 +457,39 @@ pub fn command_replay_mock_mining(argv: &[String], conf: Option<&Config>) {
/// Arguments:
/// - `argv`: Args in CLI format: `<command-name> [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<String>, 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} <working-dir> [min-fee [max-time]]");
eprintln!("Usage: {n} [options...] <data-dir>");
eprintln!("");
eprintln!("Options:");
eprintln!(" --min-fee <u64>: Minimum fee for miner to include transaction");
eprintln!(" --max-time <u64>: Max time to spend mining a block, in ms");
eprintln!(" --max-blocks <u64>: Max blocks to mine");
eprintln!(" --reset-tenure: Force a tenure reset (only matters post-Nakamoto)");
eprintln!("");
eprintln!("Given a <working-dir>, 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 <data-dir>, 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");
Expand Down Expand Up @@ -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());
Expand All @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions stackslib/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
7 changes: 4 additions & 3 deletions testnet/stacks-node/src/tests/neon_integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> = 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<String> = 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 ----------

Expand Down

0 comments on commit f3d6c96

Please sign in to comment.