Skip to content
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

feat: Add --reset-tenure flag to try-mine #5608

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
206 changes: 167 additions & 39 deletions stackslib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, Sta
use stacks_common::types::sqlite::NO_PARAMS;
use stacks_common::util::get_epoch_time_ms;
use stacks_common::util::hash::Hash160;
use stacks_common::util::secp256k1::Secp256k1PublicKey;
use stacks_common::util::vrf::VRFProof;

use crate::burnchains::db::BurnchainDB;
Expand All @@ -53,25 +54,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 +128,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 +258,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 +274,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 +291,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 +363,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 +439,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 +458,38 @@ 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>", n = &argv[0]);
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 @@ -445,17 +525,24 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) {
NakamotoChainState::get_canonical_block_header(chainstate.db(), &sort_db)
.unwrap_or_else(|e| panic!("Error looking up chain tip: {e}"))
.expect("No chain tip found");
let parent_consensus_hash = parent_stacks_header.consensus_hash;
let parent_block_id = parent_stacks_header.index_block_hash();

let burn_dbconn = sort_db.index_handle(&chain_tip.sortition_id);

let mut settings = BlockBuilderSettings::limited();
settings.max_miner_time_ms = max_time;

// In case we need to submit transactions
let miner_privk = StacksPrivateKey::new();
let miner_pubkey = Secp256k1PublicKey::from_private(&miner_privk);
let miner_pubkey_hash = Hash160::from_node_public_key(&miner_pubkey);
let miner_nonce = 0;

let result = match &parent_stacks_header.anchored_header {
StacksBlockHeaderTypes::Epoch2(..) => {
let sk = StacksPrivateKey::new();
let mut tx_auth = TransactionAuth::from_p2pkh(&sk).unwrap();
tx_auth.set_origin_nonce(0);
let mut tx_auth = TransactionAuth::from_p2pkh(&miner_privk).unwrap();
tx_auth.set_origin_nonce(miner_nonce);

let mut coinbase_tx = StacksTransaction::new(
TransactionVersion::Mainnet,
Expand All @@ -466,7 +553,7 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) {
coinbase_tx.chain_id = conf.burnchain.chain_id;
coinbase_tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
let mut tx_signer = StacksTransactionSigner::new(&coinbase_tx);
tx_signer.sign_origin(&sk).unwrap();
tx_signer.sign_origin(&miner_privk).unwrap();
let coinbase_tx = tx_signer.get_tx().unwrap();

StacksBlockBuilder::build_anchored_block(
Expand All @@ -490,6 +577,49 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) {
.map(|(block, cost, size)| (block.block_hash(), block.txs, cost, size))
}
StacksBlockHeaderTypes::Nakamoto(..) => {
let tenure_info = if reset_tenure {
let num_blocks_so_far = NakamotoChainState::get_nakamoto_tenure_length(
chainstate.db(),
&parent_block_id,
)
.unwrap_or_else(|e| panic!("Error getting tenure length: {e}"));
let payload = TenureChangePayload {
tenure_consensus_hash: parent_consensus_hash,
prev_tenure_consensus_hash: parent_consensus_hash,
burn_view_consensus_hash: parent_consensus_hash,
previous_tenure_end: parent_block_id,
previous_tenure_blocks: num_blocks_so_far,
cause: TenureChangeCause::Extended,
pubkey_hash: miner_pubkey_hash,
};
let tenure_change_tx_payload = TransactionPayload::TenureChange(payload);

let mut tx_auth = TransactionAuth::from_p2pkh(&miner_privk).unwrap();
tx_auth.set_origin_nonce(miner_nonce);

let version = if conf.is_mainnet() {
TransactionVersion::Mainnet
} else {
TransactionVersion::Testnet
};

let mut tx = StacksTransaction::new(version, tx_auth, tenure_change_tx_payload);

tx.chain_id = conf.burnchain.chain_id;
tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
let mut tx_signer = StacksTransactionSigner::new(&tx);
tx_signer
.sign_origin(&miner_privk)
.unwrap_or_else(|e| panic!("Failed to sign transaction: {e}"));

let tenure_change_tx = Some(tx_signer.get_tx().expect("Failed to get tx"));
NakamotoTenureInfo {
coinbase_tx: None,
tenure_change_tx,
}
} else {
NakamotoTenureInfo::default()
};
NakamotoBlockBuilder::build_nakamoto_block(
&chainstate,
&burn_dbconn,
Expand All @@ -499,7 +629,7 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) {
&parent_stacks_header.consensus_hash,
// the burn so far on the burnchain (i.e. from the last burnchain block)
chain_tip.total_burn,
NakamotoTenureInfo::default(),
tenure_info,
settings,
None,
0,
Expand All @@ -510,10 +640,8 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) {

let elapsed = start.elapsed();
let summary = format!(
"block @ height = {h} off of {pid} ({pch}/{pbh}) in {t}ms. Min-fee: {min_fee}, Max-time: {max_time}",
"block @ height = {h} off of {parent_block_id} ({parent_consensus_hash}/{pbh}) in {t}ms. Min-fee: {min_fee}, Max-time: {max_time}",
h=parent_stacks_header.stacks_block_height + 1,
pid=&parent_stacks_header.index_block_hash(),
pch=&parent_stacks_header.consensus_hash,
pbh=&parent_stacks_header.anchored_header.block_hash(),
t=elapsed.as_millis(),
);
Expand Down Expand Up @@ -1131,8 +1259,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 +1269,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
Loading
Loading