diff --git a/Cargo.lock b/Cargo.lock index f094d98..230d945 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3980,9 +3980,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", - "futures-channel", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -4110,6 +4108,7 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", + "revive-dt-common", "revive-dt-config", "revive-dt-format", "revive-dt-node-interaction", @@ -4154,6 +4153,7 @@ dependencies = [ "semver 1.0.26", "serde", "sha2 0.10.9", + "tokio", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index 2587665..92bb064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ clap = { version = "4", features = ["derive"] } foundry-compilers-artifacts = { version = "0.18.0" } futures = { version = "0.3.31" } hex = "0.4.3" -reqwest = { version = "0.12.15", features = ["blocking", "json"] } +reqwest = { version = "0.12.15", features = ["json"] } once_cell = "1.21" semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/crates/common/src/fs/clear_dir.rs b/crates/common/src/fs/clear_dir.rs new file mode 100644 index 0000000..211bde3 --- /dev/null +++ b/crates/common/src/fs/clear_dir.rs @@ -0,0 +1,29 @@ +use std::{ + fs::{read_dir, remove_dir_all, remove_file}, + path::Path, +}; + +use anyhow::{Result, bail}; + +/// This method clears the passed directory of all of the files and directories contained within +/// without deleting the directory. +pub fn clear_directory(path: impl AsRef) -> Result<()> { + let path = path.as_ref(); + + if !path.is_dir() { + bail!("The provided path is not a directory: {}", path.display()); + } + + for entry in read_dir(path)? { + let entry = entry?; + let entry_path = entry.path(); + + if entry_path.is_file() { + remove_file(entry_path)? + } else { + remove_dir_all(entry_path)? + } + } + + Ok(()) +} diff --git a/crates/common/src/fs/mod.rs b/crates/common/src/fs/mod.rs new file mode 100644 index 0000000..f41c82e --- /dev/null +++ b/crates/common/src/fs/mod.rs @@ -0,0 +1,3 @@ +mod clear_dir; + +pub use clear_dir::*; diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index f137fb9..e280af5 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,6 +1,7 @@ //! This crate provides common concepts, functionality, types, macros, and more that other crates in //! the workspace can benefit from. +pub mod fs; pub mod iterators; pub mod macros; pub mod types; diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index a9c5df5..551831e 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -40,7 +40,7 @@ pub trait SolidityCompiler { fn get_compiler_executable( config: &Arguments, version: impl Into, - ) -> anyhow::Result; + ) -> impl Future>; fn version(&self) -> anyhow::Result; } diff --git a/crates/compiler/src/revive_resolc.rs b/crates/compiler/src/revive_resolc.rs index b1f3bca..0771211 100644 --- a/crates/compiler/src/revive_resolc.rs +++ b/crates/compiler/src/revive_resolc.rs @@ -197,7 +197,7 @@ impl SolidityCompiler for Resolc { Resolc { resolc_path } } - fn get_compiler_executable( + async fn get_compiler_executable( config: &Arguments, _version: impl Into, ) -> anyhow::Result { @@ -235,11 +235,13 @@ impl SolidityCompiler for Resolc { mod test { use super::*; - #[test] - fn compiler_version_can_be_obtained() { + #[tokio::test] + async fn compiler_version_can_be_obtained() { // Arrange let args = Arguments::default(); - let path = Resolc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap(); + let path = Resolc::get_compiler_executable(&args, Version::new(0, 7, 6)) + .await + .unwrap(); let compiler = Resolc::new(path); // Act diff --git a/crates/compiler/src/solc.rs b/crates/compiler/src/solc.rs index cb12e55..f296e3e 100644 --- a/crates/compiler/src/solc.rs +++ b/crates/compiler/src/solc.rs @@ -179,11 +179,11 @@ impl SolidityCompiler for Solc { Self { solc_path } } - fn get_compiler_executable( + async fn get_compiler_executable( config: &Arguments, version: impl Into, ) -> anyhow::Result { - let path = download_solc(config.directory(), version, config.wasm)?; + let path = download_solc(config.directory(), version, config.wasm).await?; Ok(path) } @@ -218,11 +218,15 @@ impl SolidityCompiler for Solc { mod test { use super::*; - #[test] - fn compiler_version_can_be_obtained() { + #[tokio::test] + async fn compiler_version_can_be_obtained() { // Arrange let args = Arguments::default(); - let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6)).unwrap(); + println!("Getting compiler path"); + let path = Solc::get_compiler_executable(&args, Version::new(0, 7, 6)) + .await + .unwrap(); + println!("Got compiler path"); let compiler = Solc::new(path); // Act diff --git a/crates/compiler/tests/lib.rs b/crates/compiler/tests/lib.rs index 6815b62..733e2d3 100644 --- a/crates/compiler/tests/lib.rs +++ b/crates/compiler/tests/lib.rs @@ -8,7 +8,10 @@ use semver::Version; async fn contracts_can_be_compiled_with_solc() { // Arrange let args = Arguments::default(); - let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30)).unwrap(); + let compiler_path = Solc::get_compiler_executable(&args, Version::new(0, 8, 30)) + .await + .unwrap(); + println!("About to assert"); // Act let output = Compiler::::new() @@ -47,7 +50,9 @@ async fn contracts_can_be_compiled_with_solc() { async fn contracts_can_be_compiled_with_resolc() { // Arrange let args = Arguments::default(); - let compiler_path = Resolc::get_compiler_executable(&args, Version::new(0, 8, 30)).unwrap(); + let compiler_path = Resolc::get_compiler_executable(&args, Version::new(0, 8, 30)) + .await + .unwrap(); // Act let output = Compiler::::new() diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index 8f14861..660c6d7 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -469,7 +469,7 @@ async fn compile_contracts( ) -> anyhow::Result<(Version, CompilerOutput)> { let compiler_version_or_requirement = mode.compiler_version_to_use(config.solc.clone()); let compiler_path = - P::Compiler::get_compiler_executable(config, compiler_version_or_requirement)?; + P::Compiler::get_compiler_executable(config, compiler_version_or_requirement).await?; let compiler_version = P::Compiler::new(compiler_path.clone()).version()?; let compiler = Compiler::::new() diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 87ae110..a930312 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -14,6 +14,7 @@ alloy = { workspace = true } tracing = { workspace = true } tokio = { workspace = true } +revive-dt-common = { workspace = true } revive-dt-config = { workspace = true } revive-dt-format = { workspace = true } revive-dt-node-interaction = { workspace = true } diff --git a/crates/node/src/geth.rs b/crates/node/src/geth.rs index 6dc008c..1b802ad 100644 --- a/crates/node/src/geth.rs +++ b/crates/node/src/geth.rs @@ -25,6 +25,7 @@ use alloy::{ }, signers::local::PrivateKeySigner, }; +use revive_dt_common::fs::clear_directory; use revive_dt_config::Arguments; use revive_dt_format::traits::ResolverApi; use revive_dt_node_interaction::EthereumNode; @@ -80,6 +81,9 @@ impl Instance { /// Create the node directory and call `geth init` to configure the genesis. #[tracing::instrument(skip_all, fields(geth_node_id = self.id))] fn init(&mut self, genesis: String) -> anyhow::Result<&mut Self> { + clear_directory(&self.base_directory)?; + clear_directory(&self.logs_directory)?; + create_dir_all(&self.base_directory)?; create_dir_all(&self.logs_directory)?; diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/kitchensink.rs index f9faa2a..a7aa599 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/kitchensink.rs @@ -30,6 +30,7 @@ use alloy::{ }, signers::local::PrivateKeySigner, }; +use revive_dt_common::fs::clear_directory; use revive_dt_format::traits::ResolverApi; use serde::{Deserialize, Serialize}; use serde_json::{Value as JsonValue, json}; @@ -85,6 +86,9 @@ impl KitchensinkNode { #[tracing::instrument(skip_all, fields(kitchensink_node_id = self.id))] fn init(&mut self, genesis: &str) -> anyhow::Result<&mut Self> { + clear_directory(&self.base_directory)?; + clear_directory(&self.logs_directory)?; + create_dir_all(&self.base_directory)?; create_dir_all(&self.logs_directory)?; diff --git a/crates/solc-binaries/Cargo.toml b/crates/solc-binaries/Cargo.toml index 9bb6090..be5dcf7 100644 --- a/crates/solc-binaries/Cargo.toml +++ b/crates/solc-binaries/Cargo.toml @@ -14,6 +14,7 @@ revive-dt-common = { workspace = true } anyhow = { workspace = true } hex = { workspace = true } tracing = { workspace = true } +tokio = { workspace = true } reqwest = { workspace = true } semver = { workspace = true } serde = { workspace = true } diff --git a/crates/solc-binaries/src/cache.rs b/crates/solc-binaries/src/cache.rs index 364ea01..0b7daaf 100644 --- a/crates/solc-binaries/src/cache.rs +++ b/crates/solc-binaries/src/cache.rs @@ -6,15 +6,17 @@ use std::{ io::{BufWriter, Write}, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, - sync::{LazyLock, Mutex}, + sync::LazyLock, }; +use tokio::sync::Mutex; + use crate::download::GHDownloader; pub const SOLC_CACHE_DIRECTORY: &str = "solc"; pub(crate) static SOLC_CACHER: LazyLock>> = LazyLock::new(Default::default); -pub(crate) fn get_or_download( +pub(crate) async fn get_or_download( working_directory: &Path, downloader: &GHDownloader, ) -> anyhow::Result { @@ -23,20 +25,20 @@ pub(crate) fn get_or_download( .join(downloader.version.to_string()); let target_file = target_directory.join(downloader.target); - let mut cache = SOLC_CACHER.lock().unwrap(); + let mut cache = SOLC_CACHER.lock().await; if cache.contains(&target_file) { tracing::debug!("using cached solc: {}", target_file.display()); return Ok(target_file); } create_dir_all(target_directory)?; - download_to_file(&target_file, downloader)?; + download_to_file(&target_file, downloader).await?; cache.insert(target_file.clone()); Ok(target_file) } -fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<()> { +async fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<()> { tracing::info!("caching file: {}", path.display()); let Ok(file) = File::create_new(path) else { @@ -52,7 +54,7 @@ fn download_to_file(path: &Path, downloader: &GHDownloader) -> anyhow::Result<() } let mut file = BufWriter::new(file); - file.write_all(&downloader.download()?)?; + file.write_all(&downloader.download().await?)?; file.flush()?; drop(file); diff --git a/crates/solc-binaries/src/download.rs b/crates/solc-binaries/src/download.rs index 067102c..93ee451 100644 --- a/crates/solc-binaries/src/download.rs +++ b/crates/solc-binaries/src/download.rs @@ -25,12 +25,12 @@ impl List { /// /// Caches the list retrieved from the `url` into [LIST_CACHE], /// subsequent calls with the same `url` will return the cached list. - pub fn download(url: &'static str) -> anyhow::Result { + pub async fn download(url: &'static str) -> anyhow::Result { if let Some(list) = LIST_CACHE.lock().unwrap().get(url) { return Ok(list.clone()); } - let body: List = reqwest::blocking::get(url)?.json()?; + let body: List = reqwest::get(url).await?.json().await?; LIST_CACHE.lock().unwrap().insert(url, body.clone()); @@ -54,7 +54,7 @@ impl GHDownloader { pub const WINDOWS_NAME: &str = "solc-windows.exe"; pub const WASM_NAME: &str = "soljson.js"; - fn new( + async fn new( version: impl Into, target: &'static str, list: &'static str, @@ -67,7 +67,8 @@ impl GHDownloader { list, }), VersionOrRequirement::Requirement(requirement) => { - let Some(version) = List::download(list)? + let Some(version) = List::download(list) + .await? .builds .into_iter() .map(|build| build.version) @@ -85,20 +86,20 @@ impl GHDownloader { } } - pub fn linux(version: impl Into) -> anyhow::Result { - Self::new(version, Self::LINUX_NAME, List::LINUX_URL) + pub async fn linux(version: impl Into) -> anyhow::Result { + Self::new(version, Self::LINUX_NAME, List::LINUX_URL).await } - pub fn macosx(version: impl Into) -> anyhow::Result { - Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL) + pub async fn macosx(version: impl Into) -> anyhow::Result { + Self::new(version, Self::MACOSX_NAME, List::MACOSX_URL).await } - pub fn windows(version: impl Into) -> anyhow::Result { - Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL) + pub async fn windows(version: impl Into) -> anyhow::Result { + Self::new(version, Self::WINDOWS_NAME, List::WINDOWS_URL).await } - pub fn wasm(version: impl Into) -> anyhow::Result { - Self::new(version, Self::WASM_NAME, List::WASM_URL) + pub async fn wasm(version: impl Into) -> anyhow::Result { + Self::new(version, Self::WASM_NAME, List::WASM_URL).await } /// Returns the download link. @@ -110,16 +111,17 @@ impl GHDownloader { /// /// Errors out if the download fails or the digest of the downloaded file /// mismatches the expected digest from the release [List]. - pub fn download(&self) -> anyhow::Result> { + pub async fn download(&self) -> anyhow::Result> { tracing::info!("downloading solc: {self:?}"); - let expected_digest = List::download(self.list)? + let expected_digest = List::download(self.list) + .await? .builds .iter() .find(|build| build.version == self.version) .ok_or_else(|| anyhow::anyhow!("solc v{} not found builds", self.version)) .map(|b| b.sha256.strip_prefix("0x").unwrap_or(&b.sha256).to_string())?; - let file = reqwest::blocking::get(self.url())?.bytes()?.to_vec(); + let file = reqwest::get(self.url()).await?.bytes().await?.to_vec(); if hex::encode(Sha256::digest(&file)) != expected_digest { anyhow::bail!("sha256 mismatch for solc version {}", self.version); @@ -133,27 +135,56 @@ impl GHDownloader { mod tests { use crate::{download::GHDownloader, list::List}; - #[test] - fn try_get_windows() { - let version = List::download(List::WINDOWS_URL).unwrap().latest_release; - GHDownloader::windows(version).unwrap().download().unwrap(); + #[tokio::test] + async fn try_get_windows() { + let version = List::download(List::WINDOWS_URL) + .await + .unwrap() + .latest_release; + GHDownloader::windows(version) + .await + .unwrap() + .download() + .await + .unwrap(); } - #[test] - fn try_get_macosx() { - let version = List::download(List::MACOSX_URL).unwrap().latest_release; - GHDownloader::macosx(version).unwrap().download().unwrap(); + #[tokio::test] + async fn try_get_macosx() { + let version = List::download(List::MACOSX_URL) + .await + .unwrap() + .latest_release; + GHDownloader::macosx(version) + .await + .unwrap() + .download() + .await + .unwrap(); } - #[test] - fn try_get_linux() { - let version = List::download(List::LINUX_URL).unwrap().latest_release; - GHDownloader::linux(version).unwrap().download().unwrap(); + #[tokio::test] + async fn try_get_linux() { + let version = List::download(List::LINUX_URL) + .await + .unwrap() + .latest_release; + GHDownloader::linux(version) + .await + .unwrap() + .download() + .await + .unwrap(); } - #[test] - fn try_get_wasm() { - let version = List::download(List::WASM_URL).unwrap().latest_release; - GHDownloader::wasm(version).unwrap().download().unwrap(); + #[tokio::test] + async fn try_get_wasm() { + let version = List::download(List::WASM_URL).await.unwrap().latest_release; + GHDownloader::wasm(version) + .await + .unwrap() + .download() + .await + .unwrap(); } } diff --git a/crates/solc-binaries/src/lib.rs b/crates/solc-binaries/src/lib.rs index 5fefbd8..7251c7c 100644 --- a/crates/solc-binaries/src/lib.rs +++ b/crates/solc-binaries/src/lib.rs @@ -19,22 +19,22 @@ pub mod list; /// /// Subsequent calls for the same version will use a cached artifact /// and not download it again. -pub fn download_solc( +pub async fn download_solc( cache_directory: &Path, version: impl Into, wasm: bool, ) -> anyhow::Result { let downloader = if wasm { - GHDownloader::wasm(version) + GHDownloader::wasm(version).await } else if cfg!(target_os = "linux") { - GHDownloader::linux(version) + GHDownloader::linux(version).await } else if cfg!(target_os = "macos") { - GHDownloader::macosx(version) + GHDownloader::macosx(version).await } else if cfg!(target_os = "windows") { - GHDownloader::windows(version) + GHDownloader::windows(version).await } else { unimplemented!() }?; - get_or_download(cache_directory, &downloader) + get_or_download(cache_directory, &downloader).await }