@@ -10,9 +10,12 @@ extern crate stacks;
10
10
#[ macro_use( o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error) ]
11
11
extern crate slog;
12
12
13
+ use regex:: Regex ;
13
14
pub use stacks_common:: util;
14
15
use stacks_common:: util:: hash:: hex_bytes;
15
16
17
+ use crate :: neon_node:: AssembledAnchorBlock ;
18
+
16
19
pub mod monitoring;
17
20
18
21
pub mod burnchains;
@@ -31,7 +34,8 @@ pub mod syncctl;
31
34
pub mod tenure;
32
35
33
36
use std:: collections:: HashMap ;
34
- use std:: { env, panic, process} ;
37
+ use std:: path:: PathBuf ;
38
+ use std:: { env, fs, panic, process} ;
35
39
36
40
use backtrace:: Backtrace ;
37
41
use pico_args:: Arguments ;
@@ -166,10 +170,8 @@ fn cli_get_miner_spend(
166
170
return 0.0 ;
167
171
} ;
168
172
let Ok ( active_miners_and_commits) =
169
- MinerStats :: get_active_miners ( & sortdb, Some ( burn_block_height) ) . map_err ( |e| {
170
- warn ! ( "Failed to get active miners: {:?}" , & e) ;
171
- e
172
- } )
173
+ MinerStats :: get_active_miners ( & sortdb, Some ( burn_block_height) )
174
+ . inspect_err ( |e| warn ! ( "Failed to get active miners: {e:?}" ) )
173
175
else {
174
176
return 0.0 ;
175
177
} ;
@@ -187,10 +189,7 @@ fn cli_get_miner_spend(
187
189
188
190
let Ok ( unconfirmed_block_commits) = miner_stats
189
191
. get_unconfirmed_commits ( burn_block_height + 1 , & active_miners)
190
- . map_err ( |e| {
191
- warn ! ( "Failed to find unconfirmed block-commits: {}" , & e) ;
192
- e
193
- } )
192
+ . inspect_err ( |e| warn ! ( "Failed to find unconfirmed block-commits: {e}" ) )
194
193
else {
195
194
return 0.0 ;
196
195
} ;
@@ -229,10 +228,7 @@ fn cli_get_miner_spend(
229
228
& commit_outs,
230
229
at_burnchain_height,
231
230
)
232
- . map_err ( |e| {
233
- warn ! ( "Failed to get unconfirmed burn distribution: {:?}" , & e) ;
234
- e
235
- } )
231
+ . inspect_err ( |e| warn ! ( "Failed to get unconfirmed burn distribution: {e:?}" ) )
236
232
else {
237
233
return 0.0 ;
238
234
} ;
@@ -265,6 +261,82 @@ fn cli_get_miner_spend(
265
261
spend_amount
266
262
}
267
263
264
+ fn cli_replay_mock_mining ( config_path : & str , path : & str ) {
265
+ info ! ( "Loading config at path {config_path}" ) ;
266
+ let config = match ConfigFile :: from_path ( & config_path) {
267
+ Ok ( config_file) => Config :: from_config_file ( config_file, true ) . unwrap ( ) ,
268
+ Err ( e) => {
269
+ warn ! ( "Invalid config file: {e}" ) ;
270
+ process:: exit ( 1 ) ;
271
+ }
272
+ } ;
273
+
274
+ // Validate directory path
275
+ let dir = PathBuf :: from ( path) ;
276
+ let dir = fs:: canonicalize ( dir) . unwrap_or_else ( |e| panic ! ( "{path} is not a valid path: {e}" ) ) ;
277
+
278
+ if !dir. is_dir ( ) {
279
+ panic ! ( "{path} is not a valid directory" ) ;
280
+ }
281
+
282
+ // Read entries in directory
283
+ let dir_entries = dir
284
+ . read_dir ( )
285
+ . unwrap_or_else ( |e| panic ! ( "Failed to read {path}: {e}" ) )
286
+ . filter_map ( |e| e. ok ( ) ) ;
287
+
288
+ // Get filenames, filtering out anything that isn't a regular file
289
+ let filenames = dir_entries. filter_map ( |e| match e. file_type ( ) {
290
+ Ok ( t) if t. is_file ( ) => e. file_name ( ) . into_string ( ) . ok ( ) ,
291
+ _ => None ,
292
+ } ) ;
293
+
294
+ // Get vec of (block_height, filename), to prepare for sorting
295
+ //
296
+ // NOTE: Trusting the filename is not ideal. We could sort on data read from the file,
297
+ // but that requires reading all files
298
+ let re = Regex :: new ( r"^([0-9]+\.json)$" ) . unwrap ( ) ;
299
+ let mut indexed_files = filenames
300
+ . filter_map ( |filename| {
301
+ // Use regex to extract block number from filename
302
+ let Some ( cap) = re. captures ( & filename) else {
303
+ return None ;
304
+ } ;
305
+ let Some ( m) = cap. get ( 0 ) else {
306
+ return None ;
307
+ } ;
308
+ let Ok ( bh) = m. as_str ( ) . parse :: < u64 > ( ) else {
309
+ return None ;
310
+ } ;
311
+ Some ( ( bh, filename) )
312
+ } )
313
+ . collect :: < Vec < _ > > ( ) ;
314
+
315
+ // Sort by block height
316
+ indexed_files. sort_by_key ( |( bh, _) | * bh) ;
317
+
318
+ if indexed_files. is_empty ( ) {
319
+ panic ! ( "No block files found" ) ;
320
+ }
321
+
322
+ info ! (
323
+ "Replaying {} blocks starting at {}" ,
324
+ indexed_files. len( ) ,
325
+ indexed_files[ 0 ] . 0
326
+ ) ;
327
+
328
+ for ( bh, filename) in indexed_files {
329
+ let filepath = dir. join ( filename) ;
330
+ let block = AssembledAnchorBlock :: deserialize_from_file ( & filepath)
331
+ . unwrap_or_else ( |e| panic ! ( "Error reading block {bh} from file: {e}" ) ) ;
332
+ debug ! ( "Replaying block from {filepath:?}" ;
333
+ "block_height" => bh,
334
+ "block" => ?block
335
+ ) ;
336
+ // TODO: Actually replay block
337
+ }
338
+ }
339
+
268
340
fn main ( ) {
269
341
panic:: set_hook ( Box :: new ( |panic_info| {
270
342
error ! ( "Process abort due to thread panic: {}" , panic_info) ;
@@ -412,6 +484,13 @@ fn main() {
412
484
println ! ( "Will spend {}" , spend_amount) ;
413
485
process:: exit ( 0 ) ;
414
486
}
487
+ "replay-mock-mining" => {
488
+ let path: String = args. value_from_str ( "--path" ) . unwrap ( ) ;
489
+ let config_path: String = args. value_from_str ( "--config" ) . unwrap ( ) ;
490
+ args. finish ( ) ;
491
+ cli_replay_mock_mining ( & config_path, & path) ;
492
+ process:: exit ( 0 ) ;
493
+ }
415
494
_ => {
416
495
print_help ( ) ;
417
496
return ;
@@ -502,6 +581,11 @@ key-for-seed\tOutput the associated secret key for a burnchain signer created wi
502
581
\t \t Can be passed a config file for the seed via the `--config <file>` option *or* by supplying the hex seed on
503
582
\t \t the command line directly.
504
583
584
+ replay-mock-mining\t Replay mock mined blocks from <dir>
585
+ \t \t Arguments:
586
+ \t \t --path: path to directory of mock mined blocks
587
+ \t \t --config: path to the config file
588
+
505
589
help\t \t Display this help.
506
590
507
591
OPTIONAL ARGUMENTS:
0 commit comments