mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
Integrated Tor arti Client (#750)
* tor: integrated client for listener, update tokio to last version * tor: send transaction with integrated client * build: code format * tor: optimize bootstrap percent check * tor: handle config build error * tor: do not show slatepack address if tor not launched * tor: handle keystore errors * tor: keep service launched * tor: bootstrap timeout, clean cache before client launch * tor: post request timeout * fix: post timeout * build: update deps after merge * tor: store arti runtime * tor: do not send over tor if `manual` arg presents or `skip_send_attempt` set at config * fix: arg name * tor: handle arti runtime creation error * tor: handle connection error * tor: handle bootstrap error * tor: handle start service name error * tor: handle service config creation error * tor: service proxy errors * tor: handle service key creation emptiness * build: format code * build: update arti to 0.43, hyper to 0.10.1 * api: start foreign listener before tor connection * tor: handle json parse errors * tor: prevent to create several client runtimes, ability to recreate * tor: return bootstrap error * tor: handle host parse error * tor: restart proxy on error * tor: do not use integrated client when config value use_integrated is missing, add logging * build: update lock file
This commit is contained in:
Generated
+4296
-507
File diff suppressed because it is too large
Load Diff
+18
-20
@@ -23,8 +23,8 @@ use uuid::Uuid;
|
||||
use crate::config::{TorConfig, WalletConfig};
|
||||
use crate::core::core::OutputFeatures;
|
||||
use crate::core::global;
|
||||
use crate::impls::HttpSlateSender;
|
||||
use crate::impls::SlateSender as _;
|
||||
use crate::impls::TorSlateSender;
|
||||
use crate::keychain::{Identifier, Keychain};
|
||||
use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage};
|
||||
use crate::libwallet::api_impl::{owner, owner_updater};
|
||||
@@ -669,17 +669,15 @@ where
|
||||
};
|
||||
// Helper functionality. If send arguments exist, attempt to send sync and
|
||||
// finalize
|
||||
let skip_tor = match send_args.as_ref() {
|
||||
None => false,
|
||||
Some(sa) => sa.skip_tor,
|
||||
};
|
||||
match send_args {
|
||||
Some(sa) => {
|
||||
let tor_config_lock = self.tor_config.lock();
|
||||
let tc = tor_config_lock.clone();
|
||||
let tc = match tc {
|
||||
Some(mut c) => {
|
||||
c.skip_send_attempt = Some(skip_tor);
|
||||
if let Some(skip_tor) = sa.skip_tor {
|
||||
c.skip_send_attempt = Some(skip_tor);
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
None => None,
|
||||
@@ -845,7 +843,9 @@ where
|
||||
let tc = tor_config_lock.clone();
|
||||
let tc = match tc {
|
||||
Some(mut c) => {
|
||||
c.skip_send_attempt = Some(sa.skip_tor);
|
||||
if let Some(skip_tor) = sa.skip_tor {
|
||||
c.skip_send_attempt = Some(skip_tor);
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
None => None,
|
||||
@@ -2495,7 +2495,7 @@ pub fn try_slatepack_sync_workflow(
|
||||
slate: &Slate,
|
||||
dest: &str,
|
||||
tor_config: Option<TorConfig>,
|
||||
tor_sender: Option<HttpSlateSender>,
|
||||
tor_sender: Option<TorSlateSender>,
|
||||
send_to_finalize: bool,
|
||||
test_mode: bool,
|
||||
) -> Result<Option<Slate>, Error> {
|
||||
@@ -2505,7 +2505,7 @@ pub fn try_slatepack_sync_workflow(
|
||||
}
|
||||
}
|
||||
let mut ret_slate = Slate::blank(2, false);
|
||||
let mut send_sync = |mut sender: HttpSlateSender, method_str: &str| match sender
|
||||
let mut send_sync = |mut sender: TorSlateSender, method_str: &str| match sender
|
||||
.send_tx(&slate, send_to_finalize)
|
||||
{
|
||||
Ok(s) => {
|
||||
@@ -2531,18 +2531,16 @@ pub fn try_slatepack_sync_workflow(
|
||||
if test_mode {
|
||||
None
|
||||
} else {
|
||||
match HttpSlateSender::with_socks_proxy(
|
||||
&tor_addr.to_http_str(),
|
||||
&tor_config.as_ref().unwrap().socks_proxy_addr,
|
||||
&tor_config.as_ref().unwrap().send_config_dir,
|
||||
tor_config.as_ref().unwrap().bridge.clone(),
|
||||
tor_config.as_ref().unwrap().proxy.clone(),
|
||||
) {
|
||||
Ok(s) => Some(s),
|
||||
Err(e) => {
|
||||
debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e);
|
||||
None
|
||||
if let Some(tc) = tor_config {
|
||||
match TorSlateSender::new(&tor_addr.to_http_str(), tc) {
|
||||
Ok(s) => Some(s),
|
||||
Err(e) => {
|
||||
debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::TorConfig("Tor config is not set".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -206,16 +206,24 @@ fn comments() -> HashMap<String, String> {
|
||||
"[tor]".to_string(),
|
||||
"
|
||||
#########################################
|
||||
### TOR CONFIGURATION (Experimental) ###
|
||||
### TOR CONFIGURATION ###
|
||||
#########################################
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"use_integrated".to_string(),
|
||||
"
|
||||
#Whether to use integrated Tor library
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
retval.insert(
|
||||
"skip_send_attempt".to_string(),
|
||||
"
|
||||
#Whether to skip send attempts (used for debugging)
|
||||
#Whether to skip send attempts (default false)
|
||||
"
|
||||
.to_string(),
|
||||
);
|
||||
@@ -259,8 +267,11 @@ fn comments() -> HashMap<String, String> {
|
||||
"
|
||||
#Tor bridge relay: allow to send and receive via TOR in a country where it is censored.
|
||||
#Enable it by entering a single bridge line. To disable it, you must comment it.
|
||||
#Support of the transport: obfs4, meek and snowflake.
|
||||
#obfs4proxy or snowflake client binary must be installed and on your path.
|
||||
#Support of the transport: webtunnel, obfs4, and snowflake.
|
||||
#webtunnel, obfs4proxy or snowflake client binary must be installed and on your path.
|
||||
#Custom path for client binary
|
||||
#bridge_bin_path = \"\"
|
||||
|
||||
#For example, the bridge line must be in the following format for obfs4 transport: \"obfs4 [IP:PORT] [FINGERPRINT] cert=[CERT] iat-mode=[IAT-MODE]\"
|
||||
#bridge_line = \"\"
|
||||
|
||||
|
||||
@@ -162,6 +162,8 @@ impl From<io::Error> for ConfigError {
|
||||
/// Tor configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorConfig {
|
||||
/// whether to use integrated Tor library
|
||||
pub use_integrated: Option<bool>,
|
||||
/// whether to skip any attempts to send via TOR
|
||||
pub skip_send_attempt: Option<bool>,
|
||||
/// Whether to start tor listener on listener startup (default true)
|
||||
@@ -181,6 +183,7 @@ pub struct TorConfig {
|
||||
impl Default for TorConfig {
|
||||
fn default() -> TorConfig {
|
||||
TorConfig {
|
||||
use_integrated: Some(true),
|
||||
skip_send_attempt: Some(false),
|
||||
use_tor_listener: true,
|
||||
socks_proxy_addr: "127.0.0.1:59050".to_owned(),
|
||||
@@ -194,6 +197,8 @@ impl Default for TorConfig {
|
||||
/// Tor Bridge Config
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TorBridgeConfig {
|
||||
/// Path to bridge binary to use with integrated Tor library
|
||||
pub bridge_bin_path: Option<String>,
|
||||
/// Bridge Line
|
||||
pub bridge_line: Option<String>,
|
||||
/// Client Option
|
||||
@@ -203,6 +208,7 @@ pub struct TorBridgeConfig {
|
||||
impl Default for TorBridgeConfig {
|
||||
fn default() -> TorBridgeConfig {
|
||||
TorBridgeConfig {
|
||||
bridge_bin_path: None,
|
||||
bridge_line: None,
|
||||
client_option: None,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ log = "0.4"
|
||||
prettytable-rs = "0.10"
|
||||
ring = "0.16"
|
||||
term = "0.6"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
url = "2.1"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
|
||||
@@ -224,7 +224,7 @@ where
|
||||
g_args.tls_conf.clone(),
|
||||
tor_config.use_tor_listener,
|
||||
test_mode,
|
||||
Some(tor_config.clone()),
|
||||
tor_config,
|
||||
);
|
||||
if let Err(e) = res {
|
||||
error!("Error starting listener: {}", e);
|
||||
@@ -335,7 +335,7 @@ pub struct SendArgs {
|
||||
pub target_slate_version: Option<u16>,
|
||||
pub payment_proof_address: Option<SlatepackAddress>,
|
||||
pub ttl_blocks: Option<u64>,
|
||||
pub skip_tor: bool,
|
||||
pub skip_tor: Option<bool>,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
pub slatepack_qr: bool,
|
||||
@@ -431,7 +431,9 @@ where
|
||||
if let Some(b) = args.bridge.clone() {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
if let Some(s) = args.skip_tor {
|
||||
c.skip_send_attempt = Some(s);
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
None => None,
|
||||
@@ -630,7 +632,7 @@ where
|
||||
pub struct ReceiveArgs {
|
||||
pub input_file: Option<String>,
|
||||
pub input_slatepack_message: Option<String>,
|
||||
pub skip_tor: bool,
|
||||
pub skip_tor: Option<bool>,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
pub slatepack_qr: bool,
|
||||
@@ -666,7 +668,9 @@ where
|
||||
if let Some(b) = args.bridge {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
if let Some(s) = args.skip_tor {
|
||||
c.skip_send_attempt = Some(s);
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
None => None,
|
||||
@@ -688,7 +692,7 @@ where
|
||||
Ok(Some(_)) => {
|
||||
println!();
|
||||
println!(
|
||||
"Transaction recieved and sent back to sender at {} for finalization.",
|
||||
"Transaction received and sent back to sender at {} for finalization.",
|
||||
dest
|
||||
);
|
||||
println!();
|
||||
@@ -925,7 +929,7 @@ pub struct ProcessInvoiceArgs {
|
||||
pub slate: Slate,
|
||||
pub estimate_selection_strategies: bool,
|
||||
pub ttl_blocks: Option<u64>,
|
||||
pub skip_tor: bool,
|
||||
pub skip_tor: Option<bool>,
|
||||
pub outfile: Option<String>,
|
||||
pub bridge: Option<String>,
|
||||
pub slatepack_qr: bool,
|
||||
@@ -1008,7 +1012,9 @@ where
|
||||
if let Some(b) = args.bridge {
|
||||
c.bridge.bridge_line = Some(b);
|
||||
}
|
||||
c.skip_send_attempt = Some(args.skip_tor);
|
||||
if let Some(skip_tor) = args.skip_tor {
|
||||
c.skip_send_attempt = Some(skip_tor);
|
||||
}
|
||||
Some(c)
|
||||
}
|
||||
None => None,
|
||||
|
||||
@@ -48,6 +48,8 @@ use crate::apiwallet::{
|
||||
};
|
||||
use easy_jsonrpc_mw;
|
||||
use easy_jsonrpc_mw::{Handler, MaybeReply};
|
||||
use grin_wallet_impls::tor::arti::start_tor_service;
|
||||
use grin_wallet_impls::tor::Tor;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref GRIN_OWNER_BASIC_REALM: HeaderValue =
|
||||
@@ -82,32 +84,16 @@ fn check_middleware(
|
||||
}
|
||||
|
||||
/// initiate the tor listener
|
||||
fn init_tor_listener<L, C, K>(
|
||||
wallet: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K> + 'static>>>,
|
||||
keychain_mask: Arc<Mutex<Option<SecretKey>>>,
|
||||
fn init_tor_listener(
|
||||
sec_key: SecretKey,
|
||||
tor_dir: String,
|
||||
addr: &str,
|
||||
bridge: TorBridgeConfig,
|
||||
tor_proxy: TorProxyConfig,
|
||||
) -> Result<(tor_process::TorProcess, SlatepackAddress), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
C: NodeClient + 'static,
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
) -> Result<Tor, Error> {
|
||||
info!("Starting external Tor Process listener.");
|
||||
|
||||
let mut process = tor_process::TorProcess::new();
|
||||
let mask = keychain_mask.lock();
|
||||
// eventually want to read a list of service config keys
|
||||
let mut w_lock = wallet.lock();
|
||||
let lc = w_lock.lc_provider()?;
|
||||
let w_inst = lc.wallet_inst()?;
|
||||
let k = w_inst.keychain((&mask).as_ref())?;
|
||||
let parent_key_id = w_inst.parent_key_id();
|
||||
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
|
||||
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
|
||||
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
let mut tor_timeout = 20;
|
||||
@@ -129,10 +115,6 @@ where
|
||||
.map_err(|e| Error::TorConfig(format!("{}", e).into()))?;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
|
||||
onion_address, addr
|
||||
);
|
||||
tor_config::output_tor_listener_config(
|
||||
&tor_dir,
|
||||
addr,
|
||||
@@ -149,7 +131,11 @@ where
|
||||
.completion_percent(100)
|
||||
.launch()
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
Ok((process, sp_address))
|
||||
Ok(Tor {
|
||||
process: Some(process),
|
||||
service: None,
|
||||
client: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Instantiate wallet Owner API for a single-use (command line) call
|
||||
@@ -285,7 +271,7 @@ pub fn foreign_listener<L, C, K>(
|
||||
tls_config: Option<TLSConfig>,
|
||||
use_tor: bool,
|
||||
test_mode: bool,
|
||||
tor_config: Option<TorConfig>,
|
||||
tor_config: TorConfig,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
L: WalletLCProvider<'static, C, K> + 'static,
|
||||
@@ -293,41 +279,27 @@ where
|
||||
K: Keychain + 'static,
|
||||
{
|
||||
// Check if wallet has been opened first
|
||||
{
|
||||
let (sec_key, tor_dir, onion_address) = {
|
||||
let mask = keychain_mask.lock();
|
||||
let mut w_lock = wallet.lock();
|
||||
let lc = w_lock.lc_provider()?;
|
||||
let _ = lc.wallet_inst()?;
|
||||
}
|
||||
|
||||
let (tor_bridge, tor_proxy) = match tor_config.clone() {
|
||||
Some(s) => (s.bridge, s.proxy),
|
||||
None => (TorBridgeConfig::default(), TorProxyConfig::default()),
|
||||
let w_inst = lc.wallet_inst()?;
|
||||
let k = w_inst.keychain((&mask).as_ref())?;
|
||||
let parent_key_id = w_inst.parent_key_id();
|
||||
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
|
||||
let onion_address = OnionV3Address::from_private(&sec_key.0)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
(sec_key, tor_dir, onion_address)
|
||||
};
|
||||
|
||||
// need to keep in scope while the main listener is running
|
||||
let (_tor_process, address) = match use_tor {
|
||||
true => {
|
||||
match init_tor_listener(
|
||||
wallet.clone(),
|
||||
keychain_mask.clone(),
|
||||
addr,
|
||||
tor_bridge,
|
||||
tor_proxy,
|
||||
) {
|
||||
Ok((tp, addr)) => (Some(tp), Some(addr)),
|
||||
Err(e) => {
|
||||
warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path");
|
||||
error!("Tor Error: {}", e);
|
||||
warn!("Listener will be available via HTTP only");
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
false => (None, None),
|
||||
};
|
||||
|
||||
let api_handler_v2 =
|
||||
ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config));
|
||||
let api_handler_v2 = ForeignAPIHandlerV2::new(
|
||||
wallet,
|
||||
keychain_mask,
|
||||
test_mode,
|
||||
Mutex::new(Some(tor_config.clone())),
|
||||
);
|
||||
let mut router = Router::new();
|
||||
|
||||
router
|
||||
@@ -343,14 +315,52 @@ where
|
||||
let api_thread = apis
|
||||
.start(socket_addr, router, tls_config, api_chan)
|
||||
.map_err(|_| Error::GenericError("API thread failed to start".to_string()))?;
|
||||
|
||||
warn!("HTTP Foreign listener started.");
|
||||
if let Some(a) = address {
|
||||
let qr_string = match QrCode::new(a.to_string()) {
|
||||
Ok(qr) => qr.to_string(false, 3),
|
||||
Err(_) => "Failed to generate QR code!".to_string(),
|
||||
|
||||
// Need to keep external process in scope while the listener is running.
|
||||
let tor_service = if use_tor {
|
||||
let use_integrated = tor_config.use_integrated.unwrap_or(false);
|
||||
let res = if use_integrated {
|
||||
start_tor_service(sec_key, &tor_dir, addr, tor_config.clone())
|
||||
} else {
|
||||
init_tor_listener(
|
||||
sec_key,
|
||||
tor_dir,
|
||||
addr,
|
||||
tor_config.bridge.clone(),
|
||||
tor_config.proxy.clone(),
|
||||
)
|
||||
};
|
||||
warn!("Slatepack Address is: {}\n{}", a, qr_string);
|
||||
match res {
|
||||
Ok(service) => {
|
||||
warn!(
|
||||
"Starting Tor Hidden Service for API listener at address {}, binding to {}",
|
||||
onion_address, addr
|
||||
);
|
||||
Ok(Some(service))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Unable to start TOR listener");
|
||||
error!("Tor Error: {}", e);
|
||||
warn!("Listener is available on {}", addr);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
if tor_service.is_ok() {
|
||||
if let Some(_) = tor_service.as_ref().unwrap() {
|
||||
let sp_address = SlatepackAddress::try_from(onion_address.clone())?;
|
||||
let qr_string = match QrCode::new(sp_address.to_string()) {
|
||||
Ok(qr) => qr.to_string(false, 3),
|
||||
Err(_) => "Failed to generate QR code!".to_string(),
|
||||
};
|
||||
warn!("Slatepack Address is: {}\n{}", sp_address, qr_string);
|
||||
} else {
|
||||
warn!("Listener is available on {}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
api_thread
|
||||
|
||||
+18
-5
@@ -22,20 +22,33 @@ ring = "0.16"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
lazy_static = "1"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
reqwest = { version = "0.10", features = ["rustls-tls", "socks"] }
|
||||
tokio = { version = "1.52.3", features = ["full"] }
|
||||
reqwest = { version = "0.13.3", features = ["socks"] }
|
||||
|
||||
#Socks/Tor/Bridge/Proxy
|
||||
byteorder = "1"
|
||||
ed25519-dalek = "1.0.0-pre.4"
|
||||
x25519-dalek = "0.6"
|
||||
data-encoding = "2"
|
||||
arti-ed25519-dalek = { version = "2.1.1", package = "ed25519-dalek" }
|
||||
regex = "1.3"
|
||||
timer = "0.2"
|
||||
sysinfo = "0.29"
|
||||
base64 = "0.12.0"
|
||||
url = "2.1"
|
||||
|
||||
arti-client = { version = "0.43.0", features = ["static", "pt-client", "onion-service-service", "onion-service-client"] }
|
||||
tor-rtcompat = { version = "0.43.0", features = ["static"] }
|
||||
fs-mistrust = "0.14.2"
|
||||
tor-hsservice = "0.43.0"
|
||||
tor-hsrproxy = "0.43.0"
|
||||
tor-keymgr = "0.43.0"
|
||||
tor-llcrypto = "0.43.0"
|
||||
tor-hscrypto = "0.43.0"
|
||||
sha2 = "0.10.8"
|
||||
curve25519-dalek = "4.1.3"
|
||||
hyper = { version = "1.10.1", features = ["http1", "client"] }
|
||||
hyper-util = { version = "0.1.20", features = ["tokio"] }
|
||||
http-body-util = "0.1.3"
|
||||
bytes = "1.11.1"
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_config = { path = "../config", version = "5.4.0-alpha.1" }
|
||||
grin_wallet_libwallet = { path = "../libwallet", version = "5.4.0-alpha.1" }
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod file;
|
||||
pub mod http;
|
||||
mod slatepack;
|
||||
pub mod tor;
|
||||
|
||||
pub use self::file::PathToSlate;
|
||||
pub use self::http::HttpSlateSender;
|
||||
pub use self::slatepack::PathToSlatepack;
|
||||
pub use self::tor::TorSlateSender;
|
||||
|
||||
use crate::config::WalletConfig;
|
||||
use crate::libwallet::{Error, Slate};
|
||||
|
||||
@@ -12,128 +12,111 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/// HTTP Wallet 'plugin' implementation
|
||||
use crate::client_utils::{Client, ClientError};
|
||||
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
||||
use crate::libwallet::{Error, Slate};
|
||||
use crate::tor::bridge::TorBridge;
|
||||
use crate::tor::proxy::TorProxy;
|
||||
use crate::SlateSender;
|
||||
use grin_wallet_config::types::{TorBridgeConfig, TorProxyConfig};
|
||||
use grin_wallet_config::TorConfig;
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::tor::config as tor_config;
|
||||
use crate::tor::process as tor_process;
|
||||
|
||||
const TOR_CONFIG_PATH: &str = "tor/sender";
|
||||
use crate::client_utils::{Client, ClientError};
|
||||
use crate::libwallet::slate_versions::{SlateVersion, VersionedSlate};
|
||||
use crate::libwallet::{Error, Slate};
|
||||
use crate::tor::arti::{start_tor_client, tor_post};
|
||||
use crate::tor::bridge::TorBridge;
|
||||
use crate::tor::process::TorProcess;
|
||||
use crate::tor::proxy::TorProxy;
|
||||
use crate::tor::{config as tor_config, Tor};
|
||||
use crate::SlateSender;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpSlateSender {
|
||||
pub struct TorSlateSender {
|
||||
base_url: String,
|
||||
use_socks: bool,
|
||||
socks_proxy_addr: Option<SocketAddr>,
|
||||
tor_config_dir: String,
|
||||
process: Option<Arc<tor_process::TorProcess>>,
|
||||
bridge: TorBridgeConfig,
|
||||
proxy: TorProxyConfig,
|
||||
config: TorConfig,
|
||||
tor: Arc<Tor>,
|
||||
}
|
||||
|
||||
impl HttpSlateSender {
|
||||
impl TorSlateSender {
|
||||
/// Create, return Err if scheme is not "http"
|
||||
fn new(base_url: &str) -> Result<HttpSlateSender, SchemeNotHttp> {
|
||||
pub fn new(base_url: &str, config: TorConfig) -> Result<TorSlateSender, Error> {
|
||||
if !base_url.starts_with("http") && !base_url.starts_with("https") {
|
||||
Err(SchemeNotHttp)
|
||||
Err(Error::GenericError("Scheme must be http".to_string()))
|
||||
} else {
|
||||
Ok(HttpSlateSender {
|
||||
let tor_dir = {
|
||||
let mut path = PathBuf::from(&config.send_config_dir);
|
||||
path.push("tor");
|
||||
path.push("sender");
|
||||
path
|
||||
};
|
||||
let tor = if config.use_integrated.unwrap_or(false) {
|
||||
start_tor_client(tor_dir.to_str().unwrap(), config.clone())?
|
||||
} else {
|
||||
Self::launch_tor_process(&config, &tor_dir)?
|
||||
};
|
||||
Ok(TorSlateSender {
|
||||
base_url: base_url.to_owned(),
|
||||
use_socks: false,
|
||||
socks_proxy_addr: None,
|
||||
tor_config_dir: String::from(""),
|
||||
process: None,
|
||||
bridge: TorBridgeConfig::default(),
|
||||
proxy: TorProxyConfig::default(),
|
||||
config,
|
||||
tor: Arc::new(tor),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch to using socks proxy
|
||||
pub fn with_socks_proxy(
|
||||
base_url: &str,
|
||||
proxy_addr: &str,
|
||||
tor_config_dir: &str,
|
||||
tor_bridge: TorBridgeConfig,
|
||||
tor_proxy: TorProxyConfig,
|
||||
) -> Result<HttpSlateSender, SchemeNotHttp> {
|
||||
let mut ret = Self::new(base_url)?;
|
||||
ret.use_socks = true;
|
||||
//TODO: Unwrap
|
||||
ret.socks_proxy_addr = Some(SocketAddr::V4(proxy_addr.parse().unwrap()));
|
||||
ret.tor_config_dir = tor_config_dir.into();
|
||||
ret.bridge = tor_bridge;
|
||||
ret.proxy = tor_proxy;
|
||||
Ok(ret)
|
||||
}
|
||||
/// Launch external Tor process.
|
||||
fn launch_tor_process(config: &TorConfig, tor_dir: &PathBuf) -> Result<Tor, Error> {
|
||||
let mut tor = TorProcess::new();
|
||||
let socks_proxy_addr = SocketAddr::V4(
|
||||
config
|
||||
.socks_proxy_addr
|
||||
.parse()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?,
|
||||
);
|
||||
info!("Starting TOR Process for send at {:?}", socks_proxy_addr);
|
||||
|
||||
/// launch TOR process
|
||||
pub fn launch_tor(&mut self) -> Result<(), Error> {
|
||||
// set up tor send process if needed
|
||||
let mut tor = tor_process::TorProcess::new();
|
||||
if self.use_socks && self.process.is_none() {
|
||||
let tor_dir = format!(
|
||||
"{}{}{}",
|
||||
&self.tor_config_dir, MAIN_SEPARATOR, TOR_CONFIG_PATH
|
||||
);
|
||||
info!(
|
||||
"Starting TOR Process for send at {:?}",
|
||||
self.socks_proxy_addr
|
||||
);
|
||||
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
if self.bridge.bridge_line.is_some() {
|
||||
let bridge_struct = TorBridge::try_from(self.bridge.clone())
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
hm_tor_bridge = bridge_struct
|
||||
.to_hashmap()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
}
|
||||
|
||||
let mut hm_tor_proxy: HashMap<String, String> = HashMap::new();
|
||||
if self.proxy.transport.is_some() || self.proxy.allowed_port.is_some() {
|
||||
let proxy = TorProxy::try_from(self.proxy.clone())
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
hm_tor_proxy = proxy
|
||||
.to_hashmap()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
}
|
||||
|
||||
tor_config::output_tor_sender_config(
|
||||
&tor_dir,
|
||||
&self.socks_proxy_addr.unwrap().to_string(),
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
// Start TOR process
|
||||
tor.torrc_path(&format!("{}/torrc", &tor_dir))
|
||||
.working_dir(&tor_dir)
|
||||
.timeout(20)
|
||||
.completion_percent(100)
|
||||
.launch()
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
self.process = Some(Arc::new(tor));
|
||||
let mut hm_tor_bridge: HashMap<String, String> = HashMap::new();
|
||||
if config.bridge.bridge_line.is_some() {
|
||||
let bridge_struct = TorBridge::try_from(config.bridge.clone())
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
hm_tor_bridge = bridge_struct
|
||||
.to_hashmap()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
let mut hm_tor_proxy: HashMap<String, String> = HashMap::new();
|
||||
if config.proxy.transport.is_some() || config.proxy.allowed_port.is_some() {
|
||||
let proxy = TorProxy::try_from(config.proxy.clone())
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
hm_tor_proxy = proxy
|
||||
.to_hashmap()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
}
|
||||
|
||||
tor_config::output_tor_sender_config(
|
||||
tor_dir.to_str().unwrap(),
|
||||
socks_proxy_addr.to_string().as_str(),
|
||||
hm_tor_bridge,
|
||||
hm_tor_proxy,
|
||||
)
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
// Start TOR process
|
||||
let mut path = tor_dir.clone();
|
||||
path.push("torrc");
|
||||
tor.torrc_path(path.to_str().unwrap())
|
||||
.working_dir(tor_dir.to_str().unwrap())
|
||||
.timeout(20)
|
||||
.completion_percent(100)
|
||||
.launch()
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
Ok(Tor {
|
||||
process: Some(tor),
|
||||
service: None,
|
||||
client: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check version of the listening wallet
|
||||
pub fn check_other_version(&mut self, url: &str) -> Result<SlateVersion, Error> {
|
||||
self.launch_tor()?;
|
||||
let req = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "check_version",
|
||||
@@ -141,7 +124,7 @@ impl HttpSlateSender {
|
||||
"params": []
|
||||
});
|
||||
|
||||
let res: String = self.post(url, None, req).map_err(|e| {
|
||||
let res: String = self.post(url, req).map_err(|e| {
|
||||
let mut report = format!("Performing version check (is recipient listening?): {}", e);
|
||||
let err_string = format!("{}", e);
|
||||
if err_string.contains("404") {
|
||||
@@ -154,7 +137,8 @@ impl HttpSlateSender {
|
||||
Error::ClientCallback(report)
|
||||
})?;
|
||||
|
||||
let res: Value = serde_json::from_str(&res).unwrap();
|
||||
let res: Value = serde_json::from_str(&res)
|
||||
.map_err(|_| Error::ClientCallback(format!("Can not parse response {:?}", res)))?;
|
||||
trace!("Response: {}", res);
|
||||
if res["error"] != json!(null) {
|
||||
let report = format!(
|
||||
@@ -168,9 +152,11 @@ impl HttpSlateSender {
|
||||
let resp_value = res["result"]["Ok"].clone();
|
||||
trace!("resp_value: {}", resp_value.clone());
|
||||
let foreign_api_version: u16 =
|
||||
serde_json::from_value(resp_value["foreign_api_version"].clone()).unwrap();
|
||||
serde_json::from_value(resp_value["foreign_api_version"].clone())
|
||||
.map_err(|_| Error::ClientCallback(format!("Can not parse response {:?}", res)))?;
|
||||
let supported_slate_versions: Vec<String> =
|
||||
serde_json::from_value(resp_value["supported_slate_versions"].clone()).unwrap();
|
||||
serde_json::from_value(resp_value["supported_slate_versions"].clone())
|
||||
.map_err(|_| Error::ClientCallback(format!("Can not parse response {:?}", res)))?;
|
||||
|
||||
// trivial tests for now, but will be expanded later
|
||||
if foreign_api_version < 2 {
|
||||
@@ -188,32 +174,33 @@ impl HttpSlateSender {
|
||||
Err(Error::ClientCallback(report))
|
||||
}
|
||||
|
||||
fn post<IN>(
|
||||
&self,
|
||||
url: &str,
|
||||
api_secret: Option<String>,
|
||||
input: IN,
|
||||
) -> Result<String, ClientError>
|
||||
fn post<IN>(&self, url: &str, input: IN) -> Result<String, ClientError>
|
||||
where
|
||||
IN: Serialize,
|
||||
{
|
||||
let client = if !self.use_socks {
|
||||
Client::new()
|
||||
let res = if self.tor.process.is_some() {
|
||||
let socks_proxy_addr =
|
||||
SocketAddr::V4(self.config.socks_proxy_addr.parse().map_err(|_| {
|
||||
ClientError::Internal("Socks proxy address is not set".to_string())
|
||||
})?);
|
||||
let client = Client::with_proxy(socks_proxy_addr, "socks5h://")
|
||||
.map_err(|_| ClientError::Internal("Unable to create http client".into()))?;
|
||||
let req = client.create_post_request(url, None, &input)?;
|
||||
let res = client.send_request(req)?;
|
||||
res
|
||||
} else {
|
||||
Client::with_proxy(
|
||||
self.socks_proxy_addr
|
||||
.ok_or_else(|| ClientError::Internal("No socks proxy address set".into()))?,
|
||||
"socks5h://",
|
||||
)
|
||||
}
|
||||
.map_err(|_| ClientError::Internal("Unable to create http client".into()))?;
|
||||
let req = client.create_post_request(url, api_secret, &input)?;
|
||||
let res = client.send_request(req)?;
|
||||
if let Some(client) = &self.tor.client {
|
||||
tor_post(client.clone(), &input, url)
|
||||
.map_err(|e| ClientError::RequestError(format!("{:?}", e)))?
|
||||
} else {
|
||||
return Err(ClientError::Internal("Tor is not configured".to_string()));
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl SlateSender for HttpSlateSender {
|
||||
impl SlateSender for TorSlateSender {
|
||||
fn send_tx(&mut self, slate: &Slate, finalize: bool) -> Result<Slate, Error> {
|
||||
let trailing = match self.base_url.ends_with('/') {
|
||||
true => "",
|
||||
@@ -221,8 +208,6 @@ impl SlateSender for HttpSlateSender {
|
||||
};
|
||||
let url_str = format!("{}{}v2/foreign", self.base_url, trailing);
|
||||
|
||||
self.launch_tor()?;
|
||||
|
||||
let slate_send = match self.check_other_version(&url_str)? {
|
||||
SlateVersion::V4 => VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?,
|
||||
};
|
||||
@@ -250,7 +235,7 @@ impl SlateSender for HttpSlateSender {
|
||||
|
||||
trace!("Sending receive_tx request: {}", req);
|
||||
|
||||
let res: String = self.post(&url_str, None, req).map_err(|e| {
|
||||
let res: String = self.post(&url_str, req).map_err(|e| {
|
||||
let report = format!(
|
||||
"Sending transaction slate to other wallet (is recipient listening?): {}",
|
||||
e
|
||||
@@ -258,7 +243,8 @@ impl SlateSender for HttpSlateSender {
|
||||
Error::ClientCallback(report)
|
||||
})?;
|
||||
|
||||
let res: Value = serde_json::from_str(&res).unwrap();
|
||||
let res: Value = serde_json::from_str(&res)
|
||||
.map_err(|_| Error::ClientCallback(format!("Can not parse response {:?}", res)))?;
|
||||
trace!("Response: {}", res);
|
||||
if res["error"] != json!(null) {
|
||||
let report = format!(
|
||||
@@ -272,22 +258,15 @@ impl SlateSender for HttpSlateSender {
|
||||
let slate_value = res["result"]["Ok"].clone();
|
||||
|
||||
trace!("slate_value: {}", slate_value);
|
||||
let slate = Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap())
|
||||
.map_err(|e| {
|
||||
if let Ok(slate_value) = serde_json::to_string(&slate_value) {
|
||||
let slate = Slate::deserialize_upgrade(&slate_value).map_err(|e| {
|
||||
error!("Error deserializing response slate: {}", e);
|
||||
Error::SlateDeser
|
||||
})?;
|
||||
|
||||
Ok(slate)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
pub struct SchemeNotHttp;
|
||||
|
||||
impl Into<Error> for SchemeNotHttp {
|
||||
fn into(self) -> Error {
|
||||
let err_str = "url scheme must be http".to_string();
|
||||
Error::GenericError(err_str)
|
||||
Ok(slate)
|
||||
} else {
|
||||
error!("Error deserializing response slate: {}", slate_value);
|
||||
Err(Error::SlateDeser)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,23 +21,14 @@ use reqwest::{ClientBuilder, Method, Proxy, RequestBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::{Builder, Handle, Runtime};
|
||||
|
||||
// Global Tokio runtime.
|
||||
// Needs a `Mutex` because `Runtime::block_on` requires mutable access.
|
||||
// Tokio v0.3 requires immutable self, but we are waiting on upstream
|
||||
// updates before we can upgrade.
|
||||
// See: https://github.com/seanmonstar/reqwest/pull/1076
|
||||
lazy_static! {
|
||||
pub static ref RUNTIME: Arc<Mutex<Runtime>> = Arc::new(Mutex::new(
|
||||
Builder::new()
|
||||
.threaded_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
));
|
||||
pub static ref RUNTIME: Arc<Runtime> =
|
||||
Arc::new(Builder::new_multi_thread().enable_all().build().unwrap());
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, thiserror::Error, PartialEq, Debug)]
|
||||
@@ -273,18 +264,11 @@ impl Client {
|
||||
if Handle::try_current().is_ok() {
|
||||
let rt = RUNTIME.clone();
|
||||
let client = self.clone();
|
||||
std::thread::spawn(move || {
|
||||
rt.lock()
|
||||
.unwrap()
|
||||
.block_on(async { client.send_request_async(req).await })
|
||||
})
|
||||
.join()
|
||||
.unwrap()
|
||||
} else {
|
||||
RUNTIME
|
||||
.lock()
|
||||
std::thread::spawn(move || rt.block_on(async { client.send_request_async(req).await }))
|
||||
.join()
|
||||
.unwrap()
|
||||
.block_on(self.send_request_async(req))
|
||||
} else {
|
||||
RUNTIME.block_on(self.send_request_async(req))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -42,8 +42,8 @@ pub mod test_framework;
|
||||
pub mod tor;
|
||||
|
||||
pub use crate::adapters::{
|
||||
HttpSlateSender, PathToSlate, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver,
|
||||
SlateSender,
|
||||
PathToSlate, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender,
|
||||
TorSlateSender,
|
||||
};
|
||||
pub use crate::error::Error;
|
||||
pub use crate::lifecycle::DefaultLCProvider;
|
||||
|
||||
@@ -272,7 +272,7 @@ impl NodeClient for HTTPNodeClient {
|
||||
|
||||
let rt = RUNTIME.clone();
|
||||
let res: Result<Vec<_>, _> =
|
||||
std::thread::spawn(move || rt.lock().unwrap().block_on(async move { task.await }))
|
||||
std::thread::spawn(move || rt.block_on(async move { task.await }))
|
||||
.join()
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
// Copyright 2026 The Grin Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::tor::{ArtiRuntimeWrapper, Tor};
|
||||
use arti_client::config::pt::TransportConfigBuilder;
|
||||
use arti_client::config::{BridgeConfigBuilder, TorClientConfigBuilder};
|
||||
use arti_client::{TorClient, TorClientConfig};
|
||||
use arti_ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use bytes::Bytes;
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use fs_mistrust::Mistrust;
|
||||
use grin_util::secp::SecretKey;
|
||||
use grin_wallet_config::TorConfig;
|
||||
use grin_wallet_libwallet::Error;
|
||||
use grin_wallet_util::OnionV3Address;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{Request, Uri};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Serialize;
|
||||
use sha2::Sha512;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use tor_hscrypto::pk::{HsIdKey, HsIdKeypair};
|
||||
use tor_hsrproxy::config::{
|
||||
Encapsulation, ProxyAction, ProxyConfigBuilder, ProxyPattern, ProxyRule, TargetAddr,
|
||||
};
|
||||
use tor_hsrproxy::OnionServiceReverseProxy;
|
||||
use tor_hsservice::config::OnionServiceConfigBuilder;
|
||||
use tor_hsservice::{HsIdKeypairSpecifier, HsIdPublicKeySpecifier, HsNickname};
|
||||
use tor_keymgr::config::CfgPath;
|
||||
use tor_keymgr::{ArtiNativeKeystore, KeyMgrBuilder, KeystoreSelector};
|
||||
use tor_llcrypto::pk::ed25519::ExpandedKeypair;
|
||||
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
|
||||
use tor_rtcompat::{SleepProviderExt, ToplevelBlockOn};
|
||||
|
||||
// Arti Tokio runtime.
|
||||
lazy_static! {
|
||||
pub static ref ARTI_RUNTIME: LazyLock<Mutex<Option<ArtiRuntimeWrapper>>> =
|
||||
LazyLock::new(|| Mutex::new(ArtiRuntimeWrapper::create().ok()));
|
||||
}
|
||||
|
||||
/// Get Tor client runtime.
|
||||
fn runtime() -> Result<TokioNativeTlsRuntime, Error> {
|
||||
let mut runtime = ARTI_RUNTIME.lock().unwrap();
|
||||
let r = match runtime.as_ref() {
|
||||
None => runtime.insert(ArtiRuntimeWrapper::create()?),
|
||||
Some(r) => r
|
||||
};
|
||||
Ok(r.runtime.clone())
|
||||
}
|
||||
|
||||
/// Start Tor service from provided key.
|
||||
pub fn start_tor_service(
|
||||
key: SecretKey,
|
||||
tor_dir: &str,
|
||||
addr: &str,
|
||||
config: TorConfig,
|
||||
) -> Result<Tor, Error> {
|
||||
info!("Starting integrated Tor listener.");
|
||||
let use_proxy = config.proxy.transport.is_some() && config.proxy.address.is_some();
|
||||
if use_proxy {
|
||||
info!("Proxy configuration will be ignored.");
|
||||
}
|
||||
|
||||
let state_path = Path::new(&tor_dir).join("state");
|
||||
let cache_path = Path::new(&tor_dir).join("cache");
|
||||
let (client, config) = init_client(&state_path, &cache_path, config)?;
|
||||
|
||||
// Add service key to keystore.
|
||||
let onion_address =
|
||||
OnionV3Address::from_private(&key.0).map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let hs = HsNickname::new(onion_address.to_string())
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let keystore_path = Path::new(&state_path).join("keystore");
|
||||
let _ = add_service_key(config.fs_mistrust(), &key, &hs, keystore_path)?;
|
||||
|
||||
// Launch Onion service.
|
||||
let service_config = OnionServiceConfigBuilder::default()
|
||||
.nickname(hs.clone())
|
||||
.build()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
let running_onion_service = match client.launch_onion_service(service_config) {
|
||||
Ok(res) => {
|
||||
if let Some((service, mut request)) = res {
|
||||
let addr: SocketAddr = addr
|
||||
.parse()
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
let c = client.clone();
|
||||
thread::spawn(move || {
|
||||
c.clone().runtime().block_on(async move {
|
||||
// Launch service proxy.
|
||||
async fn run_proxy<S> (c: Arc<TorClient<TokioNativeTlsRuntime>>,
|
||||
addr: SocketAddr,
|
||||
request: &mut S,
|
||||
hs: HsNickname)
|
||||
where
|
||||
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
|
||||
{
|
||||
match run_service_proxy(c.clone(), addr, request, hs.clone()).await {
|
||||
Ok(_) => {
|
||||
info!("Tor proxy stopped");
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Tor proxy error: {:?}, restarting", e);
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
Box::pin(run_proxy(c, addr, request, hs)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
run_proxy(c.clone(), addr, &mut request, hs.clone()).await;
|
||||
})
|
||||
});
|
||||
service
|
||||
} else {
|
||||
return Err(Error::TorProcess("Can not launch onion service".to_owned()));
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(Error::TorProcess(format!("{:?}", e))),
|
||||
};
|
||||
Ok(Tor {
|
||||
process: None,
|
||||
service: Some(running_onion_service),
|
||||
client: Some(client),
|
||||
})
|
||||
}
|
||||
|
||||
/// Start Tor client to send requests.
|
||||
pub fn start_tor_client(tor_dir: &str, config: TorConfig) -> Result<Tor, Error> {
|
||||
let state_path = Path::new(tor_dir).join("state");
|
||||
let cache_path = Path::new(tor_dir).join("cache");
|
||||
|
||||
let (client, _) = init_client(&state_path, &cache_path, config)?;
|
||||
Ok(Tor {
|
||||
process: None,
|
||||
service: None,
|
||||
client: Some(client),
|
||||
})
|
||||
}
|
||||
|
||||
/// Tor request timeout in milliseconds.
|
||||
const REQUEST_TIMEOUT_MS: u64 = 60000;
|
||||
|
||||
/// Make POST request with provided client.
|
||||
pub fn tor_post<IN>(
|
||||
client: Arc<TorClient<TokioNativeTlsRuntime>>,
|
||||
input: &IN,
|
||||
url: &str,
|
||||
) -> Result<String, Error>
|
||||
where
|
||||
IN: Serialize,
|
||||
{
|
||||
let json = serde_json::to_string(input)
|
||||
.map_err(|_| Error::GenericError("Could not serialize data to JSON".to_owned()))?;
|
||||
let url = url.to_string();
|
||||
let url: Uri = url
|
||||
.parse()
|
||||
.map_err(|_| Error::GenericError(format!("Bad URL: {}", url)))?;
|
||||
let host = match url.host() {
|
||||
None => return Err(Error::GenericError(format!("URL {} has bad host", url))),
|
||||
Some(h) => h
|
||||
}.to_string();
|
||||
let res: Result<String, Error> = thread::spawn(move || {
|
||||
let c = client.clone();
|
||||
client.runtime().block_on(async move {
|
||||
let res = c
|
||||
.runtime()
|
||||
.timeout(Duration::from_millis(REQUEST_TIMEOUT_MS), async {
|
||||
let stream = c
|
||||
.connect((host, url.port_u16().unwrap_or(80)))
|
||||
.await
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(TokioIo::new(stream))
|
||||
.await
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
|
||||
// Spawn a task to poll the connection and drive the HTTP state.
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
error!("Tor connection error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let resp = request_sender
|
||||
.send_request(
|
||||
Request::builder()
|
||||
.uri(url)
|
||||
.method("POST")
|
||||
.body::<Full<Bytes>>(Full::from(json))
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
|
||||
let body_resp = resp
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
let body = body_resp.to_bytes().into();
|
||||
let body_text = String::from_utf8(body)
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
Ok(body_text)
|
||||
})
|
||||
.await;
|
||||
match res {
|
||||
Err(e) => Err(Error::TorProcess(format!("{:?}", e))),
|
||||
Ok(body) => Ok(body),
|
||||
}
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap_or_else(|e| return Err(Error::TorProcess(format!("{:?}", e))))?;
|
||||
res
|
||||
}
|
||||
|
||||
/// Client Bootstrap timeout in milliseconds.
|
||||
const BOOTSTRAP_TIMEOUT_MS: u64 = 60000;
|
||||
|
||||
/// Create Tor client.
|
||||
fn init_client(
|
||||
state_path: &PathBuf,
|
||||
cache_path: &PathBuf,
|
||||
config: TorConfig,
|
||||
) -> Result<(Arc<TorClient<TokioNativeTlsRuntime>>, TorClientConfig), Error> {
|
||||
let mut builder = TorClientConfigBuilder::from_directories(&state_path, cache_path);
|
||||
builder.address_filter().allow_onion_addrs(true);
|
||||
|
||||
// Configure bridge.
|
||||
if let Some(bridge_line) = config.bridge.bridge_line {
|
||||
let bridge: BridgeConfigBuilder = bridge_line
|
||||
.parse()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
builder.bridges().bridges().push(bridge.clone());
|
||||
match bridge.get_transport() {
|
||||
None => {
|
||||
return Err(Error::TorConfig(format!(
|
||||
"No transport found at {}",
|
||||
bridge_line
|
||||
)))
|
||||
}
|
||||
Some(t) => {
|
||||
// Now configure bridge transport. (Requires the "pt-client" feature)
|
||||
let bin_path = config.bridge.bridge_bin_path.unwrap_or(t.to_owned());
|
||||
let mut transport = TransportConfigBuilder::default();
|
||||
transport
|
||||
.protocols(vec![t
|
||||
.parse()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?])
|
||||
.path(CfgPath::new(bin_path))
|
||||
.run_on_startup(true);
|
||||
builder.bridges().transports().push(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
let config = builder
|
||||
.build()
|
||||
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
|
||||
|
||||
// Launch client.
|
||||
let r = runtime()?;
|
||||
let client = TorClient::with_runtime(r)
|
||||
.config(config.clone())
|
||||
.create_unbootstrapped()
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))?;
|
||||
let c = client.clone();
|
||||
let res = client.runtime().block_on(async move {
|
||||
let bootstrap = async || {
|
||||
return match c.bootstrap().await {
|
||||
Ok(_) => {
|
||||
let mut percent = 0.0;
|
||||
let mut prev_percent = 0.0;
|
||||
while percent < 1.0 {
|
||||
percent = c.bootstrap_status().as_frac();
|
||||
if percent != prev_percent {
|
||||
info!("Starting Tor {}%", percent * 100.0);
|
||||
}
|
||||
prev_percent = percent;
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
};
|
||||
match c
|
||||
.runtime()
|
||||
.timeout(Duration::from_millis(BOOTSTRAP_TIMEOUT_MS), bootstrap())
|
||||
.await
|
||||
{
|
||||
Ok(r) => {
|
||||
match r {
|
||||
Err(e) => Err(Error::TorProcess(format!("{:?}", e))),
|
||||
Ok(_) => Ok(())
|
||||
}
|
||||
},
|
||||
Err(e) => Err(Error::TorProcess(format!("{:?}", e))),
|
||||
}
|
||||
});
|
||||
match res {
|
||||
Ok(_) => {
|
||||
info!("Tor client bootstrapped successfully");
|
||||
Ok((client, config))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch Onion service proxy.
|
||||
async fn run_service_proxy<S>(
|
||||
client: Arc<TorClient<TokioNativeTlsRuntime>>,
|
||||
addr: SocketAddr,
|
||||
request: &mut S,
|
||||
nickname: HsNickname,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
S: futures::Stream<Item = tor_hsservice::RendRequest> + Unpin + Send + 'static,
|
||||
{
|
||||
let runtime = client.runtime().clone();
|
||||
|
||||
// Setup proxy to forward request from Tor address to local address.
|
||||
let proxy_rule = ProxyRule::new(
|
||||
ProxyPattern::one_port(80).map_err(|e| Error::TorConfig(format!("{}", e)))?,
|
||||
ProxyAction::Forward(Encapsulation::Simple, TargetAddr::Inet(addr)),
|
||||
);
|
||||
let mut proxy_cfg_builder = ProxyConfigBuilder::default();
|
||||
proxy_cfg_builder.set_proxy_ports(vec![proxy_rule]);
|
||||
let proxy_cfg = proxy_cfg_builder
|
||||
.build()
|
||||
.map_err(|e| Error::TorConfig(format!("{}", e)))?;
|
||||
let proxy = OnionServiceReverseProxy::new(proxy_cfg);
|
||||
|
||||
// Start proxy for launched service.
|
||||
proxy
|
||||
.handle_requests(runtime, nickname, request)
|
||||
.await
|
||||
.map_err(|e| Error::TorProcess(format!("{:?}", e)))
|
||||
}
|
||||
|
||||
/// Save Onion service key to keystore.
|
||||
fn add_service_key(
|
||||
mistrust: &Mistrust,
|
||||
key: &SecretKey,
|
||||
hs_nickname: &HsNickname,
|
||||
path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
let arti_store = ArtiNativeKeystore::from_path_and_mistrust(path, mistrust)
|
||||
.map_err(|e| Error::TorProcess(format!("{}", e)))?;
|
||||
|
||||
let key_manager = KeyMgrBuilder::default()
|
||||
.primary_store(Box::new(arti_store))
|
||||
.build()
|
||||
.map_err(|e| Error::TorProcess(format!("{}", e)))?;
|
||||
|
||||
let expanded_sk =
|
||||
ExpandedSecretKey::from_bytes(Sha512::default().chain_update(key).finalize().as_ref());
|
||||
|
||||
let mut sk_bytes = [0_u8; 64];
|
||||
sk_bytes[0..32].copy_from_slice(&expanded_sk.scalar.to_bytes());
|
||||
sk_bytes[32..64].copy_from_slice(&expanded_sk.hash_prefix);
|
||||
match ExpandedKeypair::from_secret_key_bytes(sk_bytes) {
|
||||
None => {
|
||||
return Err(Error::TorProcess(
|
||||
"Hidden service key can not be created".into(),
|
||||
))
|
||||
}
|
||||
Some(expanded_kp) => {
|
||||
key_manager
|
||||
.insert(
|
||||
HsIdKey::from(expanded_kp.public().clone()),
|
||||
&HsIdPublicKeySpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Primary,
|
||||
true,
|
||||
)
|
||||
.map_err(|e| Error::TorProcess(format!("{}", e)))?;
|
||||
key_manager
|
||||
.insert(
|
||||
HsIdKeypair::from(expanded_kp),
|
||||
&HsIdKeypairSpecifier::new(hs_nickname.clone()),
|
||||
KeystoreSelector::Primary,
|
||||
true,
|
||||
)
|
||||
.map_err(|e| Error::TorProcess(format!("{}", e)))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -12,7 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod arti;
|
||||
pub mod bridge;
|
||||
pub mod config;
|
||||
pub mod process;
|
||||
pub mod proxy;
|
||||
|
||||
/// Running Tor instance control.
|
||||
pub struct Tor {
|
||||
/// External to process control.
|
||||
pub process: Option<process::TorProcess>,
|
||||
/// Integrated service.
|
||||
pub service: Option<std::sync::Arc<tor_hsservice::RunningOnionService>>,
|
||||
/// Integrated client.
|
||||
pub client:
|
||||
Option<std::sync::Arc<arti_client::TorClient<tor_rtcompat::tokio::TokioNativeTlsRuntime>>>,
|
||||
}
|
||||
|
||||
/// Arti client runtime wrapper.
|
||||
#[derive(Clone)]
|
||||
pub struct ArtiRuntimeWrapper {
|
||||
pub runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime,
|
||||
}
|
||||
|
||||
impl ArtiRuntimeWrapper {
|
||||
fn create() -> Result<ArtiRuntimeWrapper, std::io::Error> {
|
||||
Ok(Self {
|
||||
runtime: tor_rtcompat::tokio::TokioNativeTlsRuntime::create()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ pub struct InitTxSendArgs {
|
||||
/// Whether to use dandelion when posting. If false, skip the dandelion relay
|
||||
pub fluff: bool,
|
||||
/// If set, skip the Slatepack TOR send attempt
|
||||
pub skip_tor: bool,
|
||||
pub skip_tor: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for InitTxArgs {
|
||||
|
||||
+71
-53
@@ -309,7 +309,7 @@ pub fn parse_global_args(
|
||||
let key = match config.tls_certificate_key.clone() {
|
||||
Some(k) => k,
|
||||
None => {
|
||||
let msg = format!("Private key for certificate is not set");
|
||||
let msg = "Private key for certificate is not set".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
};
|
||||
@@ -319,11 +319,11 @@ pub fn parse_global_args(
|
||||
|
||||
Ok(command::GlobalArgs {
|
||||
account: account.to_owned(),
|
||||
show_spent: show_spent,
|
||||
api_secret: api_secret,
|
||||
node_api_secret: node_api_secret,
|
||||
password: password,
|
||||
tls_conf: tls_conf,
|
||||
show_spent,
|
||||
api_secret,
|
||||
node_api_secret,
|
||||
password,
|
||||
tls_conf,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ pub fn parse_scan_rewind_hash_args(
|
||||
let start_height = parse_u64_or_none(args.value_of("start_height"));
|
||||
let backwards_from_tip = parse_u64_or_none(args.value_of("backwards_from_tip"));
|
||||
if backwards_from_tip.is_some() && start_height.is_some() {
|
||||
let msg = format!("backwards_from tip and start_height cannot both be present");
|
||||
let msg = "backwards_from tip and start_height cannot both be present".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::ViewWalletScanArgs {
|
||||
@@ -430,7 +430,7 @@ pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountA
|
||||
None => None,
|
||||
Some(s) => Some(s.to_owned()),
|
||||
};
|
||||
Ok(command::AccountArgs { create: create })
|
||||
Ok(command::AccountArgs { create })
|
||||
}
|
||||
|
||||
pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseError> {
|
||||
@@ -466,10 +466,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||
let late_lock = args.is_present("late_lock");
|
||||
|
||||
// dest
|
||||
let dest = match args.value_of("dest") {
|
||||
Some(d) => d,
|
||||
None => "default",
|
||||
};
|
||||
let dest = args.value_of("dest").unwrap_or_else(|| "default");
|
||||
|
||||
// change_outputs
|
||||
let change_outputs = parse_required(args, "change_outputs")?;
|
||||
@@ -512,6 +509,12 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let skip_tor = if args.is_present("manual") {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bridge = match args.value_of("bridge") {
|
||||
Some(b) => Some(b.to_string()),
|
||||
None => None,
|
||||
@@ -520,24 +523,24 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseErro
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
|
||||
Ok(command::SendArgs {
|
||||
amount: amount,
|
||||
amount_includes_fee: amount_includes_fee,
|
||||
amount,
|
||||
amount_includes_fee,
|
||||
use_max_amount: spend_max,
|
||||
minimum_confirmations: min_c,
|
||||
selection_strategy: selection_strategy.to_owned(),
|
||||
estimate_selection_strategies,
|
||||
late_lock,
|
||||
dest: dest.to_owned(),
|
||||
change_outputs: change_outputs,
|
||||
fluff: fluff,
|
||||
max_outputs: max_outputs,
|
||||
change_outputs,
|
||||
fluff,
|
||||
max_outputs,
|
||||
payment_proof_address,
|
||||
ttl_blocks,
|
||||
target_slate_version: target_slate_version,
|
||||
target_slate_version,
|
||||
outfile,
|
||||
skip_tor: args.is_present("manual"),
|
||||
bridge: bridge,
|
||||
slatepack_qr: slatepack_qr,
|
||||
skip_tor,
|
||||
bridge,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -563,6 +566,12 @@ pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Par
|
||||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let skip_tor = if args.is_present("manual") {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
@@ -570,10 +579,10 @@ pub fn parse_receive_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Par
|
||||
Ok(command::ReceiveArgs {
|
||||
input_file,
|
||||
input_slatepack_message,
|
||||
skip_tor: args.is_present("manual"),
|
||||
skip_tor,
|
||||
outfile,
|
||||
bridge,
|
||||
slatepack_qr: slatepack_qr,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -599,6 +608,12 @@ pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Pars
|
||||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let skip_tor = if args.is_present("manual") {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
@@ -606,10 +621,10 @@ pub fn parse_unpack_args(args: &ArgMatches) -> Result<command::ReceiveArgs, Pars
|
||||
Ok(command::ReceiveArgs {
|
||||
input_file,
|
||||
input_slatepack_message,
|
||||
skip_tor: args.is_present("manual"),
|
||||
skip_tor,
|
||||
outfile,
|
||||
bridge,
|
||||
slatepack_qr: slatepack_qr,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -642,10 +657,10 @@ pub fn parse_finalize_args(args: &ArgMatches) -> Result<command::FinalizeArgs, P
|
||||
Ok(command::FinalizeArgs {
|
||||
input_file,
|
||||
input_slatepack_message,
|
||||
fluff: fluff,
|
||||
nopost: nopost,
|
||||
fluff,
|
||||
nopost,
|
||||
outfile,
|
||||
slatepack_qr: slatepack_qr,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -677,10 +692,7 @@ pub fn parse_issue_invoice_args(
|
||||
};
|
||||
|
||||
// dest, for encryption
|
||||
let dest = match args.value_of("dest") {
|
||||
Some(d) => d,
|
||||
None => "default",
|
||||
};
|
||||
let dest = args.value_of("dest").unwrap_or_else(|| "default");
|
||||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
@@ -694,7 +706,7 @@ pub fn parse_issue_invoice_args(
|
||||
target_slate_version,
|
||||
},
|
||||
outfile,
|
||||
slatepack_qr: slatepack_qr,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -770,6 +782,12 @@ pub fn parse_process_invoice_args(
|
||||
|
||||
let outfile = parse_optional(args, "outfile")?;
|
||||
|
||||
let skip_tor = if args.is_present("manual") {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bridge = parse_optional(args, "bridge")?;
|
||||
|
||||
let slatepack_qr = args.is_present("slatepack_qr");
|
||||
@@ -782,10 +800,10 @@ pub fn parse_process_invoice_args(
|
||||
slate,
|
||||
max_outputs,
|
||||
ttl_blocks,
|
||||
skip_tor: args.is_present("manual"),
|
||||
skip_tor,
|
||||
outfile,
|
||||
bridge,
|
||||
slatepack_qr: slatepack_qr,
|
||||
slatepack_qr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -803,7 +821,7 @@ pub fn parse_check_args(args: &ArgMatches) -> Result<command::CheckArgs, ParseEr
|
||||
let start_height = parse_u64_or_none(args.value_of("start_height"));
|
||||
let backwards_from_tip = parse_u64_or_none(args.value_of("backwards_from_tip"));
|
||||
if backwards_from_tip.is_some() && start_height.is_some() {
|
||||
let msg = format!("backwards_from tip and start_height cannot both be present");
|
||||
let msg = "backwards_from tip and start_height cannot both be present".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::CheckArgs {
|
||||
@@ -829,7 +847,7 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError>
|
||||
},
|
||||
};
|
||||
if tx_id.is_some() && tx_slate_id.is_some() {
|
||||
let msg = format!("At most one of 'id' (-i) or 'txid' (-t) may be provided.");
|
||||
let msg = "At most one of 'id' (-i) or 'txid' (-t) may be provided.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
let count = match args.value_of("count") {
|
||||
@@ -838,8 +856,8 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError>
|
||||
};
|
||||
Ok(command::TxsArgs {
|
||||
id: tx_id,
|
||||
tx_slate_id: tx_slate_id,
|
||||
count: count,
|
||||
tx_slate_id,
|
||||
count,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -886,8 +904,8 @@ pub fn parse_repost_args(args: &ArgMatches) -> Result<command::RepostArgs, Parse
|
||||
|
||||
Ok(command::RepostArgs {
|
||||
id: tx_id.unwrap(),
|
||||
dump_file: dump_file,
|
||||
fluff: fluff,
|
||||
dump_file,
|
||||
fluff,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -911,12 +929,12 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Parse
|
||||
},
|
||||
};
|
||||
if (tx_id.is_none() && tx_slate_id.is_none()) || (tx_id.is_some() && tx_slate_id.is_some()) {
|
||||
let msg = format!("'id' (-i) or 'txid' (-t) argument is required.");
|
||||
let msg = "'id' (-i) or 'txid' (-t) argument is required.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::CancelArgs {
|
||||
tx_id: tx_id,
|
||||
tx_slate_id: tx_slate_id,
|
||||
tx_id,
|
||||
tx_slate_id,
|
||||
tx_id_string: tx_id_string.to_owned(),
|
||||
})
|
||||
}
|
||||
@@ -938,17 +956,17 @@ pub fn parse_export_proof_args(args: &ArgMatches) -> Result<command::ProofExport
|
||||
},
|
||||
};
|
||||
if tx_id.is_some() && tx_slate_id.is_some() {
|
||||
let msg = format!("At most one of 'id' (-i) or 'txid' (-t) may be provided.");
|
||||
let msg = "At most one of 'id' (-i) or 'txid' (-t) may be provided.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
if tx_id.is_none() && tx_slate_id.is_none() {
|
||||
let msg = format!("Either 'id' (-i) or 'txid' (-t) must be provided.");
|
||||
let msg = "Either 'id' (-i) or 'txid' (-t) must be provided.".to_string();
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::ProofExportArgs {
|
||||
output_file: output_file.to_owned(),
|
||||
id: tx_id,
|
||||
tx_slate_id: tx_slate_id,
|
||||
tx_slate_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1026,12 +1044,12 @@ where
|
||||
|
||||
{
|
||||
let mut wallet_lock = wallet.lock();
|
||||
let lc = wallet_lock.lc_provider().unwrap();
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
|
||||
}
|
||||
|
||||
// provide wallet instance back to the caller (handy for testing with
|
||||
// local wallet proxy, etc)
|
||||
// local wallet proxy, etc.)
|
||||
wallet_inst_cb(wallet.clone());
|
||||
|
||||
// don't open wallet for certain lifecycle commands
|
||||
@@ -1043,7 +1061,7 @@ where
|
||||
("owner_api", _) => {
|
||||
// If wallet exists and password is present then open it. Otherwise, that's fine too.
|
||||
let mut wallet_lock = wallet.lock();
|
||||
let lc = wallet_lock.lc_provider().unwrap();
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
open_wallet = wallet_args.is_present("pass") && lc.wallet_exists(None)?;
|
||||
}
|
||||
_ => {}
|
||||
@@ -1052,7 +1070,7 @@ where
|
||||
let keychain_mask = match open_wallet {
|
||||
true => {
|
||||
let mut wallet_lock = wallet.lock();
|
||||
let lc = wallet_lock.lc_provider().unwrap();
|
||||
let lc = wallet_lock.lc_provider()?;
|
||||
let mask = lc.open_wallet(
|
||||
None,
|
||||
prompt_password(&global_wallet_args.password),
|
||||
@@ -1290,8 +1308,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
let msg = format!("Unknown wallet command, use 'grin-wallet help' for details");
|
||||
return Err(Error::ArgumentError(msg));
|
||||
let msg = "Unknown wallet command, use 'grin-wallet help' for details".to_string();
|
||||
Err(Error::ArgumentError(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user