diff --git a/.github/workflows/ci_run.yml b/.github/workflows/ci_run.yml index 8dee30fc..c9fcccc0 100644 --- a/.github/workflows/ci_run.yml +++ b/.github/workflows/ci_run.yml @@ -286,12 +286,12 @@ jobs: # Download and extract Filecoin proof parameters from S3 - name: "EXEC: {Download proof parameters from S3}, independent" run: | - mkdir -p ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters/ + mkdir -p /var/tmp/filecoin-proof-parameters/ curl -L https://fil-proof-params-2k-cache.s3.us-east-2.amazonaws.com/filecoin-proof-params-2k.tar -o /tmp/filecoin-proof-params-2k.tar - tar -xf /tmp/filecoin-proof-params-2k.tar -C ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters/ + tar -xf /tmp/filecoin-proof-params-2k.tar -C /var/tmp/filecoin-proof-parameters/ rm /tmp/filecoin-proof-params-2k.tar - ls -lath ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters/ - PROOF_PARAMS_HASH=$(find ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters -type f -exec sha256sum {} \; | cut -d' ' -f1 | sort | sha256sum | cut -d' ' -f1) + ls -lath /var/tmp/filecoin-proof-parameters/ + PROOF_PARAMS_HASH=$(find /var/tmp/filecoin-proof-parameters -type f -exec sha256sum {} \; | cut -d' ' -f1 | sort | sha256sum | cut -d' ' -f1) echo "Downloaded proof parameters with hash: $PROOF_PARAMS_HASH" # Verify cluster is running correctly diff --git a/README_ADVANCED.md b/README_ADVANCED.md index b02c5f2b..42646a5d 100644 --- a/README_ADVANCED.md +++ b/README_ADVANCED.md @@ -39,7 +39,7 @@ foc-devnet init [OPTIONS] - `--lotus ` - Lotus source location - `--filecoin-services ` - Filecoin Services source location - `--pdp ` - PDP source location. If omitted, uses the PDP submodule bundled with Filecoin Services. -- `--proof-params-dir ` - Local proof params directory +- `--proof-params-dir ` - Local proof params directory to copy into the global cache at `/var/tmp/filecoin-proof-parameters` - `--rand` - Use random mnemonic instead of deterministic one. Use this for unique test scenarios. **Source Format:** @@ -359,6 +359,15 @@ $FOC_DEVNET_BASEDIR/ └── tmp/ ``` +Filecoin proof parameters are not stored under `FOC_DEVNET_BASEDIR`. They use the standard global host cache at `/var/tmp/filecoin-proof-parameters`, which is bind-mounted into Lotus, lotus-miner, and Curio containers at the same path. + +To reuse parameters from an older foc-devnet-private cache, copy them manually before starting: + +```bash +mkdir -p /var/tmp/filecoin-proof-parameters +cp -R ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters/. /var/tmp/filecoin-proof-parameters/ +``` + --- ## Directory Structure @@ -374,8 +383,7 @@ $FOC_DEVNET_BASEDIR/ │ └── multicall3/ # Multicall3 contracts ├── docker/ │ └── volumes/ -│ ├── cache/ # Shared cache (proof params, etc.) -│ │ └── filecoin-proof-parameters/ +│ ├── cache/ # Shared foc-devnet build/cache data │ └── run-specific/ # Run-isolated volumes │ └── / # Each run has its own volumes │ ├── lotus-data/ # Lotus blockchain data diff --git a/src/commands/init/artifacts.rs b/src/commands/init/artifacts.rs index 3529a8cd..684282b2 100644 --- a/src/commands/init/artifacts.rs +++ b/src/commands/init/artifacts.rs @@ -29,7 +29,7 @@ pub fn stage_artifacts(proof_params_dir: Option) -> Result<(), Box Result<(), Box Result<(), Box> { let params_dir = foc_devnet_proof_parameters(); // Check if parameters already exist - if params_dir.exists() && params_dir.read_dir()?.next().is_some() { + if dir_has_entries(¶ms_dir)? { info!( "✓ Proof parameters already exist at: {}", params_dir.display() @@ -36,10 +40,7 @@ pub fn ensure_proof_parameters() -> Result<(), Box> { return Ok(()); } - info!( - "Proof parameters directory does not exist: {}", - params_dir.display() - ); + info!("Proof parameters not found at: {}", params_dir.display()); info!("⬇ Downloading proof parameters (this may take a while)..."); @@ -68,14 +69,12 @@ pub fn ensure_proof_parameters() -> Result<(), Box> { /// /// This is the primary download method that uses the lotus binary's /// built-in parameter fetching functionality. -fn download_via_lotus_fetch_params( - params_dir: &std::path::Path, -) -> Result<(), Box> { +fn download_via_lotus_fetch_params(params_dir: &Path) -> Result<(), Box> { // Retry the download operation in case of network issues retry_with_fixed_delay( || { - // Ensure directory exists for each attempt (in case cleanup removed it) - fs::create_dir_all(params_dir)?; + let staging_dir = proof_params_staging_dir("foc-proof-params-fetch-", params_dir)?; + let staging_path = staging_dir.path().to_path_buf(); // Run lotus fetch-params in builder container let bin_dir = foc_devnet_bin(); @@ -89,22 +88,19 @@ fn download_via_lotus_fetch_params( .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), ); - let bytes_downloaded = Arc::new(Mutex::new(0u64)); let start_time = Instant::now(); - let bytes_clone = Arc::clone(&bytes_downloaded); - let params_dir_clone = params_dir.to_path_buf(); + let staging_path_for_progress = staging_path.clone(); + let stop_progress = Arc::new(AtomicBool::new(false)); + let stop_progress_clone = Arc::clone(&stop_progress); // Spawn a thread to update progress by monitoring directory size let pb_clone = pb.clone(); let update_handle = thread::spawn(move || { - loop { + while !stop_progress_clone.load(Ordering::Relaxed) { thread::sleep(Duration::from_millis(500)); // Calculate directory size - if let Ok(size) = get_dir_size(¶ms_dir_clone) { - let mut total = bytes_clone.lock().unwrap(); - *total = size; - + if let Ok(size) = get_dir_size(&staging_path_for_progress) { let elapsed = start_time.elapsed().as_secs_f64(); if elapsed > 0.0 { let speed_mbps = (size as f64 / 1_048_576.0) / elapsed; @@ -116,11 +112,7 @@ fn download_via_lotus_fetch_params( } } - if !pb_clone.is_finished() { - pb_clone.tick(); - } else { - break; - } + pb_clone.tick(); } }); @@ -143,7 +135,7 @@ fn download_via_lotus_fetch_params( push_bind_mount(&mut docker_args, &bin_dir, "/output")?; push_bind_mount( &mut docker_args, - params_dir, + &staging_path, CONTAINER_FILECOIN_PROOF_PARAMS_PATH, )?; docker_args.extend([ @@ -155,27 +147,36 @@ fn download_via_lotus_fetch_params( super::constants::PROOF_PARAMS_SECTOR_SIZE ), ]); - let child = Command::new("docker") + let child = match Command::new("docker") .args(&docker_args) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn()?; - - let output = child.wait_with_output()?; + .spawn() + { + Ok(child) => child, + Err(error) => { + stop_progress.store(true, Ordering::Relaxed); + pb.finish_and_clear(); + let _ = update_handle.join(); + return Err(error.into()); + } + }; + + let output = match child.wait_with_output() { + Ok(output) => output, + Err(error) => { + stop_progress.store(true, Ordering::Relaxed); + pb.finish_and_clear(); + let _ = update_handle.join(); + return Err(error.into()); + } + }; + stop_progress.store(true, Ordering::Relaxed); pb.finish_and_clear(); - drop(update_handle); + let _ = update_handle.join(); if !output.status.success() { - // Clean up partial download on failure - if params_dir.exists() { - if let Err(cleanup_err) = fs::remove_dir_all(params_dir) { - warn!( - "Failed to clean up partial proof parameters download: {}", - cleanup_err - ); - } - } return Err(format!( "Failed to download proof parameters: {}", String::from_utf8_lossy(&output.stderr) @@ -183,6 +184,12 @@ fn download_via_lotus_fetch_params( .into()); } + if !dir_has_entries(&staging_path)? { + return Err("lotus fetch-params produced no files".into()); + } + + merge_dir_contents(&staging_path, params_dir)?; + Ok(()) }, DEFAULT_MAX_RETRIES, @@ -195,11 +202,11 @@ fn download_via_lotus_fetch_params( /// /// This method downloads a pre-packaged tarball of proof parameters from S3, /// extracts it, and places the files in the correct location. -fn download_from_s3(params_dir: &std::path::Path) -> Result<(), Box> { - let tarball_path = std::env::temp_dir().join("filecoin-proof-params-2k.tar"); - - // Ensure params directory exists - fs::create_dir_all(params_dir)?; +fn download_from_s3(params_dir: &Path) -> Result<(), Box> { + let staging_dir = proof_params_staging_dir("foc-proof-params-s3-", params_dir)?; + let tarball_path = staging_dir.path().join("filecoin-proof-params-2k.tar"); + let extract_dir = staging_dir.path().join("extracted"); + fs::create_dir_all(&extract_dir)?; // Download tarball with retry retry_with_fixed_delay( @@ -252,16 +259,12 @@ fn download_from_s3(params_dir: &std::path::Path) -> Result<(), Box Result<(), Box std::io::Result { Ok(total_size) } + +fn dir_has_entries(path: &Path) -> std::io::Result { + Ok(path.is_dir() && path.read_dir()?.next().is_some()) +} + +fn proof_params_staging_dir( + prefix: &str, + params_dir: &Path, +) -> Result> { + let staging_parent = params_dir.parent().unwrap_or_else(|| Path::new("/var/tmp")); + fs::create_dir_all(staging_parent)?; + + Ok(tempfile::Builder::new() + .prefix(prefix) + .tempdir_in(staging_parent)?) +} + +fn merge_dir_contents(src: &Path, dst: &Path) -> Result<(), Box> { + fs::create_dir_all(dst)?; + + for entry in fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + let dest_path = dst.join(entry.file_name()); + + if path.is_dir() { + merge_dir_contents(&path, &dest_path)?; + continue; + } + + if dest_path.exists() { + warn!( + "Proof parameter file already exists, keeping existing file: {}", + dest_path.display() + ); + continue; + } + + match fs::rename(&path, &dest_path) { + Ok(()) => {} + Err(_) => { + fs::copy(&path, &dest_path)?; + fs::remove_file(&path)?; + } + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{dir_has_entries, merge_dir_contents, proof_params_staging_dir}; + use std::fs; + + #[test] + fn dir_has_entries_is_false_for_missing_directory() { + let root = tempfile::tempdir().unwrap(); + assert!(!dir_has_entries(&root.path().join("missing")).unwrap()); + } + + #[test] + fn merge_dir_contents_keeps_existing_files() { + let root = tempfile::tempdir().unwrap(); + let src = root.path().join("src"); + let dst = root.path().join("dst"); + fs::create_dir_all(&src).unwrap(); + fs::create_dir_all(&dst).unwrap(); + fs::write(src.join("param"), "new").unwrap(); + fs::write(dst.join("param"), "existing").unwrap(); + + merge_dir_contents(&src, &dst).unwrap(); + + assert_eq!(fs::read_to_string(dst.join("param")).unwrap(), "existing"); + } + + #[test] + fn merge_dir_contents_copies_nested_files() { + let root = tempfile::tempdir().unwrap(); + let src = root.path().join("src"); + let dst = root.path().join("dst"); + fs::create_dir_all(src.join("nested")).unwrap(); + fs::write(src.join("nested").join("param"), "contents").unwrap(); + + merge_dir_contents(&src, &dst).unwrap(); + + assert_eq!( + fs::read_to_string(dst.join("nested").join("param")).unwrap(), + "contents" + ); + } + + #[test] + fn proof_params_staging_dir_uses_params_parent() { + let root = tempfile::tempdir().unwrap(); + let params_dir = root.path().join("filecoin-proof-parameters"); + + let staging_dir = proof_params_staging_dir("test-proof-params-", ¶ms_dir).unwrap(); + + assert_eq!(staging_dir.path().parent().unwrap(), root.path()); + } +} diff --git a/src/paths.rs b/src/paths.rs index 85e26561..402c1754 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -169,10 +169,13 @@ pub fn foc_devnet_config() -> PathBuf { foc_devnet_home().join("config.toml") } -/// Returns the path to the Filecoin proof parameters directory -/// e.g., ~/.foc-devnet/docker/volumes/cache/filecoin-proof-parameters +/// Host path where Filecoin proof parameters are cached globally. +pub const HOST_FILECOIN_PROOF_PARAMS_PATH: &str = "/var/tmp/filecoin-proof-parameters"; + +/// Returns the path to the global Filecoin proof parameters directory +/// e.g., /var/tmp/filecoin-proof-parameters pub fn foc_devnet_proof_parameters() -> PathBuf { - foc_devnet_docker_volumes_cache().join("filecoin-proof-parameters") + PathBuf::from(HOST_FILECOIN_PROOF_PARAMS_PATH) } /// Returns the path to store BLS keys for lotus @@ -368,4 +371,23 @@ mod tests { assert!(foc_devnet_bin().starts_with("/tmp/test-foc")); assert_eq!(foc_devnet_bin(), PathBuf::from("/tmp/test-foc/bin")); } + + #[test] + fn test_proof_parameters_use_global_cache_path() { + assert_eq!( + foc_devnet_proof_parameters(), + PathBuf::from(HOST_FILECOIN_PROOF_PARAMS_PATH) + ); + } + + #[test] + fn test_proof_parameters_ignore_custom_basedir() { + let _lock = ENV_MUTEX.lock().unwrap(); + let _guard = EnvGuard::new("FOC_DEVNET_BASEDIR", Some("/tmp/test-foc")); + + assert_eq!( + foc_devnet_proof_parameters(), + PathBuf::from("/var/tmp/filecoin-proof-parameters") + ); + } }