Skip to content

Commit 7f12563

Browse files
committed
feat: stacks-inspect reads mock mined blocks from files. No replay yet
1 parent f949691 commit 7f12563

File tree

4 files changed

+138
-27
lines changed

4 files changed

+138
-27
lines changed

stacks-common/src/util/macros.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,28 @@ macro_rules! impl_byte_array_serde {
617617
};
618618
}
619619

620+
#[allow(unused_macros)]
621+
#[macro_export]
622+
macro_rules! impl_file_io_serde_json {
623+
($thing:ident) => {
624+
impl $thing {
625+
pub fn serialize_to_file<P>(&self, path: P) -> Result<(), std::io::Error>
626+
where
627+
P: AsRef<std::path::Path>,
628+
{
629+
$crate::util::serialize_json_to_file(self, path)
630+
}
631+
632+
pub fn deserialize_from_file<P>(path: P) -> Result<Self, std::io::Error>
633+
where
634+
P: AsRef<std::path::Path>,
635+
{
636+
$crate::util::deserialize_json_from_file(path)
637+
}
638+
}
639+
};
640+
}
641+
620642
// print debug statements while testing
621643
#[allow(unused_macros)]
622644
#[macro_export]

stacks-common/src/util/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub mod uint;
2828
pub mod vrf;
2929

3030
use std::collections::HashMap;
31+
use std::fs::File;
32+
use std::io::{BufReader, BufWriter, Write};
33+
use std::path::Path;
3134
use std::time::{SystemTime, UNIX_EPOCH};
3235
use std::{error, fmt, thread, time};
3336

@@ -120,3 +123,26 @@ pub mod db_common {
120123
true
121124
}
122125
}
126+
127+
/// Write any `serde_json` object directly to a file
128+
pub fn serialize_json_to_file<J, P>(json: &J, path: P) -> Result<(), std::io::Error>
129+
where
130+
J: ?Sized + serde::Serialize,
131+
P: AsRef<Path>,
132+
{
133+
let file = File::create(path)?;
134+
let mut writer = BufWriter::new(file);
135+
serde_json::to_writer(&mut writer, json)?;
136+
writer.flush()
137+
}
138+
139+
/// Read any `serde_json` object directly from a file
140+
pub fn deserialize_json_from_file<J, P>(path: P) -> Result<J, std::io::Error>
141+
where
142+
J: serde::de::DeserializeOwned,
143+
P: AsRef<Path>,
144+
{
145+
let file = File::open(path)?;
146+
let reader = BufReader::new(file);
147+
serde_json::from_reader::<_, J>(reader).map_err(std::io::Error::from)
148+
}

stackslib/src/main.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern crate stacks_common;
2626
#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)]
2727
extern crate slog;
2828

29+
use regex::Regex;
2930
use stacks_common::types::MempoolCollectionBehavior;
3031
#[cfg(not(any(target_os = "macos", target_os = "windows", target_arch = "arm")))]
3132
use tikv_jemallocator::Jemalloc;
@@ -38,6 +39,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
3839
use std::fs::{File, OpenOptions};
3940
use std::io::prelude::*;
4041
use std::io::BufReader;
42+
use std::path::PathBuf;
4143
use std::time::Instant;
4244
use std::{env, fs, io, process, thread};
4345

@@ -92,7 +94,7 @@ use stacks_common::util::hash::{hex_bytes, to_hex, Hash160};
9294
use stacks_common::util::retry::LogReader;
9395
use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
9496
use stacks_common::util::vrf::VRFProof;
95-
use stacks_common::util::{get_epoch_time_ms, log, sleep_ms};
97+
use stacks_common::util::{deserialize_json_from_file, get_epoch_time_ms, log, sleep_ms};
9698

9799
#[cfg_attr(test, mutants::skip)]
98100
fn main() {
@@ -1339,6 +1341,88 @@ simulating a miner.
13391341
);
13401342
return;
13411343
}
1344+
if argv[1] == "replay-mock-mining" {
1345+
let print_help_and_exit = || {
1346+
let n = &argv[0];
1347+
eprintln!("Usage:");
1348+
eprintln!(" {n} <mock-miner-output-dir>");
1349+
process::exit(1);
1350+
};
1351+
1352+
// Process CLI args
1353+
let dir = argv
1354+
.get(2)
1355+
.map(PathBuf::from)
1356+
.map(fs::canonicalize)
1357+
.transpose()
1358+
.unwrap_or_else(|e| panic!("Not a valid path: {e}"))
1359+
.unwrap_or_else(print_help_and_exit);
1360+
1361+
if !dir.is_dir() {
1362+
panic!("Not a valid directory: {dir:?}");
1363+
}
1364+
1365+
// Read entries in directory
1366+
let dir_entries = dir
1367+
.read_dir()
1368+
.unwrap_or_else(|e| panic!("Failed to read directory: {e}"))
1369+
.filter_map(|e| e.ok());
1370+
1371+
// Get filenames, filtering out anything that isn't a regular file
1372+
let filenames = dir_entries.filter_map(|e| match e.file_type() {
1373+
Ok(t) if t.is_file() => e.file_name().into_string().ok(),
1374+
_ => None,
1375+
});
1376+
1377+
// Get vec of (block_height, filename), to prepare for sorting
1378+
//
1379+
// NOTE: Trusting the filename is not ideal. We could sort on data read from the file,
1380+
// but that requires reading all files
1381+
let re = Regex::new(r"^([0-9]+\.json)$").unwrap();
1382+
let mut indexed_files = filenames
1383+
.filter_map(|filename| {
1384+
// Use regex to extract block number from filename
1385+
let Some(cap) = re.captures(&filename) else {
1386+
return None;
1387+
};
1388+
let Some(m) = cap.get(0) else {
1389+
return None;
1390+
};
1391+
let Ok(bh) = m.as_str().parse::<u64>() else {
1392+
return None;
1393+
};
1394+
Some((bh, filename))
1395+
})
1396+
.collect::<Vec<_>>();
1397+
1398+
// Sort by block height
1399+
indexed_files.sort_by_key(|(bh, _)| *bh);
1400+
1401+
if indexed_files.is_empty() {
1402+
panic!("No block files found");
1403+
}
1404+
1405+
info!(
1406+
"Replaying {} blocks starting at {}",
1407+
indexed_files.len(),
1408+
indexed_files[0].0
1409+
);
1410+
1411+
for (bh, filename) in indexed_files {
1412+
let filepath = dir.join(filename);
1413+
info!("Replaying block from file";
1414+
"block_height" => bh,
1415+
"filepath" => ?filepath
1416+
);
1417+
// let block = AssembledAnchorBlock::deserialize_json_from_file(filepath)
1418+
// .unwrap_or_else(|e| panic!("Error reading block {block} from file: {e}"));
1419+
// debug!("Replaying block from {filepath:?}";
1420+
// "block_height" => bh,
1421+
// "block" => %block
1422+
// );
1423+
// TODO: Actually replay block
1424+
}
1425+
}
13421426

13431427
if argv.len() < 4 {
13441428
eprintln!("Usage: {} blockchain network working_dir", argv[0]);

testnet/stacks-node/src/neon_node.rs

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,13 @@
140140
use std::cmp;
141141
use std::cmp::Ordering as CmpOrdering;
142142
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
143-
use std::fs::{self, File};
144-
use std::io::{BufWriter, Read, Write};
143+
use std::io::{Read, Write};
145144
use std::net::SocketAddr;
146145
use std::path::Path;
147146
use std::sync::mpsc::{Receiver, TrySendError};
148147
use std::thread::JoinHandle;
149148
use std::time::Duration;
150-
use std::{mem, thread};
149+
use std::{fs, mem, thread};
151150

152151
use clarity::vm::ast::ASTRules;
153152
use clarity::vm::costs::ExecutionCost;
@@ -191,6 +190,7 @@ use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs};
191190
use stacks::net::{
192191
Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags,
193192
};
193+
use stacks::util::{deserialize_json_from_file, serialize_json_to_file};
194194
use stacks::util_lib::strings::{UrlString, VecDisplay};
195195
use stacks_common::codec::StacksMessageCodec;
196196
use stacks_common::types::chainstate::{
@@ -239,7 +239,7 @@ pub(crate) enum MinerThreadResult {
239239
/// Fully-assembled Stacks anchored, block as well as some extra metadata pertaining to how it was
240240
/// linked to the burnchain and what view(s) the miner had of the burnchain before and after
241241
/// completing the block.
242-
#[derive(Clone, Serialize)]
242+
#[derive(Clone, Serialize, Deserialize)]
243243
pub struct AssembledAnchorBlock {
244244
/// Consensus hash of the parent Stacks block
245245
parent_consensus_hash: ConsensusHash,
@@ -256,28 +256,7 @@ pub struct AssembledAnchorBlock {
256256
/// Epoch timestamp in milliseconds when we started producing the block.
257257
tenure_begin: u128,
258258
}
259-
260-
/// Write any `serde_json` object to a file
261-
/// TODO: Move this somewhere else
262-
pub fn serialize_json_to_file<J, P>(json: &J, filepath: P) -> Result<(), std::io::Error>
263-
where
264-
J: ?Sized + serde::Serialize,
265-
P: AsRef<Path>,
266-
{
267-
let file = File::create(filepath)?;
268-
let mut writer = BufWriter::new(file);
269-
serde_json::to_writer(&mut writer, json)?;
270-
writer.flush()
271-
}
272-
273-
impl AssembledAnchorBlock {
274-
pub fn serialize_to_file<P>(&self, filepath: P) -> Result<(), std::io::Error>
275-
where
276-
P: AsRef<Path>,
277-
{
278-
serialize_json_to_file(self, filepath)
279-
}
280-
}
259+
impl_file_io_serde_json!(AssembledAnchorBlock);
281260

282261
/// Miner chain tip, on top of which to build microblocks
283262
#[derive(Debug, Clone, PartialEq)]

0 commit comments

Comments
 (0)