diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34f8b0f5..2bc0037a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,12 +44,14 @@ jobs: build: strategy: matrix: - target: [ x86_64-unknown-linux-gnu, x86_64-apple-darwin ] + target: [ x86_64-unknown-linux-gnu, x86_64-apple-darwin, x86_64-pc-windows-msvc ] include: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - target: x86_64-apple-darwin os: macos-latest + - target: x86_64-pc-windows-msvc + os: windows-latest runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 82ea752e..13c4efb8 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -14,6 +14,8 @@ jobs: os: ubuntu-latest - target: x86_64-apple-darwin os: macos-latest + - target: x86_64-pc-windows-msvc + os: windows-latest runs-on: ${{ matrix.os }} steps: - name: Checkout tagged commit diff --git a/Cargo.lock b/Cargo.lock index 5453e121..128a5684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aead" version = "0.3.2" @@ -121,7 +127,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b72c1f1154e234325b50864a349b9c8e56939e266a4c307c0f159812df2f9537" dependencies = [ - "bzip2", + "bzip2 0.4.2", "futures-core", "memchr", "pin-project-lite", @@ -498,6 +504,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "bzip2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" +dependencies = [ + "bzip2-sys", + "libc", +] + [[package]] name = "bzip2" version = "0.4.2" @@ -1068,6 +1084,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "flate2" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" +dependencies = [ + "cfg-if 0.1.10", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1979,6 +2007,15 @@ dependencies = [ "serde", ] +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + [[package]] name = "mio" version = "0.7.7" @@ -3580,7 +3617,7 @@ dependencies = [ "tempfile", "testcontainers 0.12.0", "thiserror", - "time", + "time 0.2.24", "tokio", "tokio-tar", "tokio-tungstenite", @@ -3593,6 +3630,7 @@ dependencies = [ "url", "uuid", "void", + "zip", ] [[package]] @@ -3722,6 +3760,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "time" version = "0.2.24" @@ -4452,3 +4500,17 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zip" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8977234acab718eb2820494b2f96cbb16004c19dddf88b7445b27381450997" +dependencies = [ + "byteorder", + "bzip2 0.3.3", + "crc32fast", + "flate2", + "thiserror", + "time 0.1.43", +] diff --git a/bors.toml b/bors.toml index 8f1b1ab7..11761796 100644 --- a/bors.toml +++ b/bors.toml @@ -2,6 +2,7 @@ status = [ "static_analysis", "build (x86_64-unknown-linux-gnu)", "build (x86_64-apple-darwin)", + "build (x86_64-pc-windows-msvc)", "test (x86_64-unknown-linux-gnu)", "test (x86_64-apple-darwin)", "docker_tests (happy_path)", diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 165e5bae..142a691c 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -51,7 +51,6 @@ strum = { version = "0.20", features = ["derive"] } thiserror = "1" time = "0.2" 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" @@ -63,6 +62,12 @@ url = { version = "2.1", features = ["serde"] } uuid = { version = "0.8", features = ["serde", "v4"] } void = "1" +[target.'cfg(not(windows))'.dependencies] +tokio-tar = { path = "../tokio-tar" } + +[target.'cfg(windows)'.dependencies] +zip = "0.5" + [dev-dependencies] bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs" } get-port = "3" diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index 2cfe1324..bb28b3af 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -1,6 +1,5 @@ use ::monero::Network; use anyhow::{Context, Result}; -use async_compression::tokio::bufread::BzDecoder; use big_bytes::BigByte; use futures::{StreamExt, TryStreamExt}; use reqwest::{header::CONTENT_LENGTH, Url}; @@ -14,23 +13,33 @@ use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::{Child, Command}, }; -use tokio_tar::Archive; use tokio_util::{ codec::{BytesCodec, FramedRead}, io::StreamReader, }; +#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] +compile_error!("unsupported operating system"); + #[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"); +#[cfg(target_os = "windows")] +const DOWNLOAD_URL: &str = "https://downloads.getmonero.org/cli/monero-win-x64-v0.17.1.9.zip"; +#[cfg(any(target_os = "macos", target_os = "linux"))] const PACKED_FILE: &str = "monero-wallet-rpc"; +#[cfg(target_os = "windows")] +const PACKED_FILE: &str = "monero-wallet-rpc.exe"; + +#[derive(Debug, Clone, Copy, thiserror::Error)] +#[error("monero wallet rpc executable not found in downloaded archive")] +pub struct ExecutableNotFoundInArchive; + pub struct WalletRpcProcess { _child: Child, port: u16, @@ -59,8 +68,8 @@ impl 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.archive_path().exists() { + remove_file(monero_wallet_rpc.archive_path()).await?; } if !monero_wallet_rpc.exec_path().exists() { @@ -69,7 +78,7 @@ impl WalletRpc { .read(true) .write(true) .create_new(true) - .open(monero_wallet_rpc.tar_path()) + .open(monero_wallet_rpc.archive_path()) .await?; let response = reqwest::get(DOWNLOAD_URL).await?; @@ -88,43 +97,28 @@ impl WalletRpc { .bytes_stream() .map_err(|err| std::io::Error::new(ErrorKind::Other, err)); + #[cfg(not(target_os = "windows"))] let mut stream = FramedRead::new( - BzDecoder::new(StreamReader::new(byte_stream)), + async_compression::tokio::bufread::BzDecoder::new(StreamReader::new(byte_stream)), BytesCodec::new(), ) .map_ok(|bytes| bytes.freeze()); + #[cfg(target_os = "windows")] + let mut stream = FramedRead::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?; + Self::extract_archive(&monero_wallet_rpc).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? @@ -169,11 +163,78 @@ impl WalletRpc { }) } - fn tar_path(&self) -> PathBuf { - self.working_dir.join("monero-cli-wallet.tar") + fn archive_path(&self) -> PathBuf { + self.working_dir.join("monero-cli-wallet.archive") } fn exec_path(&self) -> PathBuf { self.working_dir.join(PACKED_FILE) } + + #[cfg(not(target_os = "windows"))] + async fn extract_archive(monero_wallet_rpc: &Self) -> Result<()> { + use anyhow::bail; + use tokio_tar::Archive; + + let mut options = OpenOptions::new(); + let file = options + .read(true) + .open(monero_wallet_rpc.archive_path()) + .await?; + + let mut ar = Archive::new(file); + let mut entries = ar.entries()?; + + loop { + match entries.next().await { + Some(file) => { + 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?; + break; + } + } + None => bail!(ExecutableNotFoundInArchive), + } + } + + remove_file(monero_wallet_rpc.archive_path()).await?; + + Ok(()) + } + + #[cfg(target_os = "windows")] + async fn extract_archive(monero_wallet_rpc: &Self) -> Result<()> { + use std::fs::File; + use tokio::task::JoinHandle; + use zip::ZipArchive; + + let archive_path = monero_wallet_rpc.archive_path(); + let exec_path = monero_wallet_rpc.exec_path(); + + let extract: JoinHandle> = tokio::task::spawn_blocking(|| { + let file = File::open(archive_path)?; + let mut zip = ZipArchive::new(file)?; + + let name = zip + .file_names() + .find(|name| name.contains(PACKED_FILE)) + .context(ExecutableNotFoundInArchive)? + .to_string(); + + let mut rpc = zip.by_name(&name)?; + let mut file = File::create(exec_path)?; + std::io::copy(&mut rpc, &mut file)?; + Ok(()) + }); + extract.await??; + + remove_file(monero_wallet_rpc.archive_path()).await?; + + Ok(()) + } }