|
|
|
@ -102,12 +102,27 @@ impl Config {
|
|
|
|
|
{
|
|
|
|
|
let config_file = Path::new(&config_file);
|
|
|
|
|
|
|
|
|
|
let mut config = config::Config::new();
|
|
|
|
|
config.merge(config::File::from(config_file))?;
|
|
|
|
|
let config = config::Config::builder()
|
|
|
|
|
.add_source(config::File::from(config_file))
|
|
|
|
|
.add_source(
|
|
|
|
|
config::Environment::with_prefix("ASB")
|
|
|
|
|
.separator("__")
|
|
|
|
|
.list_separator(","),
|
|
|
|
|
)
|
|
|
|
|
.build()?;
|
|
|
|
|
|
|
|
|
|
config.try_into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<config::Config> for Config {
|
|
|
|
|
type Error = config::ConfigError;
|
|
|
|
|
|
|
|
|
|
fn try_from(value: config::Config) -> Result<Self, Self::Error> {
|
|
|
|
|
value.try_deserialize()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct Data {
|
|
|
|
@ -117,13 +132,55 @@ pub struct Data {
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct Network {
|
|
|
|
|
#[serde(deserialize_with = "addr_list::deserialize")]
|
|
|
|
|
pub listen: Vec<Multiaddr>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub rendezvous_point: Option<Multiaddr>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
#[serde(default, deserialize_with = "addr_list::deserialize")]
|
|
|
|
|
pub external_addresses: Vec<Multiaddr>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mod addr_list {
|
|
|
|
|
use libp2p::Multiaddr;
|
|
|
|
|
use serde::de::Unexpected;
|
|
|
|
|
use serde::{de, Deserialize, Deserializer};
|
|
|
|
|
use serde_json::Value;
|
|
|
|
|
|
|
|
|
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Multiaddr>, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
let s = Value::deserialize(deserializer)?;
|
|
|
|
|
return match s {
|
|
|
|
|
Value::String(s) => {
|
|
|
|
|
let list: Result<Vec<_>, _> = s
|
|
|
|
|
.split(',')
|
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
|
.map(|s| s.parse().map_err(de::Error::custom))
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(list?)
|
|
|
|
|
}
|
|
|
|
|
Value::Array(a) => {
|
|
|
|
|
let list: Result<Vec<_>, _> = a
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|v| {
|
|
|
|
|
if let Value::String(s) = v {
|
|
|
|
|
s.parse().map_err(de::Error::custom)
|
|
|
|
|
} else {
|
|
|
|
|
Err(de::Error::custom("expected a string"))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(list?)
|
|
|
|
|
}
|
|
|
|
|
value => Err(de::Error::invalid_type(
|
|
|
|
|
Unexpected::Other(&value.to_string()),
|
|
|
|
|
&"a string or array",
|
|
|
|
|
)),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct Bitcoin {
|
|
|
|
@ -172,7 +229,7 @@ impl Default for TorConf {
|
|
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug, Clone, Copy)]
|
|
|
|
|
#[error("config not initialized")]
|
|
|
|
|
pub struct ConfigNotInitialized {}
|
|
|
|
|
pub struct ConfigNotInitialized;
|
|
|
|
|
|
|
|
|
|
pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotInitialized>> {
|
|
|
|
|
if config_path.exists() {
|
|
|
|
@ -334,9 +391,12 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use serial_test::serial;
|
|
|
|
|
use tempfile::tempdir;
|
|
|
|
|
|
|
|
|
|
// these tests are run serially since env vars affect the whole process
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn config_roundtrip_testnet() {
|
|
|
|
|
let temp_dir = tempdir().unwrap().path().to_path_buf();
|
|
|
|
|
let config_path = Path::join(&temp_dir, "config.toml");
|
|
|
|
@ -358,7 +418,6 @@ mod tests {
|
|
|
|
|
rendezvous_point: None,
|
|
|
|
|
external_addresses: vec![],
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
monero: Monero {
|
|
|
|
|
wallet_rpc_url: defaults.monero_wallet_rpc_url,
|
|
|
|
|
finality_confirmations: None,
|
|
|
|
@ -380,6 +439,7 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn config_roundtrip_mainnet() {
|
|
|
|
|
let temp_dir = tempdir().unwrap().path().to_path_buf();
|
|
|
|
|
let config_path = Path::join(&temp_dir, "config.toml");
|
|
|
|
@ -401,7 +461,59 @@ mod tests {
|
|
|
|
|
rendezvous_point: None,
|
|
|
|
|
external_addresses: vec![],
|
|
|
|
|
},
|
|
|
|
|
monero: Monero {
|
|
|
|
|
wallet_rpc_url: defaults.monero_wallet_rpc_url,
|
|
|
|
|
finality_confirmations: None,
|
|
|
|
|
network: monero::Network::Mainnet,
|
|
|
|
|
},
|
|
|
|
|
tor: Default::default(),
|
|
|
|
|
maker: Maker {
|
|
|
|
|
min_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MIN_BUY_AMOUNT).unwrap(),
|
|
|
|
|
max_buy_btc: bitcoin::Amount::from_btc(DEFAULT_MAX_BUY_AMOUNT).unwrap(),
|
|
|
|
|
ask_spread: Decimal::from_f64(DEFAULT_SPREAD).unwrap(),
|
|
|
|
|
price_ticker_ws_url: defaults.price_ticker_ws_url,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
initial_setup(config_path.clone(), expected.clone()).unwrap();
|
|
|
|
|
let actual = read_config(config_path).unwrap().unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn env_override() {
|
|
|
|
|
let temp_dir = tempfile::tempdir().unwrap().path().to_path_buf();
|
|
|
|
|
let config_path = Path::join(&temp_dir, "config.toml");
|
|
|
|
|
|
|
|
|
|
let defaults = Mainnet::getConfigFileDefaults().unwrap();
|
|
|
|
|
|
|
|
|
|
let dir = PathBuf::from("/tmp/dir");
|
|
|
|
|
std::env::set_var("ASB__DATA__DIR", dir.clone());
|
|
|
|
|
let addr1 = "/dns4/example.com/tcp/9939";
|
|
|
|
|
let addr2 = "/ip4/1.2.3.4/tcp/9940";
|
|
|
|
|
let external_addresses = vec![addr1.parse().unwrap(), addr2.parse().unwrap()];
|
|
|
|
|
let listen = external_addresses.clone();
|
|
|
|
|
std::env::set_var(
|
|
|
|
|
"ASB__NETWORK__EXTERNAL_ADDRESSES",
|
|
|
|
|
format!("{},{}", addr1, addr2),
|
|
|
|
|
);
|
|
|
|
|
std::env::set_var("ASB__NETWORK__LISTEN", format!("{},{}", addr1, addr2));
|
|
|
|
|
|
|
|
|
|
let expected = Config {
|
|
|
|
|
data: Data { dir },
|
|
|
|
|
bitcoin: Bitcoin {
|
|
|
|
|
electrum_rpc_url: defaults.electrum_rpc_url,
|
|
|
|
|
target_block: defaults.bitcoin_confirmation_target,
|
|
|
|
|
finality_confirmations: None,
|
|
|
|
|
network: bitcoin::Network::Bitcoin,
|
|
|
|
|
},
|
|
|
|
|
network: Network {
|
|
|
|
|
listen,
|
|
|
|
|
rendezvous_point: None,
|
|
|
|
|
external_addresses,
|
|
|
|
|
},
|
|
|
|
|
monero: Monero {
|
|
|
|
|
wallet_rpc_url: defaults.monero_wallet_rpc_url,
|
|
|
|
|
finality_confirmations: None,
|
|
|
|
@ -420,5 +532,8 @@ mod tests {
|
|
|
|
|
let actual = read_config(config_path).unwrap().unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(expected, actual);
|
|
|
|
|
std::env::remove_var("ASB__DATA__DIR");
|
|
|
|
|
std::env::remove_var("ASB__NETWORK__EXTERNAL_ADDRESSES");
|
|
|
|
|
std::env::remove_var("ASB__NETWORK__LISTEN");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|