Skip to content

Commit

Permalink
Merge pull request #21 from mattsse/matt/blocking-install
Browse files Browse the repository at this point in the history
feat: add blocking api calls
  • Loading branch information
roynalnaruto authored Feb 12, 2022
2 parents a0b4f9c + 622a0fb commit 4f48ca7
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 23 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,4 @@ default = ["rustls"]
openssl = ["reqwest/native-tls"]
rustls = ["reqwest/rustls-tls"]
sha2-asm = ["sha2/asm"]
blocking = ["reqwest/blocking"]
136 changes: 116 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,53 @@ pub fn installed_versions() -> Result<Vec<Version>, SolcVmError> {
Ok(versions)
}

/// Blocking version of [`all_versions`]
#[cfg(feature = "blocking")]
pub fn blocking_all_versions() -> Result<Vec<Version>, 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<Vec<Version>, SolcVmError> {
let mut releases = releases::all_releases(platform::platform())
Ok(releases::all_releases(platform::platform())
.await?
.releases
.keys()
.cloned()
.collect::<Vec<Version>>();
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.
Expand All @@ -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));
Expand All @@ -191,20 +221,24 @@ 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<u8>, lock_path: PathBuf) -> Result<(), SolcVmError> {
let installer = {
setup_version(version.to_string().as_str())?;

// create lock file.
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.
Expand All @@ -229,6 +263,23 @@ pub fn setup_home() -> Result<PathBuf, SolcVmError> {
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();
Expand All @@ -247,6 +298,21 @@ fn setup_version(version: &str) -> Result<(), SolcVmError> {
Ok(())
}

fn ensure_checksum(
binbytes: impl AsRef<[u8]>,
version: &Version,
expected_checksum: Vec<u8>,
) -> 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::{
Expand Down Expand Up @@ -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();
Expand All @@ -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"));
}
}
39 changes: 36 additions & 3 deletions src/releases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Version> {
let mut versions = self.releases.into_keys().collect::<Vec<_>>();
versions.sort_unstable();
versions
}
}

/// Build info contains the SHA256 checksum of a solc binary.
Expand Down Expand Up @@ -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<Releases, SolcVmError> {
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::<Releases>()?;
Ok(unified_releases(releases, platform))
}

/// Fetch all releases available for the provided platform.
pub async fn all_releases(platform: Platform) -> Result<Releases, SolcVmError> {
if platform == Platform::LinuxAarch64 {
Expand All @@ -122,14 +150,19 @@ pub async fn all_releases(platform: Platform) -> Result<Releases, SolcVmError> {
.json::<Releases>()
.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.
Expand Down

0 comments on commit 4f48ca7

Please sign in to comment.