16
16
17
17
//! Subcommands used by `stacks-inspect` binary
18
18
19
+ use std:: any:: type_name;
19
20
use std:: cell:: LazyCell ;
20
- use std:: path:: PathBuf ;
21
+ use std:: path:: { Path , PathBuf } ;
21
22
use std:: time:: Instant ;
22
23
use std:: { env, fs, io, process, thread} ;
23
24
@@ -28,9 +29,12 @@ use regex::Regex;
28
29
use rusqlite:: { Connection , OpenFlags } ;
29
30
use stacks_common:: types:: chainstate:: { BlockHeaderHash , BurnchainHeaderHash , StacksBlockId } ;
30
31
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 ;
31
35
32
36
use crate :: burnchains:: db:: BurnchainDB ;
33
- use crate :: burnchains:: PoxConstants ;
37
+ use crate :: burnchains:: { Burnchain , PoxConstants } ;
34
38
use crate :: chainstate:: burn:: db:: sortdb:: {
35
39
get_ancestor_sort_id, SortitionDB , SortitionHandle , SortitionHandleContext ,
36
40
} ;
@@ -43,6 +47,8 @@ use crate::chainstate::stacks::miner::*;
43
47
use crate :: chainstate:: stacks:: { Error as ChainstateError , * } ;
44
48
use crate :: clarity_vm:: clarity:: ClarityInstance ;
45
49
use crate :: core:: * ;
50
+ use crate :: cost_estimates:: metrics:: UnitMetric ;
51
+ use crate :: cost_estimates:: UnitEstimator ;
46
52
use crate :: util_lib:: db:: IndexDBTx ;
47
53
48
54
/// Can be used with CLI commands to support non-mainnet chainstate
@@ -58,6 +64,10 @@ pub struct StacksChainConfig {
58
64
}
59
65
60
66
impl StacksChainConfig {
67
+ pub fn type_name ( ) -> & ' static str {
68
+ type_name :: < Self > ( )
69
+ }
70
+
61
71
pub fn default_mainnet ( ) -> Self {
62
72
Self {
63
73
chain_id : CHAIN_ID_MAINNET ,
@@ -107,6 +117,18 @@ impl StacksChainConfig {
107
117
epochs,
108
118
}
109
119
}
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
+ }
110
132
}
111
133
112
134
const STACKS_CHAIN_CONFIG_DEFAULT_MAINNET : LazyCell < StacksChainConfig > =
@@ -369,6 +391,143 @@ pub fn command_replay_mock_mining(argv: &[String], conf: Option<&StacksChainConf
369
391
}
370
392
}
371
393
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
+
372
531
/// Fetch and process a `StagingBlock` from database and call `replay_block()` to validate
373
532
fn replay_staging_block (
374
533
db_path : & str ,
0 commit comments