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
)]
pub struct Arguments {
#[structopt(long, help = "Swap on testnet")]
pub testnet: bool,
#[structopt(
short,
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::monero;
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
use anyhow::{bail, Context, Result};
use config::ConfigError;
@ -11,14 +13,69 @@ use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing::info;
use url::Url;
const DEFAULT_LISTEN_ADDRESS_TCP: &str = "/ip4/0.0.0.0/tcp/9939";
const DEFAULT_LISTEN_ADDRESS_WS: &str = "/ip4/0.0.0.0/tcp/9940/ws";
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 trait GetDefaults {
fn getConfigFileDefaults() -> Result<Defaults>;
}
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_MAX_BUY_AMOUNT: f64 = 0.02f64;
@ -64,12 +121,14 @@ pub struct Network {
pub struct Bitcoin {
pub electrum_rpc_url: Url,
pub target_block: usize,
pub network: bitcoin::Network,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Monero {
pub wallet_rpc_url: Url,
pub network: monero::Network,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
@ -118,30 +177,11 @@ pub fn read_config(config_path: PathBuf) -> Result<Result<Config, ConfigNotIniti
Ok(Ok(file))
}
/// Default location for storing the config file for the ASB
// 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<()>
pub fn initial_setup<F>(config_path: PathBuf, config_file: F, testnet: bool) -> Result<()>
where
F: Fn() -> Result<Config>,
F: Fn(bool) -> Result<Config>,
{
info!("Config file not found, running initial setup...");
let initial_config = config_file()?;
let initial_config = config_file(testnet)?;
let toml = toml::to_string(&initial_config)?;
ensure_directory_exists(config_path.as_path())?;
@ -154,13 +194,30 @@ where
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!();
let data_dir = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter data directory for asb or hit return to use default")
.default(
default_data_dir()
.context("No default data dir value for this system")?
defaults
.data_dir
.to_str()
.context("Unsupported characters in default path")?
.to_string(),
@ -170,28 +227,27 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
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")
.default(DEFAULT_BITCOIN_CONFIRMATION_TARGET)
.default(defaults.bitcoin_confirmation_target)
.interact_text()?;
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")
.default( format!("{},{}", DEFAULT_LISTEN_ADDRESS_TCP, DEFAULT_LISTEN_ADDRESS_WS))
.default( format!("{},{}", defaults.listen_address_tcp, defaults.listen_address_ws))
.interact_text()?;
let listen_addresses = listen_addresses
.split(',')
.map(|str| str.parse())
.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")
.default(DEFAULT_ELECTRUM_RPC_URL.to_owned())
.default(defaults.electrum_rpc_url)
.interact_text()?;
let electrum_rpc_url = Url::parse(electrum_rpc_url.as_str())?;
let monero_wallet_rpc_url = Input::with_theme(&ColorfulTheme::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()?;
let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?;
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.")
@ -234,9 +290,11 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
bitcoin: Bitcoin {
electrum_rpc_url,
target_block,
network: bitcoin_network,
},
monero: Monero {
wallet_rpc_url: monero_wallet_rpc_url,
network: monero_network,
},
tor: TorConf {
control_port: tor_control_port,
@ -253,31 +311,31 @@ pub fn query_user_for_initial_testnet_config() -> Result<Config> {
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use tempfile::tempdir;
#[test]
fn config_roundtrip() {
fn config_roundtrip_testnet() {
let temp_dir = tempdir().unwrap().path().to_path_buf();
let config_path = Path::join(&temp_dir, "config.toml");
let defaults = Testnet::getConfigFileDefaults().unwrap();
let expected = Config {
data: Data {
dir: Default::default(),
},
bitcoin: Bitcoin {
electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
electrum_rpc_url: defaults.electrum_rpc_url,
target_block: defaults.bitcoin_confirmation_target,
network: bitcoin::Network::Testnet,
},
network: Network {
listen: vec![
DEFAULT_LISTEN_ADDRESS_TCP.parse().unwrap(),
DEFAULT_LISTEN_ADDRESS_WS.parse().unwrap(),
],
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
},
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(),
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();
assert_eq!(expected, actual);

View File

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