Skip to content

Commit 5256dc1

Browse files
authored
fix: fix AVM instability with solana-verify (#3867)
* style(avm): Construct `Version` instead of parsing * fix(avm): Only install `solana-verify` for Anchor >= 0.32 * style(avm): Refactor solana-verify install * feat(avm): Install solana-verify from binary releases where possible * feat(avm): Only install `solana-verify` when requested * feat(avm): Don't re-install `solana-verify` if possible * fix(avm): Always install solana-verify 0.4.7 * refactor(cli): Refactor AVM installation to another function * feat(cli): Install `solana-verify` only when needed * Formatting * chore(avm): Update `solana-verify` version to 0.4.11
1 parent fdc5c51 commit 5256dc1

File tree

3 files changed

+139
-34
lines changed

3 files changed

+139
-34
lines changed

avm/src/lib.rs

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{anyhow, Error, Result};
1+
use anyhow::{anyhow, bail, Context, Error, Result};
22
use cargo_toml::Manifest;
33
use chrono::{TimeZone, Utc};
44
use reqwest::header::USER_AGENT;
@@ -201,7 +201,9 @@ pub fn use_version(opt_version: Option<Version>) -> Result<()> {
201201
.next()
202202
.expect("Expected input")?;
203203
match input.as_str() {
204-
"y" | "yes" => return install_version(InstallTarget::Version(version), false, false),
204+
"y" | "yes" => {
205+
return install_version(InstallTarget::Version(version), false, false, false)
206+
}
205207
_ => return Err(anyhow!("Installation rejected.")),
206208
};
207209
}
@@ -222,7 +224,7 @@ pub enum InstallTarget {
222224
/// Update to the latest version
223225
pub fn update() -> Result<()> {
224226
let latest_version = get_latest_version()?;
225-
install_version(InstallTarget::Version(latest_version), false, false)
227+
install_version(InstallTarget::Version(latest_version), false, false, false)
226228
}
227229

228230
/// The commit sha provided can be shortened,
@@ -284,6 +286,7 @@ pub fn install_version(
284286
install_target: InstallTarget,
285287
force: bool,
286288
from_source: bool,
289+
with_solana_verify: bool,
287290
) -> Result<()> {
288291
let (version, from_source) = match &install_target {
289292
InstallTarget::Version(version) => (version.to_owned(), from_source),
@@ -308,7 +311,7 @@ pub fn install_version(
308311
}
309312

310313
let is_commit = matches!(install_target, InstallTarget::Commit(_));
311-
let is_older_than_v0_31_0 = version < Version::parse("0.31.0")?;
314+
let is_older_than_v0_31_0 = version < Version::new(0, 31, 0);
312315
if from_source || is_commit || is_older_than_v0_31_0 {
313316
// Build from source using `cargo install`
314317
let mut args: Vec<String> = vec![
@@ -430,37 +433,108 @@ pub fn install_version(
430433
)?;
431434
}
432435

436+
let is_at_least_0_32 = version >= Version::new(0, 32, 0);
437+
if with_solana_verify {
438+
if is_at_least_0_32 {
439+
if !solana_verify_installed().is_ok_and(|v| v) {
440+
#[cfg(any(target_os = "linux", target_os = "macos"))]
441+
install_solana_verify()?;
442+
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
443+
install_solana_verify_from_source()?;
444+
println!("solana-verify successfully installed");
445+
} else {
446+
println!("solana-verify already installed");
447+
}
448+
} else {
449+
println!("Not installing solana-verify for anchor < 0.32");
450+
}
451+
}
452+
453+
// If .version file is empty or not parseable, write the newly installed version to it
454+
if current_version().is_err() {
455+
let mut current_version_file = fs::File::create(current_version_file_path())?;
456+
current_version_file.write_all(version.to_string().as_bytes())?;
457+
}
458+
459+
use_version(Some(version))
460+
}
461+
462+
const SOLANA_VERIFY_VERSION: Version = Version::new(0, 4, 11);
463+
464+
/// Check if `solana-verify` is both installed and >= [`SOLANA_VERIFY_VERSION`].
465+
fn solana_verify_installed() -> Result<bool> {
466+
let bin_path = get_bin_dir_path().join("solana-verify");
467+
if !bin_path.exists() {
468+
return Ok(false);
469+
}
470+
let output = Command::new(bin_path)
471+
.arg("-V")
472+
.output()
473+
.context("executing `solana-verify` to check version")?;
474+
let stdout =
475+
String::from_utf8(output.stdout).context("expected `solana-verify` to output utf8")?;
476+
let Some(("solana-verify", version)) = stdout.trim().split_once(" ") else {
477+
bail!("invalid `solana-verify` output: `{stdout}`");
478+
};
479+
if Version::parse(version).with_context(|| "parsing solana-verify version `{version}")?
480+
>= SOLANA_VERIFY_VERSION
481+
{
482+
Ok(true)
483+
} else {
484+
Ok(false)
485+
}
486+
}
487+
488+
/// Install `solana-verify` from binary releases. Only available on Linux and Mac
489+
#[cfg(any(target_os = "linux", target_os = "macos"))]
490+
fn install_solana_verify() -> Result<()> {
433491
println!("Installing solana-verify...");
434-
let solana_verify_install_output = Command::new("cargo")
492+
let os = std::env::consts::OS;
493+
let url = format!(
494+
"https://github.com/Ellipsis-Labs/solana-verifiable-build/releases/download/v{SOLANA_VERIFY_VERSION}/solana-verify-{os}"
495+
);
496+
let res = reqwest::blocking::get(url)?;
497+
if !res.status().is_success() {
498+
bail!(
499+
"Failed to download `solana-verify-{os} v{SOLANA_VERIFY_VERSION} (status code: {})",
500+
res.status()
501+
);
502+
} else {
503+
let bin_path = get_bin_dir_path().join("solana-verify");
504+
fs::write(&bin_path, res.bytes()?)?;
505+
#[cfg(unix)]
506+
fs::set_permissions(
507+
bin_path,
508+
<fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o775),
509+
)?;
510+
Ok(())
511+
}
512+
}
513+
514+
/// Install `solana-verify` by building from Git sources
515+
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
516+
fn install_solana_verify_from_source() -> Result<()> {
517+
println!("Installing solana-verify from source...");
518+
let status = Command::new("cargo")
435519
.args([
436520
"install",
437521
"solana-verify",
438522
"--git",
439523
"https://github.com/Ellipsis-Labs/solana-verifiable-build",
440524
"--rev",
441-
"568cb334709e88b9b45fc24f1f440eecacf5db54",
525+
&format!("v{SOLANA_VERIFY_VERSION}"),
442526
"--root",
443527
AVM_HOME.to_str().unwrap(),
444528
"--force",
445529
"--locked",
446530
])
447-
.stdout(Stdio::inherit())
448-
.stderr(Stdio::inherit())
449-
.output()
450-
.map_err(|e| anyhow!("`cargo install` for `solana-verify` failed: {e}"))?;
451-
452-
if !solana_verify_install_output.status.success() {
453-
return Err(anyhow!("Failed to install `solana-verify`"));
454-
}
455-
println!("solana-verify successfully installed.");
456-
457-
// If .version file is empty or not parseable, write the newly installed version to it
458-
if current_version().is_err() {
459-
let mut current_version_file = fs::File::create(current_version_file_path())?;
460-
current_version_file.write_all(version.to_string().as_bytes())?;
531+
.status()
532+
.context("executing `cargo install solana-verify`")?;
533+
if status.success() {
534+
Ok(())
535+
} else {
536+
bail!("failed to install `solana-verify`");
461537
}
462-
463-
use_version(Some(version))
464538
}
465539

466540
/// Remove an installed version of anchor-cli

avm/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub enum Commands {
3333
#[clap(long)]
3434
/// Build from source code rather than downloading prebuilt binaries
3535
from_source: bool,
36+
#[clap(long)]
37+
/// Install `solana-verify` as well
38+
verify: bool,
3639
},
3740
#[clap(about = "Uninstall a version of Anchor")]
3841
Uninstall {
@@ -80,13 +83,14 @@ pub fn entry(opts: Cli) -> Result<()> {
8083
path,
8184
force,
8285
from_source,
86+
verify,
8387
} => {
8488
let install_target = if let Some(path) = path {
8589
InstallTarget::Path(path.into())
8690
} else {
8791
parse_install_target(&version_or_commit.unwrap())?
8892
};
89-
avm::install_version(install_target, force, from_source)
93+
avm::install_version(install_target, force, from_source, verify)
9094
}
9195
Commands::Uninstall { version } => avm::uninstall_version(&version),
9296
Commands::List {} => avm::list_versions(),

cli/src/lib.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use anchor_lang::solana_program::bpf_loader_upgradeable;
1010
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize, Discriminator};
1111
use anchor_lang_idl::convert::convert_idl;
1212
use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
13-
use anyhow::{anyhow, Context, Result};
13+
use anyhow::{anyhow, bail, Context, Result};
1414
use checks::{check_anchor_version, check_deps, check_idl_build_feature, check_overflow};
1515
use clap::{CommandFactory, Parser};
1616
use dirs::home_dir;
@@ -41,6 +41,7 @@ use std::path::{Path, PathBuf};
4141
use std::process::{Child, Stdio};
4242
use std::str::FromStr;
4343
use std::string::ToString;
44+
use std::sync::LazyLock;
4445

4546
mod checks;
4647
pub mod config;
@@ -53,6 +54,16 @@ pub const DOCKER_BUILDER_VERSION: &str = VERSION;
5354
/// Default RPC port
5455
pub const DEFAULT_RPC_PORT: u16 = 8899;
5556

57+
pub static AVM_HOME: LazyLock<PathBuf> = LazyLock::new(|| {
58+
if let Ok(avm_home) = std::env::var("AVM_HOME") {
59+
PathBuf::from(avm_home)
60+
} else {
61+
let mut user_home = dirs::home_dir().expect("Could not find home directory");
62+
user_home.push(".avm");
63+
user_home
64+
}
65+
});
66+
5667
#[derive(Debug, Parser)]
5768
#[clap(version = VERSION)]
5869
pub struct Opts {
@@ -662,17 +673,10 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
662673
"`anchor` {anchor_version} is not installed with `avm`. Installing...\n"
663674
);
664675

665-
let exit_status = std::process::Command::new("avm")
666-
.arg("install")
667-
.arg(anchor_version)
668-
.spawn()?
669-
.wait()?;
670-
if !exit_status.success() {
676+
if let Err(e) = install_with_avm(anchor_version, false) {
671677
eprintln!(
672-
"Failed to install `anchor` {anchor_version}, \
673-
using {current_version} instead"
678+
"Failed to install `anchor`: {e}, using {current_version} instead"
674679
);
675-
676680
return Ok(restore_cbs);
677681
}
678682
}
@@ -692,6 +696,23 @@ fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainC
692696
Ok(restore_cbs)
693697
}
694698

699+
/// Installs Anchor using AVM, passing `--force` (and optionally) installing
700+
/// `solana-verify`.
701+
fn install_with_avm(version: &str, verify: bool) -> Result<()> {
702+
let mut cmd = std::process::Command::new("avm");
703+
cmd.arg("install");
704+
cmd.arg(version);
705+
cmd.arg("--force");
706+
if verify {
707+
cmd.arg("--verify");
708+
}
709+
let status = cmd.status().context("running AVM")?;
710+
if !status.success() {
711+
bail!("failed to install `anchor` {version} with avm");
712+
}
713+
Ok(())
714+
}
715+
695716
/// Restore toolchain to how it was before the command was run.
696717
fn restore_toolchain(restore_cbs: RestoreToolchainCallbacks) -> Result<()> {
697718
for restore_toolchain in restore_cbs {
@@ -1898,7 +1919,13 @@ pub fn verify(
18981919
command_args.extend(args);
18991920

19001921
println!("Verifying program {program_id}");
1901-
let status = std::process::Command::new("solana-verify")
1922+
let verify_path = AVM_HOME.join("bin").join("solana-verify");
1923+
if !verify_path.exists() {
1924+
install_with_avm(env!("CARGO_PKG_VERSION"), true)
1925+
.context("installing Anchor with solana-verify")?;
1926+
}
1927+
1928+
let status = std::process::Command::new(verify_path)
19021929
.arg("verify-from-repo")
19031930
.args(&command_args)
19041931
.stdout(std::process::Stdio::inherit())

0 commit comments

Comments
 (0)