From b75e9b436c0d130f3d64c4c5636e65ab4afed029 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Thu, 28 Jan 2021 12:31:03 +1100 Subject: [PATCH] Bitcoind RPC url, wallet name and Monero RPC url through config On startup, if the config file does not exist yet initial setup is triggered and the user is queried for Bitcoind rpc url and Bitcoin wallet name, and Monero wallet rpc url. The values entered by the user is written to config file in operating system specific default path. Upon subsequent startups the value are read from the config file. The corresponding CLI parameters were removed. --- Cargo.lock | 140 ++++++++++++++++++++++++++++++++++++++++--- swap/Cargo.toml | 6 +- swap/src/cli.rs | 49 --------------- swap/src/config.rs | 110 ++++++++++++++++++++++++++++++++++ swap/src/fs.rs | 17 +++++- swap/src/main.rs | 29 +++------ swap/src/settings.rs | 16 +++-- 7 files changed, 281 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26c27b34..55440813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -466,7 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "244fbce0d47e97e8ef2f63b81d5e05882cb518c68531eb33194990d7b7e85845" dependencies = [ "stream-cipher", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -479,7 +479,7 @@ dependencies = [ "chacha20", "poly1305", "stream-cipher", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -506,6 +506,18 @@ dependencies = [ "bitflags", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "serde", + "toml", +] + [[package]] name = "conquer-once" version = "0.3.1" @@ -521,6 +533,22 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e763eef8846b13b380f37dfecda401770b0ca4e56e95170237bd7c25c7db3582" +[[package]] +name = "console" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi 0.3.9", + "winapi-util", +] + [[package]] name = "const_fn" version = "0.4.4" @@ -677,7 +705,7 @@ dependencies = [ "rand_core 0.5.1", "serde", "subtle 2.4.0", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -690,7 +718,7 @@ dependencies = [ "digest 0.9.0", "rand_core 0.5.1", "subtle 2.4.0", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -710,6 +738,18 @@ dependencies = [ "syn", ] +[[package]] +name = "dialoguer" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f807b2943dc90f9747497d9d65d7e92472149be0b88bf4ce1201b4ac979c26" +dependencies = [ + "console", + "lazy_static", + "tempfile", + "zeroize 0.9.3", +] + [[package]] name = "digest" version = "0.8.1" @@ -741,6 +781,16 @@ dependencies = [ "sha2 0.8.2", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "1.0.5" @@ -752,6 +802,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "discard" version = "1.0.4" @@ -789,7 +850,7 @@ dependencies = [ "serde", "serde_bytes", "sha2 0.9.2", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -1526,6 +1587,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions 1.1.0", +] + [[package]] name = "libc" version = "0.2.81" @@ -1590,7 +1664,7 @@ dependencies = [ "thiserror", "unsigned-varint", "void", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -1651,7 +1725,7 @@ dependencies = [ "snow", "static_assertions 1.1.0", "x25519-dalek", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -2020,6 +2094,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "num" version = "0.1.42" @@ -3291,10 +3376,13 @@ dependencies = [ "base64 0.12.3", "bitcoin", "bitcoin-harness", + "config", "conquer-once", "cross-curve-dleq", "curve25519-dalek 2.1.0", "derivative", + "dialoguer", + "directories-next", "ecdsa_fun", "ed25519-dalek", "futures", @@ -3326,6 +3414,7 @@ dependencies = [ "thiserror", "time", "tokio", + "toml", "tracing", "tracing-core", "tracing-futures", @@ -3384,6 +3473,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "terminal_size" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "testcontainers" version = "0.11.0" @@ -3580,6 +3679,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.0" @@ -3777,6 +3885,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] @@ -3959,6 +4068,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3992,7 +4110,7 @@ checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" dependencies = [ "curve25519-dalek 3.0.0", "rand_core 0.5.1", - "zeroize", + "zeroize 1.2.0", ] [[package]] @@ -4009,6 +4127,12 @@ dependencies = [ "static_assertions 1.1.0", ] +[[package]] +name = "zeroize" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" + [[package]] name = "zeroize" version = "1.2.0" diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 028c5796..406a3c71 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -14,10 +14,13 @@ backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" bitcoin = { version = "0.25", features = ["rand", "use-serde"] } bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "864b55fcba2e770105f135781dd2e3002c503d12" } +config = { version = "0.10", default-features = false, features = ["toml"] } conquer-once = "0.3" cross-curve-dleq = { git = "https://github.com/comit-network/cross-curve-dleq", rev = "eddcdea1d1f16fa33ef581d1744014ece535c920", features = ["serde"] } curve25519-dalek = "2" derivative = "2" +dialoguer = "0.7" +directories-next = "2" ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "cdfbc766045ea678a41780919d6228dd5acee3be", features = ["libsecp_compat", "serde"] } ed25519-dalek = { version = "1.0.0-pre.4", features = ["serde"] }# Cannot be 1 because they depend on curve25519-dalek version 3 futures = { version = "0.3", default-features = false } @@ -44,12 +47,13 @@ tempfile = "3" thiserror = "1" time = "0.2" tokio = { version = "0.2", features = ["rt-threaded", "time", "macros", "sync"] } +toml = "0.5" tracing = { version = "0.1", features = ["attributes"] } tracing-core = "0.1" tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] } tracing-log = "0.1" tracing-subscriber = { version = "0.2", default-features = false, features = ["fmt", "ansi", "env-filter"] } -url = "2.1" +url = { version = "2.1", features = ["serde"] } uuid = { version = "0.8", features = ["serde", "v4"] } void = "1" diff --git a/swap/src/cli.rs b/swap/src/cli.rs index a2a48a48..5b0c30e2 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,6 +1,5 @@ use crate::{bitcoin, monero}; use libp2p::{core::Multiaddr, PeerId}; -use url::Url; use uuid::Uuid; #[derive(structopt::StructOpt, Debug)] @@ -17,18 +16,6 @@ pub struct Options { #[structopt(name = "xmr_btc-swap", about = "XMR BTC atomic swap")] pub enum Command { SellXmr { - #[structopt(long = "bitcoind-rpc", default_value = "http://127.0.0.1:8332")] - bitcoind_url: Url, - - #[structopt(long = "bitcoin-wallet-name")] - bitcoin_wallet_name: String, - - #[structopt( - long = "monero-wallet-rpc", - default_value = "http://127.0.0.1:18083/json_rpc" - )] - monero_wallet_rpc_url: Url, - #[structopt(long = "p2p-address", default_value = "/ip4/0.0.0.0/tcp/9876")] listen_addr: Multiaddr, @@ -45,18 +32,6 @@ pub enum Command { #[structopt(long = "connect-addr")] alice_addr: Multiaddr, - #[structopt(long = "bitcoind-rpc", default_value = "http://127.0.0.1:8332")] - bitcoind_url: Url, - - #[structopt(long = "bitcoin-wallet-name")] - bitcoin_wallet_name: String, - - #[structopt( - long = "monero-wallet-rpc", - default_value = "http://127.0.0.1:18083/json_rpc" - )] - monero_wallet_rpc_url: Url, - #[structopt(long = "send-btc", help = "Bitcoin amount as floating point nr without denomination (e.g. 1.25)", parse(try_from_str = parse_btc))] send_bitcoin: bitcoin::Amount, @@ -73,18 +48,6 @@ pub enum Resume { #[structopt(long = "swap-id")] swap_id: Uuid, - #[structopt(long = "bitcoind-rpc", default_value = "http://127.0.0.1:8332")] - bitcoind_url: Url, - - #[structopt(long = "bitcoin-wallet-name")] - bitcoin_wallet_name: String, - - #[structopt( - long = "monero-wallet-rpc", - default_value = "http://127.0.0.1:18083/json_rpc" - )] - monero_wallet_rpc_url: Url, - #[structopt(long = "listen-address", default_value = "/ip4/127.0.0.1/tcp/9876")] listen_addr: Multiaddr, }, @@ -97,18 +60,6 @@ pub enum Resume { #[structopt(long = "counterpart-addr")] alice_addr: Multiaddr, - - #[structopt(long = "bitcoind-rpc", default_value = "http://127.0.0.1:8332")] - bitcoind_url: Url, - - #[structopt(long = "bitcoin-wallet-name")] - bitcoin_wallet_name: String, - - #[structopt( - long = "monero-wallet-rpc", - default_value = "http://127.0.0.1:18083/json_rpc" - )] - monero_wallet_rpc_url: Url, }, } diff --git a/swap/src/config.rs b/swap/src/config.rs index 01fbddaa..291e717c 100644 --- a/swap/src/config.rs +++ b/swap/src/config.rs @@ -1 +1,111 @@ +use crate::fs::{default_config_path, ensure_directory_exists}; +use anyhow::{Context, Result}; +use config::{Config, ConfigError}; +use dialoguer::{theme::ColorfulTheme, Input}; +use serde::{Deserialize, Serialize}; +use std::{ + ffi::OsStr, + fs, + path::{Path, PathBuf}, +}; +use tracing::info; +use url::Url; + pub mod seed; + +const DEFAULT_BITCOIND_TESTNET_URL: &str = "http://127.0.0.1:18332"; +const DEFAULT_MONERO_WALLET_RPC_TESTNET_URL: &str = "http://127.0.0.1:38083/json_rpc"; + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct File { + pub bitcoin: Bitcoin, + pub monero: Monero, +} + +impl File { + pub fn read(config_file: D) -> Result + where + D: AsRef, + { + let config_file = Path::new(&config_file); + + let mut config = Config::new(); + config.merge(config::File::from(config_file))?; + config.try_into() + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Bitcoin { + pub bitcoind_url: Url, + pub wallet_name: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Monero { + pub wallet_rpc_url: Url, +} + +pub fn read_config() -> anyhow::Result { + let default_path = default_config_path()?; + + if default_path.exists() { + info!( + "Using config file at default path: {}", + default_path.display() + ); + } else { + initial_setup(default_path.clone())?; + } + + File::read(&default_path) + .with_context(|| format!("failed to read config file {}", default_path.display())) +} + +fn initial_setup(config_path: PathBuf) -> Result<()> { + info!("Config file not found, running initial setup..."); + ensure_directory_exists(config_path.as_path())?; + let initial_config = query_user_for_initial_testnet_config()?; + + let toml = toml::to_string(&initial_config)?; + fs::write(config_path.clone(), toml)?; + + info!( + "Initial setup complete, config file created at {} ", + config_path.as_path().display() + ); + Ok(()) +} + +fn query_user_for_initial_testnet_config() -> Result { + println!(); + let bitcoind_url: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter Bitcoind URL (including username and password if applicable) or hit return to use default") + .default(DEFAULT_BITCOIND_TESTNET_URL.to_owned()) + .interact_text()?; + let bitcoind_url = Url::parse(bitcoind_url.as_str())?; + + let bitcoin_wallet_name: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Enter Bitcoind wallet name") + .interact_text()?; + + let monero_wallet_rpc_url: String = 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()) + .interact_text()?; + let monero_wallet_rpc_url = Url::parse(monero_wallet_rpc_url.as_str())?; + println!(); + + Ok(File { + bitcoin: Bitcoin { + bitcoind_url, + wallet_name: bitcoin_wallet_name, + }, + monero: Monero { + wallet_rpc_url: monero_wallet_rpc_url, + }, + }) +} diff --git a/swap/src/fs.rs b/swap/src/fs.rs index f3dc889b..ee12585b 100644 --- a/swap/src/fs.rs +++ b/swap/src/fs.rs @@ -1,4 +1,19 @@ -use std::path::Path; +use anyhow::Context; +use directories_next::ProjectDirs; +use std::path::{Path, PathBuf}; + +/// This is to store the configuration and seed files +// Linux: /home//.config/xmr-btc-swap/ +// OSX: /Users//Library/Preferences/xmr-btc-swap/ +fn config_dir() -> Option { + ProjectDirs::from("", "", "xmr-btc-swap").map(|proj_dirs| proj_dirs.config_dir().to_path_buf()) +} + +pub fn default_config_path() -> anyhow::Result { + config_dir() + .map(|dir| Path::join(&dir, "config.toml")) + .context("Could not generate default configuration path") +} pub fn ensure_directory_exists(file: &Path) -> Result<(), std::io::Error> { if let Some(path) = file.parent() { diff --git a/swap/src/main.rs b/swap/src/main.rs index 3c042736..69668bd7 100644 --- a/swap/src/main.rs +++ b/swap/src/main.rs @@ -12,7 +12,10 @@ #![forbid(unsafe_code)] #![allow(non_snake_case)] -use crate::cli::{Command, Options, Resume}; +use crate::{ + cli::{Command, Options, Resume}, + config::read_config, +}; use anyhow::{Context, Result}; use database::Database; use prettytable::{row, Table}; @@ -60,15 +63,11 @@ async fn main() -> Result<()> { match opt.cmd { Command::SellXmr { - bitcoind_url, - bitcoin_wallet_name, - monero_wallet_rpc_url, listen_addr, send_monero, receive_bitcoin, } => { - let settings = - Settings::testnet(bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url); + let settings = Settings::from_config_file_and_defaults(read_config()?); let swap_amounts = SwapAmounts { xmr: send_monero, @@ -103,14 +102,10 @@ async fn main() -> Result<()> { Command::BuyXmr { alice_peer_id, alice_addr, - bitcoind_url, - bitcoin_wallet_name, - monero_wallet_rpc_url, send_bitcoin, receive_monero, } => { - let settings = - Settings::testnet(bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url); + let settings = Settings::from_config_file_and_defaults(read_config()?); let swap_amounts = SwapAmounts { btc: send_bitcoin, @@ -157,13 +152,9 @@ async fn main() -> Result<()> { } Command::Resume(Resume::SellXmr { swap_id, - bitcoind_url, - bitcoin_wallet_name, - monero_wallet_rpc_url, listen_addr, }) => { - let settings = - Settings::testnet(bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url); + let settings = Settings::from_config_file_and_defaults(read_config()?); let (bitcoin_wallet, monero_wallet) = setup_wallets(settings.wallets).await?; @@ -184,14 +175,10 @@ async fn main() -> Result<()> { } Command::Resume(Resume::BuyXmr { swap_id, - bitcoind_url, - bitcoin_wallet_name, - monero_wallet_rpc_url, alice_peer_id, alice_addr, }) => { - let settings = - Settings::testnet(bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url); + let settings = Settings::from_config_file_and_defaults(read_config()?); let (bitcoin_wallet, monero_wallet) = setup_wallets(settings.wallets).await?; diff --git a/swap/src/settings.rs b/swap/src/settings.rs index 74571078..dee7b84d 100644 --- a/swap/src/settings.rs +++ b/swap/src/settings.rs @@ -1,4 +1,4 @@ -use crate::bitcoin::Timelock; +use crate::{bitcoin::Timelock, config::File}; use conquer_once::Lazy; use std::time::Duration; use url::Url; @@ -9,11 +9,15 @@ pub struct Settings { } impl Settings { - pub fn testnet( - bitcoind_url: Url, - bitcoin_wallet_name: String, - monero_wallet_rpc_url: Url, - ) -> Self { + pub fn from_config_file_and_defaults(config: File) -> Self { + Settings::testnet( + config.bitcoin.bitcoind_url, + config.bitcoin.wallet_name, + config.monero.wallet_rpc_url, + ) + } + + fn testnet(bitcoind_url: Url, bitcoin_wallet_name: String, monero_wallet_rpc_url: Url) -> Self { Self { wallets: Wallets::testnet(bitcoind_url, bitcoin_wallet_name, monero_wallet_rpc_url), protocol: Protocol::testnet(),