@@ -47,6 +47,7 @@ use crate::util_lib::db::IndexDBTx;
47
47
48
48
/// Can be used with CLI commands to support non-mainnet chainstate
49
49
/// Allows integration testing of these functions
50
+ #[ derive( Deserialize ) ]
50
51
pub struct StacksChainConfig {
51
52
pub chain_id : u32 ,
52
53
pub first_block_height : u64 ,
@@ -68,10 +69,48 @@ impl StacksChainConfig {
68
69
epochs : STACKS_EPOCHS_MAINNET . to_vec ( ) ,
69
70
}
70
71
}
72
+
73
+ pub fn default_testnet ( ) -> Self {
74
+ let mut pox_constants = PoxConstants :: regtest_default ( ) ;
75
+ pox_constants. prepare_length = 100 ;
76
+ pox_constants. reward_cycle_length = 900 ;
77
+ pox_constants. v1_unlock_height = 3 ;
78
+ pox_constants. v2_unlock_height = 5 ;
79
+ pox_constants. pox_3_activation_height = 5 ;
80
+ pox_constants. pox_4_activation_height = 6 ;
81
+ pox_constants. v3_unlock_height = 7 ;
82
+ let mut epochs = STACKS_EPOCHS_REGTEST . to_vec ( ) ;
83
+ epochs[ 0 ] . start_height = 0 ;
84
+ epochs[ 0 ] . end_height = 0 ;
85
+ epochs[ 1 ] . start_height = 0 ;
86
+ epochs[ 1 ] . end_height = 1 ;
87
+ epochs[ 2 ] . start_height = 1 ;
88
+ epochs[ 2 ] . end_height = 2 ;
89
+ epochs[ 3 ] . start_height = 2 ;
90
+ epochs[ 3 ] . end_height = 3 ;
91
+ epochs[ 4 ] . start_height = 3 ;
92
+ epochs[ 4 ] . end_height = 4 ;
93
+ epochs[ 5 ] . start_height = 4 ;
94
+ epochs[ 5 ] . end_height = 5 ;
95
+ epochs[ 6 ] . start_height = 5 ;
96
+ epochs[ 6 ] . end_height = 6 ;
97
+ epochs[ 7 ] . start_height = 6 ;
98
+ epochs[ 7 ] . end_height = 56_457 ;
99
+ epochs[ 8 ] . start_height = 56_457 ;
100
+ Self {
101
+ chain_id : CHAIN_ID_TESTNET ,
102
+ first_block_height : 0 ,
103
+ first_burn_header_hash : BurnchainHeaderHash :: from_hex ( BITCOIN_REGTEST_FIRST_BLOCK_HASH )
104
+ . unwrap ( ) ,
105
+ first_burn_header_timestamp : BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP . into ( ) ,
106
+ pox_constants,
107
+ epochs,
108
+ }
109
+ }
71
110
}
72
111
73
112
const STACKS_CHAIN_CONFIG_DEFAULT_MAINNET : LazyCell < StacksChainConfig > =
74
- LazyCell :: new ( StacksChainConfig :: default_mainnet ) ;
113
+ LazyCell :: new ( StacksChainConfig :: default_testnet ) ;
75
114
76
115
/// Replay blocks from chainstate database
77
116
/// Terminates on error using `process::exit()`
@@ -151,6 +190,91 @@ pub fn command_replay_block(argv: &[String], conf: Option<&StacksChainConfig>) {
151
190
println ! ( "Finished. run_time_seconds = {}" , start. elapsed( ) . as_secs( ) ) ;
152
191
}
153
192
193
+ /// Replay blocks from chainstate database
194
+ /// Terminates on error using `process::exit()`
195
+ ///
196
+ /// Arguments:
197
+ /// - `argv`: Args in CLI format: `<command-name> [args...]`
198
+ pub fn command_replay_block_nakamoto ( argv : & [ String ] , conf : Option < & StacksChainConfig > ) {
199
+ let print_help_and_exit = || -> ! {
200
+ let n = & argv[ 0 ] ;
201
+ eprintln ! ( "Usage:" ) ;
202
+ eprintln ! ( " {n} <database-path>" ) ;
203
+ eprintln ! ( " {n} <database-path> prefix <index-block-hash-prefix>" ) ;
204
+ eprintln ! ( " {n} <database-path> index-range <start-block> <end-block>" ) ;
205
+ eprintln ! ( " {n} <database-path> range <start-block> <end-block>" ) ;
206
+ eprintln ! ( " {n} <database-path> <first|last> <block-count>" ) ;
207
+ process:: exit ( 1 ) ;
208
+ } ;
209
+ let start = Instant :: now ( ) ;
210
+ let db_path = argv. get ( 1 ) . unwrap_or_else ( || print_help_and_exit ( ) ) ;
211
+ let mode = argv. get ( 2 ) . map ( String :: as_str) ;
212
+
213
+ let chain_state_path = format ! ( "{db_path}/chainstate/" ) ;
214
+
215
+ let default_conf = STACKS_CHAIN_CONFIG_DEFAULT_MAINNET ;
216
+ let conf = conf. unwrap_or ( & default_conf) ;
217
+
218
+ let mainnet = conf. chain_id == CHAIN_ID_MAINNET ;
219
+ let ( chainstate, _) =
220
+ StacksChainState :: open ( mainnet, conf. chain_id , & chain_state_path, None ) . unwrap ( ) ;
221
+
222
+ let conn = chainstate. nakamoto_blocks_db ( ) ;
223
+
224
+ let query = match mode {
225
+ Some ( "prefix" ) => format ! (
226
+ "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 AND index_block_hash LIKE \" {}%\" " ,
227
+ argv[ 3 ]
228
+ ) ,
229
+ Some ( "first" ) => format ! (
230
+ "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {}" ,
231
+ argv[ 3 ]
232
+ ) ,
233
+ Some ( "range" ) => {
234
+ let arg4 = argv[ 3 ]
235
+ . parse :: < u64 > ( )
236
+ . expect ( "<start_block> not a valid u64" ) ;
237
+ let arg5 = argv[ 4 ] . parse :: < u64 > ( ) . expect ( "<end-block> not a valid u64" ) ;
238
+ let start = arg4. saturating_sub ( 1 ) ;
239
+ let blocks = arg5. saturating_sub ( arg4) ;
240
+ format ! ( "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height ASC LIMIT {start}, {blocks}" )
241
+ }
242
+ Some ( "index-range" ) => {
243
+ let start = argv[ 3 ]
244
+ . parse :: < u64 > ( )
245
+ . expect ( "<start_block> not a valid u64" ) ;
246
+ let end = argv[ 4 ] . parse :: < u64 > ( ) . expect ( "<end-block> not a valid u64" ) ;
247
+ let blocks = end. saturating_sub ( start) ;
248
+ format ! ( "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY index_block_hash ASC LIMIT {start}, {blocks}" )
249
+ }
250
+ Some ( "last" ) => format ! (
251
+ "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0 ORDER BY height DESC LIMIT {}" ,
252
+ argv[ 3 ]
253
+ ) ,
254
+ Some ( _) => print_help_and_exit ( ) ,
255
+ // Default to ALL blocks
256
+ None => "SELECT index_block_hash FROM nakamoto_staging_blocks WHERE orphaned = 0" . into ( ) ,
257
+ } ;
258
+
259
+ let mut stmt = conn. prepare ( & query) . unwrap ( ) ;
260
+ let mut hashes_set = stmt. query ( NO_PARAMS ) . unwrap ( ) ;
261
+
262
+ let mut index_block_hashes: Vec < String > = vec ! [ ] ;
263
+ while let Ok ( Some ( row) ) = hashes_set. next ( ) {
264
+ index_block_hashes. push ( row. get ( 0 ) . unwrap ( ) ) ;
265
+ }
266
+
267
+ let total = index_block_hashes. len ( ) ;
268
+ println ! ( "Will check {total} blocks" ) ;
269
+ for ( i, index_block_hash) in index_block_hashes. iter ( ) . enumerate ( ) {
270
+ if i % 100 == 0 {
271
+ println ! ( "Checked {i}..." ) ;
272
+ }
273
+ replay_naka_staging_block ( db_path, index_block_hash, & conf) ;
274
+ }
275
+ println ! ( "Finished. run_time_seconds = {}" , start. elapsed( ) . as_secs( ) ) ;
276
+ }
277
+
154
278
/// Replay mock mined blocks from JSON files
155
279
/// Terminates on error using `process::exit()`
156
280
///
@@ -525,11 +649,39 @@ fn replay_block(
525
649
} ;
526
650
}
527
651
652
+ /// Fetch and process a NakamotoBlock from database and call `replay_block_nakamoto()` to validate
653
+ fn replay_naka_staging_block ( db_path : & str , index_block_hash_hex : & str , conf : & StacksChainConfig ) {
654
+ let block_id = StacksBlockId :: from_hex ( index_block_hash_hex) . unwrap ( ) ;
655
+ let chain_state_path = format ! ( "{db_path}/chainstate/" ) ;
656
+ let sort_db_path = format ! ( "{db_path}/burnchain/sortition" ) ;
657
+
658
+ let mainnet = conf. chain_id == CHAIN_ID_MAINNET ;
659
+ let ( mut chainstate, _) =
660
+ StacksChainState :: open ( mainnet, conf. chain_id , & chain_state_path, None ) . unwrap ( ) ;
661
+
662
+ let mut sortdb = SortitionDB :: connect (
663
+ & sort_db_path,
664
+ conf. first_block_height ,
665
+ & conf. first_burn_header_hash ,
666
+ conf. first_burn_header_timestamp ,
667
+ & conf. epochs ,
668
+ conf. pox_constants . clone ( ) ,
669
+ None ,
670
+ true ,
671
+ )
672
+ . unwrap ( ) ;
673
+
674
+ let ( block, block_size) = chainstate
675
+ . nakamoto_blocks_db ( )
676
+ . get_nakamoto_block ( & block_id)
677
+ . unwrap ( )
678
+ . unwrap ( ) ;
679
+ replay_block_nakamoto ( & mut sortdb, & mut chainstate, & block, block_size) . unwrap ( ) ;
680
+ }
681
+
528
682
fn replay_block_nakamoto (
529
683
sort_db : & mut SortitionDB ,
530
684
stacks_chain_state : & mut StacksChainState ,
531
- mut chainstate_tx : ChainstateTx ,
532
- clarity_instance : & mut ClarityInstance ,
533
685
block : & NakamotoBlock ,
534
686
block_size : u64 ,
535
687
) -> Result < ( ) , ChainstateError > {
@@ -758,6 +910,7 @@ fn replay_block_nakamoto(
758
910
commit_burn,
759
911
sortition_burn,
760
912
& active_reward_set,
913
+ true ,
761
914
) {
762
915
Ok ( next_chain_tip_info) => ( Some ( next_chain_tip_info) , None ) ,
763
916
Err ( e) => ( None , Some ( e) ) ,
@@ -785,18 +938,5 @@ fn replay_block_nakamoto(
785
938
return Err ( e) ;
786
939
} ;
787
940
788
- let ( receipt, clarity_commit, reward_set_data) = ok_opt. expect ( "FATAL: unreachable" ) ;
789
-
790
- assert_eq ! (
791
- receipt. header. anchored_header. block_hash( ) ,
792
- block. header. block_hash( )
793
- ) ;
794
- assert_eq ! ( receipt. header. consensus_hash, block. header. consensus_hash) ;
795
-
796
- info ! (
797
- "Advanced to new tip! {}/{}" ,
798
- & receipt. header. consensus_hash,
799
- & receipt. header. anchored_header. block_hash( )
800
- ) ;
801
941
Ok ( ( ) )
802
942
}
0 commit comments