• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 23943270448

03 Apr 2026 10:32AM UTC coverage: 77.559% (-8.2%) from 85.712%
23943270448

Pull #7077

github

52f01d
web-flow
Merge fa3f939ed into c529ad924
Pull Request #7077: feat: add burnchain DB copy and validation

3654 of 4220 new or added lines in 18 files covered. (86.59%)

19324 existing lines in 182 files now uncovered.

171991 of 221755 relevant lines covered (77.56%)

7658447.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/stacks-node/src/burnchains/bitcoin/core_controller.rs
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
UNCOV
61
    pub fn from_stx_config(config: &Config) -> Self {
×
UNCOV
62
        let client =
×
UNCOV
63
            BitcoinRpcClient::from_stx_config(config).expect("rpc client creation failed!");
×
UNCOV
64
        Self::from_stx_config_and_client(config, client)
×
UNCOV
65
    }
×
66

67
    /// Create a [`BitcoinCoreController`] from Stacks Configuration (mainly using [`stacks::config::BurnchainConfig`])
68
    /// and an rpc client [`BitcoinRpcClient`]
UNCOV
69
    pub fn from_stx_config_and_client(config: &Config, client: BitcoinRpcClient) -> Self {
×
UNCOV
70
        let mut result = BitcoinCoreController {
×
UNCOV
71
            bitcoind_process: None,
×
UNCOV
72
            args: vec![],
×
UNCOV
73
            data_path: config.get_burnchain_path_str(),
×
UNCOV
74
            rpc_client: client,
×
UNCOV
75
        };
×
76

UNCOV
77
        result.add_arg("-regtest");
×
UNCOV
78
        result.add_arg("-nodebug");
×
UNCOV
79
        result.add_arg("-nodebuglogfile");
×
UNCOV
80
        result.add_arg("-rest");
×
UNCOV
81
        result.add_arg("-persistmempool=1");
×
UNCOV
82
        result.add_arg("-dbcache=100");
×
UNCOV
83
        result.add_arg("-txindex=1");
×
UNCOV
84
        result.add_arg("-server=1");
×
UNCOV
85
        result.add_arg("-listenonion=0");
×
UNCOV
86
        result.add_arg("-rpcbind=127.0.0.1");
×
UNCOV
87
        result.add_arg(format!("-datadir={}", result.data_path));
×
88

UNCOV
89
        let peer_port = config.burnchain.peer_port;
×
UNCOV
90
        if peer_port == BURNCHAIN_CONFIG_PEER_PORT_DISABLED {
×
UNCOV
91
            info!("Peer Port is disabled. So `-listen=0` flag will be used");
×
UNCOV
92
            result.add_arg("-listen=0");
×
UNCOV
93
        } else {
×
UNCOV
94
            result.add_arg(format!("-port={peer_port}"));
×
UNCOV
95
        }
×
96

UNCOV
97
        result.add_arg(format!("-rpcport={}", config.burnchain.rpc_port));
×
98

UNCOV
99
        if let (Some(username), Some(password)) =
×
UNCOV
100
            (&config.burnchain.username, &config.burnchain.password)
×
UNCOV
101
        {
×
UNCOV
102
            result.add_arg(format!("-rpcuser={username}"));
×
UNCOV
103
            result.add_arg(format!("-rpcpassword={password}"));
×
UNCOV
104
        }
×
105

UNCOV
106
        result
×
UNCOV
107
    }
×
108

109
    /// Add argument (like "-name=value") to be used to run bitcoind process
UNCOV
110
    pub fn add_arg(&mut self, arg: impl Into<String>) -> &mut Self {
×
UNCOV
111
        self.args.push(arg.into());
×
UNCOV
112
        self
×
UNCOV
113
    }
×
114

115
    /// Start Bitcoind process
UNCOV
116
    pub fn start_bitcoind(&mut self) -> BitcoinResult<()> {
×
UNCOV
117
        std::fs::create_dir_all(&self.data_path).unwrap();
×
118

UNCOV
119
        let mut command = Command::new("bitcoind");
×
UNCOV
120
        command.stdout(Stdio::piped());
×
121

UNCOV
122
        command.args(self.args.clone());
×
123

UNCOV
124
        info!("bitcoind spawn: {command:?}");
×
125

UNCOV
126
        let mut process = match command.spawn() {
×
UNCOV
127
            Ok(child) => child,
×
128
            Err(e) => return Err(BitcoinCoreError::SpawnFailed(format!("{e:?}"))),
×
129
        };
130

UNCOV
131
        let mut out_reader = BufReader::new(process.stdout.take().unwrap());
×
132

UNCOV
133
        let mut line = String::new();
×
UNCOV
134
        while let Ok(bytes_read) = out_reader.read_line(&mut line) {
×
UNCOV
135
            if bytes_read == 0 {
×
136
                return Err(BitcoinCoreError::SpawnFailed(
×
137
                    "Bitcoind closed before spawning network".into(),
×
138
                ));
×
UNCOV
139
            }
×
UNCOV
140
            if line.contains("Done loading") {
×
UNCOV
141
                break;
×
UNCOV
142
            }
×
143
        }
144

UNCOV
145
        info!("bitcoind startup finished");
×
146

UNCOV
147
        self.bitcoind_process = Some(process);
×
148

UNCOV
149
        Ok(())
×
UNCOV
150
    }
×
151

152
    /// Gracefully stop bitcoind process
UNCOV
153
    pub fn stop_bitcoind(&mut self) -> BitcoinResult<()> {
×
UNCOV
154
        if let Some(mut bitcoind_process) = self.bitcoind_process.take() {
×
UNCOV
155
            let res = self
×
UNCOV
156
                .rpc_client
×
UNCOV
157
                .stop()
×
UNCOV
158
                .map_err(|e| BitcoinCoreError::StopFailed(format!("{e:?}")))?;
×
UNCOV
159
            info!("bitcoind stop started with message: '{res}'");
×
UNCOV
160
            bitcoind_process
×
UNCOV
161
                .wait()
×
UNCOV
162
                .map_err(|e| BitcoinCoreError::StopFailed(format!("{e:?}")))?;
×
UNCOV
163
            info!("bitcoind stop finished");
×
164
        }
×
UNCOV
165
        Ok(())
×
UNCOV
166
    }
×
167

168
    /// Kill bitcoind process
UNCOV
169
    pub fn kill_bitcoind(&mut self) -> BitcoinResult<()> {
×
UNCOV
170
        if let Some(mut bitcoind_process) = self.bitcoind_process.take() {
×
UNCOV
171
            info!("bitcoind kill started");
×
UNCOV
172
            bitcoind_process
×
UNCOV
173
                .kill()
×
UNCOV
174
                .map_err(|e| BitcoinCoreError::KillFailed(format!("{e:?}")))?;
×
UNCOV
175
            info!("bitcoind kill finished");
×
UNCOV
176
        }
×
UNCOV
177
        Ok(())
×
UNCOV
178
    }
×
179

180
    /// Check if bitcoind process is running
UNCOV
181
    pub fn is_running(&self) -> bool {
×
UNCOV
182
        self.bitcoind_process.is_some()
×
UNCOV
183
    }
×
184
}
185

186
impl Drop for BitcoinCoreController {
UNCOV
187
    fn drop(&mut self) {
×
UNCOV
188
        self.kill_bitcoind().unwrap();
×
UNCOV
189
    }
×
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc