diff --git a/api/src/owner.rs b/api/src/owner.rs index f0b9f57ff..01f4b0fcb 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -2324,7 +2324,7 @@ where } } -/// attempt to send slate synchronously with TOR +/// attempt to send slate synchronously, starting with TOR and downgrading to HTTP pub fn try_slatepack_sync_workflow( slate: &Slate, dest: &str, @@ -2355,7 +2355,7 @@ pub fn try_slatepack_sync_workflow( } }; - // Try parsing Slatepack address + // First, try TOR match SlatepackAddress::try_from(dest) { Ok(address) => { let tor_addr = OnionV3Address::try_from(&address).unwrap(); @@ -2392,18 +2392,34 @@ pub fn try_slatepack_sync_workflow( Ok(_) => return Ok(Some(ret_slate)), Err(e) => { debug!("Unable to send via TOR: {}", e); - warn!("Unable to send transaction via TOR"); + warn!("Unable to send transaction via TOR. Attempting alternate methods."); } } } } Err(e) => { debug!("Send (TOR): Destination is not SlatepackAddress {:?}", e); - warn!("Destination is not a valid Slatepack address. Will output Slatepack.") } } - Ok(None) + // Try Fallback to HTTP for deprecation period + match HttpSlateSender::new(&dest) { + Ok(sender) => { + println!("Attempting to send transaction via HTTP (deprecated)"); + match send_sync(sender, "HTTP") { + Ok(_) => return Ok(Some(ret_slate)), + Err(e) => { + debug!("Unable to send via HTTP: {}", e); + warn!("Unable to send transaction via HTTP. Will output Slatepack."); + return Ok(None); + } + } + } + Err(e) => { + debug!("Send (HTTP): Cannot create HTTP Slate sender {:?}", e); + return Ok(None); + } + } } #[doc(hidden)] diff --git a/config/src/types.rs b/config/src/types.rs index 4f57488af..aa3df09c9 100644 --- a/config/src/types.rs +++ b/config/src/types.rs @@ -26,6 +26,9 @@ use crate::util::logger::LoggingConfig; pub struct WalletConfig { /// Chain parameters (default to Mainnet if none at the moment) pub chain_type: Option, + /// The api interface/ip_address that this api server (i.e. this wallet) will run + /// by default this is 127.0.0.1 (and will not accept connections from external clients) + pub api_listen_interface: String, /// The port this wallet will run on pub api_listen_port: u16, /// The port this wallet's owner API will run on @@ -62,6 +65,7 @@ impl Default for WalletConfig { fn default() -> WalletConfig { WalletConfig { chain_type: Some(ChainTypes::Mainnet), + api_listen_interface: "127.0.0.1".to_string(), api_listen_port: 3415, owner_api_listen_port: Some(WalletConfig::default_owner_api_listen_port()), api_secret_path: Some(".owner_api_secret".to_string()), @@ -82,7 +86,7 @@ impl Default for WalletConfig { impl WalletConfig { /// API Listen address pub fn api_listen_addr(&self) -> String { - format!("127.0.0.1:{}", self.api_listen_port) + format!("{}:{}", self.api_listen_interface, self.api_listen_port) } /// Default listener port diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index 4f1572501..0313e0d65 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -39,7 +39,7 @@ pub struct HttpSlateSender { impl HttpSlateSender { /// Create, return Err if scheme is not "http" - fn new(base_url: &str) -> Result { + pub fn new(base_url: &str) -> Result { if !base_url.starts_with("http") && !base_url.starts_with("https") { Err(SchemeNotHttp) } else { diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index 9ca9ce6a5..6dace27b1 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -22,8 +22,9 @@ pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; pub use self::slatepack::PathToSlatepack; -use crate::config::WalletConfig; -use crate::libwallet::{Error, Slate}; +use crate::config::{TorConfig, WalletConfig}; +use crate::libwallet::{Error, ErrorKind, Slate}; +use crate::tor::config::complete_tor_address; use crate::util::ZeroingString; /// Sends transactions to a corresponding SlateReceiver @@ -58,3 +59,64 @@ pub trait SlateGetter { /// Returns (Slate, whether it was in binary form) fn get_tx(&self) -> Result<(Slate, bool), Error>; } + +/// select a SlateSender based on method and dest fields from, e.g., SendArgs +pub fn create_sender( + method: &str, + dest: &str, + tor_config: Option, +) -> Result, Error> { + let invalid = || { + ErrorKind::WalletComms(format!( + "Invalid wallet comm type and destination. method: {}, dest: {}", + method, dest + )) + }; + + let mut method = method; + + // will test if this is a tor address and fill out + // the http://[].onion if missing + let dest = match complete_tor_address(dest) { + Ok(d) => { + method = "tor"; + d + } + Err(_) => dest.into(), + }; + + Ok(match method { + "http" => Box::new(HttpSlateSender::new(&dest).map_err(|_| invalid())?), + "tor" => match tor_config { + None => { + return Err( + ErrorKind::WalletComms("Tor Configuration required".to_string()).into(), + ); + } + Some(tc) => Box::new( + HttpSlateSender::with_socks_proxy(&dest, &tc.socks_proxy_addr, &tc.send_config_dir) + .map_err(|_| invalid())?, + ), + }, + "keybase" => Box::new(KeybaseChannel::new(dest)?), + "self" => { + return Err(ErrorKind::WalletComms( + "No sender implementation for \"self\".".to_string(), + ) + .into()); + } + "file" => { + return Err(ErrorKind::WalletComms( + "File based transactions must be performed asynchronously.".to_string(), + ) + .into()); + } + _ => { + return Err(ErrorKind::WalletComms(format!( + "Wallet comm method \"{}\" does not exist.", + method + )) + .into()); + } + }) +} diff --git a/impls/src/lib.rs b/impls/src/lib.rs index 365b7e89b..39eca1e6e 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -44,8 +44,8 @@ pub mod test_framework; pub mod tor; pub use crate::adapters::{ - HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, PathToSlatepack, SlateGetter, - SlatePutter, SlateReceiver, SlateSender, + create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, + PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender, }; pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::error::{Error, ErrorKind}; diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 7c4523644..365c2b053 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -27,6 +27,11 @@ args: short: t long: top_level_dir takes_value: true + - external: + help: Listen on 0.0.0.0 interface to allow external connections (default is 127.0.0.1) + short: e + long: external + takes_value: false - show_spent: help: Show spent outputs on wallet output commands short: s diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 1db6bedeb..e875aaab2 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -219,6 +219,39 @@ fn prompt_pay_invoice(slate: &Slate, dest: &str) -> Result { } } +fn prompt_deprecate_http() -> Result { + let interface = Arc::new(Interface::new("http")?); + interface.set_report_signal(Signal::Interrupt, true); + interface.set_prompt("To proceed, type 'UNDERSTOOD' > ")?; + println!(); + println!("Http(s) is being deprecated in favour of the Slatepack Workflow"); + println!("This sending method is planned for removal as of the last scheduled Hardfork in Grin 5.0.0"); + println!("Please see https://github.com/mimblewimble/grin-rfcs/pull/55 for details"); + loop { + let res = interface.read_line()?; + match res { + ReadResult::Eof => return Ok(false), + ReadResult::Signal(sig) => { + if sig == Signal::Interrupt { + interface.cancel_read_line()?; + return Err(ParseError::CancelledError); + } + } + ReadResult::Input(line) => match line.trim() { + "Q" | "q" => return Err(ParseError::CancelledError), + result => { + if result == "UNDERSTOOD" { + return Ok(true); + } else { + println!("Please enter the phrase 'UNDERSTOOD' (without quotes) to continue or Q to quit"); + println!(); + } + } + }, + } + } +} + // instantiate wallet (needed by most functions) pub fn inst_wallet( @@ -454,6 +487,10 @@ pub fn parse_send_args(args: &ArgMatches) -> Result "default", }; + if dest.to_uppercase().starts_with("HTTP") { + prompt_deprecate_http()?; + } + // change_outputs let change_outputs = parse_required(args, "change_outputs")?; let change_outputs = parse_u64(change_outputs, "change_outputs")? as usize; @@ -927,6 +964,10 @@ where >, ), { + if wallet_args.is_present("external") { + wallet_config.api_listen_interface = "0.0.0.0".to_string(); + } + if let Some(dir) = wallet_args.value_of("top_level_dir") { wallet_config.data_file_dir = dir.to_string().clone(); }