Activate mainnet for the ASB

To run the ASB on testnet, one actively has to provide the `--testnet` flag.
Mainnet and testnet data and config are separated into sub-folders, i.e. `{data/config-dir}/asb/testnet` and `{data-dir}/asb/mainnet`.
The initial setup is also per network. If (default) config for the network cannot be found the initial setup is triggered.
Startup includes network check to ensure the bitcoin/monero network in config file is the same as the one in the `env::Config`.

Note: Wallet initialization is done with the network set in the `env::Config`, the network saved in the config file is just to indicate what network the config file is for.
This commit is contained in:
Daniel Karzel 2021-05-10 18:06:10 +10:00
parent 9ac5b635d7
commit 02974811ad
No known key found for this signature in database
GPG Key ID: 30C3FC2E438ADB6E
3 changed files with 181 additions and 58 deletions

View File

@ -10,6 +10,9 @@ use uuid::Uuid;
author author
)] )]
pub struct Arguments { pub struct Arguments {
#[structopt(long, help = "Swap on testnet")]
pub testnet: bool,
#[structopt( #[structopt(
short, short,
long = "json", long = "json",

View File

@ -1,4 +1,6 @@
use crate::env::{Mainnet, Testnet};
use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir}; use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir};
use crate::monero;
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT}; use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use config::ConfigError; use config::ConfigError;
@ -11,14 +13,69 @@ use serde::{Deserialize, Serialize};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing::info; use tracing::info;
use url::Url; use url::Url;
const DEFAULT_LISTEN_ADDRESS_TCP: &str = "/ip4/0.0.0.0/tcp/9939"; pub trait GetDefaults {
const DEFAULT_LISTEN_ADDRESS_WS: &str = "/ip4/0.0.0.0/tcp/9940/ws"; fn getConfigFileDefaults() -> Result<Defaults>;
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:60002"; }
const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc";
const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3; pub struct Defaults {
pub config_path: PathBuf,
data_dir: PathBuf,
listen_address_tcp: Multiaddr,
listen_address_ws: Multiaddr,
electrum_rpc_url: Url,
monero_wallet_rpc_url: Url,
bitcoin_confirmation_target: usize,
}
impl GetDefaults for Testnet {
fn getConfigFileDefaults() -> Result<Defaults> {
let defaults = Defaults {
config_path: system_config_dir()
.map(|dir| Path::join(&dir, "asb"))
.map(|dir| Path::join(&dir, "testnet"))
.map(|dir| Path::join(&dir, "config.toml"))
.context("Could not generate default config file path")?,
data_dir: system_data_dir()
.map(|proj_dir| Path::join(&proj_dir, "asb"))
.map(|proj_dir| Path::join(&proj_dir, "testnet"))
.context("Could not generate default data dir")?,
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
electrum_rpc_url: Url::parse("ssl://electrum.blockstream.info:60002")?,
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:38083/json_rpc")?,
bitcoin_confirmation_target: 1,
};
Ok(defaults)
}
}
impl GetDefaults for Mainnet {
fn getConfigFileDefaults() -> Result<Defaults> {
let defaults = Defaults {
config_path: system_config_dir()
.map(|dir| Path::join(&dir, "asb"))
.map(|dir| Path::join(&dir, "mainnet"))
.map(|dir| Path::join(&dir, "config.toml"))
.context("Could not generate default config file path")?,
data_dir: system_data_dir()
.map(|proj_dir| Path::join(&proj_dir, "asb"))
.map(|proj_dir| Path::join(&proj_dir, "mainnet"))
.context("Could not generate default data dir")?,
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
electrum_rpc_url: Url::parse("ssl://electrum.blockstream.info:50002")?,
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:18083/json_rpc")?,
bitcoin_confirmation_target: 3,
};
Ok(defaults)
}
}
const DEFAULT_MIN_BUY_AMOUNT: f64 = 0.002f64; const DEFAULT_MIN_BUY_AMOUNT: f64 = 0.002f64;
const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64; const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64;
@ -64,12 +121,14 @@ pub struct Network {
pub struct Bitcoin { pub struct Bitcoin {
pub electrum_rpc_url: Url, pub electrum_rpc_url: Url,
pub target_block: usize, pub target_block: usize,
pub network: bitcoin::Network,
} }
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Monero { pub struct Monero {
pub wallet_rpc_url: Url, pub wallet_rpc_url: Url,
pub network: monero::Network,
} }
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
@ -118,30 +177,11 @@ pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotIniti
Ok(Ok(file)) Ok(Ok(file))
} }
/// Default location for storing the config file for the ASB pub fn initial_setup<F>(config_path: PathBuf, config_file: F, testnet: bool) -> Result<()>
// Takes the default system config-dir and adds a `/asb/config.toml`
pub fn default_config_path() -> Result<PathBuf> {
system_config_dir()
.map(|dir| Path::join(&dir, "asb"))
.map(|dir| Path::join(&dir, "config.toml"))
.context("Could not generate default config file path")
}
/// Default location for storing data for the CLI
// Takes the default system data-dir and adds a `/asb`
fn default_data_dir() -> Result<PathBuf> {
system_data_dir()
.map(|proj_dir| Path::join(&proj_dir, "asb"))
.context("Could not generate default data dir")
}
pub fn initial_setup<F>(config_path: PathBuf, config_file: F) -> Result<()>
where where
F: Fn() -> Result<Config>, F: Fn(bool) -> Result<Config>,
{ {
info!("Config file not found, running initial setup..."); let initial_config = config_file(testnet)?;
let initial_config = config_file()?;
let toml = toml::to_string(&initial_config)?; let toml = toml::to_string(&initial_config)?;
ensure_directory_exists(config_path.as_path())?; ensure_directory_exists(config_path.as_path())?;
@ -154,13 +194,30 @@ where
Ok(()) Ok(())
} }
pub fn query_user_for_initial_testnet_config() -> Result<Config> { pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
let (bitcoin_network, monero_network, defaults) = if testnet {
tracing::info!("Running initial setup for testnet");
let bitcoin_network = bitcoin::Network::Testnet;
let monero_network = monero::Network::Stagenet;
let defaults = Testnet::getConfigFileDefaults()?;
(bitcoin_network, monero_network, defaults)
} else {
tracing::info!("Running initial setup for mainnet");
let bitcoin_network = bitcoin::Network::Bitcoin;
let monero_network = monero::Network::Mainnet;
let defaults = Mainnet::getConfigFileDefaults()?;
(bitcoin_network, monero_network, defaults)
};
println!(); println!();
let data_dir = Input::with_theme(&ColorfulTheme::default()) let data_dir = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter data directory for asb or hit return to use default") .with_prompt("Enter data directory for asb or hit return to use default")
.default( .default(
default_data_dir() defaults
.context("No default data dir value for this system")? .data_dir
.to_str() .to_str()
.context("Unsupported characters in default path")? .context("Unsupported characters in default path")?
.to_string(), .to_string(),
@ -170,28 +227,27 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
let target_block = Input::with_theme(&ColorfulTheme::default()) let target_block = Input::with_theme(&ColorfulTheme::default())
.with_prompt("How fast should your Bitcoin transactions be confirmed? Your transaction fee will be calculated based on this target. Hit return to use default") .with_prompt("How fast should your Bitcoin transactions be confirmed? Your transaction fee will be calculated based on this target. Hit return to use default")
.default(DEFAULT_BITCOIN_CONFIRMATION_TARGET) .default(defaults.bitcoin_confirmation_target)
.interact_text()?; .interact_text()?;
let listen_addresses = Input::with_theme(&ColorfulTheme::default()) let listen_addresses = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter multiaddresses (comma separated) on which asb should list for peer-to-peer communications or hit return to use default") .with_prompt("Enter multiaddresses (comma separated) on which asb should list for peer-to-peer communications or hit return to use default")
.default( format!("{},{}", DEFAULT_LISTEN_ADDRESS_TCP, DEFAULT_LISTEN_ADDRESS_WS)) .default( format!("{},{}", defaults.listen_address_tcp, defaults.listen_address_ws))
.interact_text()?; .interact_text()?;
let listen_addresses = listen_addresses let listen_addresses = listen_addresses
.split(',') .split(',')
.map(|str| str.parse()) .map(|str| str.parse())
.collect::<Result<Vec<Multiaddr>, _>>()?; .collect::<Result<Vec<Multiaddr>, _>>()?;
let electrum_rpc_url: String = Input::with_theme(&ColorfulTheme::default()) let electrum_rpc_url: Url = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter Electrum RPC URL or hit return to use default") .with_prompt("Enter Electrum RPC URL or hit return to use default")
.default(DEFAULT_ELECTRUM_RPC_URL.to_owned()) .default(defaults.electrum_rpc_url)
.interact_text()?; .interact_text()?;
let electrum_rpc_url = Url::parse(electrum_rpc_url.as_str())?;
let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::default()) let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter Monero Wallet RPC URL or hit enter to use default") .with_prompt("Enter Monero Wallet RPC URL or hit enter to use default")
.default(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL.to_owned()) .default(defaults.monero_wallet_rpc_url)
.interact_text()?; .interact_text()?;
let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?;
let tor_control_port = Input::with_theme(&ColorfulTheme::default()) let tor_control_port = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter Tor control port or hit enter to use default. If Tor is not running on your machine, no hidden service will be created.") .with_prompt("Enter Tor control port or hit enter to use default. If Tor is not running on your machine, no hidden service will be created.")
@ -234,9 +290,11 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
bitcoin: Bitcoin { bitcoin: Bitcoin {
electrum_rpc_url, electrum_rpc_url,
target_block, target_block,
network: bitcoin_network,
}, },
monero: Monero { monero: Monero {
wallet_rpc_url: monero_wallet_rpc_url, wallet_rpc_url: monero_wallet_rpc_url,
network: monero_network,
}, },
tor: TorConf { tor: TorConf {
control_port: tor_control_port, control_port: tor_control_port,
@ -253,31 +311,31 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::str::FromStr;
use tempfile::tempdir; use tempfile::tempdir;
#[test] #[test]
fn config_roundtrip() { fn config_roundtrip_testnet() {
let temp_dir = tempdir().unwrap().path().to_path_buf(); let temp_dir = tempdir().unwrap().path().to_path_buf();
let config_path = Path::join(&temp_dir, "config.toml"); let config_path = Path::join(&temp_dir, "config.toml");
let defaults = Testnet::getConfigFileDefaults().unwrap();
let expected = Config { let expected = Config {
data: Data { data: Data {
dir: Default::default(), dir: Default::default(),
}, },
bitcoin: Bitcoin { bitcoin: Bitcoin {
electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), electrum_rpc_url: defaults.electrum_rpc_url,
target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, target_block: defaults.bitcoin_confirmation_target,
network: bitcoin::Network::Testnet,
}, },
network: Network { network: Network {
listen: vec![ listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
DEFAULT_LISTEN_ADDRESS_TCP.parse().unwrap(),
DEFAULT_LISTEN_ADDRESS_WS.parse().unwrap(),
],
}, },
monero: Monero { monero: Monero {
wallet_rpc_url: Url::from_str(DEFAULT_MONERO_WALLET_RPC_TESTNET_URL).unwrap(), wallet_rpc_url: defaults.monero_wallet_rpc_url,
network: monero::Network::Testnet,
}, },
tor: Default::default(), tor: Default::default(),
maker: Maker { maker: Maker {
@ -287,7 +345,45 @@ mod tests {
}, },
}; };
initial_setup(config_path.clone(), || Ok(expected.clone())).unwrap(); initial_setup(config_path.clone(), |_| Ok(expected.clone()), false).unwrap();
let actual = read_config(config_path).unwrap().unwrap();
assert_eq!(expected, actual);
}
#[test]
fn config_roundtrip_mainnet() {
let temp_dir = tempdir().unwrap().path().to_path_buf();
let config_path = Path::join(&temp_dir, "config.toml");
let defaults = Mainnet::getConfigFileDefaults().unwrap();
let expected = Config {
data: Data {
dir: Default::default(),
},
bitcoin: Bitcoin {
electrum_rpc_url: defaults.electrum_rpc_url,
target_block: defaults.bitcoin_confirmation_target,
network: bitcoin::Network::Bitcoin,
},
network: Network {
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
},
monero: Monero {
wallet_rpc_url: defaults.monero_wallet_rpc_url,
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(),
},
};
initial_setup(config_path.clone(), |_| Ok(expected.clone()), true).unwrap();
let actual = read_config(config_path).unwrap().unwrap(); let actual = read_config(config_path).unwrap().unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);

View File

@ -12,7 +12,7 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use libp2p::core::multiaddr::Protocol; use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use libp2p::Swarm; use libp2p::Swarm;
@ -22,8 +22,8 @@ use std::sync::Arc;
use structopt::StructOpt; use structopt::StructOpt;
use swap::asb::command::{Arguments, Command, ManualRecovery, RecoverCommandParams}; use swap::asb::command::{Arguments, Command, ManualRecovery, RecoverCommandParams};
use swap::asb::config::{ use swap::asb::config::{
default_config_path, initial_setup, query_user_for_initial_testnet_config, read_config, Config, initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
ConfigNotInitialized, GetDefaults,
}; };
use swap::database::Database; use swap::database::Database;
use swap::env::GetConfig; use swap::env::GetConfig;
@ -45,23 +45,49 @@ const DEFAULT_WALLET_NAME: &str = "asb-wallet";
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let opt = Arguments::from_args(); let Arguments {
asb::tracing::init(LevelFilter::DEBUG, opt.json).expect("initialize tracing"); testnet,
json,
config,
cmd,
} = Arguments::from_args();
asb::tracing::init(LevelFilter::DEBUG, json).expect("initialize tracing");
let config_path = if let Some(config_path) = opt.config { let config_path = if let Some(config_path) = config {
config_path config_path
} else if testnet {
env::Testnet::getConfigFileDefaults()?.config_path
} else { } else {
default_config_path()? env::Mainnet::getConfigFileDefaults()?.config_path
}; };
let config = match read_config(config_path.clone())? { let config = match read_config(config_path.clone())? {
Ok(config) => config, Ok(config) => config,
Err(ConfigNotInitialized {}) => { Err(ConfigNotInitialized {}) => {
initial_setup(config_path.clone(), query_user_for_initial_testnet_config)?; initial_setup(config_path.clone(), query_user_for_initial_config, testnet)?;
read_config(config_path)?.expect("after initial setup config can be read") read_config(config_path)?.expect("after initial setup config can be read")
} }
}; };
let env_config = if testnet {
env::Testnet::get_config()
} else {
env::Mainnet::get_config()
};
if config.monero.network != env_config.monero_network {
bail!(format!(
"Expected monero network in config file to be {:?} but was {:?}",
env_config.monero_network, config.monero.network
));
}
if config.bitcoin.network != env_config.bitcoin_network {
bail!(format!(
"Expected bitcoin network in config file to be {:?} but was {:?}",
env_config.bitcoin_network, config.bitcoin.network
));
}
info!( info!(
db_folder = %config.data.dir.display(), db_folder = %config.data.dir.display(),
"Database and Seed will be stored in", "Database and Seed will be stored in",
@ -75,9 +101,7 @@ async fn main() -> Result<()> {
let seed = let seed =
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed"); Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
let env_config = env::Testnet::get_config(); match cmd {
match opt.cmd {
Command::Start { resume_only } => { Command::Start { resume_only } => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?; let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;