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