|
1 | | -use std::env; |
2 | | -use std::process::Command; |
| 1 | +use risc0_binfmt::compute_image_id; |
| 2 | +use risc0_build::{embed_methods_with_options, DockerOptionsBuilder, GuestOptionsBuilder}; |
| 3 | +use std::{collections::HashMap, env, fs, path::Path}; |
3 | 4 |
|
4 | 5 | fn main() { |
5 | | - println!("cargo:rerun-if-changed=../build.dockerfile"); |
6 | | - |
7 | | - if env::var("REPR_GUEST_BUILD").is_ok() { |
8 | | - // Get the absolute path to the project root |
9 | | - let current_dir = env::current_dir().expect("Failed to get current directory"); |
10 | | - let project_root = current_dir.parent().unwrap().parent().unwrap(); |
11 | | - let output_dir = project_root.join("target/riscv-guest/riscv32im-risc0-zkvm-elf/docker"); |
12 | | - |
13 | | - eprintln!("Current directory: {:?}", current_dir); |
14 | | - eprintln!("Project root: {:?}", project_root); |
15 | | - eprintln!("Output directory: {:?}", output_dir); |
16 | | - |
17 | | - // Ensure the output directory exists |
18 | | - std::fs::create_dir_all(&output_dir).expect("Failed to create output directory"); |
19 | | - |
20 | | - let output = Command::new("docker") |
21 | | - .args([ |
22 | | - "buildx", |
23 | | - "build", |
24 | | - "--platform", |
25 | | - "linux/amd64", |
26 | | - "-f", |
27 | | - "final-spv/build.dockerfile", |
28 | | - "--output", |
29 | | - &format!("type=local,dest=."), |
30 | | - ".", // Use current directory as context |
31 | | - "--build-arg", |
32 | | - &format!( |
33 | | - "BITCOIN_NETWORK={}", |
34 | | - std::env::var("BITCOIN_NETWORK").unwrap().as_str() |
35 | | - ), |
36 | | - ]) |
37 | | - .current_dir(project_root) // Set working directory to project root |
38 | | - .output() |
39 | | - .expect("Failed to execute Docker command"); |
40 | | - |
41 | | - if !output.status.success() { |
42 | | - eprintln!("Docker build failed:"); |
43 | | - eprintln!("stdout: {}", String::from_utf8_lossy(&output.stdout)); |
44 | | - eprintln!("stderr: {}", String::from_utf8_lossy(&output.stderr)); |
45 | | - panic!("Docker build failed"); |
| 6 | + // Build environment variables |
| 7 | + println!("cargo:rerun-if-env-changed=SKIP_GUEST_BUILD"); |
| 8 | + println!("cargo:rerun-if-env-changed=REPR_GUEST_BUILD"); |
| 9 | + println!("cargo:rerun-if-env-changed=OUT_DIR"); |
| 10 | + |
| 11 | + // Compile time constant environment variables |
| 12 | + println!("cargo:rerun-if-env-changed=BITCOIN_NETWORK"); |
| 13 | + println!("cargo:rerun-if-env-changed=TEST_SKIP_GUEST_BUILD"); |
| 14 | + |
| 15 | + if std::env::var("CLIPPY_ARGS").is_ok() { |
| 16 | + let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); |
| 17 | + let dummy_path = Path::new(&out_dir).join("methods.rs"); |
| 18 | + fs::write(dummy_path, "// dummy methods.rs for Clippy\n") |
| 19 | + .expect("Failed to write dummy methods.rs"); |
| 20 | + println!("cargo:warning=Skipping guest build in Clippy"); |
| 21 | + return; |
| 22 | + } |
| 23 | + |
| 24 | + // Check if we should skip the guest build for tests |
| 25 | + if let Ok("1" | "true") = env::var("TEST_SKIP_GUEST_BUILD").as_deref() { |
| 26 | + println!("cargo:warning=Skipping guest build in test. Exiting"); |
| 27 | + return; |
| 28 | + } |
| 29 | + |
| 30 | + let network = env::var("BITCOIN_NETWORK").unwrap_or_else(|_| { |
| 31 | + println!("cargo:warning=BITCOIN_NETWORK not set, defaulting to 'mainnet'"); |
| 32 | + "mainnet".to_string() |
| 33 | + }); |
| 34 | + println!("cargo:warning=Building for Bitcoin network: {}", network); |
| 35 | + |
| 36 | + let is_repr_guest_build = match env::var("REPR_GUEST_BUILD") { |
| 37 | + Ok(value) => match value.as_str() { |
| 38 | + "1" | "true" => { |
| 39 | + println!("cargo:warning=REPR_GUEST_BUILD is set to true"); |
| 40 | + true |
| 41 | + } |
| 42 | + "0" | "false" => { |
| 43 | + println!("cargo:warning=REPR_GUEST_BUILD is set to false"); |
| 44 | + false |
| 45 | + } |
| 46 | + _ => { |
| 47 | + println!("cargo:warning=Invalid value for REPR_GUEST_BUILD: '{}'. Expected '0', '1', 'true', or 'false'. Defaulting to false.", value); |
| 48 | + false |
| 49 | + } |
| 50 | + }, |
| 51 | + Err(env::VarError::NotPresent) => { |
| 52 | + println!("cargo:warning=REPR_GUEST_BUILD not set. Defaulting to false."); |
| 53 | + false |
| 54 | + } |
| 55 | + Err(env::VarError::NotUnicode(_)) => { |
| 56 | + println!( |
| 57 | + "cargo:warning=REPR_GUEST_BUILD contains invalid Unicode. Defaulting to false." |
| 58 | + ); |
| 59 | + false |
| 60 | + } |
| 61 | + }; |
| 62 | + |
| 63 | + // Use embed_methods_with_options with our custom options |
| 64 | + let guest_pkg_to_options = get_guest_options(network.clone()); |
| 65 | + embed_methods_with_options(guest_pkg_to_options); |
| 66 | + |
| 67 | + // After the build is complete, copy the generated file to the elfs folder |
| 68 | + if is_repr_guest_build { |
| 69 | + println!("cargo:warning=Copying binary to elfs folder"); |
| 70 | + copy_binary_to_elfs_folder(network); |
| 71 | + } else { |
| 72 | + println!("cargo:warning=Not copying binary to elfs folder"); |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +fn get_guest_options(network: String) -> HashMap<&'static str, risc0_build::GuestOptions> { |
| 77 | + let mut guest_pkg_to_options = HashMap::new(); |
| 78 | + |
| 79 | + let opts = if env::var("REPR_GUEST_BUILD").is_ok() { |
| 80 | + let manifest_dir = env!("CARGO_MANIFEST_DIR"); |
| 81 | + let root_dir = format!("{manifest_dir}/../../"); |
| 82 | + |
| 83 | + println!( |
| 84 | + "cargo:warning=Using Docker for guest build with root dir: {}", |
| 85 | + root_dir |
| 86 | + ); |
| 87 | + |
| 88 | + let docker_opts = DockerOptionsBuilder::default() |
| 89 | + .root_dir(root_dir) |
| 90 | + .env(vec![("BITCOIN_NETWORK".to_string(), network.clone())]) |
| 91 | + .build() |
| 92 | + .unwrap(); |
| 93 | + |
| 94 | + GuestOptionsBuilder::default() |
| 95 | + // .features(features) |
| 96 | + .use_docker(docker_opts) |
| 97 | + .build() |
| 98 | + .unwrap() |
| 99 | + } else { |
| 100 | + println!("cargo:warning=Guest code is not built in docker"); |
| 101 | + GuestOptionsBuilder::default() |
| 102 | + // .features(features) |
| 103 | + .build() |
| 104 | + .unwrap() |
| 105 | + }; |
| 106 | + |
| 107 | + guest_pkg_to_options.insert("final-spv-guest", opts); |
| 108 | + guest_pkg_to_options |
| 109 | +} |
| 110 | + |
| 111 | +fn copy_binary_to_elfs_folder(network: String) { |
| 112 | + let current_dir = env::current_dir().expect("Failed to get current dir"); |
| 113 | + // base_dir will be /home/ozan/workspace/BitVM/ |
| 114 | + let base_dir = current_dir |
| 115 | + .join("../..") |
| 116 | + .canonicalize() |
| 117 | + .expect("Failed to canonicalize base_dir"); |
| 118 | + |
| 119 | + // Create elfs directory if it doesn't exist |
| 120 | + let elfs_dir = base_dir.join("prover/elfs"); |
| 121 | + if !elfs_dir.exists() { |
| 122 | + fs::create_dir_all(&elfs_dir).expect("Failed to create elfs directory"); |
| 123 | + println!("cargo:warning=Created elfs directory at {:?}", elfs_dir); |
| 124 | + } |
| 125 | + |
| 126 | + // Build source path (ensure this path is correct based on risc0_build's actual output location) |
| 127 | + // This path assumes a specific structure relative to the workspace root. |
| 128 | + // A more robust way might be to get this path from OUT_DIR if risc0_build places it there predictably. |
| 129 | + let src_path = base_dir.join("target/riscv-guest/final-spv-circuit/final-spv-guest/riscv32im-risc0-zkvm-elf/docker/final-spv-guest.bin"); |
| 130 | + |
| 131 | + if !src_path.exists() { |
| 132 | + // If the source ELF from the Docker build isn't found, the copy will fail. |
| 133 | + // This could indicate an issue with the guest build itself or the path construction. |
| 134 | + // The Docker build log (#8 and #9) suggests the ELF is built. |
| 135 | + // RISC0 build scripts often place final ELFs in OUT_DIR/{method_name}. |
| 136 | + // Consider using: let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); |
| 137 | + // let src_path = out_dir.join("final-spv-guest"); // or "final-spv-guest.elf" / ".bin" |
| 138 | + println!( |
| 139 | + "cargo:warning=Source binary not found at {:?}, skipping copy. This might be the root cause if copy fails.", |
| 140 | + src_path |
| 141 | + ); |
| 142 | + // It's important to ensure src_path is correct. For now, we assume it is based on your log. |
| 143 | + // If the copy fails below, investigate src_path more deeply. |
| 144 | + // return; // Or panic, as subsequent steps depend on this. |
| 145 | + } |
| 146 | + |
| 147 | + // Build destination path with network prefix |
| 148 | + let dest_filename = format!("{}-final-spv-guest.bin", network.to_lowercase()); |
| 149 | + let dest_path = elfs_dir.join(&dest_filename); // This is an absolute PathBuf |
| 150 | + |
| 151 | + // Copy the file |
| 152 | + match fs::copy(&src_path, &dest_path) { |
| 153 | + Ok(_) => println!( |
| 154 | + "cargo:warning=Successfully copied binary from {:?} to {:?}", |
| 155 | + src_path, dest_path |
| 156 | + ), |
| 157 | + Err(e) => { |
| 158 | + // If the copy fails, the subsequent read will definitely fail. |
| 159 | + // It's crucial to handle this, e.g., by panicking to stop the build with a clear message. |
| 160 | + panic!( |
| 161 | + "Failed to copy binary from {:?} to {:?}: {}. Subsequent ELF read will fail.", |
| 162 | + src_path, dest_path, e |
| 163 | + ); |
46 | 164 | } |
47 | 165 | } |
48 | 166 |
|
49 | | - risc0_build::embed_methods(); |
| 167 | + // The `elf_path` string below is used as a logical identifier or for constructing paths within the prover, |
| 168 | + // but it should not be used directly for fs::read from the build script's CWD. |
| 169 | + let logical_elf_path_for_id = match network.as_str() { |
| 170 | + "mainnet" => "prover/elfs/mainnet-final-spv-guest.bin", |
| 171 | + "testnet4" => "prover/elfs/testnet4-final-spv-guest.bin", |
| 172 | + "signet" => "prover/elfs/signet-final-spv-guest.bin", |
| 173 | + "regtest" => "prover/elfs/regtest-final-spv-guest.bin", |
| 174 | + _ => { |
| 175 | + println!( |
| 176 | + "cargo:warning=Invalid network specified, defaulting to mainnet for logical path" |
| 177 | + ); |
| 178 | + "prover/elfs/mainnet-final-spv-guest.bin" |
| 179 | + } |
| 180 | + }; |
| 181 | + |
| 182 | + println!( |
| 183 | + "cargo:warning=Logical ELF path for ID computation: {:?}", |
| 184 | + logical_elf_path_for_id |
| 185 | + ); |
| 186 | + |
| 187 | + // --- THIS IS THE FIX --- |
| 188 | + // Read the ELF file from `dest_path`, where it was actually copied. |
| 189 | + // `dest_path` is already an absolute `PathBuf`. |
| 190 | + println!( |
| 191 | + "cargo:warning=Attempting to read ELF file from: {:?}", |
| 192 | + dest_path |
| 193 | + ); |
| 194 | + let elf_bytes: Vec<u8> = fs::read(&dest_path) // Use &dest_path here |
| 195 | + .unwrap_or_else(|e| panic!("Failed to read ELF file from {:?}: {}", dest_path, e)); |
| 196 | + |
| 197 | + let method_id = compute_image_id(elf_bytes.as_slice()).unwrap(); |
| 198 | + println!("cargo:warning=Computed method ID: {:x?}", method_id); |
| 199 | + println!( |
| 200 | + "cargo:warning=Computed method ID words: {:?}", |
| 201 | + method_id.as_words() |
| 202 | + ); |
50 | 203 | } |
0 commit comments