Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add blocking api calls #21

Merged
merged 2 commits into from
Feb 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorta would've preferred Result<(), TimeoutError> here but is fine

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