diff --git a/Cargo.lock b/Cargo.lock index 1ef9cc74d59..31652204374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3181,6 +3181,7 @@ dependencies = [ "clarity 0.0.1", "libstackerdb 0.0.1", "mutants", + "regex", "rusqlite", "serde_json", "slog", @@ -3222,6 +3223,7 @@ dependencies = [ "slog", "stacks-common 0.0.1", "stacks-common 0.0.1 (git+https://github.com/stacks-network/stacks-core.git?rev=8a79aaa7df0f13dfc5ab0d0d0bcb8201c90bcba2)", + "stacks-inspect", "stacks-signer 0.0.1", "stacks-signer 0.0.1 (git+https://github.com/stacks-network/stacks-core.git?rev=8a79aaa7df0f13dfc5ab0d0d0bcb8201c90bcba2)", "stackslib 0.0.1", diff --git a/contrib/stacks-inspect/Cargo.toml b/contrib/stacks-inspect/Cargo.toml index 354c1fdc549..785b9993981 100644 --- a/contrib/stacks-inspect/Cargo.toml +++ b/contrib/stacks-inspect/Cargo.toml @@ -8,6 +8,7 @@ stackslib = { package = "stackslib", path = "../../stackslib", default-features clarity = { path = "../../clarity", default-features = false } libstackerdb = { path = "../../libstackerdb", default-features = false } stacks-common = { path = "../../stacks-common", default-features = false } +regex = { version = "1", default-features = false } rusqlite = { workspace = true } serde_json = { workspace = true } slog = { workspace = true } diff --git a/contrib/stacks-inspect/README.md b/contrib/stacks-inspect/README.md index b3ea3b52b95..3929c57b16c 100644 --- a/contrib/stacks-inspect/README.md +++ b/contrib/stacks-inspect/README.md @@ -17,18 +17,18 @@ cargo build -p stacks-inspect Basic usage: ```bash # Show version -./target/debug/stacks-inspect --version +cargo run -p stacks-inspect -- --version # Example: decode a bitcoin header from file -./target/debug/stacks-inspect decode-bitcoin-header +cargo run -p stacks-inspect -- decode-bitcoin-header # Example: analyze anti-MEV behavior over a height range -./target/debug/stacks-inspect analyze-sortition-mev [miner advantage ...] +cargo run -p stacks-inspect -- analyze-sortition-mev [miner advantage ...] ``` For detailed commands and flags, run: ```bash -./target/debug/stacks-inspect --help +cargo run -p stacks-inspect -- --help ``` Notes: diff --git a/stackslib/src/cli.rs b/contrib/stacks-inspect/src/lib.rs similarity index 92% rename from stackslib/src/cli.rs rename to contrib/stacks-inspect/src/lib.rs index ea4261c85c6..444a016dbd6 100644 --- a/stackslib/src/cli.rs +++ b/contrib/stacks-inspect/src/lib.rs @@ -1,5 +1,4 @@ -// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation -// Copyright (C) 2020 Stacks Open Internet Foundation +// Copyright (C) 2025 Stacks Open Internet Foundation // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -14,41 +13,42 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Subcommands used by `stacks-inspect` binary - use std::path::PathBuf; use std::time::Instant; use std::{fs, process}; use clarity::types::chainstate::SortitionId; -use clarity::util::hash::{to_hex, Sha512Trunc256Sum}; -use db::blocks::DummyEventDispatcher; -use db::ChainstateTx; +use clarity::util::hash::{Sha512Trunc256Sum, to_hex}; use regex::Regex; use rusqlite::{Connection, OpenFlags}; use stacks_common::types::chainstate::{BlockHeaderHash, StacksBlockId}; use stacks_common::types::sqlite::NO_PARAMS; use stacks_common::util::hash::Hash160; use stacks_common::util::vrf::VRFProof; - -use crate::burnchains::Burnchain; -use crate::chainstate::burn::db::sortdb::{ - get_ancestor_sort_id, SortitionDB, SortitionHandleContext, +use stacks_common::{debug, info, warn}; +use stackslib::burnchains::Burnchain; +use stackslib::chainstate::burn::ConsensusHash; +use stackslib::chainstate::burn::db::sortdb::{ + SortitionDB, SortitionHandleContext, get_ancestor_sort_id, +}; +use stackslib::chainstate::coordinator::OnChainRewardSetProvider; +use stackslib::chainstate::nakamoto::miner::{ + BlockMetadata, NakamotoBlockBuilder, NakamotoTenureInfo, +}; +use stackslib::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; +use stackslib::chainstate::stacks::db::blocks::DummyEventDispatcher; +use stackslib::chainstate::stacks::db::{ + ChainstateTx, StacksBlockHeaderTypes, StacksChainState, StacksHeaderInfo, }; -use crate::chainstate::burn::ConsensusHash; -use crate::chainstate::coordinator::OnChainRewardSetProvider; -use crate::chainstate::nakamoto::miner::{BlockMetadata, NakamotoBlockBuilder, NakamotoTenureInfo}; -use crate::chainstate::nakamoto::{NakamotoBlock, NakamotoChainState}; -use crate::chainstate::stacks::db::{StacksBlockHeaderTypes, StacksChainState, StacksHeaderInfo}; -use crate::chainstate::stacks::miner::*; -use crate::chainstate::stacks::{Error as ChainstateError, *}; -use crate::clarity_vm::clarity::ClarityInstance; -use crate::clarity_vm::database::GetTenureStartId; -use crate::config::{Config, ConfigFile, DEFAULT_MAINNET_CONFIG}; -use crate::core::*; -use crate::cost_estimates::metrics::UnitMetric; -use crate::cost_estimates::UnitEstimator; -use crate::util_lib::db::IndexDBTx; +use stackslib::chainstate::stacks::miner::*; +use stackslib::chainstate::stacks::{Error as ChainstateError, *}; +use stackslib::clarity_vm::clarity::ClarityInstance; +use stackslib::clarity_vm::database::GetTenureStartId; +use stackslib::config::{Config, ConfigFile, DEFAULT_MAINNET_CONFIG}; +use stackslib::core::*; +use stackslib::cost_estimates::UnitEstimator; +use stackslib::cost_estimates::metrics::UnitMetric; +use stackslib::util_lib::db::IndexDBTx; /// Options common to many `stacks-inspect` subcommands /// Returned by `process_common_opts()` @@ -141,13 +141,13 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { let query = match mode { Some("prefix") => format!( - "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 AND index_block_hash LIKE \"{}%\"", - argv[3] - ), + "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 AND index_block_hash LIKE \"{}%\"", + argv[3] + ), Some("first") => format!( - "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {}", - argv[3] - ), + "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {}", + argv[3] + ), Some("range") => { let arg4 = argv[3] .parse::() @@ -155,7 +155,9 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { let arg5 = argv[4].parse::().expect(" not a valid u64"); let start = arg4.saturating_sub(1); let blocks = arg5.saturating_sub(arg4); - format!("SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {start}, {blocks}") + format!( + "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {start}, {blocks}" + ) } Some("index-range") => { let start = argv[3] @@ -163,12 +165,14 @@ pub fn command_replay_block(argv: &[String], conf: Option<&Config>) { .expect(" not a valid u64"); let end = argv[4].parse::().expect(" not a valid u64"); let blocks = end.saturating_sub(start); - format!("SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY index_block_hash ASC LIMIT {start}, {blocks}") + format!( + "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY index_block_hash ASC LIMIT {start}, {blocks}" + ) } Some("last") => format!( - "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height DESC LIMIT {}", - argv[3] - ), + "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0 ORDER BY height DESC LIMIT {}", + argv[3] + ), Some(_) => print_help_and_exit(), // Default to ALL blocks None => "SELECT index_block_hash FROM staging_blocks WHERE orphaned = 0".into(), @@ -229,13 +233,13 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { let query = match mode { Some("prefix") => format!( - "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 AND index_block_hash LIKE \"{}%\"", - argv[3] - ), + "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 AND index_block_hash LIKE \"{}%\"", + argv[3] + ), Some("first") => format!( - "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {}", - argv[3] - ), + "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {}", + argv[3] + ), Some("range") => { let arg4 = argv[3] .parse::() @@ -243,7 +247,9 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { let arg5 = argv[4].parse::().expect(" not a valid u64"); let start = arg4.saturating_sub(1); let blocks = arg5.saturating_sub(arg4); - format!("SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {start}, {blocks}") + format!( + "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {start}, {blocks}" + ) } Some("index-range") => { let start = argv[3] @@ -251,12 +257,14 @@ pub fn command_replay_block_nakamoto(argv: &[String], conf: Option<&Config>) { .expect(" not a valid u64"); let end = argv[4].parse::().expect(" not a valid u64"); let blocks = end.saturating_sub(start); - format!("SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY index_block_hash ASC LIMIT {start}, {blocks}") + format!( + "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY index_block_hash ASC LIMIT {start}, {blocks}" + ) } Some("last") => format!( - "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height DESC LIMIT {}", - argv[3] - ), + "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height DESC LIMIT {}", + argv[3] + ), Some(_) => print_help_and_exit(), // Default to ALL blocks None => "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0".into(), @@ -386,8 +394,12 @@ pub fn command_try_mine(argv: &[String], conf: Option<&Config>) { let n = &argv[0]; eprintln!("Usage: {n} [min-fee [max-time]]"); 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!( + "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."); process::exit(1); @@ -523,11 +535,11 @@ 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}", - 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(), + 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(), ); let code = match result { @@ -770,7 +782,9 @@ fn replay_block( ), None => { // shouldn't happen - panic!("CORRUPTION: staging block {block_consensus_hash}/{block_hash} does not correspond to a burn block"); + panic!( + "CORRUPTION: staging block {block_consensus_hash}/{block_hash} does not correspond to a burn block" + ); } }; @@ -835,8 +849,10 @@ fn replay_block( ) { Ok((receipt, _, _)) => { if receipt.anchored_block_cost != cost { - println!("Failed processing block! block = {block_id}. Unexpected cost. expected = {cost}, evaluated = {}", - receipt.anchored_block_cost); + println!( + "Failed processing block! block = {block_id}. Unexpected cost. expected = {cost}, evaluated = {}", + receipt.anchored_block_cost + ); process::exit(1); } @@ -1153,7 +1169,9 @@ fn replay_block_nakamoto( // check the cost let evaluated_cost = receipt.anchored_block_cost.clone(); if evaluated_cost != expected_cost { - println!("Failed processing block! block = {block_id}. Unexpected cost. expected = {expected_cost}, evaluated = {evaluated_cost}"); + println!( + "Failed processing block! block = {block_id}. Unexpected cost. expected = {expected_cost}, evaluated = {evaluated_cost}" + ); process::exit(1); } } @@ -1197,7 +1215,7 @@ 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, 0); let opts = drain_common_opts(&mut argv, 1); assert_eq!(argv, argv_init); diff --git a/contrib/stacks-inspect/src/main.rs b/contrib/stacks-inspect/src/main.rs index 9c43923920d..5743a8fc586 100644 --- a/contrib/stacks-inspect/src/main.rs +++ b/contrib/stacks-inspect/src/main.rs @@ -19,6 +19,11 @@ extern crate stacks_common; use clarity::consts::CHAIN_ID_MAINNET; use clarity::types::StacksEpochId; use clarity::types::chainstate::StacksPrivateKey; +use clarity_cli::DEFAULT_CLI_EPOCH; +use stacks_inspect::{ + command_contract_hash, command_replay_block, command_replay_block_nakamoto, + command_replay_mock_mining, command_try_mine, drain_common_opts, +}; use stackslib::chainstate::stacks::miner::BlockBuilderSettings; use stackslib::chainstate::stacks::{ CoinbasePayload, StacksBlock, StacksBlockBuilder, StacksMicroblock, StacksTransaction, @@ -80,6 +85,7 @@ use stackslib::chainstate::stacks::index::marf::{MARF, MARFOpenOpts, MarfConnect use stackslib::clarity::vm::ClarityVersion; use stackslib::clarity::vm::costs::ExecutionCost; use stackslib::clarity::vm::types::StacksAddressExtensions; +use stackslib::clarity_cli; use stackslib::core::MemPoolDB; use stackslib::cost_estimates::UnitEstimator; use stackslib::cost_estimates::metrics::UnitMetric; @@ -91,7 +97,6 @@ use stackslib::net::relay::Relayer; use stackslib::net::{GetNakamotoInvData, HandshakeData, StacksMessage, StacksMessageType}; use stackslib::util_lib::db::sqlite_open; use stackslib::util_lib::strings::UrlString; -use stackslib::{clarity_cli, cli}; struct P2PSession { pub local_peer: LocalPeer, @@ -301,7 +306,7 @@ fn main() { process::exit(1); } - let common_opts = cli::drain_common_opts(&mut argv, 1); + let common_opts = drain_common_opts(&mut argv, 1); if argv[1] == "--version" { println!( @@ -789,7 +794,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()); + command_try_mine(&argv[1..], common_opts.config.as_ref()); process::exit(0); } @@ -896,7 +901,7 @@ check if the associated microblocks can be downloaded } let program: String = fs::read_to_string(&argv[2]) .unwrap_or_else(|_| panic!("Error reading file: {}", argv[2])); - let clarity_version = ClarityVersion::default_for_epoch(clarity_cli::DEFAULT_CLI_EPOCH); + let clarity_version = ClarityVersion::default_for_epoch(DEFAULT_CLI_EPOCH); match clarity_cli::vm_execute(&program, clarity_version) { Ok(Some(result)) => println!("{result}"), Ok(None) => println!(), @@ -1582,17 +1587,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()); + command_replay_block(&argv[1..], common_opts.config.as_ref()); process::exit(0); } if argv[1] == "replay-naka-block" { - cli::command_replay_block_nakamoto(&argv[1..], common_opts.config.as_ref()); + command_replay_block_nakamoto(&argv[1..], common_opts.config.as_ref()); process::exit(0); } if argv[1] == "replay-mock-mining" { - cli::command_replay_mock_mining(&argv[1..], common_opts.config.as_ref()); + command_replay_mock_mining(&argv[1..], common_opts.config.as_ref()); process::exit(0); } @@ -1601,7 +1606,7 @@ check if the associated microblocks can be downloaded } if argv[1] == "contract-hash" { - cli::command_contract_hash(&argv[1..], common_opts.config.as_ref()); + command_contract_hash(&argv[1..], common_opts.config.as_ref()); process::exit(0); } diff --git a/stacks-node/Cargo.toml b/stacks-node/Cargo.toml index d2b8e916b99..5f53b93e6bb 100644 --- a/stacks-node/Cargo.toml +++ b/stacks-node/Cargo.toml @@ -48,6 +48,7 @@ clarity = { path = "../clarity", features = ["default", "testing"]} stacks-common = { path = "../stacks-common", features = ["default", "testing"] } stacks = { package = "stackslib", path = "../stackslib", features = ["default", "testing"] } stacks-signer = { path = "../stacks-signer", features = ["testing"] } +stacks-inspect = { path = "../contrib/stacks-inspect", default-features = false } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } mutants = "0.0.3" diff --git a/stacks-node/src/tests/neon_integrations.rs b/stacks-node/src/tests/neon_integrations.rs index c2760a595fc..32a974f3096 100644 --- a/stacks-node/src/tests/neon_integrations.rs +++ b/stacks-node/src/tests/neon_integrations.rs @@ -37,7 +37,6 @@ use stacks::chainstate::stacks::{ StacksTransaction, TransactionContractCall, TransactionPayload, }; use stacks::clarity_cli::vm_execute as execute; -use stacks::cli; use stacks::codec::StacksMessageCodec; use stacks::config::{EventKeyType, EventObserverConfig, FeeEstimatorName, InitialBalance}; use stacks::core::mempool::{MemPoolWalkStrategy, MemPoolWalkTxTypes}; @@ -80,6 +79,7 @@ use stacks_common::types::StacksPublicKeyBuffer; use stacks_common::util::hash::{bytes_to_hex, hex_bytes, to_hex, Hash160}; use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey}; use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs, sleep_ms}; +use stacks_inspect; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; use tokio::net::{TcpListener, TcpStream}; @@ -9600,7 +9600,7 @@ fn mock_miner_replay() { 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)); + stacks_inspect::command_replay_mock_mining(&args, Some(&conf)); // ---------- Test finished, clean up ---------- diff --git a/stackslib/src/chainstate/nakamoto/mod.rs b/stackslib/src/chainstate/nakamoto/mod.rs index de350275be4..52bc37d78a5 100644 --- a/stackslib/src/chainstate/nakamoto/mod.rs +++ b/stackslib/src/chainstate/nakamoto/mod.rs @@ -4550,7 +4550,7 @@ impl NakamotoChainState { /// Append a Nakamoto Stacks block to the Stacks chain state. /// NOTE: This does _not_ set the block as processed! The caller must do this. - pub(crate) fn append_block<'a>( + pub fn append_block<'a>( chainstate_tx: &mut ChainstateTx, clarity_instance: &'a mut ClarityInstance, burn_dbconn: &mut SortitionHandleConn, diff --git a/stackslib/src/lib.rs b/stackslib/src/lib.rs index 6561af78114..16e618da101 100644 --- a/stackslib/src/lib.rs +++ b/stackslib/src/lib.rs @@ -64,9 +64,6 @@ pub mod burnchains; pub mod clarity_cli; /// A high level library for interacting with the Clarity vm pub mod clarity_vm; -/// Allow panics in CLI commands -#[allow(clippy::indexing_slicing)] -pub mod cli; pub mod config; pub mod core; pub mod cost_estimates;