diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 391d00322c..241f4b0bea 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -168,6 +168,8 @@ jobs: - name: Install SP1 toolchain run: | cargo run -p sp1-cli -- prove install-toolchain + cd crates/cli + cargo install --locked --path . - name: Run cargo fmt run: | diff --git a/Cargo.lock b/Cargo.lock index 21b1074ae0..6d03ed4704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1288,6 +1288,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-lock" +version = "10.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6469776d007022d505bbcc2be726f5f096174ae76d710ebc609eb3029a45b551" +dependencies = [ + "semver 1.0.23", + "serde", + "toml", + "url", +] + [[package]] name = "cargo-platform" version = "0.1.8" @@ -5267,6 +5279,15 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5513,10 +5534,13 @@ name = "sp1-build" version = "3.0.0" dependencies = [ "anyhow", + "cargo-lock", "cargo_metadata", "chrono", "clap", "dirs", + "semver 1.0.23", + "serde", ] [[package]] @@ -6624,11 +6648,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -6648,6 +6687,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap 2.6.0", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.6.20", ] diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index b28b75d621..d81f3e36d3 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -15,3 +15,6 @@ anyhow = { version = "1.0.83" } clap = { version = "4.5.9", features = ["derive", "env"] } dirs = "5.0.1" chrono = { version = "0.4.38", default-features = false, features = ["clock"] } +serde = { workspace = true, features = ["derive"] } +semver = "1.0.23" +cargo-lock = "10.0.1" diff --git a/crates/build/src/build.rs b/crates/build/src/build.rs index 37ee7392ac..1cf0beac4e 100644 --- a/crates/build/src/build.rs +++ b/crates/build/src/build.rs @@ -1,7 +1,6 @@ -use std::path::PathBuf; - use anyhow::Result; use cargo_metadata::camino::Utf8PathBuf; +use std::path::{Path, PathBuf}; use crate::{ command::{docker::create_docker_command, local::create_local_command, utils::execute_command}, @@ -32,6 +31,8 @@ pub fn execute_build_program( let program_dir: Utf8PathBuf = program_dir.try_into().expect("Failed to convert PathBuf to Utf8PathBuf"); + verify_locked_version(&program_dir, args.verbose).expect("Version check mismatch"); + // Get the program metadata. let program_metadata_file = program_dir.join("Cargo.toml"); let mut program_metadata_cmd = cargo_metadata::MetadataCommand::new(); @@ -54,9 +55,15 @@ pub fn execute_build_program( } /// Internal helper function to build the program with or without arguments. +/// +/// Note: This function is not intended to be used by the CLI, as it looks for the sp1-sdk, +/// which is probably in the same crate lockfile as this function is only called by build script. pub(crate) fn build_program_internal(path: &str, args: Option) { // Get the root package name and metadata. let program_dir = std::path::Path::new(path); + verify_locked_version(program_dir, args.as_ref().map(|args| args.verbose).unwrap_or(false)) + .expect("Locked version mismatch"); + let metadata_file = program_dir.join("Cargo.toml"); let mut metadata_cmd = cargo_metadata::MetadataCommand::new(); let metadata = metadata_cmd.manifest_path(metadata_file).exec().unwrap(); @@ -180,3 +187,120 @@ fn print_elf_paths_cargo_directives(target_elf_paths: &[(String, Utf8PathBuf)]) println!("cargo:rustc-env=SP1_ELF_{}={}", target_name, elf_path); } } + +/// Verify that the locked version of `sp1-zkvm` in the Cargo.lock file is compatible with the +/// current version of this crate. +/// +/// This also checks to ensure that `sp1-sdk` is also the correct version. +/// +/// ## Note: This function assumes that version compatibility is given by matching major and minor +/// semver. +/// +/// This is also correct if future releases sharing the workspace version, which should be the case. +/// +/// # Errors: +/// - If the locked version of `sp1-zkvm` is incompatible with the current toolchain version. +/// - If the locked version of `sp1-sdk` is incompatible with the current toolchain version. +/// - +/// +/// # Panics +/// - If we cant IO +/// - If we cant find your lockfile +/// - If we cant parse the version from the `cargo-prove` +fn verify_locked_version(program_dir: impl AsRef, verbose: bool) -> Result<()> { + if std::env::var("DEV_SP1_SKIP_VERSION_CHECK").is_ok() { + println!("cargo:warning=Skipping version check"); + return Ok(()); + } + + // We need an exception for our test fixtures. + if std::env::var("CARGO_PKG_NAME").unwrap_or_default() == "test-artifacts" { + return Ok(()); + } + + // This might be a workspace, need to optionally search parent dirs for lock files + let canon = + program_dir.as_ref().canonicalize().expect("Failed to canonicalize path to program dir"); + + let mut lock_path = canon.join("Cargo.lock"); + if !lock_path.is_file() { + let mut curr_path: &Path = canon.as_ref(); + + while let Some(parent) = curr_path.parent() { + let maybe_lock_path = parent.join("Cargo.lock"); + + if maybe_lock_path.is_file() { + lock_path = maybe_lock_path; + break; + } else { + curr_path = parent; + } + } + + if !lock_path.is_file() { + panic!("Failed to find Cargo.lock in the program directory or its parent directories"); + } + } + + println!("cargo:warning=Found Cargo.lock at {}", lock_path.display()); + + let lock_file = cargo_lock::Lockfile::load(lock_path).expect("Failed to load Cargo.lock"); + + // You need an entrypoint, therefore you need sp1-zkvm. + let vm_version = &lock_file + .packages + .iter() + .find(|p| p.name.as_str() == "sp1-zkvm") + .expect("The guest program should have a sp1-zkvm dependency") + .version; + + let maybe_sdk = + lock_file.packages.iter().find(|p| p.name.as_str() == "sp1-sdk").map(|p| &p.version); + + // Print these just to be useful. + // Most people will install the actual rust toolchain along with thier cargo-prove, + // so we can just use the cargo-prove version. + let toolchain_version = { + let output = std::process::Command::new("cargo") + .args(["prove", "--version"]) + .output() + .expect("Failed to find check toolchain version, see https://docs.succinct.xyz/getting-started/install.html"); + + let version = String::from_utf8(output.stdout).expect("Failed to parse version string"); + + semver::Version::parse( + version + .split_once('\n') + .expect("The version should have whitespace, this is a bug") + .1 + .trim(), + ) + .expect("Failed to parse version string, this is a bug") + }; + + if verbose { + maybe_sdk.inspect(|v| { + println!("cargo:warning=Locked version of sp1-sdk is {}", v); + }); + println!("cargo:warning=Locked version of sp1-zkvm is {}", vm_version); + println!("cargo:warning=Current toolchain version = {}", toolchain_version); + } + + if vm_version.major != toolchain_version.major || vm_version.minor != toolchain_version.minor { + return Err(anyhow::anyhow!( + "Locked version of sp1-zkvm is incompatible with the current toolchain version" + )); + } + + if let Some(sdk_version) = maybe_sdk { + if sdk_version.major != toolchain_version.major + || sdk_version.minor != toolchain_version.minor + { + return Err(anyhow::anyhow!( + "Locked version of sp1-sdk is incompatible with the current toolchain version" + )); + } + } + + Ok(()) +} diff --git a/crates/build/src/lib.rs b/crates/build/src/lib.rs index 1dbbec1347..b9e448aca8 100644 --- a/crates/build/src/lib.rs +++ b/crates/build/src/lib.rs @@ -76,6 +76,8 @@ pub struct BuildArgs { default_value = DEFAULT_OUTPUT_DIR )] pub output_directory: String, + #[clap(long, action, help = "Any debug logs from the build script will be printed to stdout")] + pub verbose: bool, } // Implement default args to match clap defaults. @@ -93,6 +95,7 @@ impl Default for BuildArgs { output_directory: DEFAULT_OUTPUT_DIR.to_string(), locked: false, no_default_features: false, + verbose: false, } } } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 9e717aadae..32817a1e92 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -6,8 +6,16 @@ use std::process::{Command, Stdio}; pub const RUSTUP_TOOLCHAIN_NAME: &str = "succinct"; -pub const SP1_VERSION_MESSAGE: &str = - concat!("sp1", " (", env!("VERGEN_GIT_SHA"), " ", env!("VERGEN_BUILD_TIMESTAMP"), ")"); +pub const SP1_VERSION_MESSAGE: &str = concat!( + "sp1", + " (", + env!("VERGEN_GIT_SHA"), + " ", + env!("VERGEN_BUILD_TIMESTAMP"), + ")", + "\n", + env!("CARGO_PKG_VERSION") +); trait CommandExecutor { fn run(&mut self) -> Result<()>; diff --git a/crates/test-artifacts/Cargo.toml b/crates/test-artifacts/Cargo.toml index 6954ca7c0c..14b9571aae 100644 --- a/crates/test-artifacts/Cargo.toml +++ b/crates/test-artifacts/Cargo.toml @@ -11,4 +11,4 @@ categories.workspace = true sp1-build = { workspace = true } [build-dependencies] -sp1-build = { workspace = true } \ No newline at end of file +sp1-build = { workspace = true }