Skip to content

Commit aa5a439

Browse files
authored
Merge pull request #5207 from stacks-network/test/stop-bitcoind
test: try stopping bitcoind within Rust, rather than spawning the CLI
2 parents b5250c6 + 550cd52 commit aa5a439

File tree

4 files changed

+82
-33
lines changed

4 files changed

+82
-33
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ jobs:
7575
- tests::neon_integrations::vote_for_aggregate_key_burn_op_test
7676
- tests::neon_integrations::mock_miner_replay
7777
- tests::neon_integrations::listunspent_max_utxos
78+
- tests::neon_integrations::bitcoin_reorg_flap
79+
- tests::neon_integrations::bitcoin_reorg_flap_with_follower
80+
- tests::neon_integrations::start_stop_bitcoind
7881
- tests::epoch_25::microblocks_disabled
7982
- tests::should_succeed_handling_malformed_and_valid_txs
8083
- tests::nakamoto_integrations::simple_neon_integration
@@ -122,9 +125,6 @@ jobs:
122125
- tests::nakamoto_integrations::follower_bootup_across_multiple_cycles
123126
- tests::nakamoto_integrations::utxo_check_on_startup_panic
124127
- tests::nakamoto_integrations::utxo_check_on_startup_recover
125-
# Do not run this one until we figure out why it fails in CI
126-
# - tests::neon_integrations::bitcoin_reorg_flap
127-
# - tests::neon_integrations::bitcoin_reorg_flap_with_follower
128128
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
129129
# - tests::signer::v1::dkg
130130
# - tests::signer::v1::sign_request_rejected

testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2831,7 +2831,7 @@ impl BitcoinRPCRequest {
28312831
Ok(())
28322832
}
28332833

2834-
fn send(config: &Config, payload: BitcoinRPCRequest) -> RPCResult<serde_json::Value> {
2834+
pub fn send(config: &Config, payload: BitcoinRPCRequest) -> RPCResult<serde_json::Value> {
28352835
let request = BitcoinRPCRequest::build_rpc_request(&config, &payload);
28362836
let timeout = Duration::from_secs(u64::from(config.burnchain.timeout));
28372837

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

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use stacks::core::StacksEpochId;
1111
use stacks_common::util::hash::hex_bytes;
1212

1313
use super::PUBLISH_CONTRACT;
14+
use crate::burnchains::bitcoin_regtest_controller::BitcoinRPCRequest;
1415
use crate::config::InitialBalance;
1516
use crate::helium::RunLoop;
1617
use crate::tests::to_addr;
@@ -19,12 +20,14 @@ use crate::Config;
1920
#[derive(Debug)]
2021
pub enum BitcoinCoreError {
2122
SpawnFailed(String),
23+
StopFailed(String),
2224
}
2325

2426
impl std::fmt::Display for BitcoinCoreError {
2527
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2628
match self {
2729
Self::SpawnFailed(msg) => write!(f, "bitcoind spawn failed: {msg}"),
30+
Self::StopFailed(msg) => write!(f, "bitcoind stop failed: {msg}"),
2831
}
2932
}
3033
}
@@ -109,25 +112,25 @@ impl BitcoinCoreController {
109112

110113
pub fn stop_bitcoind(&mut self) -> Result<(), BitcoinCoreError> {
111114
if let Some(_) = self.bitcoind_process.take() {
112-
let mut command = Command::new("bitcoin-cli");
113-
command.stdout(Stdio::piped()).arg("-rpcconnect=127.0.0.1");
114-
115-
self.add_rpc_cli_args(&mut command);
116-
117-
command.arg("stop");
118-
119-
let mut process = match command.spawn() {
120-
Ok(child) => child,
121-
Err(e) => return Err(BitcoinCoreError::SpawnFailed(format!("{e:?}"))),
115+
let payload = BitcoinRPCRequest {
116+
method: "stop".to_string(),
117+
params: vec![],
118+
id: "stacks".to_string(),
119+
jsonrpc: "2.0".to_string(),
122120
};
123121

124-
let mut out_reader = BufReader::new(process.stdout.take().unwrap());
125-
let mut line = String::new();
126-
while let Ok(bytes_read) = out_reader.read_line(&mut line) {
127-
if bytes_read == 0 {
128-
break;
122+
let res = BitcoinRPCRequest::send(&self.config, payload)
123+
.map_err(|e| BitcoinCoreError::StopFailed(format!("{e:?}")))?;
124+
125+
if let Some(err) = res.get("error") {
126+
if !err.is_null() {
127+
return Err(BitcoinCoreError::StopFailed(format!("{err}")));
129128
}
130-
eprintln!("{line}");
129+
} else {
130+
return Err(BitcoinCoreError::StopFailed(format!(
131+
"Invalid response: {:?}",
132+
res
133+
)));
131134
}
132135
}
133136
Ok(())

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

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12398,6 +12398,10 @@ fn bitcoin_reorg_flap() {
1239812398
channel.stop_chains_coordinator();
1239912399
}
1240012400

12401+
/// Advance the bitcoin chain and wait for the miner and any followers to
12402+
/// process the next block.
12403+
/// NOTE: This only works if the followers are mock-mining, or else the counter
12404+
/// will not be updated.
1240112405
fn next_block_and_wait_all(
1240212406
btc_controller: &mut BitcoinRegtestController,
1240312407
miner_blocks_processed: &Arc<AtomicU64>,
@@ -12447,7 +12451,7 @@ fn bitcoin_reorg_flap_with_follower() {
1244712451
}
1244812452

1244912453
let (conf, _miner_account) = neon_integration_test_conf();
12450-
let timeout = None;
12454+
let timeout = Some(Duration::from_secs(60));
1245112455

1245212456
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
1245312457
btcd_controller
@@ -12461,10 +12465,12 @@ fn bitcoin_reorg_flap_with_follower() {
1246112465
eprintln!("Chain bootstrapped...");
1246212466

1246312467
let mut miner_run_loop = neon::RunLoop::new(conf.clone());
12468+
let run_loop_stopper = miner_run_loop.get_termination_switch();
1246412469
let miner_blocks_processed = miner_run_loop.get_blocks_processed_arc();
1246512470
let miner_channel = miner_run_loop.get_coordinator_channel().unwrap();
1246612471

1246712472
let mut follower_conf = conf.clone();
12473+
follower_conf.node.mock_mining = true;
1246812474
follower_conf.events_observers.clear();
1246912475
follower_conf.node.working_dir = format!("{}-follower", &conf.node.working_dir);
1247012476
follower_conf.node.seed = vec![0x01; 32];
@@ -12483,7 +12489,7 @@ fn bitcoin_reorg_flap_with_follower() {
1248312489
follower_conf.node.data_url = format!("http://{}:{}", &localhost, rpc_port);
1248412490
follower_conf.node.p2p_address = format!("{}:{}", &localhost, p2p_port);
1248512491

12486-
thread::spawn(move || miner_run_loop.start(None, 0));
12492+
let run_loop_thread = thread::spawn(move || miner_run_loop.start(None, 0));
1248712493
wait_for_runloop(&miner_blocks_processed);
1248812494

1248912495
// figure out the started node's port
@@ -12499,23 +12505,20 @@ fn bitcoin_reorg_flap_with_follower() {
1249912505
);
1250012506

1250112507
let mut follower_run_loop = neon::RunLoop::new(follower_conf.clone());
12508+
let follower_run_loop_stopper = follower_run_loop.get_termination_switch();
1250212509
let follower_blocks_processed = follower_run_loop.get_blocks_processed_arc();
1250312510
let follower_channel = follower_run_loop.get_coordinator_channel().unwrap();
1250412511

12505-
thread::spawn(move || follower_run_loop.start(None, 0));
12512+
let follower_thread = thread::spawn(move || follower_run_loop.start(None, 0));
1250612513
wait_for_runloop(&follower_blocks_processed);
1250712514

1250812515
eprintln!("Follower bootup complete!");
1250912516

1251012517
// first block wakes up the run loop
12511-
next_block_and_wait_all(
12512-
&mut btc_regtest_controller,
12513-
&miner_blocks_processed,
12514-
&[],
12515-
timeout,
12516-
);
12518+
next_block_and_wait_with_timeout(&mut btc_regtest_controller, &miner_blocks_processed, 60);
1251712519

12518-
// first block will hold our VRF registration
12520+
// next block will hold our VRF registration
12521+
// Note that the follower will not see its block processed counter bumped here
1251912522
next_block_and_wait_all(
1252012523
&mut btc_regtest_controller,
1252112524
&miner_blocks_processed,
@@ -12609,9 +12612,11 @@ fn bitcoin_reorg_flap_with_follower() {
1260912612
assert_eq!(miner_channel.get_sortitions_processed(), 225);
1261012613
assert_eq!(follower_channel.get_sortitions_processed(), 225);
1261112614

12612-
btcd_controller.stop_bitcoind().unwrap();
12613-
miner_channel.stop_chains_coordinator();
12614-
follower_channel.stop_chains_coordinator();
12615+
run_loop_stopper.store(false, Ordering::SeqCst);
12616+
follower_run_loop_stopper.store(false, Ordering::SeqCst);
12617+
12618+
run_loop_thread.join().unwrap();
12619+
follower_thread.join().unwrap();
1261512620
}
1261612621

1261712622
/// Tests the following:
@@ -12841,3 +12846,44 @@ fn listunspent_max_utxos() {
1284112846
let utxos = res.expect("Failed to get utxos");
1284212847
assert_eq!(utxos.num_utxos(), 10);
1284312848
}
12849+
12850+
#[test]
12851+
#[ignore]
12852+
/// Test out stopping bitcoind and restarting it
12853+
fn start_stop_bitcoind() {
12854+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
12855+
return;
12856+
}
12857+
12858+
let (mut conf, _miner_account) = neon_integration_test_conf();
12859+
let prom_bind = format!("{}:{}", "127.0.0.1", 6000);
12860+
conf.node.prometheus_bind = Some(prom_bind.clone());
12861+
12862+
conf.burnchain.max_rbf = 1000000;
12863+
12864+
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
12865+
btcd_controller
12866+
.start_bitcoind()
12867+
.map_err(|_e| ())
12868+
.expect("Failed starting bitcoind");
12869+
12870+
let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None);
12871+
12872+
btc_regtest_controller.bootstrap_chain(201);
12873+
12874+
eprintln!("Chain bootstrapped...");
12875+
12876+
btcd_controller
12877+
.stop_bitcoind()
12878+
.expect("Failed to stop bitcoind");
12879+
12880+
thread::sleep(Duration::from_secs(5));
12881+
12882+
btcd_controller
12883+
.start_bitcoind()
12884+
.expect("Failed to start bitcoind");
12885+
12886+
btcd_controller
12887+
.stop_bitcoind()
12888+
.expect("Failed to stop bitcoind");
12889+
}

0 commit comments

Comments
 (0)