diff --git a/src/commands/start/lotus/lotus_step.rs b/src/commands/start/lotus/lotus_step.rs index 36071e8..80f33bb 100644 --- a/src/commands/start/lotus/lotus_step.rs +++ b/src/commands/start/lotus/lotus_step.rs @@ -7,7 +7,7 @@ use super::super::step::{SetupContext, Step}; use super::container_management::{ check_existing_container, start_container, wait_for_container_init, }; -use super::prerequisites::{check_genesis_and_params, check_image_and_binary}; +use super::prerequisites::check_genesis_and_params; use super::setup::{build_docker_command, setup_directories}; use super::verification::{verify_api_connectivity, verify_ports, wait_for_api_file}; use std::error::Error; @@ -39,7 +39,6 @@ impl Step for LotusStep { /// Perform pre-execution checks fn pre_execute(&self, context: &SetupContext) -> Result<(), Box> { let run_id = context.run_id(); - check_image_and_binary()?; check_genesis_and_params(run_id)?; // Allocate ports for Lotus diff --git a/src/commands/start/lotus/prerequisites.rs b/src/commands/start/lotus/prerequisites.rs index c2bf1f0..26007b1 100644 --- a/src/commands/start/lotus/prerequisites.rs +++ b/src/commands/start/lotus/prerequisites.rs @@ -4,10 +4,7 @@ //! before starting the Lotus daemon container. use super::super::genesis::constants::GENESIS_FILE; -use crate::constants::LOTUS_DOCKER_IMAGE; -use crate::paths::{ - foc_devnet_bin, foc_devnet_genesis, foc_devnet_genesis_sectors, foc_devnet_proof_parameters, -}; +use crate::paths::{foc_devnet_genesis, foc_devnet_genesis_sectors, foc_devnet_proof_parameters}; use std::error::Error; use tracing::info; @@ -26,28 +23,6 @@ pub fn verify_genesis_file(run_id: &str) -> Result Result<(), Box> { - // Verify Docker image exists - if !crate::docker::core::image_exists(LOTUS_DOCKER_IMAGE).unwrap_or(true) { - return Err(format!( - "Docker image '{}' not found. Please run 'foc-devnet init' to build the image.", - LOTUS_DOCKER_IMAGE - ) - .into()); - } - info!("✓ Docker image '{}' found", LOTUS_DOCKER_IMAGE); - - // Verify lotus binary exists - let lotus_bin = foc_devnet_bin().join("lotus"); - if !lotus_bin.exists() { - return Err("Lotus binary not found. Please run 'foc-devnet build lotus' first.".into()); - } - - info!("✓ Lotus binary found"); - Ok(()) -} - /// Check that genesis file, proof parameters, and sectors exist pub fn check_genesis_and_params(run_id: &str) -> Result<(), Box> { // Verify genesis file exists diff --git a/src/commands/start/mod.rs b/src/commands/start/mod.rs index fb09d89..0eda03c 100644 --- a/src/commands/start/mod.rs +++ b/src/commands/start/mod.rs @@ -9,6 +9,7 @@ mod lotus_miner; mod lotus_utils; mod multicall3_deploy; mod pdp_service_provider; +pub mod prerequisites_check; pub mod step; mod synapse_test_e2e; mod usdfc_deploy; @@ -23,6 +24,7 @@ use lotus::LotusStep; use lotus_miner::LotusMinerStep; use multicall3_deploy::MultiCall3DeployStep; use pdp_service_provider::PdpSpRegistrationStep; +use prerequisites_check::PrerequisitesCheckStep; pub use step::{execute_steps, execute_steps_parallel, SetupContext, Step}; use synapse_test_e2e::SynapseTestE2EStep; use usdfc_deploy::USDFCDeployStep; @@ -254,18 +256,22 @@ fn create_steps( /// /// # Parallelization Strategy /// -/// - Epoch 1: Lotus + Yugabyte (independent services) -/// - Epoch 2: Lotus Miner (depends on Lotus) -/// - Epoch 3: ETH Account Funding (needs blockchain running) -/// - Epoch 4: MockUSDFC Deploy + MultiCall3 Deploy + FOC Deploy (can be parallelized) -/// - Epoch 5: MockUSDFC Funding + Curio daemons (can be parallelized, needs FOC Deploy) -/// - Epoch 6: PDP SP Registration (needs Curio daemons started) +/// - Epoch 1: Prerequisites check (binaries & Docker images - must run first) +/// - Epoch 2: Lotus (daemon start) +/// - Epoch 3: Lotus Miner (depends on Lotus) +/// - Epoch 4: ETH Account Funding (needs blockchain running) +/// - Epoch 5: MockUSDFC Deploy + MultiCall3 Deploy (can be parallelized) +/// - Epoch 6: FOC Deploy + MockUSDFC Funding + Yugabyte (can be parallelized, needs USDFC deployed) +/// - Epoch 7: Curio daemons (needs Yugabyte) +/// - Epoch 8: PDP SP Registration (needs Curio running, for port information) +/// - Epoch 9: Synapse E2E Test (final validation) fn create_step_epochs( volumes_dir: &Path, run_dir: &Path, config: &Config, notest: bool, ) -> Vec>> { + let prerequisites_check_step = PrerequisitesCheckStep::new(); let lotus_step = LotusStep::new(volumes_dir.to_path_buf(), run_dir.to_path_buf()); let yugabyte_step = YugabyteStep::new( volumes_dir.to_path_buf(), @@ -296,28 +302,30 @@ fn create_step_epochs( SynapseTestE2EStep::new(volumes_dir.to_path_buf(), run_dir.to_path_buf(), notest); vec![ - // Epoch 1: Start Lotus + // Epoch 1: Prerequisites check (binaries & Docker images - must run first) + vec![Box::new(prerequisites_check_step)], + // Epoch 2: Start Lotus vec![Box::new(lotus_step)], - // Epoch 2: Start Lotus Miner (depends on Lotus) + // Epoch 3: Start Lotus Miner (depends on Lotus) vec![Box::new(lotus_miner_step)], - // Epoch 3: ETH Account Funding (needs blockchain running) + // Epoch 4: ETH Account Funding (needs blockchain running) vec![Box::new(eth_acc_funding_step)], - // Epoch 4: Deploy contracts (can be parallelized) + // Epoch 5: Deploy contracts (can be parallelized) vec![ Box::new(usdfc_deploy_step), Box::new(multicall3_deploy_step), ], - // Epoch 5: Fund accounts with USDFC, deploy foc (needs usdfc deployed), start yugabyte for curio later + // Epoch 6: Fund accounts with USDFC, deploy foc (needs usdfc deployed), start yugabyte for curio later vec![ Box::new(foc_deploy_step), Box::new(usdfc_funding_step), Box::new(yugabyte_step), ], - // Epoch 6: Start Curio daemons + // Epoch 7: Start Curio daemons vec![Box::new(curio_step)], - // Epoch 7: Register PDP SPs (needs Curio running, for port information) + // Epoch 8: Register PDP SPs (needs Curio running, for port information) vec![Box::new(pdp_sp_reg_step)], - // Epoch 8: Run Synapse E2E Test + // Epoch 9: Run Synapse E2E Test vec![Box::new(synapse_test_step)], ] } diff --git a/src/commands/start/prerequisites_check.rs b/src/commands/start/prerequisites_check.rs new file mode 100644 index 0000000..652b89f --- /dev/null +++ b/src/commands/start/prerequisites_check.rs @@ -0,0 +1,140 @@ +//! Binary and Docker image availability check for cluster startup. +//! +//! This module provides a unified check for all required binaries and Docker images +//! before starting the cluster. It provides clear error messages directing users to +//! build missing components. + +use super::step::{SetupContext, Step}; +use crate::constants::{REQUIRED_BINARIES, REQUIRED_DOCKER_IMAGES}; +use crate::docker::core::image_exists; +use crate::paths::foc_devnet_bin; +use std::error::Error; +use tracing::info; + +/// Check that all required binaries exist in the bin directory. +/// +/// This check runs early in cluster startup before any containers are started, +/// ensuring that we fail fast with a helpful message if binaries are missing. +/// +/// # Errors +/// +/// Returns an error if any required binary is missing, listing all missing binaries +/// and providing instructions on how to build them. +fn check_all_binaries() -> Result<(), Box> { + let bin_dir = foc_devnet_bin(); + + let mut missing_binaries = Vec::new(); + let mut found_binaries = Vec::new(); + + for binary_name in REQUIRED_BINARIES { + let binary_path = bin_dir.join(binary_name); + if binary_path.exists() { + found_binaries.push(*binary_name); + } else { + missing_binaries.push(*binary_name); + } + } + + // Log what we found + for binary in &found_binaries { + info!("✓ Binary '{}' found", binary); + } + + // If any binaries are missing, return an error with instructions + if !missing_binaries.is_empty() { + let missing_list = missing_binaries.join("', '"); + return Err(format!( + "Missing required binaries: '{}'\n\nPlease build them with 'foc-devnet build ' \ + (e.g., 'foc-devnet build lotus' or 'foc-devnet build curio')", + missing_list + ) + .into()); + } + + info!("✓ All required binaries are available"); + Ok(()) +} + +/// Check that all required Docker images exist. +/// +/// This check runs early in cluster startup before any containers are started, +/// ensuring that we fail fast with a helpful message if Docker images are missing. +/// +/// # Errors +/// +/// Returns an error if any required Docker image is missing, listing all missing images +/// and providing instructions on how to build them. +fn check_all_docker_images() -> Result<(), Box> { + let mut missing_images = Vec::new(); + let mut found_images = Vec::new(); + + for image_name in REQUIRED_DOCKER_IMAGES { + let exists = image_exists(image_name) + .map_err(|e| format!("Failed to check Docker image '{}': {}", image_name, e))?; + + if exists { + found_images.push(*image_name); + } else { + missing_images.push(*image_name); + } + } + + // Log what we found + for image in &found_images { + info!("✓ Docker image '{}' found", image); + } + + // If any images are missing, return an error with instructions + if !missing_images.is_empty() { + let missing_list = missing_images.join("', '"); + return Err(format!( + "Missing required Docker images: '{}'\n\nPlease run 'foc-devnet init' to build all Docker images.", + missing_list + ) + .into()); + } + + info!("✓ All required Docker images are available"); + Ok(()) +} + +/// Prerequisites check step for cluster startup. +/// +/// This step verifies that all required binaries and Docker images are available +/// before starting any containers. It runs as the very first step in the startup sequence. +pub struct PrerequisitesCheckStep; + +impl PrerequisitesCheckStep { + /// Create a new PrerequisitesCheckStep + pub fn new() -> Self { + Self + } +} + +impl Default for PrerequisitesCheckStep { + fn default() -> Self { + Self::new() + } +} + +impl Step for PrerequisitesCheckStep { + fn name(&self) -> &str { + "Prerequisites Check (Binaries & Docker Images)" + } + + fn pre_execute(&self, _context: &SetupContext) -> Result<(), Box> { + // No pre-checks needed + Ok(()) + } + + fn execute(&self, _context: &SetupContext) -> Result<(), Box> { + check_all_binaries()?; + check_all_docker_images()?; + Ok(()) + } + + fn post_execute(&self, _context: &SetupContext) -> Result<(), Box> { + // No post-checks needed + Ok(()) + } +} diff --git a/src/commands/status/build_status.rs b/src/commands/status/build_status.rs index 7f9c3f7..4ad0283 100644 --- a/src/commands/status/build_status.rs +++ b/src/commands/status/build_status.rs @@ -7,7 +7,7 @@ //! - Display build timestamps //! - Show relative time since build -use crate::paths::foc_devnet_bin; +use crate::{constants::REQUIRED_BINARIES, paths::foc_devnet_bin}; use chrono::{DateTime, Utc}; use std::process::Command; use tracing::info; @@ -19,6 +19,9 @@ use super::utils::format_time_ago; /// This function displays the build status of all expected foc-devnet binaries, /// including whether they exist, their file sizes, and when they were last built. /// +/// Note: The list of expected binaries is shared with the startup binary check +/// to ensure consistency. +/// /// # Examples /// /// ```rust,no_run @@ -33,16 +36,8 @@ use super::utils::format_time_ago; pub fn print_build_status() -> Result<(), Box> { let bin_dir = foc_devnet_bin(); - // Check for expected binaries - let expected_binaries = vec![ - "lotus", - "lotus-miner", - "lotus-shed", - "lotus-seed", - "curio", - "pdptool", - "sptool", - ]; + // Get expected binaries from shared source of truth + let expected_binaries = REQUIRED_BINARIES; for binary in expected_binaries { let binary_path = bin_dir.join(binary); diff --git a/src/constants.rs b/src/constants.rs index 6453232..72eac1d 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -11,6 +11,26 @@ pub const YUGABYTE_DOCKER_IMAGE: &str = "foc-yugabyte"; pub const CURIO_DOCKER_IMAGE: &str = "foc-curio"; pub const PORTAINER_DOCKER_IMAGE: &str = "foc-portainer"; +/// Required binaries for cluster startup +pub const REQUIRED_BINARIES: &[&str] = &[ + "lotus", + "lotus-miner", + "lotus-shed", + "lotus-seed", + "curio", + "pdptool", + "sptool", +]; + +/// Required Docker images for cluster startup +pub const REQUIRED_DOCKER_IMAGES: &[&str] = &[ + LOTUS_DOCKER_IMAGE, + LOTUS_MINER_DOCKER_IMAGE, + BUILDER_DOCKER_IMAGE, + YUGABYTE_DOCKER_IMAGE, + CURIO_DOCKER_IMAGE, +]; + /// Docker container names (base - will be prefixed with foc-c-- in practice) pub const LOTUS_CONTAINER: &str = "foc-c-lotus"; pub const LOTUS_MINER_CONTAINER: &str = "foc-c-lotus-miner";