Skip to content

Commit e63ecca

Browse files
authored
Merge pull request #6385 from fdefelici/refactor/btc-core-ctrl
refactor: `BitcoinCoreController` rcp rollout + housekeeping
2 parents 073d2f4 + 4a9dfd9 commit e63ecca

21 files changed

+556
-896
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ jobs:
5858
exclude:
5959
# The following tests are excluded from CI runs. Some of these may be
6060
# worth investigating adding back into the CI
61-
- test-name: tests::bitcoin_regtest::bitcoind_integration_test_segwit
6261
- test-name: tests::nakamoto_integrations::consensus_hash_event_dispatcher
6362
- test-name: tests::neon_integrations::atlas_integration_test
6463
- test-name: tests::neon_integrations::atlas_stress_integration_test
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright (C) 2025 Stacks Open Internet Foundation
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
//! Bitcoin Core module
17+
//!
18+
//! This module provides convenient APIs for managing a `bitcoind` process,
19+
//! including utilities to quickly start and stop instances for testing or
20+
//! development purposes.
21+
22+
use std::io::{BufRead, BufReader};
23+
use std::process::{Child, Command, Stdio};
24+
25+
use crate::burnchains::rpc::bitcoin_rpc_client::BitcoinRpcClient;
26+
use crate::Config;
27+
28+
// Value usable as `BurnchainConfig::peer_port` to avoid bitcoind peer port binding
29+
pub const BURNCHAIN_CONFIG_PEER_PORT_DISABLED: u16 = 0;
30+
31+
/// Errors that can occur when managing a `bitcoind` process.
32+
#[derive(Debug, thiserror::Error)]
33+
pub enum BitcoinCoreError {
34+
/// Returned when the `bitcoind` process fails to start.
35+
#[error("bitcoind spawn failed: {0}")]
36+
SpawnFailed(String),
37+
/// Returned when an attempt to stop the `bitcoind` process fails.
38+
#[error("bitcoind stop failed: {0}")]
39+
StopFailed(String),
40+
/// Returned when an attempt to forcibly kill the `bitcoind` process fails.
41+
#[error("bitcoind kill failed: {0}")]
42+
KillFailed(String),
43+
}
44+
45+
type BitcoinResult<T> = Result<T, BitcoinCoreError>;
46+
47+
/// Represents a managed `bitcoind` process instance.
48+
pub struct BitcoinCoreController {
49+
/// Handle to the spawned `bitcoind` process.
50+
bitcoind_process: Option<Child>,
51+
/// Command-line arguments used to launch the process.
52+
args: Vec<String>,
53+
/// Path to the data directory used by `bitcoind`.
54+
data_path: String,
55+
/// RPC client for communicating with the `bitcoind` instance.
56+
rpc_client: BitcoinRpcClient,
57+
}
58+
59+
impl BitcoinCoreController {
60+
/// Create a [`BitcoinCoreController`] from Stacks Configuration
61+
pub fn from_stx_config(config: &Config) -> Self {
62+
let client =
63+
BitcoinRpcClient::from_stx_config(config).expect("rpc client creation failed!");
64+
Self::from_stx_config_and_client(config, client)
65+
}
66+
67+
/// Create a [`BitcoinCoreController`] from Stacks Configuration (mainly using [`stacks::config::BurnchainConfig`])
68+
/// and an rpc client [`BitcoinRpcClient`]
69+
pub fn from_stx_config_and_client(config: &Config, client: BitcoinRpcClient) -> Self {
70+
let mut result = BitcoinCoreController {
71+
bitcoind_process: None,
72+
args: vec![],
73+
data_path: config.get_burnchain_path_str(),
74+
rpc_client: client,
75+
};
76+
77+
result.add_arg("-regtest");
78+
result.add_arg("-nodebug");
79+
result.add_arg("-nodebuglogfile");
80+
result.add_arg("-rest");
81+
result.add_arg("-persistmempool=1");
82+
result.add_arg("-dbcache=100");
83+
result.add_arg("-txindex=1");
84+
result.add_arg("-server=1");
85+
result.add_arg("-listenonion=0");
86+
result.add_arg("-rpcbind=127.0.0.1");
87+
result.add_arg(format!("-datadir={}", result.data_path));
88+
89+
let peer_port = config.burnchain.peer_port;
90+
if peer_port == BURNCHAIN_CONFIG_PEER_PORT_DISABLED {
91+
info!("Peer Port is disabled. So `-listen=0` flag will be used");
92+
result.add_arg("-listen=0");
93+
} else {
94+
result.add_arg(format!("-port={peer_port}"));
95+
}
96+
97+
result.add_arg(format!("-rpcport={}", config.burnchain.rpc_port));
98+
99+
if let (Some(username), Some(password)) =
100+
(&config.burnchain.username, &config.burnchain.password)
101+
{
102+
result.add_arg(format!("-rpcuser={username}"));
103+
result.add_arg(format!("-rpcpassword={password}"));
104+
}
105+
106+
result
107+
}
108+
109+
/// Add argument (like "-name=value") to be used to run bitcoind process
110+
pub fn add_arg(&mut self, arg: impl Into<String>) -> &mut Self {
111+
self.args.push(arg.into());
112+
self
113+
}
114+
115+
/// Start Bitcoind process
116+
pub fn start_bitcoind(&mut self) -> BitcoinResult<()> {
117+
std::fs::create_dir_all(&self.data_path).unwrap();
118+
119+
let mut command = Command::new("bitcoind");
120+
command.stdout(Stdio::piped());
121+
122+
command.args(self.args.clone());
123+
124+
info!("bitcoind spawn: {command:?}");
125+
126+
let mut process = match command.spawn() {
127+
Ok(child) => child,
128+
Err(e) => return Err(BitcoinCoreError::SpawnFailed(format!("{e:?}"))),
129+
};
130+
131+
let mut out_reader = BufReader::new(process.stdout.take().unwrap());
132+
133+
let mut line = String::new();
134+
while let Ok(bytes_read) = out_reader.read_line(&mut line) {
135+
if bytes_read == 0 {
136+
return Err(BitcoinCoreError::SpawnFailed(
137+
"Bitcoind closed before spawning network".into(),
138+
));
139+
}
140+
if line.contains("Done loading") {
141+
break;
142+
}
143+
}
144+
145+
info!("bitcoind startup finished");
146+
147+
self.bitcoind_process = Some(process);
148+
149+
Ok(())
150+
}
151+
152+
/// Gracefully stop bitcoind process
153+
pub fn stop_bitcoind(&mut self) -> BitcoinResult<()> {
154+
if let Some(mut bitcoind_process) = self.bitcoind_process.take() {
155+
let res = self
156+
.rpc_client
157+
.stop()
158+
.map_err(|e| BitcoinCoreError::StopFailed(format!("{e:?}")))?;
159+
info!("bitcoind stop started with message: '{res}'");
160+
bitcoind_process
161+
.wait()
162+
.map_err(|e| BitcoinCoreError::StopFailed(format!("{e:?}")))?;
163+
info!("bitcoind stop finished");
164+
}
165+
Ok(())
166+
}
167+
168+
/// Kill bitcoind process
169+
pub fn kill_bitcoind(&mut self) -> BitcoinResult<()> {
170+
if let Some(mut bitcoind_process) = self.bitcoind_process.take() {
171+
info!("bitcoind kill started");
172+
bitcoind_process
173+
.kill()
174+
.map_err(|e| BitcoinCoreError::KillFailed(format!("{e:?}")))?;
175+
info!("bitcoind kill finished");
176+
}
177+
Ok(())
178+
}
179+
180+
/// Check if bitcoind process is running
181+
pub fn is_running(&self) -> bool {
182+
self.bitcoind_process.is_some()
183+
}
184+
}
185+
186+
impl Drop for BitcoinCoreController {
187+
fn drop(&mut self) {
188+
self.kill_bitcoind().unwrap();
189+
}
190+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (C) 2025 Stacks Open Internet Foundation
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
//! Bitcoin Module
17+
//!
18+
//! Entry point for all bitcoin related modules
19+
20+
#[cfg(test)]
21+
pub mod core_controller;

0 commit comments

Comments
 (0)