Skip to content

Commit c5be755

Browse files
committed
feat: Allow passing --config <file> to stacks-inspect try_mine (and other commands)
1 parent 9e9d97e commit c5be755

File tree

7 files changed

+244
-165
lines changed

7 files changed

+244
-165
lines changed

Cargo.lock

Lines changed: 18 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ rand = "0.8"
2121
rand_chacha = "0.3.1"
2222
tikv-jemallocator = "0.5.4"
2323
rusqlite = { version = "0.31.0", features = ["blob", "serde_json", "i128_blob", "bundled", "trace"] }
24-
thiserror = { version = "1.0.65" }
24+
thiserror = "1.0.65"
25+
toml = "0.5.6"
2526

2627
# Use a bit more than default optimization for
2728
# dev builds to speed up test execution

stacks-signer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ stacks-common = { path = "../stacks-common" }
4040
stackslib = { path = "../stackslib" }
4141
thiserror = { workspace = true }
4242
tiny_http = { version = "0.12", optional = true }
43-
toml = "0.5.6"
43+
toml = { workspace = true }
4444
tracing = "0.1.37"
4545
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
4646
rand = { workspace = true }

stackslib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ libstackerdb = { path = "../libstackerdb" }
5858
siphasher = "0.3.7"
5959
hashbrown = { workspace = true }
6060
rusqlite = { workspace = true }
61+
toml = { workspace = true }
6162

6263
[target.'cfg(not(any(target_os = "macos",target_os="windows", target_arch = "arm" )))'.dependencies]
6364
tikv-jemallocator = {workspace = true}

stackslib/src/cli.rs

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
//! Subcommands used by `stacks-inspect` binary
1818
19+
use std::any::type_name;
1920
use std::cell::LazyCell;
20-
use std::path::PathBuf;
21+
use std::path::{Path, PathBuf};
2122
use std::time::Instant;
2223
use std::{env, fs, io, process, thread};
2324

@@ -28,9 +29,12 @@ use regex::Regex;
2829
use rusqlite::{Connection, OpenFlags};
2930
use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, StacksBlockId};
3031
use stacks_common::types::sqlite::NO_PARAMS;
32+
use stacks_common::util::get_epoch_time_ms;
33+
use stacks_common::util::hash::Hash160;
34+
use stacks_common::util::vrf::VRFProof;
3135

3236
use crate::burnchains::db::BurnchainDB;
33-
use crate::burnchains::PoxConstants;
37+
use crate::burnchains::{Burnchain, PoxConstants};
3438
use crate::chainstate::burn::db::sortdb::{
3539
get_ancestor_sort_id, SortitionDB, SortitionHandle, SortitionHandleContext,
3640
};
@@ -43,6 +47,8 @@ use crate::chainstate::stacks::miner::*;
4347
use crate::chainstate::stacks::{Error as ChainstateError, *};
4448
use crate::clarity_vm::clarity::ClarityInstance;
4549
use crate::core::*;
50+
use crate::cost_estimates::metrics::UnitMetric;
51+
use crate::cost_estimates::UnitEstimator;
4652
use crate::util_lib::db::IndexDBTx;
4753

4854
/// Can be used with CLI commands to support non-mainnet chainstate
@@ -58,6 +64,10 @@ pub struct StacksChainConfig {
5864
}
5965

6066
impl StacksChainConfig {
67+
pub fn type_name() -> &'static str {
68+
type_name::<Self>()
69+
}
70+
6171
pub fn default_mainnet() -> Self {
6272
Self {
6373
chain_id: CHAIN_ID_MAINNET,
@@ -107,6 +117,18 @@ impl StacksChainConfig {
107117
epochs,
108118
}
109119
}
120+
121+
pub fn from_file(path: &str) -> Self {
122+
let text = fs::read_to_string(&path)
123+
.unwrap_or_else(|e| panic!("Failed to read file '{path}': {e}"));
124+
let config: Self = toml::from_str(&text).unwrap_or_else(|e| {
125+
panic!(
126+
"Failed to parse file '{path}' as `{t}`: {e}",
127+
t = Self::type_name()
128+
)
129+
});
130+
config
131+
}
110132
}
111133

112134
const STACKS_CHAIN_CONFIG_DEFAULT_MAINNET: LazyCell<StacksChainConfig> =
@@ -369,6 +391,143 @@ pub fn command_replay_mock_mining(argv: &[String], conf: Option<&StacksChainConf
369391
}
370392
}
371393

394+
/// Replay mock mined blocks from JSON files
395+
/// Terminates on error using `process::exit()`
396+
///
397+
/// Arguments:
398+
/// - `argv`: Args in CLI format: `<command-name> [args...]`
399+
/// - `conf`: Optional config for running on non-mainnet chainstate
400+
pub fn command_try_mine(argv: &[String], conf: Option<&StacksChainConfig>) {
401+
let print_help_and_exit = || -> ! {
402+
let n = &argv[0];
403+
eprintln!("Usage: {n} <working-dir> [min-fee [max-time]]");
404+
eprintln!("");
405+
eprintln!("Given a <working-dir>, try to ''mine'' an anchored block. This invokes the miner block");
406+
eprintln!("assembly, but does not attempt to broadcast a block commit. This is useful for determining");
407+
eprintln!(
408+
"what transactions a given chain state would include in an anchor block, or otherwise"
409+
);
410+
eprintln!("simulating a miner.");
411+
process::exit(1);
412+
};
413+
414+
let default_conf = STACKS_CHAIN_CONFIG_DEFAULT_MAINNET;
415+
let conf = conf.unwrap_or(&default_conf);
416+
417+
let start = get_epoch_time_ms();
418+
let db_path = &argv[2];
419+
let burnchain_path = format!("{db_path}/burnchain");
420+
let sort_db_path = format!("{db_path}/burnchain/sortition");
421+
let chain_state_path = format!("{db_path}/chainstate/");
422+
423+
let mut min_fee = u64::MAX;
424+
let mut max_time = u64::MAX;
425+
426+
if argv.len() < 2 {
427+
print_help_and_exit();
428+
}
429+
if argv.len() >= 3 {
430+
min_fee = argv[3].parse().expect("Could not parse min_fee");
431+
}
432+
if argv.len() >= 4 {
433+
max_time = argv[4].parse().expect("Could not parse max_time");
434+
}
435+
436+
let sort_db = SortitionDB::open(&sort_db_path, false, conf.pox_constants.clone())
437+
.unwrap_or_else(|_| panic!("Failed to open {sort_db_path}"));
438+
let (chain_state, _) = StacksChainState::open(true, conf.chain_id, &chain_state_path, None)
439+
.expect("Failed to open stacks chain state");
440+
let chain_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn())
441+
.expect("Failed to get sortition chain tip");
442+
443+
let estimator = Box::new(UnitEstimator);
444+
let metric = Box::new(UnitMetric);
445+
446+
let mut mempool_db = MemPoolDB::open(true, conf.chain_id, &chain_state_path, estimator, metric)
447+
.expect("Failed to open mempool db");
448+
449+
let header_tip = NakamotoChainState::get_canonical_block_header(chain_state.db(), &sort_db)
450+
.unwrap()
451+
.unwrap();
452+
let parent_header = StacksChainState::get_anchored_block_header_info(
453+
chain_state.db(),
454+
&header_tip.consensus_hash,
455+
&header_tip.anchored_header.block_hash(),
456+
)
457+
.expect("Failed to load chain tip header info")
458+
.expect("Failed to load chain tip header info");
459+
460+
let sk = StacksPrivateKey::new();
461+
let mut tx_auth = TransactionAuth::from_p2pkh(&sk).unwrap();
462+
tx_auth.set_origin_nonce(0);
463+
464+
let mut coinbase_tx = StacksTransaction::new(
465+
TransactionVersion::Mainnet,
466+
tx_auth,
467+
TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None),
468+
);
469+
470+
coinbase_tx.chain_id = conf.chain_id;
471+
coinbase_tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
472+
let mut tx_signer = StacksTransactionSigner::new(&coinbase_tx);
473+
tx_signer.sign_origin(&sk).unwrap();
474+
let coinbase_tx = tx_signer.get_tx().unwrap();
475+
476+
let mut settings = BlockBuilderSettings::limited();
477+
settings.max_miner_time_ms = max_time;
478+
479+
let result = StacksBlockBuilder::build_anchored_block(
480+
&chain_state,
481+
&sort_db.index_handle(&chain_tip.sortition_id),
482+
&mut mempool_db,
483+
&parent_header,
484+
chain_tip.total_burn,
485+
VRFProof::empty(),
486+
Hash160([0; 20]),
487+
&coinbase_tx,
488+
settings,
489+
None,
490+
&Burnchain::new(&burnchain_path, "bitcoin", "main").unwrap(),
491+
);
492+
493+
let stop = get_epoch_time_ms();
494+
495+
println!(
496+
"{} mined block @ height = {} off of {} ({}/{}) in {}ms. Min-fee: {}, Max-time: {}",
497+
if result.is_ok() {
498+
"Successfully"
499+
} else {
500+
"Failed to"
501+
},
502+
parent_header.stacks_block_height + 1,
503+
StacksBlockHeader::make_index_block_hash(
504+
&parent_header.consensus_hash,
505+
&parent_header.anchored_header.block_hash()
506+
),
507+
&parent_header.consensus_hash,
508+
&parent_header.anchored_header.block_hash(),
509+
stop.saturating_sub(start),
510+
min_fee,
511+
max_time
512+
);
513+
514+
if let Ok((block, execution_cost, size)) = result {
515+
let mut total_fees = 0;
516+
for tx in block.txs.iter() {
517+
total_fees += tx.get_tx_fee();
518+
}
519+
println!(
520+
"Block {}: {} uSTX, {} bytes, cost {:?}",
521+
block.block_hash(),
522+
total_fees,
523+
size,
524+
&execution_cost
525+
);
526+
}
527+
528+
process::exit(0);
529+
}
530+
372531
/// Fetch and process a `StagingBlock` from database and call `replay_block()` to validate
373532
fn replay_staging_block(
374533
db_path: &str,

0 commit comments

Comments
 (0)