diff --git a/.github/workflows/committer_and_os_cli_push.yml b/.github/workflows/committer_and_os_cli_push.yml index f635e41d583..5a6624bcdfd 100644 --- a/.github/workflows/committer_and_os_cli_push.yml +++ b/.github/workflows/committer_and_os_cli_push.yml @@ -79,7 +79,7 @@ jobs: - run: pip install -r scripts/requirements.txt - name: Build CLI binary - run: ./build_native_in_docker.sh rustup toolchain install && cargo build -p starknet_committer_and_os_cli -r --bin starknet_committer_and_os_cli --target-dir CLI_TARGET + run: ./build_native_in_docker.sh cargo build -p starknet_committer_and_os_cli -r --bin starknet_committer_and_os_cli --target-dir CLI_TARGET - id: auth uses: "google-github-actions/auth@v2" diff --git a/crates/apollo_starknet_os_program/Cargo.toml b/crates/apollo_starknet_os_program/Cargo.toml index ff53d25c7e2..1cdbefdf2a9 100644 --- a/crates/apollo_starknet_os_program/Cargo.toml +++ b/crates/apollo_starknet_os_program/Cargo.toml @@ -14,6 +14,7 @@ dump_source_files = [] workspace = true [dependencies] +apollo_infra_utils.workspace = true cairo-vm.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/crates/apollo_starknet_os_program/src/lib.rs b/crates/apollo_starknet_os_program/src/lib.rs index 0c3a9f28a83..bc2ec16ff72 100644 --- a/crates/apollo_starknet_os_program/src/lib.rs +++ b/crates/apollo_starknet_os_program/src/lib.rs @@ -14,15 +14,15 @@ pub static CAIRO_FILES_MAP: LazyLock> = LazyLock::new(|| .unwrap_or_else(|error| panic!("Failed to deserialize cairo_files_map.json: {error:?}.")) }); +pub const OS_PROGRAM_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/starknet_os_bytes")); + pub static OS_PROGRAM: LazyLock = LazyLock::new(|| { - Program::from_bytes( - include_bytes!(concat!(env!("OUT_DIR"), "/starknet_os_bytes")), - Some("main"), - ) - .expect("Failed to load the OS bytes.") + Program::from_bytes(OS_PROGRAM_BYTES, Some("main")).expect("Failed to load the OS bytes.") }); pub static PROGRAM_HASH: LazyLock = LazyLock::new(|| { + // As the program hash file may not exist at runtime, it's contents must be included at compile + // time. serde_json::from_str(include_str!("program_hash.json")) .expect("Failed to deserialize program_hash.json.") }); diff --git a/crates/apollo_starknet_os_program/src/program_hash.rs b/crates/apollo_starknet_os_program/src/program_hash.rs index 773b2db834f..d0183047a13 100644 --- a/crates/apollo_starknet_os_program/src/program_hash.rs +++ b/crates/apollo_starknet_os_program/src/program_hash.rs @@ -20,9 +20,9 @@ pub enum ProgramHashError { UnexpectedRelocatable, } -#[derive(Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct ProgramHash { - os: Felt, + pub os: Felt, } const BOOTLOADER_VERSION: u8 = 0; diff --git a/crates/apollo_starknet_os_program/src/program_hash_test.rs b/crates/apollo_starknet_os_program/src/program_hash_test.rs index 2327492bd71..e29c7bdb13d 100644 --- a/crates/apollo_starknet_os_program/src/program_hash_test.rs +++ b/crates/apollo_starknet_os_program/src/program_hash_test.rs @@ -1,7 +1,31 @@ -use crate::program_hash::compute_os_program_hash; +use std::path::PathBuf; +use std::sync::LazyLock; + +use apollo_infra_utils::compile_time_cargo_manifest_dir; + +use crate::program_hash::{compute_os_program_hash, ProgramHash}; use crate::PROGRAM_HASH; +static PROGRAM_HASH_PATH: LazyLock = LazyLock::new(|| { + PathBuf::from(compile_time_cargo_manifest_dir!()).join("src/program_hash.json") +}); + +/// Asserts the program hash of the compiled Starknet OS program matches the program hash in the +/// JSON. +/// To fix this test, run the following command: +/// ```bash +/// FIX_PROGRAM_HASH=1 cargo test -p apollo_starknet_os_program test_program_hash +/// ``` #[test] fn test_program_hash() { - assert_eq!(compute_os_program_hash().unwrap(), PROGRAM_HASH.os) + let computed_hash = ProgramHash { os: compute_os_program_hash().unwrap() }; + if std::env::var("FIX_PROGRAM_HASH").is_ok() { + std::fs::write( + PROGRAM_HASH_PATH.as_path(), + serde_json::to_string_pretty(&computed_hash).unwrap(), + ) + .unwrap_or_else(|error| panic!("Failed to write the program hash file: {error:?}.")); + } else { + assert_eq!(computed_hash, *PROGRAM_HASH); + } } diff --git a/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs b/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs index f59d7752eae..3dbb91288a7 100644 --- a/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs +++ b/crates/starknet_committer_and_os_cli/src/os_cli/commands.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::Path; -use apollo_starknet_os_program::CAIRO_FILES_MAP; +use apollo_starknet_os_program::{CAIRO_FILES_MAP, OS_PROGRAM_BYTES, PROGRAM_HASH}; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_vm::types::layout_name::LayoutName; use cairo_vm::vm::runners::cairo_pie::CairoPie; @@ -14,7 +14,7 @@ use starknet_os::io::os_output::StarknetOsRunnerOutput; use starknet_os::runner::run_os_stateless; use tracing::info; -use super::run_os_cli::OsCliOutput; +use crate::os_cli::run_os_cli::{OsCliOutput, ProgramToDump}; use crate::shared_utils::read::{load_input, write_to_file}; #[derive(Deserialize, Debug)] @@ -106,3 +106,18 @@ pub(crate) fn serialize_os_runner_output( pub(crate) fn dump_source_files(output_path: String) { write_to_file(&output_path, &*CAIRO_FILES_MAP); } + +pub(crate) fn dump_program(output_path: String, program: ProgramToDump) { + let bytes = match program { + ProgramToDump::Os => OS_PROGRAM_BYTES, + }; + // Dumping the `Program` struct won't work - it is not deserializable via cairo-lang's Program + // class. JSONify the raw bytes instead. + let program_json = serde_json::from_slice::(bytes) + .expect("Program bytes are JSON-serializable."); + write_to_file(&output_path, &program_json); +} + +pub(crate) fn dump_program_hashes(output_path: String) { + write_to_file(&output_path, &*PROGRAM_HASH); +} diff --git a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs index 87de29fdef6..c3636708cfc 100644 --- a/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs +++ b/crates/starknet_committer_and_os_cli/src/os_cli/run_os_cli.rs @@ -9,7 +9,12 @@ use tracing::level_filters::LevelFilter; use tracing_subscriber::reload::Handle; use tracing_subscriber::Registry; -use crate::os_cli::commands::{dump_source_files, parse_and_run_os}; +use crate::os_cli::commands::{ + dump_program, + dump_program_hashes, + dump_source_files, + parse_and_run_os, +}; use crate::os_cli::tests::python_tests::OsPythonTestRunner; use crate::shared_utils::types::{run_python_test, IoArgs, PythonTestArg}; @@ -19,8 +24,28 @@ pub struct OsCliCommand { command: Command, } +#[derive(clap::ValueEnum, Clone, Debug, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum ProgramToDump { + Os, +} + #[derive(Debug, Subcommand)] enum Command { + DumpProgram { + /// File path to output. + #[clap(long, short = 'o', default_value = "stdout")] + output_path: String, + + /// Program to dump. + #[clap(long, value_enum)] + program: ProgramToDump, + }, + DumpProgramHashes { + /// File path to output. + #[clap(long, short = 'o', default_value = "stdout")] + output_path: String, + }, DumpSourceFiles { /// File path to output. #[clap(long, short = 'o')] @@ -39,6 +64,8 @@ pub async fn run_os_cli( ) { info!("Starting starknet-os-cli with command: \n{:?}", os_command); match os_command.command { + Command::DumpProgram { output_path, program } => dump_program(output_path, program), + Command::DumpProgramHashes { output_path } => dump_program_hashes(output_path), Command::DumpSourceFiles { output_path } => dump_source_files(output_path), Command::PythonTest(python_test_arg) => { run_python_test::(python_test_arg).await;