@ -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.002 f64 ;
const DEFAULT_MAX_BUY_AMOUNT : f64 = 0.02 f64 ;
@ -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" ) ?
default s
. 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,69 @@ 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 : defaults . electrum_rpc_url ,
target_block : defaults . bitcoin_confirmation_target ,
network : bitcoin ::Network ::Testnet ,
} ,
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 ::Testnet ,
} ,
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 ( ) ) , 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 : 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 ::Bitcoin ,
} ,
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 ::Mainnet ,
} ,
tor : Default ::default ( ) ,
maker : Maker {
@ -287,7 +383,7 @@ mod tests {
} ,
} ;
initial_setup ( config_path . clone ( ) , | | Ok ( expected . clone ( ) ) ) . unwrap ( ) ;
initial_setup ( config_path . clone ( ) , | _ | Ok ( expected . clone ( ) ) , true ) . unwrap ( ) ;
let actual = read_config ( config_path ) . unwrap ( ) . unwrap ( ) ;
assert_eq! ( expected , actual ) ;