Skip to main content

ethos_bitcoind/
test_config.rs

1//! Test configuration for Bitcoin RPC testing
2//!
3//! This module provides configuration utilities for running Bitcoin nodes in test environments.
4
5use std::env;
6use std::path::PathBuf;
7
8use bitcoin::Network;
9
10use crate::config::Config;
11
12const DEFAULT_EXTRA_ARGS: [&str; 2] = ["-prune=0", "-txindex"];
13
14/// TestConfig represents the configuration needed to run a Bitcoin node in a test environment.
15/// This struct encapsulates test‑node settings: network, RPC port, username, password, and extra args.
16/// Defaults are:
17/// - `network = Network::Regtest`
18/// - `rpc_port = 0` (auto‑select a free port)
19/// - `rpc_username = "rpcuser"`
20/// - `rpc_password = "rpcpassword"`
21/// - `bitcoind_path = None` (use executable from PATH)
22/// - `extra_args = ["-prune=0", "-txindex"]` (for full blockchain history and transaction lookup)
23///
24/// To override any of these, simply modify fields on `TestConfig::default()`
25/// (or assign directly in code). If you prefer not to recompile for every change,
26/// consider using `TestConfig::from_env()` to read overrides from environment variables.
27///
28/// # Examples
29///
30/// ```rust,ignore
31/// let mut cfg = TestConfig::default();
32/// cfg.network = Network::Testnet;
33/// cfg.rpc_port = 18545;
34/// cfg.rpc_username = "alice".into();
35/// ```
36///
37/// # Environment Overrides
38///
39/// Reads `RPC_NETWORK`, `RPC_PORT`, `RPC_USER`, and `RPC_PASS`, and `BITCOIND_PATH` (path to bitcoind executable) to override defaults.
40#[derive(Debug, Clone)]
41pub struct TestConfig {
42    /// Which Bitcoin network to run against.
43    pub network: Network,
44    /// The port number for RPC communication with the Bitcoin node.
45    /// A value of 0 indicates that an available port should be automatically selected.
46    pub rpc_port: u16,
47    /// The username for RPC authentication.
48    /// Can be customized to match your `bitcoin.conf` `rpcuser` setting.
49    pub rpc_username: String,
50    /// The password for RPC authentication.
51    /// Can be customized to match your `bitcoin.conf` `rpcpassword` setting.
52    pub rpc_password: String,
53    /// Path to the bitcoind executable. If None, the default executable name is used (e.g. from PATH).
54    pub bitcoind_path: Option<PathBuf>,
55    /// Extra command-line arguments to pass to bitcoind
56    pub extra_args: Vec<String>,
57}
58
59impl TestConfig {
60    /// Return the value used with `-chain=<value>` for the configured network
61    pub fn as_chain_str(&self) -> &'static str {
62        #[allow(unreachable_patterns)]
63        match self.network {
64            Network::Bitcoin => "main",
65            Network::Regtest => "regtest",
66            Network::Signet => "signet",
67            Network::Testnet => "testnet",
68            Network::Testnet4 => "testnet4",
69            _ => panic!("Unsupported network variant"),
70        }
71    }
72
73    /// Parse network from common strings (case-insensitive). Accepts: regtest, testnet|test,
74    /// signet, mainnet|main|bitcoin, testnet4.
75    pub fn network_from_str(s: &str) -> Option<Network> {
76        match s.to_ascii_lowercase().as_str() {
77            "regtest" => Some(Network::Regtest),
78            "testnet" | "test" => Some(Network::Testnet),
79            "signet" => Some(Network::Signet),
80            "mainnet" | "main" | "bitcoin" => Some(Network::Bitcoin),
81            "testnet4" => Some(Network::Testnet4),
82            _ => None,
83        }
84    }
85
86    /// Create a `TestConfig`, overriding defaults with environment variables:
87    /// - `RPC_NETWORK`: overrides `network`; one of `regtest`, `testnet|test`, `signet`, `mainnet|main|bitcoin`, `testnet4`
88    /// - `RPC_PORT`: overrides `rpc_port`
89    /// - `RPC_USER`: overrides `rpc_username`
90    /// - `RPC_PASS`: overrides `rpc_password`
91    /// - `BITCOIND_PATH`: overrides `bitcoind_path` (path to the bitcoind executable)
92    #[allow(clippy::field_reassign_with_default)]
93    pub fn from_env() -> Self {
94        let mut cfg = Self::default();
95        if let Ok(net) = env::var("RPC_NETWORK") {
96            if let Some(n) = Self::network_from_str(&net) {
97                cfg.network = n;
98            }
99        }
100        if let Ok(port_str) = env::var("RPC_PORT") {
101            if let Ok(port) = port_str.parse() {
102                cfg.rpc_port = port;
103            }
104        }
105        if let Ok(user) = env::var("RPC_USER") {
106            cfg.rpc_username = user;
107        }
108        if let Ok(pass) = env::var("RPC_PASS") {
109            cfg.rpc_password = pass;
110        }
111        if let Ok(path) = env::var("BITCOIND_PATH") {
112            cfg.bitcoind_path = Some(PathBuf::from(path));
113        }
114        cfg
115    }
116
117    /// Convert this test configuration into a full Config instance
118    pub fn into_config(self) -> Config {
119        Config {
120            rpc_url: format!("http://127.0.0.1:{}", self.rpc_port),
121            rpc_user: self.rpc_username,
122            rpc_password: self.rpc_password,
123        }
124    }
125
126    /// Create a TestConfig from a full Config instance
127    pub fn from_config(config: &Config) -> Self {
128        // Extract port from URL, defaulting to 0 if parsing fails
129        let rpc_port =
130            config.rpc_url.split(':').next_back().and_then(|s| s.parse().ok()).unwrap_or(0);
131
132        Self {
133            network: Network::Regtest, // Default to regtest for test environments
134            rpc_port,
135            rpc_username: config.rpc_user.clone(),
136            rpc_password: config.rpc_password.clone(),
137            bitcoind_path: None,
138            extra_args: DEFAULT_EXTRA_ARGS.map(String::from).to_vec(),
139        }
140    }
141}
142
143impl Default for TestConfig {
144    fn default() -> Self {
145        Self {
146            network: Network::Regtest,
147            rpc_port: 0,
148            rpc_username: "rpcuser".to_string(),
149            rpc_password: "rpcpassword".to_string(),
150            bitcoind_path: None,
151            extra_args: DEFAULT_EXTRA_ARGS.map(String::from).to_vec(),
152        }
153    }
154}