Skip to main content

csv_adapter_bitcoin/
config.rs

1//! Bitcoin adapter configuration
2
3/// Configuration for the Bitcoin anchor layer
4#[derive(Clone, Debug)]
5pub struct BitcoinConfig {
6    /// Bitcoin network (mainnet, testnet, signet, regtest)
7    pub network: Network,
8    /// Required confirmation depth for finality
9    pub finality_depth: u32,
10    /// Publication timeout (for censorship detection)
11    pub publication_timeout_seconds: u64,
12    /// RPC endpoint URL
13    pub rpc_url: String,
14}
15
16/// Bitcoin network type
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum Network {
19    /// Bitcoin mainnet
20    Mainnet,
21    /// Bitcoin testnet3
22    Testnet,
23    /// Bitcoin signet
24    Signet,
25    /// Bitcoin regtest
26    Regtest,
27}
28
29impl Network {
30    /// Get magic bytes for the network
31    pub fn magic_bytes(&self) -> [u8; 4] {
32        match self {
33            Network::Mainnet => [0xf9, 0xbe, 0xb4, 0xd9],
34            Network::Testnet => [0x0b, 0x11, 0x09, 0x07],
35            Network::Signet => [0x0a, 0x03, 0xcf, 0x40],
36            Network::Regtest => [0xfa, 0xbf, 0xb5, 0xda],
37        }
38    }
39
40    /// Convert to bitcoin crate Network type
41    pub fn to_bitcoin_network(&self) -> bitcoin::Network {
42        match self {
43            Network::Mainnet => bitcoin::Network::Bitcoin,
44            Network::Testnet => bitcoin::Network::Testnet,
45            Network::Signet => bitcoin::Network::Signet,
46            Network::Regtest => bitcoin::Network::Regtest,
47        }
48    }
49}
50
51impl BitcoinConfig {
52    /// Validate configuration values
53    pub fn validate(&self) -> Result<String, String> {
54        if self.rpc_url.is_empty() {
55            return Err("rpc_url cannot be empty".to_string());
56        }
57        if self.finality_depth == 0 {
58            return Err("finality_depth must be greater than 0".to_string());
59        }
60        if self.finality_depth > 1000 {
61            return Err("finality_depth must be <= 1000".to_string());
62        }
63        if self.publication_timeout_seconds == 0 {
64            return Err("publication_timeout_seconds must be greater than 0".to_string());
65        }
66        let expected_mainnet =
67            self.network == Network::Mainnet && self.rpc_url.contains("127.0.0.1");
68        if expected_mainnet {
69            return Err("mainnet config should not use localhost rpc_url".to_string());
70        }
71        Ok("Configuration is valid".to_string())
72    }
73}
74
75impl Default for BitcoinConfig {
76    fn default() -> Self {
77        Self {
78            network: Network::Signet,
79            finality_depth: 6,
80            publication_timeout_seconds: 3600, // 1 hour
81            rpc_url: "http://127.0.0.1:8332".to_string(),
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_default_config() {
92        let config = BitcoinConfig::default();
93        assert_eq!(config.network, Network::Signet);
94        assert_eq!(config.finality_depth, 6);
95        assert!(config.validate().is_ok());
96    }
97
98    #[test]
99    fn test_config_validate_empty_rpc_url() {
100        let mut config = BitcoinConfig::default();
101        config.rpc_url = String::new();
102        assert!(config.validate().is_err());
103    }
104
105    #[test]
106    fn test_config_validate_zero_finality_depth() {
107        let mut config = BitcoinConfig::default();
108        config.finality_depth = 0;
109        assert!(config.validate().is_err());
110    }
111
112    #[test]
113    fn test_config_validate_excessive_finality_depth() {
114        let mut config = BitcoinConfig::default();
115        config.finality_depth = 1001;
116        assert!(config.validate().is_err());
117    }
118
119    #[test]
120    fn test_network_magic_bytes() {
121        assert_eq!(Network::Mainnet.magic_bytes(), [0xf9, 0xbe, 0xb4, 0xd9]);
122        assert_eq!(Network::Signet.magic_bytes(), [0x0a, 0x03, 0xcf, 0x40]);
123    }
124}