@@ -16,6 +16,7 @@ mod commands;
16
16
mod v0;
17
17
18
18
use std:: collections:: HashSet ;
19
+ use std:: path:: PathBuf ;
19
20
use std:: sync:: atomic:: { AtomicBool , Ordering } ;
20
21
use std:: sync:: { Arc , Mutex } ;
21
22
use std:: thread;
@@ -43,9 +44,9 @@ use stacks::net::api::postblock_proposal::{
43
44
} ;
44
45
use stacks:: types:: chainstate:: { StacksAddress , StacksBlockId , StacksPublicKey } ;
45
46
use stacks:: types:: PrivateKey ;
46
- use stacks:: util:: get_epoch_time_secs;
47
47
use stacks:: util:: hash:: MerkleHashFunc ;
48
48
use stacks:: util:: secp256k1:: { MessageSignature , Secp256k1PublicKey } ;
49
+ use stacks:: util:: { get_epoch_time_secs, sleep_ms} ;
49
50
use stacks_common:: codec:: StacksMessageCodec ;
50
51
use stacks_common:: consts:: SIGNER_SLOTS_PER_USER ;
51
52
use stacks_common:: types:: StacksEpochId ;
@@ -60,7 +61,10 @@ use stacks_signer::{Signer, SpawnedSigner};
60
61
use super :: nakamoto_integrations:: {
61
62
check_nakamoto_empty_block_heuristics, next_block_and, wait_for,
62
63
} ;
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 ;
64
68
use crate :: neon:: Counters ;
65
69
use crate :: run_loop:: boot_nakamoto;
66
70
use crate :: tests:: bitcoin_regtest:: BitcoinCoreController ;
@@ -102,6 +106,8 @@ pub struct SignerTest<S> {
102
106
pub stacks_client : StacksClient ,
103
107
/// The number of cycles to stack for
104
108
pub num_stacking_cycles : u64 ,
109
+ /// The path to the snapshot directory
110
+ pub snapshot_path : Option < PathBuf > ,
105
111
}
106
112
107
113
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
117
123
}
118
124
119
125
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
+ > (
120
148
num_signers : usize ,
121
149
initial_balances : Vec < ( StacksAddress , u64 ) > ,
122
150
mut signer_config_modifier : F ,
123
151
mut node_config_modifier : G ,
124
152
btc_miner_pubkeys : Option < Vec < Secp256k1PublicKey > > ,
125
153
signer_stacks_private_keys : Option < Vec < StacksPrivateKey > > ,
154
+ snapshot_name : Option < & str > ,
126
155
) -> Self {
127
156
// Generate Signer Data
128
157
let signer_stacks_private_keys = signer_stacks_private_keys
@@ -135,11 +164,16 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
135
164
} )
136
165
. unwrap_or_else ( || {
137
166
( 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
+ } )
139
172
. collect ( )
140
173
} ) ;
141
174
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 ( ) ) ) ;
143
177
144
178
node_config_modifier ( & mut naka_conf) ;
145
179
@@ -198,12 +232,43 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
198
232
vec ! [ pk]
199
233
} ) ;
200
234
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
+
201
265
let node = setup_stx_btc_node (
202
266
naka_conf,
203
267
& signer_stacks_private_keys,
204
268
& signer_configs,
205
269
btc_miner_pubkeys. as_slice ( ) ,
206
270
node_config_modifier,
271
+ snapshot_exists,
207
272
) ;
208
273
let config = signer_configs. first ( ) . unwrap ( ) ;
209
274
let stacks_client = StacksClient :: from ( config) ;
@@ -215,7 +280,64 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
215
280
stacks_client,
216
281
num_stacking_cycles : 12_u64 ,
217
282
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 ;
218
321
}
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) ;
219
341
}
220
342
221
343
/// Send a status request to each spawned signer
@@ -1145,11 +1267,30 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
1145
1267
. run_loop_stopper
1146
1268
. store ( false , Ordering :: SeqCst ) ;
1147
1269
self . running_nodes . run_loop_thread . join ( ) . unwrap ( ) ;
1270
+
1271
+ Self :: stop_bitcoind ( & self . running_nodes . conf ) ;
1272
+
1148
1273
for signer in self . spawned_signers {
1149
1274
assert ! ( signer. stop( ) . is_none( ) ) ;
1150
1275
}
1151
1276
}
1152
1277
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
+
1153
1294
/// Get the latest block response from the given slot
1154
1295
pub fn get_latest_block_response ( & self , slot_id : u32 ) -> BlockResponse {
1155
1296
let mut stackerdb = StackerDB :: new_normal (
@@ -1282,6 +1423,7 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
1282
1423
signer_configs : & [ SignerConfig ] ,
1283
1424
btc_miner_pubkeys : & [ Secp256k1PublicKey ] ,
1284
1425
mut node_config_modifier : G ,
1426
+ snapshot_exists : bool ,
1285
1427
) -> RunningNodes {
1286
1428
// Spawn the endpoints for observing signers
1287
1429
for signer_config in signer_configs {
@@ -1361,10 +1503,11 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
1361
1503
. expect ( "Failed to get epoch 2.5 start height" ) ;
1362
1504
let bootstrap_block = epoch_2_5_start - 6 ;
1363
1505
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
+ }
1368
1511
1369
1512
let mut run_loop = boot_nakamoto:: BootRunLoop :: new ( naka_conf. clone ( ) ) . unwrap ( ) ;
1370
1513
let run_loop_stopper = run_loop. get_termination_switch ( ) ;
@@ -1378,17 +1521,19 @@ fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
1378
1521
info ! ( "Wait for runloop..." ) ;
1379
1522
wait_for_runloop ( & blocks_processed) ;
1380
1523
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 ) ;
1384
1528
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 ) ;
1388
1532
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
+ }
1392
1537
1393
1538
RunningNodes {
1394
1539
btcd_controller,
0 commit comments