diff --git a/Cargo.lock b/Cargo.lock index bd5667d0..7c7da167 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,19 @@ dependencies = [ "syn", ] +[[package]] +name = "async-compression" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72c1f1154e234325b50864a349b9c8e56939e266a4c307c0f159812df2f9537" +dependencies = [ + "bzip2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-io" version = "1.3.1" @@ -479,6 +492,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "bzip2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf8012c8a15d5df745fcf258d93e6149dcf102882c8d8702d9cff778eab43a8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.10+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17fa3d1ac1ca21c5c4e36a97f3c3eb25084576f6fc47bf0139c1123434216c6c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cache-padded" version = "1.1.1" @@ -888,12 +922,12 @@ checksum = "21453800c95bb1aaa57490458c42d60c6277cb8a3e386030ec2381d5c2d4fa77" dependencies = [ "bitcoin", "log", - "rustls", + "rustls 0.16.0", "serde", "serde_json", "socks", "webpki", - "webpki-roots", + "webpki-roots 0.19.0", ] [[package]] @@ -962,6 +996,18 @@ dependencies = [ "instant", ] +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.4", + "winapi 0.3.9", +] + [[package]] name = "fixed-hash" version = "0.2.5" @@ -1409,6 +1455,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "futures-util", + "hyper", + "log", + "rustls 0.19.0", + "tokio", + "tokio-rustls", + "webpki", +] + [[package]] name = "idna" version = "0.2.0" @@ -2851,6 +2912,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "ipnet", "js-sys", "lazy_static", @@ -2858,14 +2920,17 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", + "rustls 0.19.0", "serde", "serde_json", "serde_urlencoded", "tokio", + "tokio-rustls", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.21.0", "winreg", ] @@ -2941,6 +3006,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "rw-stream-sink" version = "0.2.1" @@ -3193,6 +3271,15 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "1.3.0" @@ -3431,6 +3518,7 @@ name = "swap" version = "0.1.0" dependencies = [ "anyhow", + "async-compression", "async-recursion", "async-trait", "atty", @@ -3475,7 +3563,9 @@ dependencies = [ "thiserror", "time", "tokio", + "tokio-tar", "tokio-tungstenite", + "tokio-util", "toml", "tracing", "tracing-futures", @@ -3687,8 +3777,12 @@ dependencies = [ "memchr", "mio", "num_cpus", + "once_cell", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "tokio-macros", + "winapi 0.3.9", ] [[package]] @@ -3712,6 +3806,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.0", + "tokio", + "webpki", +] + [[package]] name = "tokio-stream" version = "0.1.2" @@ -3723,6 +3828,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tar" +version = "0.2.0" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.2.4", + "tempfile", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-tungstenite" version = "0.13.0" @@ -3740,9 +3859,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ae4751faa60b9f96dd8344d74592e5a17c0c9a220413dbc6942d14139bbfcc" +checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" dependencies = [ "bytes", "futures-core", @@ -3750,7 +3869,6 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-stream", ] [[package]] @@ -4164,6 +4282,15 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +dependencies = [ + "webpki", +] + [[package]] name = "wepoll-sys" version = "3.0.1" @@ -4256,6 +4383,15 @@ dependencies = [ "zeroize 1.2.0", ] +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + [[package]] name = "yamux" version = "0.8.0" diff --git a/swap/Cargo.toml b/swap/Cargo.toml index d9d15118..73f926d3 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -13,6 +13,7 @@ name = "swap" [dependencies] anyhow = "1" +async-compression = { version = "0.3", features = ["bzip2", "tokio"] } async-recursion = "0.3.1" async-trait = "0.1" atty = "0.2" @@ -36,7 +37,7 @@ pem = "0.8" prettytable-rs = "0.8" rand = "0.7" rand_chacha = "0.2.0" -reqwest = { version = "0.11", default-features = false } +reqwest = { version = "0.11", features = ["rustls-tls", "stream"], default-features = false } rust_decimal = "1.10" serde = { version = "1", features = ["derive"] } serde_cbor = "0.11" @@ -48,8 +49,10 @@ structopt = "0.3" strum = { version = "0.20", features = ["derive"] } thiserror = "1" time = "0.2" -tokio = { version = "1.0", features = ["rt-multi-thread", "time", "macros", "sync"] } +tokio = { version = "1.0", features = ["rt-multi-thread", "time", "macros", "sync", "process", "fs"] } +tokio-tar = { path = "../tokio-tar" } tokio-tungstenite = { version = "0.13", features = [ "tls" ] } +tokio-util = { version = "0.6.3", features = ["io"] } toml = "0.5" tracing = { version = "0.1", features = ["attributes"] } tracing-futures = { version = "0.2", features = ["std-future", "futures-03"] } diff --git a/swap/src/bin/swap_cli.rs b/swap/src/bin/swap_cli.rs index 4b13e6ba..c4fed8c1 100644 --- a/swap/src/bin/swap_cli.rs +++ b/swap/src/bin/swap_cli.rs @@ -14,6 +14,7 @@ use anyhow::{Context, Result}; use prettytable::{row, Table}; +use reqwest::Url; use std::{path::Path, sync::Arc}; use structopt::StructOpt; use swap::{ @@ -84,6 +85,12 @@ async fn main() -> Result<()> { let monero_network = monero::Network::Stagenet; let execution_params = execution_params::Testnet::get_execution_params(); + let monero_wallet_rpc = monero::WalletRpc::new(config.data.dir.join("monero")).await?; + + let monero_wallet_rpc_process = monero_wallet_rpc + .run(monero_network, "stagenet.community.xmr.to") + .await?; + match opt.cmd { Command::BuyXmr { alice_peer_id, @@ -96,6 +103,7 @@ async fn main() -> Result<()> { &wallet_data_dir, monero_network, seed, + monero_wallet_rpc_process.endpoint(), ) .await?; @@ -149,6 +157,7 @@ async fn main() -> Result<()> { &wallet_data_dir, monero_network, seed, + monero_wallet_rpc_process.endpoint(), ) .await?; @@ -180,6 +189,7 @@ async fn main() -> Result<()> { &wallet_data_dir, monero_network, seed, + monero_wallet_rpc_process.endpoint(), ) .await?; @@ -230,6 +240,7 @@ async fn main() -> Result<()> { &wallet_data_dir, monero_network, seed, + monero_wallet_rpc_process.endpoint(), ) .await?; @@ -268,6 +279,7 @@ async fn init_wallets( bitcoin_wallet_data_dir: &Path, monero_network: monero::Network, seed: Seed, + monero_wallet_rpc_url: Url, ) -> Result<(bitcoin::Wallet, monero::Wallet)> { let bitcoin_wallet = bitcoin::Wallet::new( config.bitcoin.electrum_rpc_url, @@ -290,7 +302,7 @@ async fn init_wallets( ); let monero_wallet = monero::Wallet::new( - config.monero.wallet_rpc_url.clone(), + monero_wallet_rpc_url.clone(), monero_network, MONERO_BLOCKCHAIN_MONITORING_WALLET_NAME.to_string(), ); @@ -306,7 +318,7 @@ async fn init_wallets( .context(format!( "Unable to create Monero wallet for blockchain monitoring.\ Please ensure that the monero-wallet-rpc is available at {}", - config.monero.wallet_rpc_url + monero_wallet_rpc_url ))?; info!( diff --git a/swap/src/cli/config.rs b/swap/src/cli/config.rs index 2252f181..156fe269 100644 --- a/swap/src/cli/config.rs +++ b/swap/src/cli/config.rs @@ -13,13 +13,11 @@ use url::Url; pub const DEFAULT_ELECTRUM_HTTP_URL: &str = "https://blockstream.info/testnet/api/"; 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"; #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)] pub struct Config { pub data: Data, pub bitcoin: Bitcoin, - pub monero: Monero, } impl Config { @@ -48,12 +46,6 @@ pub struct Bitcoin { pub electrum_rpc_url: Url, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Monero { - pub wallet_rpc_url: Url, -} - #[derive(thiserror::Error, Debug, Clone, Copy)] #[error("config not initialized")] pub struct ConfigNotInitialized {} @@ -118,11 +110,6 @@ pub fn query_user_for_initial_testnet_config() -> Result { .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()) - .interact_text()?; - let monero_wallet_rpc_url = monero_wallet_rpc_url.as_str().parse()?; println!(); Ok(Config { @@ -131,9 +118,6 @@ pub fn query_user_for_initial_testnet_config() -> Result { electrum_http_url, electrum_rpc_url, }, - monero: Monero { - wallet_rpc_url: monero_wallet_rpc_url, - }, }) } @@ -156,9 +140,6 @@ mod tests { electrum_http_url: Url::from_str(DEFAULT_ELECTRUM_HTTP_URL).unwrap(), electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), }, - monero: Monero { - wallet_rpc_url: Url::from_str("http://127.0.0.1:38083/json_rpc").unwrap(), - }, }; initial_setup(config_path.clone(), || Ok(expected.clone())).unwrap(); diff --git a/swap/src/monero.rs b/swap/src/monero.rs index e17e137b..38b091b0 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -1,8 +1,10 @@ pub mod wallet; +mod wallet_rpc; pub use ::monero::{Network, PrivateKey, PublicKey}; pub use curve25519_dalek::scalar::Scalar; pub use wallet::Wallet; +pub use wallet_rpc::{WalletRpc, WalletRpcProcess}; use crate::bitcoin; use ::bitcoin::hashes::core::fmt::Formatter; diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs new file mode 100644 index 00000000..82328210 --- /dev/null +++ b/swap/src/monero/wallet_rpc.rs @@ -0,0 +1,164 @@ +use ::monero::Network; +use anyhow::{Context, Result}; +use async_compression::tokio::bufread::BzDecoder; +use futures::{StreamExt, TryStreamExt}; +use reqwest::Url; +use std::{ + io::ErrorKind, + path::{Path, PathBuf}, + process::Stdio, +}; +use tokio::{ + fs::{remove_file, OpenOptions}, + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + process::{Child, Command}, +}; +use tokio_tar::Archive; +use tokio_util::{ + codec::{BytesCodec, FramedRead}, + io::StreamReader, +}; + +#[cfg(target_os = "macos")] +const DOWNLOAD_URL: &str = "http://downloads.getmonero.org/cli/monero-mac-x64-v0.17.1.9.tar.bz2"; + +#[cfg(target_os = "linux")] +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.1.9.tar.bz2"; + +#[cfg(not(any(target_os = "macos", target_os = "linux")))] +compile_error!("unsupported operating system"); + +const PACKED_FILE: &str = "monero-wallet-rpc"; + +pub struct WalletRpcProcess { + _child: Child, + port: u16, +} + +impl WalletRpcProcess { + pub fn endpoint(&self) -> Url { + Url::parse(&format!("http://127.0.0.1:{}/json_rpc", self.port)) + .expect("Static url template is always valid") + } +} + +pub struct WalletRpc { + working_dir: PathBuf, +} + +impl WalletRpc { + pub async fn new(working_dir: impl AsRef) -> Result { + let working_dir = working_dir.as_ref(); + + if !working_dir.exists() { + tokio::fs::create_dir(working_dir).await?; + } + + let monero_wallet_rpc = WalletRpc { + working_dir: working_dir.to_path_buf(), + }; + + if monero_wallet_rpc.tar_path().exists() { + remove_file(monero_wallet_rpc.tar_path()).await?; + } + + if !monero_wallet_rpc.exec_path().exists() { + let mut options = OpenOptions::new(); + let mut file = options + .read(true) + .write(true) + .create_new(true) + .open(monero_wallet_rpc.tar_path()) + .await?; + + let byte_stream = reqwest::get(DOWNLOAD_URL) + .await? + .bytes_stream() + .map_err(|err| std::io::Error::new(ErrorKind::Other, err)); + + let mut stream = FramedRead::new( + BzDecoder::new(StreamReader::new(byte_stream)), + BytesCodec::new(), + ) + .map_ok(|bytes| bytes.freeze()); + + while let Some(chunk) = stream.next().await { + file.write(&chunk?).await?; + } + + file.flush().await?; + + let mut options = OpenOptions::new(); + let file = options + .read(true) + .open(monero_wallet_rpc.tar_path()) + .await?; + + let mut ar = Archive::new(file); + let mut entries = ar.entries()?; + + while let Some(file) = entries.next().await { + let mut f = file?; + if f.path()? + .to_str() + .context("Could not find convert path to str in tar ball")? + .contains(PACKED_FILE) + { + f.unpack(monero_wallet_rpc.exec_path()).await?; + } + } + + remove_file(monero_wallet_rpc.tar_path()).await?; + } + + Ok(monero_wallet_rpc) + } + pub async fn run(&self, network: Network, daemon_host: &str) -> Result { + let port = tokio::net::TcpListener::bind("127.0.0.1:0") + .await? + .local_addr()? + .port(); + + let mut child = Command::new(self.exec_path()) + .stdout(Stdio::piped()) + .kill_on_drop(true) + .arg(match network { + Network::Mainnet => "--mainnet", + Network::Stagenet => "--stagenet", + Network::Testnet => "--testnet", + }) + .arg("--daemon-host") + .arg(daemon_host) + .arg("--rpc-bind-port") + .arg(format!("{}", port)) + .arg("--disable-rpc-login") + .arg("--wallet-dir") + .arg(self.working_dir.join("monero-data")) + .spawn()?; + + let stdout = child + .stdout + .take() + .expect("monero wallet rpc stdout was not piped parent process"); + + let mut reader = BufReader::new(stdout).lines(); + + while let Some(line) = reader.next_line().await? { + if line.contains("Starting wallet RPC server") { + break; + } + } + Ok(WalletRpcProcess { + _child: child, + port, + }) + } + + fn tar_path(&self) -> PathBuf { + self.working_dir.join("monero-cli-wallet.tar") + } + + fn exec_path(&self) -> PathBuf { + self.working_dir.join(PACKED_FILE) + } +} diff --git a/tokio-tar/src/archive.rs b/tokio-tar/src/archive.rs index 1e4d3b3b..6bccf558 100644 --- a/tokio-tar/src/archive.rs +++ b/tokio-tar/src/archive.rs @@ -98,10 +98,11 @@ impl ArchiveBuilder { self } - /// Ignore zeroed headers, which would otherwise indicate to the archive that it has no more - /// entries. + /// Ignore zeroed headers, which would otherwise indicate to the archive + /// that it has no more entries. /// - /// This can be used in case multiple tar archives have been concatenated together. + /// This can be used in case multiple tar archives have been concatenated + /// together. pub fn set_ignore_zeros(mut self, ignore_zeros: bool) -> Self { self.ignore_zeros = ignore_zeros; self @@ -365,8 +366,8 @@ fn poll_next_raw( } // If a header is not all zeros, we have another valid header. - // Otherwise, check if we are ignoring zeros and continue, or break as if this is the - // end of the archive. + // Otherwise, check if we are ignoring zeros and continue, or break as if this + // is the end of the archive. if !header.as_bytes().iter().all(|i| *i == 0) { *next += 512; break; @@ -559,8 +560,8 @@ impl Read for Archive { /// Try to fill the buffer from the reader. /// -/// If the reader reaches its end before filling the buffer at all, returns `false`. -/// Otherwise returns `true`. +/// If the reader reaches its end before filling the buffer at all, returns +/// `false`. Otherwise returns `true`. fn poll_try_read_all( mut source: R, cx: &mut Context<'_>, diff --git a/tokio-tar/src/builder.rs b/tokio-tar/src/builder.rs index 08c46ba0..15e008b1 100644 --- a/tokio-tar/src/builder.rs +++ b/tokio-tar/src/builder.rs @@ -597,7 +597,8 @@ async fn append_dir_all( while let Some((src, is_dir, is_symlink)) = stack.pop() { let dest = path.join(src.strip_prefix(&src_path).unwrap()); - // In case of a symlink pointing to a directory, is_dir is false, but src.is_dir() will return true + // In case of a symlink pointing to a directory, is_dir is false, but + // src.is_dir() will return true if is_dir || (is_symlink && follow && src.is_dir()) { let mut entries = fs::read_dir(&src).await?; while let Some(entry) = entries.next_entry().await.transpose() { diff --git a/tokio-tar/src/entry.rs b/tokio-tar/src/entry.rs index e239799b..d6cec73d 100644 --- a/tokio-tar/src/entry.rs +++ b/tokio-tar/src/entry.rs @@ -144,8 +144,8 @@ impl Entry { /// Returns the link name for this entry, in bytes, if listed. /// /// Note that this will not always return the same value as - /// `self.header().link_name_bytes()` as some archive formats have support for - /// longer path names described in separate entries. + /// `self.header().link_name_bytes()` as some archive formats have support + /// for longer path names described in separate entries. pub fn link_name_bytes(&self) -> Option> { self.fields.link_name_bytes() } @@ -414,14 +414,12 @@ impl EntryFields { async fn unpack_in(&mut self, dst: &Path) -> io::Result { // Notes regarding bsdtar 2.8.3 / libarchive 2.8.3: - // * Leading '/'s are trimmed. For example, `///test` is treated as - // `test`. - // * If the filename contains '..', then the file is skipped when - // extracting the tarball. - // * '//' within a filename is effectively skipped. An error is - // logged, but otherwise the effect is as if any two or more - // adjacent '/'s within the filename were consolidated into one - // '/'. + // * Leading '/'s are trimmed. For example, `///test` is treated as `test`. + // * If the filename contains '..', then the file is skipped when extracting the + // tarball. + // * '//' within a filename is effectively skipped. An error is logged, but + // otherwise the effect is as if any two or more adjacent '/'s within the + // filename were consolidated into one '/'. // // Most of this is handled by the `path` module of the standard // library, but we specially handle a few cases here as well. diff --git a/tokio-tar/src/header.rs b/tokio-tar/src/header.rs index 71f0deed..34769c6e 100644 --- a/tokio-tar/src/header.rs +++ b/tokio-tar/src/header.rs @@ -777,7 +777,8 @@ impl Header { #[cfg(windows)] fn fill_platform_from(&mut self, meta: &Metadata, mode: HeaderMode) { - // There's no concept of a file mode on Windows, so do a best approximation here. + // There's no concept of a file mode on Windows, so do a best approximation + // here. match mode { HeaderMode::Complete => { self.set_uid(0); @@ -1100,7 +1101,8 @@ impl GnuHeader { truncate(&self.uname) } - /// Gets the fullname (group:user) in a "lossy" way, used for error reporting ONLY. + /// Gets the fullname (group:user) in a "lossy" way, used for error + /// reporting ONLY. fn fullname_lossy(&self) -> String { format!( "{}:{}", diff --git a/tokio-tar/tests/all.rs b/tokio-tar/tests/all.rs index 7fefb10a..46c8841f 100644 --- a/tokio-tar/tests/all.rs +++ b/tokio-tar/tests/all.rs @@ -37,8 +37,8 @@ macro_rules! tar { mod header; -/// test that we can concatenate the simple.tar archive and extract the same entries twice when we -/// use the ignore_zeros option. +/// test that we can concatenate the simple.tar archive and extract the same +/// entries twice when we use the ignore_zeros option. #[tokio::test] async fn simple_concat() { let bytes = tar!("simple.tar"); @@ -52,7 +52,8 @@ async fn simple_concat() { // concat two archives (with null in-between); archive_bytes.extend(bytes); - // test now that when we read the archive, it stops processing at the first zero header. + // test now that when we read the archive, it stops processing at the first zero + // header. let actual = decode_names(&mut Archive::new(Cursor::new(&archive_bytes))).await; assert_eq!(expected, actual); diff --git a/tokio-tar/tests/header/mod.rs b/tokio-tar/tests/header/mod.rs index 33f479d4..d4812655 100644 --- a/tokio-tar/tests/header/mod.rs +++ b/tokio-tar/tests/header/mod.rs @@ -189,7 +189,8 @@ fn set_metadata_deterministic() { h } - // Create "the same" File twice in a row, one second apart, with differing readonly values. + // Create "the same" File twice in a row, one second apart, with differing + // readonly values. let one = mk_header(tmppath.as_path(), false); thread::sleep(time::Duration::from_millis(1050)); let two = mk_header(tmppath.as_path(), true);