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