From 7faadf0b226bb8c2038c7da57c1a2a38d88d116c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 11 Feb 2022 17:29:26 +0100 Subject: [PATCH 1/2] feat: add blocking api calls --- Cargo.lock | 9 ++++ Cargo.toml | 1 + src/lib.rs | 136 +++++++++++++++++++++++++++++++++++++++++------- src/releases.rs | 39 ++++++++++++-- 4 files changed, 162 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b04f1a..a854cc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,6 +241,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + [[package]] name = "futures-sink" version = "0.3.17" @@ -261,9 +267,12 @@ checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" dependencies = [ "autocfg", "futures-core", + "futures-io", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4099c83..0b3bae2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,4 @@ default = ["rustls"] openssl = ["reqwest/native-tls"] rustls = ["reqwest/rustls-tls"] sha2-asm = ["sha2/asm"] +blocking = ["reqwest/blocking"] diff --git a/src/lib.rs b/src/lib.rs index 3681fd2..5655b04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,17 +140,53 @@ pub fn installed_versions() -> Result, SolcVmError> { Ok(versions) } +/// Blocking version of [`all_versions`] +#[cfg(feature = "blocking")] +pub fn block_all_versions() -> Result, SolcVmError> { + Ok(releases::blocking_all_releases(platform::platform())?.into_versions()) +} + /// Fetches the list of all the available versions of Solc. The list is platform dependent, so /// different versions can be found for macosx vs linux. pub async fn all_versions() -> Result, SolcVmError> { - let mut releases = releases::all_releases(platform::platform()) + Ok(releases::all_releases(platform::platform()) .await? - .releases - .keys() - .cloned() - .collect::>(); - releases.sort(); - Ok(releases) + .into_versions()) +} + +/// Blocking version of [`install`] +#[cfg(feature = "blocking")] +pub fn blocking_install(version: &Version) -> Result<(), SolcVmError> { + setup_home()?; + + let artifacts = releases::blocking_all_releases(platform::platform())?; + let artifact = artifacts + .get_artifact(version) + .ok_or(SolcVmError::UnknownVersion)?; + let download_url = + releases::artifact_url(platform::platform(), version, artifact.to_string().as_str())?; + + let checksum = artifacts + .get_checksum(version) + .unwrap_or_else(|| panic!("checksum not available: {:?}", version.to_string())); + + let res = reqwest::blocking::get(download_url)?; + let binbytes = res.bytes()?; + ensure_checksum(&binbytes, version, checksum)?; + + // lock file to indicate that installation of this solc version will be in progress. + let lock_path = SVM_HOME.join(&format!(".lock-solc-{}", version)); + + // wait until lock file is released, possibly by another parallel thread trying to install the + // same version of solc. + if !blocking_wait_for_lock(&lock_path) { + return Err(SolcVmError::Timeout( + version.to_string(), + INSTALL_TIMEOUT.as_secs(), + )); + } + + do_install(version.clone(), binbytes.to_vec(), lock_path) } /// Installs the provided version of Solc in the machine. @@ -165,19 +201,13 @@ pub async fn install(version: &Version) -> Result<(), SolcVmError> { let download_url = releases::artifact_url(platform::platform(), version, artifact.to_string().as_str())?; - let res = reqwest::get(download_url).await?; - let binbytes = res.bytes().await?; - let mut hasher = sha2::Sha256::new(); - hasher.update(&binbytes); - let cs = &hasher.finalize()[..]; let checksum = artifacts .get_checksum(version) .unwrap_or_else(|| panic!("checksum not available: {:?}", version.to_string())); - // checksum does not match - if cs != checksum { - return Err(SolcVmError::ChecksumMismatch(version.to_string())); - } + let res = reqwest::get(download_url).await?; + let binbytes = res.bytes().await?; + ensure_checksum(&binbytes, version, checksum)?; // lock file to indicate that installation of this solc version will be in progress. let lock_path = SVM_HOME.join(&format!(".lock-solc-{}", version)); @@ -191,6 +221,10 @@ pub async fn install(version: &Version) -> Result<(), SolcVmError> { SolcVmError::Timeout(version.to_string(), INSTALL_TIMEOUT.as_secs()) })?; + do_install(version.clone(), binbytes.to_vec(), lock_path) +} + +fn do_install(version: Version, binbytes: Vec, lock_path: PathBuf) -> Result<(), SolcVmError> { let installer = { setup_version(version.to_string().as_str())?; @@ -198,13 +232,13 @@ pub async fn install(version: &Version) -> Result<(), SolcVmError> { fs::File::create(&lock_path)?; Installer { - version: version.clone(), - binbytes: binbytes.to_vec(), - lock: Some(lock_path.to_path_buf()), + version, + binbytes, + lock: Some(lock_path), } }; - Ok(installer.install()?) + installer.install() } /// Removes the provided version of Solc from the machine. @@ -229,6 +263,23 @@ pub fn setup_home() -> Result { Ok(home_dir) } +/// Returns `true` if lock is ready and `false` if timed out +#[cfg(feature = "blocking")] +fn blocking_wait_for_lock(lock_path: &Path) -> bool { + let span = tracing::trace_span!("wait for lock file", ?lock_path); + let _enter = span.enter(); + + let deadline = std::time::Instant::now() + INSTALL_TIMEOUT; + while lock_path.exists() { + if std::time::Instant::now() > deadline { + return false; + } + tracing::event!(Level::DEBUG, "time out waiting for lock file"); + std::thread::sleep(LOCKFILE_CHECK_INTERVAL); + } + true +} + async fn wait_for_lock(lock_path: &Path) { let span = tracing::trace_span!("wait for lock file", ?lock_path); let _enter = span.enter(); @@ -247,6 +298,21 @@ fn setup_version(version: &str) -> Result<(), SolcVmError> { Ok(()) } +fn ensure_checksum( + binbytes: impl AsRef<[u8]>, + version: &Version, + expected_checksum: Vec, +) -> Result<(), SolcVmError> { + let mut hasher = sha2::Sha256::new(); + hasher.update(binbytes); + let cs = &hasher.finalize()[..]; + // checksum does not match + if cs != expected_checksum { + return Err(SolcVmError::ChecksumMismatch(version.to_string())); + } + Ok(()) +} + #[cfg(test)] mod tests { use crate::{ @@ -286,6 +352,16 @@ mod tests { assert!(install(rand_version).await.is_ok()); } + #[cfg(feature = "blocking")] + #[test] + fn blocking_test_install() { + let versions = crate::releases::blocking_all_releases(platform::platform()) + .unwrap() + .into_versions(); + let rand_version = versions.choose(&mut rand::thread_rng()).unwrap(); + assert!(blocking_install(rand_version).is_ok()); + } + #[tokio::test] async fn test_version() { let version = "0.8.10".parse().unwrap(); @@ -304,4 +380,24 @@ mod tests { .as_ref() .contains("0.8.10")); } + + #[cfg(feature = "blocking")] + #[test] + fn blocking_test_version() { + let version = "0.8.10".parse().unwrap(); + blocking_install(&version).unwrap(); + let solc_path = + version_path(version.to_string().as_str()).join(&format!("solc-{}", version)); + let output = Command::new(&solc_path) + .arg("--version") + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .output() + .unwrap(); + + assert!(String::from_utf8_lossy(&output.stdout) + .as_ref() + .contains("0.8.10")); + } } diff --git a/src/releases.rs b/src/releases.rs index e202659..9b5560f 100644 --- a/src/releases.rs +++ b/src/releases.rs @@ -65,6 +65,18 @@ impl Releases { } None } + + /// Returns the artifact of the version if any + pub fn get_artifact(&self, version: &Version) -> Option<&String> { + self.releases.get(version) + } + + /// Returns a sorted list of all versions + pub fn into_versions(self) -> Vec { + let mut versions = self.releases.into_keys().collect::>(); + versions.sort_unstable(); + versions + } } /// Build info contains the SHA256 checksum of a solc binary. @@ -107,6 +119,22 @@ where Ok(v.into_iter().map(|(Wrapper(k), v)| (k, v)).collect()) } +/// Blocking version fo [`all_realeases`] +#[cfg(feature = "blocking")] +pub fn blocking_all_releases(platform: Platform) -> Result { + if platform == Platform::LinuxAarch64 { + return Ok(LINUX_AARCH64_RELEASES.clone()); + } + + let releases = reqwest::blocking::get(format!( + "{}/{}/list.json", + SOLC_RELEASES_URL, + platform.to_string() + ))? + .json::()?; + Ok(unified_releases(releases, platform)) +} + /// Fetch all releases available for the provided platform. pub async fn all_releases(platform: Platform) -> Result { if platform == Platform::LinuxAarch64 { @@ -122,14 +150,19 @@ pub async fn all_releases(platform: Platform) -> Result { .json::() .await?; + Ok(unified_releases(releases, platform)) +} + +/// unifies the releases with old releases if on linux +fn unified_releases(releases: Releases, platform: Platform) -> Releases { if platform == Platform::LinuxAmd64 { let mut all_releases = OLD_SOLC_RELEASES.clone(); all_releases.builds.extend(releases.builds); all_releases.releases.extend(releases.releases); - return Ok(all_releases); + all_releases + } else { + releases } - - Ok(releases) } /// Construct the URL to the Solc binary for the specified release version and target platform. From 622a0fb6eeb7e55e4c9a28bcc38425bf379ca754 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Sat, 12 Feb 2022 05:36:13 +0100 Subject: [PATCH 2/2] fix: minor naming edit to maintain consistency --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5655b04..b56711f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,7 +142,7 @@ pub fn installed_versions() -> Result, SolcVmError> { /// Blocking version of [`all_versions`] #[cfg(feature = "blocking")] -pub fn block_all_versions() -> Result, SolcVmError> { +pub fn blocking_all_versions() -> Result, SolcVmError> { Ok(releases::blocking_all_releases(platform::platform())?.into_versions()) }