use crate::bitvm_client::UNSPENDABLE_XONLY_PUBKEY;
use crate::deposit::SecurityCouncil;
use crate::errors::BridgeError;
use bitcoin::address::NetworkUnchecked;
use bitcoin::secp256k1::SecretKey;
use bitcoin::{Address, Amount, OutPoint};
use protocol::ProtocolParamset;
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::{fs::File, io::Read, path::PathBuf};
pub mod env;
pub mod protocol;
#[cfg(test)]
mod test;
#[cfg(test)]
pub use test::*;
#[derive(Debug, Clone, Deserialize)]
pub struct BridgeConfig {
#[serde(skip)]
pub protocol_paramset: &'static ProtocolParamset,
pub host: String,
pub port: u16,
pub secret_key: SecretKey,
pub winternitz_secret_key: Option<SecretKey>,
pub operator_withdrawal_fee_sats: Option<Amount>,
pub bitcoin_rpc_url: String,
pub bitcoin_rpc_user: SecretString,
pub bitcoin_rpc_password: SecretString,
pub db_host: String,
pub db_port: usize,
pub db_user: SecretString,
pub db_password: SecretString,
pub db_name: String,
pub citrea_rpc_url: String,
pub citrea_light_client_prover_url: String,
pub citrea_chain_id: u32,
pub bridge_contract_address: String,
pub header_chain_proof_path: Option<PathBuf>,
pub security_council: SecurityCouncil,
pub verifier_endpoints: Option<Vec<String>>,
pub operator_endpoints: Option<Vec<String>>,
pub operator_reimbursement_address: Option<Address<NetworkUnchecked>>,
pub operator_collateral_funding_outpoint: Option<OutPoint>,
pub server_cert_path: PathBuf,
pub server_key_path: PathBuf,
pub client_cert_path: PathBuf,
pub client_key_path: PathBuf,
pub ca_cert_path: PathBuf,
pub client_verification: bool,
pub aggregator_cert_path: PathBuf,
#[cfg(test)]
#[serde(skip)]
pub test_params: test::TestParams,
}
impl BridgeConfig {
pub fn new() -> Self {
BridgeConfig {
..Default::default()
}
}
pub fn protocol_paramset(&self) -> &'static ProtocolParamset {
self.protocol_paramset
}
pub fn try_parse_file(path: PathBuf) -> Result<Self, BridgeError> {
let mut contents = String::new();
let mut file = match File::open(path.clone()) {
Ok(f) => f,
Err(e) => return Err(BridgeError::ConfigError(e.to_string())),
};
if let Err(e) = file.read_to_string(&mut contents) {
return Err(BridgeError::ConfigError(e.to_string()));
}
tracing::trace!("Using configuration file: {:?}", path);
BridgeConfig::try_parse_from(contents)
}
pub fn try_parse_from(input: String) -> Result<Self, BridgeError> {
match toml::from_str::<BridgeConfig>(&input) {
Ok(c) => Ok(c),
Err(e) => Err(BridgeError::ConfigError(e.to_string())),
}
}
}
#[cfg(test)]
impl PartialEq for BridgeConfig {
fn eq(&self, other: &Self) -> bool {
let mut all_eq = self.protocol_paramset == other.protocol_paramset
&& self.host == other.host
&& self.port == other.port
&& self.secret_key == other.secret_key
&& self.winternitz_secret_key == other.winternitz_secret_key
&& self.operator_withdrawal_fee_sats == other.operator_withdrawal_fee_sats
&& self.bitcoin_rpc_url == other.bitcoin_rpc_url
&& self.bitcoin_rpc_user.expose_secret() == other.bitcoin_rpc_user.expose_secret()
&& self.bitcoin_rpc_password.expose_secret()
== other.bitcoin_rpc_password.expose_secret()
&& self.db_host == other.db_host
&& self.db_port == other.db_port
&& self.db_user.expose_secret() == other.db_user.expose_secret()
&& self.db_password.expose_secret() == other.db_password.expose_secret()
&& self.db_name == other.db_name
&& self.citrea_rpc_url == other.citrea_rpc_url
&& self.citrea_light_client_prover_url == other.citrea_light_client_prover_url
&& self.citrea_chain_id == other.citrea_chain_id
&& self.bridge_contract_address == other.bridge_contract_address
&& self.header_chain_proof_path == other.header_chain_proof_path
&& self.security_council == other.security_council
&& self.verifier_endpoints == other.verifier_endpoints
&& self.operator_endpoints == other.operator_endpoints
&& self.operator_reimbursement_address == other.operator_reimbursement_address
&& self.operator_collateral_funding_outpoint
== other.operator_collateral_funding_outpoint
&& self.server_cert_path == other.server_cert_path
&& self.server_key_path == other.server_key_path
&& self.client_cert_path == other.client_cert_path
&& self.client_key_path == other.client_key_path
&& self.ca_cert_path == other.ca_cert_path
&& self.client_verification == other.client_verification
&& self.aggregator_cert_path == other.aggregator_cert_path
&& self.test_params == other.test_params;
all_eq
}
}
impl Default for BridgeConfig {
fn default() -> Self {
Self {
protocol_paramset: Default::default(),
host: "127.0.0.1".to_string(),
port: 17000,
secret_key: SecretKey::from_str(
"1111111111111111111111111111111111111111111111111111111111111111",
)
.expect("known valid input"),
operator_withdrawal_fee_sats: Some(Amount::from_sat(100000)),
bitcoin_rpc_url: "http://127.0.0.1:18443/wallet/admin".to_string(),
bitcoin_rpc_user: "admin".to_string().into(),
bitcoin_rpc_password: "admin".to_string().into(),
db_host: "127.0.0.1".to_string(),
db_port: 5432,
db_user: "clementine".to_string().into(),
db_password: "clementine".to_string().into(),
db_name: "clementine".to_string(),
citrea_rpc_url: "".to_string(),
citrea_light_client_prover_url: "".to_string(),
citrea_chain_id: 5655,
bridge_contract_address: "3100000000000000000000000000000000000002".to_string(),
header_chain_proof_path: None,
operator_reimbursement_address: None,
operator_collateral_funding_outpoint: None,
security_council: SecurityCouncil {
pks: vec![*UNSPENDABLE_XONLY_PUBKEY],
threshold: 1,
},
winternitz_secret_key: Some(
SecretKey::from_str(
"2222222222222222222222222222222222222222222222222222222222222222",
)
.expect("known valid input"),
),
verifier_endpoints: None,
operator_endpoints: None,
server_cert_path: PathBuf::from("certs/server/server.pem"),
server_key_path: PathBuf::from("certs/server/server.key"),
client_cert_path: PathBuf::from("certs/client/client.pem"),
client_key_path: PathBuf::from("certs/client/client.key"),
ca_cert_path: PathBuf::from("certs/ca/ca.pem"),
aggregator_cert_path: PathBuf::from("certs/aggregator/aggregator.pem"),
client_verification: true,
#[cfg(test)]
test_params: test::TestParams::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::BridgeConfig;
use std::{
fs::{self, File},
io::Write,
};
#[test]
fn parse_from_string() {
let content = "brokenfilecontent";
assert!(BridgeConfig::try_parse_from(content.to_string()).is_err());
}
#[test]
fn parse_from_file() {
let file_name = "parse_from_file";
let content = "invalid file content";
let mut file = File::create(file_name).unwrap();
file.write_all(content.as_bytes()).unwrap();
assert!(BridgeConfig::try_parse_file(file_name.into()).is_err());
let base_path = env!("CARGO_MANIFEST_DIR");
let config_path = format!("{}/src/test/data/bridge_config.toml", base_path);
let content = fs::read_to_string(config_path).unwrap();
let mut file = File::create(file_name).unwrap();
file.write_all(content.as_bytes()).unwrap();
BridgeConfig::try_parse_file(file_name.into()).unwrap();
fs::remove_file(file_name).unwrap();
}
#[test]
fn parse_from_file_with_invalid_headers() {
let file_name = "parse_from_file_with_invalid_headers";
let content = "[header1]
num_verifiers = 4
[header2]
confirmation_threshold = 1
network = \"regtest\"
bitcoin_rpc_url = \"http://localhost:18443\"
bitcoin_rpc_user = \"admin\"
bitcoin_rpc_password = \"admin\"\n";
let mut file = File::create(file_name).unwrap();
file.write_all(content.as_bytes()).unwrap();
assert!(BridgeConfig::try_parse_file(file_name.into()).is_err());
fs::remove_file(file_name).unwrap();
}
#[test]
fn test_test_config_parseable() {
let content = include_str!("../test/data/bridge_config.toml");
BridgeConfig::try_parse_from(content.to_string()).unwrap();
}
#[test]
fn test_docker_config_parseable() {
let content = include_str!("../../../scripts/docker/docker_config.toml");
BridgeConfig::try_parse_from(content.to_string()).unwrap();
}
}