Skip to content

Commit 8ca8053

Browse files
committed
feat: add snapshotting to SignerTest to improve boot time
1 parent 418b0c7 commit 8ca8053

File tree

6 files changed

+249
-20
lines changed

6 files changed

+249
-20
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testnet/stacks-node/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ serial_test = "3.2.0"
5454
pinny = { git = "https://github.com/BitcoinL2-Labs/pinny-rs.git", rev = "54ba9d533a7b84525a5e65a3eae1a3ae76b9ea49" } #v0.0.2
5555
madhouse = { git = "https://github.com/stacks-network/madhouse-rs.git", rev = "fc651ddcbaf85e888b06d4a87aa788c4b7ba9309" }
5656
proptest = { git = "https://github.com/proptest-rs/proptest.git", rev = "c9bdf18c232665b2b740c667c81866b598d06dc7" }
57+
stdext = "0.3.1"
5758

5859
[[bin]]
5960
name = "stacks-node"
@@ -70,5 +71,5 @@ prod-genesis-chainstate = []
7071
default = []
7172
testing = []
7273

73-
[package.metadata.pinny]
74+
[package.metadata.pinny]
7475
allowed = ["bitcoind", "flaky", "slow"]

testnet/stacks-node/src/tests/bitcoin_regtest.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type BitcoinResult<T> = Result<T, BitcoinCoreError>;
2929

3030
pub struct BitcoinCoreController {
3131
bitcoind_process: Option<Child>,
32-
config: Config,
32+
pub config: Config,
3333
}
3434

3535
impl BitcoinCoreController {
@@ -63,6 +63,8 @@ impl BitcoinCoreController {
6363
.arg("-nodebug")
6464
.arg("-nodebuglogfile")
6565
.arg("-rest")
66+
.arg("-persistmempool=1")
67+
.arg("-dbcache=100")
6668
.arg("-txindex=1")
6769
.arg("-server=1")
6870
.arg("-listenonion=0")

testnet/stacks-node/src/tests/neon_integrations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9225,7 +9225,7 @@ fn filter_txs_by_origin() {
92259225
}
92269226

92279227
// https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust
9228-
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
9228+
pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
92299229
fs::create_dir_all(&dst)?;
92309230
for entry in fs::read_dir(src)? {
92319231
let entry = entry?;

testnet/stacks-node/src/tests/signer/mod.rs

Lines changed: 162 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod commands;
1616
mod v0;
1717

1818
use std::collections::HashSet;
19+
use std::path::PathBuf;
1920
use std::sync::atomic::{AtomicBool, Ordering};
2021
use std::sync::{Arc, Mutex};
2122
use std::thread;
@@ -43,9 +44,9 @@ use stacks::net::api::postblock_proposal::{
4344
};
4445
use stacks::types::chainstate::{StacksAddress, StacksBlockId, StacksPublicKey};
4546
use stacks::types::PrivateKey;
46-
use stacks::util::get_epoch_time_secs;
4747
use stacks::util::hash::MerkleHashFunc;
4848
use stacks::util::secp256k1::{MessageSignature, Secp256k1PublicKey};
49+
use stacks::util::{get_epoch_time_secs, sleep_ms};
4950
use stacks_common::codec::StacksMessageCodec;
5051
use stacks_common::consts::SIGNER_SLOTS_PER_USER;
5152
use stacks_common::types::StacksEpochId;
@@ -60,7 +61,10 @@ use stacks_signer::{Signer, SpawnedSigner};
6061
use super::nakamoto_integrations::{
6162
check_nakamoto_empty_block_heuristics, next_block_and, wait_for,
6263
};
63-
use super::neon_integrations::{get_account, get_sortition_info_ch, submit_tx_fallible, Account};
64+
use super::neon_integrations::{
65+
copy_dir_all, get_account, get_sortition_info_ch, submit_tx_fallible, Account,
66+
};
67+
use crate::burnchains::bitcoin_regtest_controller::BitcoinRPCRequest;
6468
use crate::neon::Counters;
6569
use crate::run_loop::boot_nakamoto;
6670
use crate::tests::bitcoin_regtest::BitcoinCoreController;
@@ -102,6 +106,8 @@ pub struct SignerTest<S> {
102106
pub stacks_client: StacksClient,
103107
/// The number of cycles to stack for
104108
pub num_stacking_cycles: u64,
109+
/// The path to the snapshot directory
110+
pub snapshot_path: Option<PathBuf>,
105111
}
106112

107113
impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<SpawnedSigner<S, T>> {
@@ -117,12 +123,35 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
117123
}
118124

119125
pub fn new_with_config_modifications<F: FnMut(&mut SignerConfig), G: FnMut(&mut NeonConfig)>(
126+
num_signers: usize,
127+
initial_balances: Vec<(StacksAddress, u64)>,
128+
signer_config_modifier: F,
129+
node_config_modifier: G,
130+
btc_miner_pubkeys: Option<Vec<Secp256k1PublicKey>>,
131+
signer_stacks_private_keys: Option<Vec<StacksPrivateKey>>,
132+
) -> Self {
133+
Self::new_with_config_modifications_and_snapshot(
134+
num_signers,
135+
initial_balances,
136+
signer_config_modifier,
137+
node_config_modifier,
138+
btc_miner_pubkeys,
139+
signer_stacks_private_keys,
140+
None,
141+
)
142+
}
143+
144+
pub fn new_with_config_modifications_and_snapshot<
145+
F: FnMut(&mut SignerConfig),
146+
G: FnMut(&mut NeonConfig),
147+
>(
120148
num_signers: usize,
121149
initial_balances: Vec<(StacksAddress, u64)>,
122150
mut signer_config_modifier: F,
123151
mut node_config_modifier: G,
124152
btc_miner_pubkeys: Option<Vec<Secp256k1PublicKey>>,
125153
signer_stacks_private_keys: Option<Vec<StacksPrivateKey>>,
154+
snapshot_name: Option<&str>,
126155
) -> Self {
127156
// Generate Signer Data
128157
let signer_stacks_private_keys = signer_stacks_private_keys
@@ -135,11 +164,16 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
135164
})
136165
.unwrap_or_else(|| {
137166
(0..num_signers)
138-
.map(|_| StacksPrivateKey::random())
167+
.map(|i| {
168+
StacksPrivateKey::from_seed(
169+
format!("signer_{i}_{}", snapshot_name.unwrap_or("")).as_bytes(),
170+
)
171+
})
139172
.collect()
140173
});
141174

142-
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
175+
let (mut naka_conf, _miner_account) =
176+
naka_neon_integration_conf(snapshot_name.map(|n| n.as_bytes()));
143177

144178
node_config_modifier(&mut naka_conf);
145179

@@ -198,12 +232,43 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
198232
vec![pk]
199233
});
200234

235+
let mut snapshot_exists = false;
236+
237+
let snapshot_path = snapshot_name.map(|name| {
238+
let working_dir = naka_conf.get_working_dir();
239+
240+
let snapshot_path: PathBuf = format!("/tmp/stacks-node-tests/snapshots/{name}/")
241+
.try_into()
242+
.unwrap();
243+
244+
info!("Snapshot path: {}", snapshot_path.clone().display());
245+
246+
snapshot_exists = std::fs::metadata(snapshot_path.clone()).is_ok();
247+
248+
if snapshot_exists {
249+
info!(
250+
"Snapshot directory already exists, copying to working dir";
251+
"snapshot_path" => %snapshot_path.display(),
252+
"working_dir" => %working_dir.display()
253+
);
254+
let err_msg = format!(
255+
"Failed to copy snapshot dir to working dir: {} -> {}",
256+
snapshot_path.display(),
257+
working_dir.display()
258+
);
259+
copy_dir_all(snapshot_path.clone(), working_dir).expect(&err_msg);
260+
}
261+
262+
snapshot_path
263+
});
264+
201265
let node = setup_stx_btc_node(
202266
naka_conf,
203267
&signer_stacks_private_keys,
204268
&signer_configs,
205269
btc_miner_pubkeys.as_slice(),
206270
node_config_modifier,
271+
snapshot_exists,
207272
);
208273
let config = signer_configs.first().unwrap();
209274
let stacks_client = StacksClient::from(config);
@@ -215,7 +280,64 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
215280
stacks_client,
216281
num_stacking_cycles: 12_u64,
217282
signer_configs,
283+
snapshot_path,
284+
}
285+
}
286+
287+
pub fn snapshot_exists(&self) -> bool {
288+
self.snapshot_path
289+
.as_ref()
290+
.map(|p| std::fs::metadata(p).is_ok())
291+
.unwrap_or(false)
292+
}
293+
294+
/// Whether the snapshot needs to be created.
295+
///
296+
/// Returns `false` if not configured to snapshot.
297+
pub fn needs_snapshot(&self) -> bool {
298+
let Some(snapshot_path) = self.snapshot_path.as_ref() else {
299+
return false;
300+
};
301+
302+
!std::fs::metadata(snapshot_path).is_ok()
303+
}
304+
305+
/// Make a snapshot of the current working directory.
306+
///
307+
/// This will stop the bitcoind node and copy the working directory to the snapshot path.
308+
pub fn make_snapshot(&self) {
309+
let snapshot_path = self.snapshot_path.as_ref().unwrap();
310+
311+
let working_dir = self.running_nodes.conf.get_working_dir();
312+
313+
let snapshot_dir_exists = self.snapshot_exists();
314+
315+
if snapshot_dir_exists {
316+
info!("Snapshot directory already exists, skipping snapshot";
317+
"snapshot_path" => %snapshot_path.display(),
318+
"working_dir" => %working_dir.display()
319+
);
320+
return;
218321
}
322+
323+
info!(
324+
"Making snapshot";
325+
"snapshot_path" => %snapshot_path.display(),
326+
"working_dir" => %working_dir.display()
327+
);
328+
329+
Self::stop_bitcoind(&self.running_nodes.conf);
330+
331+
sleep_ms(5000);
332+
333+
let err_msg = format!(
334+
"Failed to copy working dir to snapshot path: {} -> {}",
335+
working_dir.display(),
336+
snapshot_path.display()
337+
);
338+
339+
// Copy the working dir to the snapshot path
340+
copy_dir_all(working_dir, snapshot_path).expect(&err_msg);
219341
}
220342

221343
/// Send a status request to each spawned signer
@@ -1145,11 +1267,30 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
11451267
.run_loop_stopper
11461268
.store(false, Ordering::SeqCst);
11471269
self.running_nodes.run_loop_thread.join().unwrap();
1270+
1271+
Self::stop_bitcoind(&self.running_nodes.conf);
1272+
11481273
for signer in self.spawned_signers {
11491274
assert!(signer.stop().is_none());
11501275
}
11511276
}
11521277

1278+
fn stop_bitcoind(config: &NeonConfig) {
1279+
info!("Stopping bitcoind...");
1280+
let _ = BitcoinRPCRequest::send(
1281+
config,
1282+
BitcoinRPCRequest {
1283+
method: "stop".to_string(),
1284+
params: vec![],
1285+
id: "stacks".to_string(),
1286+
jsonrpc: "2.0".to_string(),
1287+
},
1288+
)
1289+
.inspect_err(|e| {
1290+
error!("Failed to stop bitcoind: {e:?}");
1291+
});
1292+
}
1293+
11531294
/// Get the latest block response from the given slot
11541295
pub fn get_latest_block_response(&self, slot_id: u32) -> BlockResponse {
11551296
let mut stackerdb = StackerDB::new_normal(
@@ -1282,6 +1423,7 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
12821423
signer_configs: &[SignerConfig],
12831424
btc_miner_pubkeys: &[Secp256k1PublicKey],
12841425
mut node_config_modifier: G,
1426+
snapshot_exists: bool,
12851427
) -> RunningNodes {
12861428
// Spawn the endpoints for observing signers
12871429
for signer_config in signer_configs {
@@ -1361,10 +1503,11 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
13611503
.expect("Failed to get epoch 2.5 start height");
13621504
let bootstrap_block = epoch_2_5_start - 6;
13631505

1364-
info!("Bootstraping to block {bootstrap_block}...");
1365-
btc_regtest_controller.bootstrap_chain_to_pks(bootstrap_block, btc_miner_pubkeys);
1366-
1367-
info!("Chain bootstrapped...");
1506+
if !snapshot_exists {
1507+
info!("Bootstraping to block {bootstrap_block}...");
1508+
btc_regtest_controller.bootstrap_chain_to_pks(bootstrap_block, btc_miner_pubkeys);
1509+
info!("Chain bootstrapped...");
1510+
}
13681511

13691512
let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap();
13701513
let run_loop_stopper = run_loop.get_termination_switch();
@@ -1378,17 +1521,19 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
13781521
info!("Wait for runloop...");
13791522
wait_for_runloop(&blocks_processed);
13801523

1381-
// First block wakes up the run loop.
1382-
info!("Mine first block...");
1383-
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
1524+
if !snapshot_exists {
1525+
// First block wakes up the run loop.
1526+
info!("Mine first block...");
1527+
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
13841528

1385-
// Second block will hold our VRF registration.
1386-
info!("Mine second block...");
1387-
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
1529+
// Second block will hold our VRF registration.
1530+
info!("Mine second block...");
1531+
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
13881532

1389-
// Third block will be the first mined Stacks block.
1390-
info!("Mine third block...");
1391-
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
1533+
// Third block will be the first mined Stacks block.
1534+
info!("Mine third block...");
1535+
next_block_and_wait(&mut btc_regtest_controller, &counters.blocks_processed);
1536+
}
13921537

13931538
RunningNodes {
13941539
btcd_controller,

0 commit comments

Comments
 (0)