From 69cf12620dd02f59049417931f5d0755798c708a Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 11 May 2021 16:52:40 +1000 Subject: [PATCH] Activate mainnet for the CLI This includes testing CLI commandline args Clap's `default_value_with` actually did not work on `Subcommand`s because the parent's flags were not picked up. This was fixed by changing parameters dependent on testnet/mainnet to options. This problem should have been detected by tests, that's why the command line parameter tests were finally (re-)added. Thanks to @rishflab for some pre-work for this. --- swap/src/bin/swap.rs | 105 ++--- swap/src/cli/command.rs | 820 +++++++++++++++++++++++++++++++++++++--- swap/src/env.rs | 2 +- 3 files changed, 813 insertions(+), 114 deletions(-) diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 1db7bac0..630b374f 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -15,21 +15,21 @@ use anyhow::{bail, Context, Result}; use prettytable::{row, Table}; use std::cmp::min; +use std::env; use std::future::Future; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use structopt::StructOpt; use swap::bitcoin::TxLock; -use swap::cli::command::{Arguments, Bitcoin, Command, Monero, SellerAddr, SwapId, Tor}; +use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command}; use swap::database::Database; -use swap::env::{Config, GetConfig}; +use swap::env::Config; use swap::network::quote::BidQuote; use swap::network::swarm; use swap::protocol::bob; use swap::protocol::bob::{EventLoop, Swap}; use swap::seed::Seed; -use swap::{bitcoin, cli, env, monero}; +use swap::{bitcoin, cli, monero}; use tracing::{debug, error, info, warn}; use url::Url; use uuid::Uuid; @@ -39,44 +39,33 @@ extern crate prettytable; #[tokio::main] async fn main() -> Result<()> { - let Arguments { data, debug, cmd } = Arguments::from_args(); + let Arguments { + env_config, + data_dir, + debug, + cmd, + } = parse_args_and_apply_defaults(env::args_os())?; match cmd { Command::BuyXmr { seller_peer_id, - seller_addr: SellerAddr { seller_addr }, - bitcoin: - Bitcoin { - electrum_rpc_url, - bitcoin_target_block, - }, - monero: - Monero { - receive_monero_address, - monero_daemon_address, - }, - tor: Tor { tor_socks5_port }, + seller_addr, + bitcoin_electrum_rpc_url, + bitcoin_target_block, + monero_receive_address, + monero_daemon_address, + tor_socks5_port, } => { let swap_id = Uuid::new_v4(); - let data_dir = data.0; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read in seed file")?; - let env_config = env::Testnet::get_config(); - - if receive_monero_address.network != env_config.monero_network { - bail!( - "Given monero address is on network {:?}, expected address on network {:?}", - receive_monero_address.network, - env_config.monero_network - ) - } let bitcoin_wallet = init_bitcoin_wallet( - electrum_rpc_url, + bitcoin_electrum_rpc_url, &seed, data_dir.clone(), env_config, @@ -118,7 +107,7 @@ async fn main() -> Result<()> { Arc::new(monero_wallet), env_config, event_loop_handle, - receive_monero_address, + monero_receive_address, send_bitcoin, ); @@ -133,8 +122,6 @@ async fn main() -> Result<()> { } } Command::History => { - let data_dir = data.0; - let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; @@ -150,34 +137,26 @@ async fn main() -> Result<()> { table.printstd(); } Command::Resume { - swap_id: SwapId { swap_id }, - seller_addr: SellerAddr { seller_addr }, - bitcoin: - Bitcoin { - electrum_rpc_url, - bitcoin_target_block, - }, - monero: - Monero { - receive_monero_address, - monero_daemon_address, - }, - tor: Tor { tor_socks5_port }, + swap_id, + seller_addr, + bitcoin_electrum_rpc_url, + bitcoin_target_block, + monero_receive_address, + monero_daemon_address, + tor_socks5_port, } => { - let data_dir = data.0; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read in seed file")?; - let env_config = env::Testnet::get_config(); - if receive_monero_address.network != env_config.monero_network { - bail!("The given monero address is on network {:?}, expected address of network {:?}.", receive_monero_address.network, env_config.monero_network) + if monero_receive_address.network != env_config.monero_network { + bail!("The given monero address is on network {:?}, expected address of network {:?}.", monero_receive_address.network, env_config.monero_network) } let bitcoin_wallet = init_bitcoin_wallet( - electrum_rpc_url, + bitcoin_electrum_rpc_url, &seed, data_dir.clone(), env_config, @@ -208,7 +187,7 @@ async fn main() -> Result<()> { Arc::new(monero_wallet), env_config, event_loop_handle, - receive_monero_address, + monero_receive_address, )?; tokio::select! { @@ -221,24 +200,19 @@ async fn main() -> Result<()> { } } Command::Cancel { - swap_id: SwapId { swap_id }, + swap_id, force, - bitcoin: - Bitcoin { - electrum_rpc_url, - bitcoin_target_block, - }, + bitcoin_electrum_rpc_url, + bitcoin_target_block, } => { - let data_dir = data.0; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read in seed file")?; - let env_config = env::Testnet::get_config(); let bitcoin_wallet = init_bitcoin_wallet( - electrum_rpc_url, + bitcoin_electrum_rpc_url, &seed, data_dir, env_config, @@ -258,24 +232,19 @@ async fn main() -> Result<()> { } } Command::Refund { - swap_id: SwapId { swap_id }, + swap_id, force, - bitcoin: - Bitcoin { - electrum_rpc_url, - bitcoin_target_block, - }, + bitcoin_electrum_rpc_url, + bitcoin_target_block, } => { - let data_dir = data.0; cli::tracing::init(debug, data_dir.join("logs"), swap_id)?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) .context("Failed to read in seed file")?; - let env_config = env::Testnet::get_config(); let bitcoin_wallet = init_bitcoin_wallet( - electrum_rpc_url, + bitcoin_electrum_rpc_url, &seed, data_dir, env_config, diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 6ceddcf4..3f8a8850 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,38 +1,236 @@ +use crate::env::GetConfig; use crate::fs::system_data_dir; +use crate::{env, monero}; use anyhow::{Context, Result}; use libp2p::core::Multiaddr; use libp2p::PeerId; -use std::path::{Path, PathBuf}; +use std::ffi::OsString; +use std::path::PathBuf; use std::str::FromStr; +use structopt::StructOpt; use url::Url; use uuid::Uuid; -pub const DEFAULT_STAGENET_MONERO_DAEMON_ADDRESS: &str = "monero-stagenet.exan.tech:38081"; +// See: https://moneroworld.com/ +pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.moneroworld.com:18089"; +pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "monero-stagenet.exan.tech:38081"; -const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:60002"; -const DEFAULT_BITCOIN_CONFIRMATION_TARGET: &str = "3"; +// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc +const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://electrum.blockstream.info:50002"; +// See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc +pub const DEFAULT_ELECTRUM_RPC_URL_TESTNET: &str = "ssl://electrum.blockstream.info:60002"; + +const DEFAULT_BITCOIN_CONFIRMATION_TARGET: usize = 3; +const DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET: usize = 1; const DEFAULT_TOR_SOCKS5_PORT: &str = "9050"; +#[derive(Debug, PartialEq)] +pub struct Arguments { + pub env_config: env::Config, + pub debug: bool, + pub data_dir: PathBuf, + pub cmd: Command, +} + +pub fn parse_args_and_apply_defaults(raw_args: I) -> Result +where + I: IntoIterator, + T: Into + Clone, +{ + let matches = RawArguments::clap().get_matches_from_safe(raw_args)?; + let args = RawArguments::from_clap(&matches); + + let debug = args.debug; + let is_testnet = args.testnet; + let data = args.data; + + match args.cmd { + RawCommand::BuyXmr { + seller_peer_id, + seller_addr: SellerAddr { seller_addr }, + bitcoin: + Bitcoin { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + }, + monero: + Monero { + monero_receive_address, + monero_daemon_address, + }, + tor: Tor { tor_socks5_port }, + } => Ok(Arguments { + env_config: env_config_from(is_testnet), + debug, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::BuyXmr { + seller_peer_id, + seller_addr, + bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( + bitcoin_electrum_rpc_url, + is_testnet, + )?, + bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), + monero_receive_address: validate_monero_address( + monero_receive_address, + is_testnet, + )?, + monero_daemon_address: monero_daemon_address_from( + monero_daemon_address, + is_testnet, + ), + tor_socks5_port, + }, + }), + RawCommand::History => Ok(Arguments { + env_config: env_config_from(is_testnet), + debug, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::History, + }), + RawCommand::Resume { + swap_id: SwapId { swap_id }, + seller_addr: SellerAddr { seller_addr }, + bitcoin: + Bitcoin { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + }, + monero: + Monero { + monero_receive_address, + monero_daemon_address, + }, + tor: Tor { tor_socks5_port }, + } => Ok(Arguments { + env_config: env_config_from(is_testnet), + debug, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::Resume { + swap_id, + seller_addr, + bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( + bitcoin_electrum_rpc_url, + is_testnet, + )?, + bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), + monero_receive_address, + monero_daemon_address: monero_daemon_address_from( + monero_daemon_address, + is_testnet, + ), + tor_socks5_port, + }, + }), + RawCommand::Cancel { + swap_id: SwapId { swap_id }, + force, + bitcoin: + Bitcoin { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + }, + } => Ok(Arguments { + env_config: env_config_from(is_testnet), + debug, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::Cancel { + swap_id, + force, + bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( + bitcoin_electrum_rpc_url, + is_testnet, + )?, + bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), + }, + }), + RawCommand::Refund { + swap_id: SwapId { swap_id }, + force, + bitcoin: + Bitcoin { + bitcoin_electrum_rpc_url, + bitcoin_target_block, + }, + } => Ok(Arguments { + env_config: env_config_from(is_testnet), + debug, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::Refund { + swap_id, + force, + bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( + bitcoin_electrum_rpc_url, + is_testnet, + )?, + bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), + }, + }), + } +} + +#[derive(Debug, PartialEq)] +pub enum Command { + BuyXmr { + seller_peer_id: PeerId, + seller_addr: Multiaddr, + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + monero_receive_address: monero::Address, + monero_daemon_address: String, + tor_socks5_port: u16, + }, + History, + Resume { + swap_id: Uuid, + seller_addr: Multiaddr, + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + monero_receive_address: monero::Address, + monero_daemon_address: String, + tor_socks5_port: u16, + }, + Cancel { + swap_id: Uuid, + force: bool, + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + }, + Refund { + swap_id: Uuid, + force: bool, + bitcoin_electrum_rpc_url: Url, + bitcoin_target_block: usize, + }, +} + #[derive(structopt::StructOpt, Debug)] #[structopt(name = "swap", about = "CLI for swapping BTC for XMR", author)] -pub struct Arguments { +pub struct RawArguments { + // global is necessary to ensure that clap can match against testnet in subcommands + #[structopt( + long, + help = "Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters", + global = true + )] + pub testnet: bool, + #[structopt( long = "--data-dir", - help = "Provide the data directory path to be used to store application data", - default_value + help = "Provide the data directory path to be used to store application data using testnet and mainnet as subfolder" )] - pub data: Data, + pub data: Option, #[structopt(long, help = "Activate debug logging.")] pub debug: bool, #[structopt(subcommand)] - pub cmd: Command, + pub cmd: RawCommand, } #[derive(structopt::StructOpt, Debug)] -pub enum Command { +pub enum RawCommand { /// Start a XMR for BTC swap BuyXmr { #[structopt(long = "seller-peer-id", help = "The seller's peer id")] @@ -99,31 +297,34 @@ pub struct Monero { help = "Provide the monero address where you would like to receive monero", parse(try_from_str = parse_monero_address) )] - pub receive_monero_address: monero::Address, + pub monero_receive_address: monero::Address, #[structopt( long = "monero-daemon-address", - help = "Specify to connect to a monero daemon of your choice: :", - default_value = DEFAULT_STAGENET_MONERO_DAEMON_ADDRESS + help = "Specify to connect to a monero daemon of your choice: :" )] - pub monero_daemon_address: String, + pub monero_daemon_address: Option, } #[derive(structopt::StructOpt, Debug)] pub struct Bitcoin { - #[structopt(long = "electrum-rpc", - help = "Provide the Bitcoin Electrum RPC URL", - default_value = DEFAULT_ELECTRUM_RPC_URL - )] - pub electrum_rpc_url: Url, + #[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")] + pub bitcoin_electrum_rpc_url: Option, - #[structopt(long = "bitcoin-target-block", help = "Within how many blocks should the Bitcoin transactions be confirmed.", default_value = DEFAULT_BITCOIN_CONFIRMATION_TARGET)] - pub bitcoin_target_block: usize, + #[structopt( + long = "bitcoin-target-block", + help = "Use for fee estimation, decides within how many blocks the Bitcoin transactions should be confirmed." + )] + pub bitcoin_target_block: Option, } #[derive(structopt::StructOpt, Debug)] pub struct Tor { - #[structopt(long = "tor-socks5-port", help = "Your local Tor socks5 proxy port", default_value = DEFAULT_TOR_SOCKS5_PORT)] + #[structopt( + long = "tor-socks5-port", + help = "Your local Tor socks5 proxy port", + default_value = DEFAULT_TOR_SOCKS5_PORT + )] pub tor_socks5_port: u16, } @@ -142,39 +343,92 @@ pub struct SellerAddr { pub seller_addr: Multiaddr, } -#[derive(Clone, Debug)] -pub struct Data(pub PathBuf); +mod data { + use super::*; -/// Default location for storing data for the CLI -// Takes the default system data-dir and adds a `/cli` -impl Default for Data { - fn default() -> Self { - Data( - system_data_dir() - .map(|proj_dir| Path::join(&proj_dir, "cli")) - .expect("computed valid path for data dir"), - ) + pub fn data_dir_from(arg_dir: Option, testnet: bool) -> Result { + let dir = if let Some(dir) = arg_dir { + dir + } else if testnet { + testnet_default()? + } else { + mainnet_default()? + }; + + Ok(dir) + } + + fn testnet_default() -> Result { + Ok(os_default()?.join("testnet")) + } + + fn mainnet_default() -> Result { + Ok(os_default()?.join("mainnet")) + } + + fn os_default() -> Result { + Ok(system_data_dir()?.join("cli")) } } -impl FromStr for Data { - type Err = core::convert::Infallible; - - fn from_str(s: &str) -> Result { - Ok(Data(PathBuf::from_str(s)?)) +fn bitcoin_electrum_rpc_url_from(url: Option, testnet: bool) -> Result { + if let Some(url) = url { + Ok(url) + } else if testnet { + Ok(Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)?) + } else { + Ok(Url::from_str(DEFAULT_ELECTRUM_RPC_URL)?) } } -impl ToString for Data { - fn to_string(&self) -> String { - self.0 - .clone() - .into_os_string() - .into_string() - .expect("default datadir to be convertible to string") +fn bitcoin_target_block_from(target_block: Option, testnet: bool) -> usize { + if let Some(target_block) = target_block { + target_block + } else if testnet { + DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET + } else { + DEFAULT_BITCOIN_CONFIRMATION_TARGET } } +fn monero_daemon_address_from(address: Option, testnet: bool) -> String { + if let Some(address) = address { + address + } else if testnet { + DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string() + } else { + DEFAULT_MONERO_DAEMON_ADDRESS.to_string() + } +} + +fn env_config_from(testnet: bool) -> env::Config { + if testnet { + env::Testnet::get_config() + } else { + env::Mainnet::get_config() + } +} + +fn validate_monero_address( + address: monero::Address, + testnet: bool, +) -> Result { + let expected_network = if testnet { + monero::Network::Stagenet + } else { + monero::Network::Mainnet + }; + + if address.network != expected_network { + return Err(MoneroAddressNetworkMismatch { + expected: expected_network, + actual: address.network, + }); + } + + Ok(address) +} + fn parse_monero_address(s: &str) -> Result { monero::Address::from_str(s).with_context(|| { format!( @@ -183,3 +437,479 @@ fn parse_monero_address(s: &str) -> Result { ) }) } + +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)] +#[error("Invalid monero address provided, expected address on network {expected:?} but address provided is on {actual:?}")] +pub struct MoneroAddressNetworkMismatch { + expected: monero::Network, + actual: monero::Network, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tor::DEFAULT_SOCKS5_PORT; + + const BINARY_NAME: &str = "swap"; + + const TESTNET: &str = "testnet"; + const MAINNET: &str = "mainnet"; + + const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; + const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa"; + const MUTLI_ADDRESS: &str = "/ip4/127.0.0.1/tcp/9939"; + const PEER_ID: &str = "12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; + const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; + + #[test] + fn given_buy_xmr_on_mainnet_then_defaults_to_mainnet() { + let raw_ars = vec![ + BINARY_NAME, + "buy-xmr", + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let expected_args = Arguments::buy_xmr_mainnet_defaults(); + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(expected_args, args); + } + + #[test] + fn given_buy_xmr_on_testnet_then_defaults_to_testnet() { + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "buy-xmr", + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::buy_xmr_testnet_defaults()); + } + + #[test] + fn given_buy_xmr_on_mainnet_with_testnet_address_then_fails() { + let raw_ars = vec![ + BINARY_NAME, + "buy-xmr", + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); + + assert_eq!( + err.downcast_ref::().unwrap(), + &MoneroAddressNetworkMismatch { + expected: monero::Network::Mainnet, + actual: monero::Network::Stagenet + } + ); + } + + #[test] + fn given_buy_xmr_on_testnet_with_mainnet_address_then_fails() { + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "buy-xmr", + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); + + assert_eq!( + err.downcast_ref::().unwrap(), + &MoneroAddressNetworkMismatch { + expected: monero::Network::Stagenet, + actual: monero::Network::Mainnet + } + ); + } + + #[test] + fn given_resume_on_mainnet_then_defaults_to_mainnet() { + let raw_ars = vec![ + BINARY_NAME, + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::resume_mainnet_defaults()); + } + + #[test] + fn given_resume_on_testnet_then_defaults_to_testnet() { + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::resume_testnet_defaults()); + } + + #[test] + fn given_cancel_on_mainnet_then_defaults_to_mainnet() { + let raw_ars = vec![BINARY_NAME, "cancel", "--swap-id", SWAP_ID]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::cancel_mainnet_defaults()); + } + + #[test] + fn given_cancel_on_testnet_then_defaults_to_testnet() { + let raw_ars = vec![BINARY_NAME, "--testnet", "cancel", "--swap-id", SWAP_ID]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::cancel_testnet_defaults()); + } + + #[test] + fn given_refund_on_mainnet_then_defaults_to_mainnet() { + let raw_ars = vec![BINARY_NAME, "refund", "--swap-id", SWAP_ID]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::refund_mainnet_defaults()); + } + + #[test] + fn given_refund_on_testnet_then_defaults_to_testnet() { + let raw_ars = vec![BINARY_NAME, "--testnet", "refund", "--swap-id", SWAP_ID]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::refund_testnet_defaults()); + } + + #[test] + fn given_with_data_dir_then_data_dir_set() { + let data_dir = "/some/path/to/dir"; + + let raw_ars = vec![ + BINARY_NAME, + "--data-dir", + data_dir, + "buy-xmr", + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!( + args, + Arguments::buy_xmr_mainnet_defaults() + .with_data_dir(PathBuf::from_str(data_dir).unwrap()) + ); + + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "--data-dir", + data_dir, + "buy-xmr", + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!( + args, + Arguments::buy_xmr_testnet_defaults() + .with_data_dir(PathBuf::from_str(data_dir).unwrap()) + ); + + let raw_ars = vec![ + BINARY_NAME, + "--data-dir", + data_dir, + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!( + args, + Arguments::resume_mainnet_defaults() + .with_data_dir(PathBuf::from_str(data_dir).unwrap()) + ); + + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "--data-dir", + data_dir, + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!( + args, + Arguments::resume_testnet_defaults() + .with_data_dir(PathBuf::from_str(data_dir).unwrap()) + ); + } + + #[test] + fn given_with_debug_then_debug_set() { + let raw_ars = vec![ + BINARY_NAME, + "--debug", + "buy-xmr", + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::buy_xmr_mainnet_defaults().with_debug()); + + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "--debug", + "buy-xmr", + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + "--seller-peer-id", + PEER_ID, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::buy_xmr_testnet_defaults().with_debug()); + + let raw_ars = vec![ + BINARY_NAME, + "--debug", + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_MAINNET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::resume_mainnet_defaults().with_debug()); + + let raw_ars = vec![ + BINARY_NAME, + "--testnet", + "--debug", + "resume", + "--swap-id", + SWAP_ID, + "--receive-address", + MONERO_STAGENET_ADDRESS, + "--seller-addr", + MUTLI_ADDRESS, + ]; + + let args = parse_args_and_apply_defaults(raw_ars).unwrap(); + assert_eq!(args, Arguments::resume_testnet_defaults().with_debug()); + } + + impl Arguments { + pub fn buy_xmr_testnet_defaults() -> Self { + Self { + env_config: env::Testnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(TESTNET), + cmd: Command::BuyXmr { + seller_peer_id: PeerId::from_str(PEER_ID).unwrap(), + seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) + .unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, + monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS) + .unwrap(), + monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(), + tor_socks5_port: DEFAULT_SOCKS5_PORT, + }, + } + } + + pub fn buy_xmr_mainnet_defaults() -> Self { + Self { + env_config: env::Mainnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(MAINNET), + cmd: Command::BuyXmr { + seller_peer_id: PeerId::from_str(PEER_ID).unwrap(), + seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, + monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) + .unwrap(), + monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(), + tor_socks5_port: DEFAULT_SOCKS5_PORT, + }, + } + } + + pub fn resume_testnet_defaults() -> Self { + Self { + env_config: env::Testnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(TESTNET), + cmd: Command::Resume { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) + .unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, + monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS) + .unwrap(), + monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(), + tor_socks5_port: DEFAULT_SOCKS5_PORT, + }, + } + } + + pub fn resume_mainnet_defaults() -> Self { + Self { + env_config: env::Mainnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(MAINNET), + cmd: Command::Resume { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, + monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) + .unwrap(), + monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(), + tor_socks5_port: DEFAULT_SOCKS5_PORT, + }, + } + } + + pub fn cancel_testnet_defaults() -> Self { + Self { + env_config: env::Testnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(TESTNET), + cmd: Command::Cancel { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + force: false, + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) + .unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, + }, + } + } + + pub fn cancel_mainnet_defaults() -> Self { + Self { + env_config: env::Mainnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(MAINNET), + cmd: Command::Cancel { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + force: false, + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, + }, + } + } + + pub fn refund_testnet_defaults() -> Self { + Self { + env_config: env::Testnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(TESTNET), + cmd: Command::Refund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + force: false, + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) + .unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, + }, + } + } + + pub fn refund_mainnet_defaults() -> Self { + Self { + env_config: env::Mainnet::get_config(), + debug: false, + data_dir: data_dir_path_cli().join(MAINNET), + cmd: Command::Refund { + swap_id: Uuid::from_str(SWAP_ID).unwrap(), + force: false, + bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), + bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, + }, + } + } + + pub fn with_data_dir(mut self, data_dir: PathBuf) -> Self { + self.data_dir = data_dir; + self + } + + pub fn with_debug(mut self) -> Self { + self.debug = true; + self + } + } + + fn data_dir_path_cli() -> PathBuf { + system_data_dir().unwrap().join("cli") + } +} diff --git a/swap/src/env.rs b/swap/src/env.rs index 04deb791..3164c3bd 100644 --- a/swap/src/env.rs +++ b/swap/src/env.rs @@ -3,7 +3,7 @@ use std::cmp::max; use std::time::Duration; use time::NumericalStdDurationShort; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Config { pub bitcoin_lock_confirmed_timeout: Duration, pub bitcoin_finality_confirmations: u32,