From 4821b3892d6bff55d172affb273e68422041f639 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Tue, 3 Feb 2026 16:26:42 +0530 Subject: [PATCH 1/9] feat: add endorsed_pdp_sp_count config parameter --- src/config.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index a0a8bc1..d1a42fc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -182,16 +182,24 @@ pub struct Config { /// /// This is the total number of Curio SPs that will be registered and approved /// in the service provider registry. These SPs can accept storage deals. - /// Must satisfy: APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT + /// Must satisfy: ENDORSED_PDP_SP_COUNT <= APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT /// Default: 1 pub approved_pdp_sp_count: usize, + /// Number of endorsed PDP service providers. + /// + /// This is the number of approved SPs that will be endorsed in the endorsements contract. + /// Endorsed providers are a privileged subset of approved providers. + /// Must satisfy: ENDORSED_PDP_SP_COUNT <= APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT + /// Default: 1 + pub endorsed_pdp_sp_count: usize, + /// Number of active PDP service providers. /// /// This is the total number of Curio SPs that will actually be started/running. /// Some may be approved, some may not (for testing unapproved SP scenarios). /// Total miners = 1 (lotus-miner) + ACTIVE_PDP_SP_COUNT (curio SPs) - /// Must satisfy: APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT + /// Must satisfy: ENDORSED_PDP_SP_COUNT <= APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT /// Default: 1 pub active_pdp_sp_count: usize, } @@ -231,6 +239,7 @@ impl Default for Config { }, yugabyte_download_url: Self::get_default_yugabyte_url(), approved_pdp_sp_count: 2, + endorsed_pdp_sp_count: 1, active_pdp_sp_count: 2, } } @@ -261,10 +270,17 @@ impl Config { /// Validate configuration values. /// /// Ensures that: - /// - APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT + /// - ENDORSED_PDP_SP_COUNT <= APPROVED_PDP_SP_COUNT <= ACTIVE_PDP_SP_COUNT <= MAX_PDP_SP_COUNT pub fn validate(&self) -> Result<(), String> { const MAX_PDP_SP_COUNT: usize = crate::constants::MAX_PDP_SP_COUNT; + if self.endorsed_pdp_sp_count > self.approved_pdp_sp_count { + return Err(format!( + "endorsed_pdp_sp_count ({}) cannot exceed approved_pdp_sp_count ({})", + self.endorsed_pdp_sp_count, self.approved_pdp_sp_count + )); + } + if self.approved_pdp_sp_count > self.active_pdp_sp_count { return Err(format!( "approved_pdp_sp_count ({}) cannot exceed active_pdp_sp_count ({})", From d490e6e814bee13d6275a2b2f8bc981811c032e1 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Tue, 3 Feb 2026 16:29:20 +0530 Subject: [PATCH 2/9] feat: propagate endorsed_pdp_sp_count through step execution --- src/commands/start/mod.rs | 2 ++ src/commands/start/step/mod.rs | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/commands/start/mod.rs b/src/commands/start/mod.rs index 3e89c50..53079fa 100644 --- a/src/commands/start/mod.rs +++ b/src/commands/start/mod.rs @@ -254,6 +254,7 @@ fn load_and_validate_config() -> Result> { info!("PDP Service Provider Configuration:"); info!("• Active PDP SPs: {}", config.active_pdp_sp_count); info!("• Approved PDP SPs: {}", config.approved_pdp_sp_count); + info!("• Endorsed PDP SPs: {}", config.endorsed_pdp_sp_count); Ok(config) } @@ -431,6 +432,7 @@ fn execute_cluster_steps( portainer_port: Some(portainer_port), active_pdp_sp_count: config.active_pdp_sp_count, approved_pdp_sp_count: config.approved_pdp_sp_count, + endorsed_pdp_sp_count: config.endorsed_pdp_sp_count, }; if parallel { diff --git a/src/commands/start/step/mod.rs b/src/commands/start/step/mod.rs index 20a3961..8ab1842 100644 --- a/src/commands/start/step/mod.rs +++ b/src/commands/start/step/mod.rs @@ -318,6 +318,7 @@ pub struct StepExecutionConfig { pub portainer_port: Option, pub active_pdp_sp_count: usize, pub approved_pdp_sp_count: usize, + pub endorsed_pdp_sp_count: usize, } pub fn execute_steps( @@ -369,6 +370,10 @@ pub fn execute_steps( "approved_pdp_sp_count", config.approved_pdp_sp_count.to_string(), ); + context.set( + "endorsed_pdp_sp_count", + config.endorsed_pdp_sp_count.to_string(), + ); let overall_start = Instant::now(); let mut all_step_timings = Vec::new(); @@ -459,6 +464,10 @@ pub fn execute_steps_parallel( "approved_pdp_sp_count", config.approved_pdp_sp_count.to_string(), ); + context.set( + "endorsed_pdp_sp_count", + config.endorsed_pdp_sp_count.to_string(), + ); let overall_start = Instant::now(); let mut all_step_timings: Vec<(String, Duration)> = Vec::new(); From 35afc61fb51df59f0d118750b94d3da7d2af6d21 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Tue, 3 Feb 2026 20:44:11 +0530 Subject: [PATCH 3/9] feat: create endorsement step module structure --- src/commands/start/endorsement/constants.rs | 10 + src/commands/start/endorsement/endorsement.rs | 187 +++++++++++++++ .../start/endorsement/endorsement_step.rs | 225 ++++++++++++++++++ src/commands/start/endorsement/mod.rs | 11 + src/commands/start/mod.rs | 2 + 5 files changed, 435 insertions(+) create mode 100644 src/commands/start/endorsement/constants.rs create mode 100644 src/commands/start/endorsement/endorsement.rs create mode 100644 src/commands/start/endorsement/endorsement_step.rs create mode 100644 src/commands/start/endorsement/mod.rs diff --git a/src/commands/start/endorsement/constants.rs b/src/commands/start/endorsement/constants.rs new file mode 100644 index 0000000..4bd8be0 --- /dev/null +++ b/src/commands/start/endorsement/constants.rs @@ -0,0 +1,10 @@ +//! Constants for endorsement operations. + +/// Container name prefix for endorsement operations +pub const ENDORSEMENT_CONTAINER_PREFIX: &str = "foc-pdp-endorse"; + +/// Wait time after endorsement transaction (seconds) +pub const ENDORSEMENT_TX_WAIT_SECS: u64 = 10; + +/// Maximum provider ID value (ProviderIdSet limitation) +pub const MAX_PROVIDER_ID: u64 = 0xFFFFFFFF; diff --git a/src/commands/start/endorsement/endorsement.rs b/src/commands/start/endorsement/endorsement.rs new file mode 100644 index 0000000..cafea27 --- /dev/null +++ b/src/commands/start/endorsement/endorsement.rs @@ -0,0 +1,187 @@ +//! Business logic for provider endorsement operations. + +use super::constants::{ENDORSEMENT_CONTAINER_PREFIX, ENDORSEMENT_TX_WAIT_SECS}; +use crate::commands::start::step::SetupContext; +use crate::constants::BUILDER_DOCKER_IMAGE; +use crate::docker::command_logger::run_and_log_command_strings; +use std::error::Error; +use std::thread; +use std::time::Duration; +use tracing::info; + +/// Parameters for endorsing a provider +pub struct EndorseParams { + pub run_id: String, + pub provider_id: u64, + pub endorsements_contract_address: String, + pub deployer_foc_address: String, + pub lotus_rpc_url: String, +} + +/// Endorse a provider in the ProviderIdSet contract. +pub fn endorse_provider( + params: EndorseParams, + context: &SetupContext, +) -> Result> { + let container_name = format!( + "{}-{}-{}", + ENDORSEMENT_CONTAINER_PREFIX, params.run_id, params.provider_id + ); + + let label = format!("Provider {}", params.provider_id); + info!("Endorsing {} in ProviderIdSet...", label); + + let cast_cmd = format!( + r#"cast send {} "addProviderId(uint256)" {} \ + --rpc-url {} \ + --from {} \ + --unlocked"#, + params.endorsements_contract_address, + params.provider_id, + params.lotus_rpc_url, + params.deployer_foc_address + ); + + let args: Vec = vec![ + "run".to_string(), + "--name".to_string(), + container_name.clone(), + "-u".to_string(), + "foc-user".to_string(), + "--network".to_string(), + "host".to_string(), + BUILDER_DOCKER_IMAGE.to_string(), + "bash".to_string(), + "-c".to_string(), + cast_cmd, + ]; + + let key = format!("pdp_endorse_{}", params.provider_id); + let output = run_and_log_command_strings("docker", &args, context, &key)?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to endorse provider: {}", stderr).into()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let tx_hash = extract_tx_hash(&stdout) + .ok_or("Failed to extract transaction hash from endorsement output")?; + + info!("Endorsement transaction: {}", tx_hash); + thread::sleep(Duration::from_secs(ENDORSEMENT_TX_WAIT_SECS)); + + verify_transaction_status(¶ms.lotus_rpc_url, &tx_hash, params.provider_id, context)?; + + info!("{} endorsed successfully", label); + + Ok(tx_hash) +} + +/// Extract transaction hash from cast send output. +fn extract_tx_hash(output: &str) -> Option { + for line in output.lines() { + if line.contains("transactionHash") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if let Some(hash) = parts.last() { + return Some(hash.to_string()); + } + } + } + None +} + +/// Verify that the endorsement transaction succeeded. +fn verify_transaction_status( + rpc_url: &str, + tx_hash: &str, + provider_id: u64, + context: &SetupContext, +) -> Result<(), Box> { + let container_name = format!("foc-verify-endorse-{}", provider_id); + + let cast_cmd = format!(r#"cast receipt {} --rpc-url {} --json"#, tx_hash, rpc_url); + + let args: Vec = vec![ + "run".to_string(), + "--name".to_string(), + container_name.clone(), + "-u".to_string(), + "foc-user".to_string(), + "--network".to_string(), + "host".to_string(), + BUILDER_DOCKER_IMAGE.to_string(), + "bash".to_string(), + "-c".to_string(), + cast_cmd, + ]; + + let key = format!("pdp_verify_endorse_tx_{}", provider_id); + let output = run_and_log_command_strings("docker", &args, context, &key)?; + + if !output.status.success() { + return Err("Failed to get transaction receipt".into()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let receipt: serde_json::Value = serde_json::from_str(&stdout)?; + + let status = receipt["status"] + .as_str() + .ok_or("Transaction status not found in receipt")?; + + if status != "0x1" { + return Err(format!( + "Endorsement transaction failed (status 0). Provider {} not endorsed.", + provider_id + ) + .into()); + } + + Ok(()) +} + +/// Parameters for verifying endorsement +pub struct VerifyEndorsementParams { + pub provider_id: u64, + pub endorsements_contract_address: String, + pub lotus_rpc_url: String, +} + +/// Verify that a provider is endorsed by checking the contract state. +pub fn verify_endorsement( + params: VerifyEndorsementParams, + context: &SetupContext, +) -> Result> { + let container_name = format!("foc-check-endorse-{}", params.provider_id); + + let cast_cmd = format!( + r#"cast call {} "containsProviderId(uint256)(bool)" {} --rpc-url {}"#, + params.endorsements_contract_address, params.provider_id, params.lotus_rpc_url + ); + + let args: Vec = vec![ + "run".to_string(), + "--name".to_string(), + container_name.clone(), + "-u".to_string(), + "foc-user".to_string(), + "--network".to_string(), + "host".to_string(), + BUILDER_DOCKER_IMAGE.to_string(), + "bash".to_string(), + "-c".to_string(), + cast_cmd, + ]; + + let key = format!("pdp_check_endorse_{}", params.provider_id); + let output = run_and_log_command_strings("docker", &args, context, &key)?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("Failed to check endorsement status: {}", stderr).into()); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(stdout.trim() == "true") +} diff --git a/src/commands/start/endorsement/endorsement_step.rs b/src/commands/start/endorsement/endorsement_step.rs new file mode 100644 index 0000000..e318dd9 --- /dev/null +++ b/src/commands/start/endorsement/endorsement_step.rs @@ -0,0 +1,225 @@ +//! Endorsement step implementation. + +use super::endorsement::{endorse_provider, verify_endorsement, EndorseParams, VerifyEndorsementParams}; +use crate::commands::start::foc_deploy::contract_addresses::ContractAddresses; +use crate::commands::start::lotus_utils; +use crate::commands::start::step::{SetupContext, Step}; +use crate::docker::containers::lotus_container_name; +use crate::docker::core::container_is_running; +use std::error::Error; +use std::path::PathBuf; +use tracing::{info, warn}; + +/// Step for endorsing PDP service providers +pub struct EndorsementStep { + #[allow(dead_code)] + run_dir: PathBuf, + endorsed_sp_count: usize, + #[allow(dead_code)] + active_sp_count: usize, +} + +impl EndorsementStep { + /// Create a new EndorsementStep + pub fn new(_volumes_dir: PathBuf, run_dir: PathBuf, endorsed_sp_count: usize, active_sp_count: usize) -> Self { + Self { + run_dir, + endorsed_sp_count, + active_sp_count, + } + } + + /// Check if Lotus is running + fn check_lotus_running(context: &SetupContext) -> Result<(), Box> { + let run_id = context.run_id(); + let container_name = lotus_container_name(run_id); + if !container_is_running(&container_name)? { + return Err("Lotus container is not running.".into()); + } + Ok(()) + } + + /// Load contract addresses from state + fn load_contract_addresses( + context: &SetupContext, + ) -> Result> { + let run_id = context.run_id(); + ContractAddresses::load(run_id) + .map_err(|e| format!("Failed to load contract addresses: {}", e).into()) + } + + /// Get deployer address from context + fn get_deployer_address(context: &SetupContext) -> Result> { + context + .get("deployer_foc_address") + .ok_or("DEPLOYER_FOC address not found in context".into()) + } +} + +impl Step for EndorsementStep { + fn name(&self) -> &str { + "PDP Provider Endorsement" + } + + fn pre_execute(&self, context: &SetupContext) -> Result<(), Box> { + info!("Pre-checking {}", self.name()); + + Self::check_lotus_running(context)?; + info!("Lotus is running"); + + let deployer_address = Self::get_deployer_address(context)?; + info!("Deployer address: {}", deployer_address); + + let contract_addresses = Self::load_contract_addresses(context)?; + let endorsements_address = contract_addresses + .foc_contracts + .get("endorsements") + .ok_or("Endorsements contract address not found")?; + info!("Endorsements contract: {}", endorsements_address); + + for sp_index in 1..=self.endorsed_sp_count { + let pdp_key = format!("pdp_sp_{}_address", sp_index); + let provider_id_key = format!("pdp_sp_{}_provider_id", sp_index); + let approved_key = format!("pdp_sp_{}_approved", sp_index); + + let sp_address = context + .get(&pdp_key) + .ok_or(format!("{} not found in context", pdp_key))?; + + let provider_id: u64 = context + .get(&provider_id_key) + .ok_or(format!("{} not found in context", provider_id_key))? + .parse()?; + + let is_approved = context + .get(&approved_key) + .and_then(|v| v.parse::().ok()) + .unwrap_or(false); + + if !is_approved { + warn!("PDP SP {} is not approved, cannot endorse", sp_index); + return Err(format!( + "PDP SP {} must be approved before it can be endorsed", + sp_index + ) + .into()); + } + + info!("PDP SP {} ready for endorsement:", sp_index); + info!(" Address: {}", sp_address); + info!(" Provider ID: {}", provider_id); + } + + Ok(()) + } + + fn execute(&self, context: &SetupContext) -> Result<(), Box> { + if self.endorsed_sp_count == 0 { + info!("No providers to endorse (endorsed_pdp_sp_count = 0), skipping"); + return Ok(()); + } + + Self::check_lotus_running(context)?; + + let run_id = context.run_id(); + let lotus_rpc_url = lotus_utils::get_lotus_rpc_url(context)?; + + let contract_addresses = Self::load_contract_addresses(context)?; + let endorsements_address = contract_addresses + .foc_contracts + .get("endorsements") + .ok_or("Endorsements contract address not found")? + .clone(); + + let deployer_address = Self::get_deployer_address(context)?; + + info!( + "Endorsing {} provider(s) in ProviderIdSet contract...", + self.endorsed_sp_count + ); + + for sp_index in 1..=self.endorsed_sp_count { + let provider_id_key = format!("pdp_sp_{}_provider_id", sp_index); + let provider_id: u64 = context + .get(&provider_id_key) + .ok_or(format!("{} not found in context", provider_id_key))? + .parse()?; + + info!("Endorsing Provider {} (ID: {})...", sp_index, provider_id); + + let params = EndorseParams { + run_id: run_id.to_string(), + provider_id, + endorsements_contract_address: endorsements_address.clone(), + deployer_foc_address: deployer_address.clone(), + lotus_rpc_url: lotus_rpc_url.clone(), + }; + + let tx_hash = endorse_provider(params, context)?; + + let tx_key = format!("pdp_sp_{}_endorsement_tx", sp_index); + context.set(&tx_key, tx_hash); + + info!("Provider {} endorsed successfully", sp_index); + } + + info!( + "All {} provider(s) endorsed successfully", + self.endorsed_sp_count + ); + + Ok(()) + } + + fn post_execute(&self, context: &SetupContext) -> Result<(), Box> { + if self.endorsed_sp_count == 0 { + return Ok(()); + } + + info!("Verifying endorsements..."); + + let lotus_rpc_url = lotus_utils::get_lotus_rpc_url(context)?; + let contract_addresses = Self::load_contract_addresses(context)?; + let endorsements_address = contract_addresses + .foc_contracts + .get("endorsements") + .ok_or("Endorsements contract address not found")? + .clone(); + + for sp_index in 1..=self.endorsed_sp_count { + let provider_id_key = format!("pdp_sp_{}_provider_id", sp_index); + let provider_id: u64 = context + .get(&provider_id_key) + .ok_or(format!("{} not found in context", provider_id_key))? + .parse()?; + + let params = VerifyEndorsementParams { + provider_id, + endorsements_contract_address: endorsements_address.clone(), + lotus_rpc_url: lotus_rpc_url.clone(), + }; + + let is_endorsed = verify_endorsement(params, context)?; + + if !is_endorsed { + return Err(format!( + "Verification failed: Provider {} is not endorsed in contract", + sp_index + ) + .into()); + } + + let endorsed_key = format!("pdp_sp_{}_endorsed", sp_index); + context.set(&endorsed_key, "true".to_string()); + + info!("Provider {} endorsement verified ✓", sp_index); + } + + info!( + "All {} endorsements verified successfully", + self.endorsed_sp_count + ); + + Ok(()) + } +} diff --git a/src/commands/start/endorsement/mod.rs b/src/commands/start/endorsement/mod.rs new file mode 100644 index 0000000..79ba298 --- /dev/null +++ b/src/commands/start/endorsement/mod.rs @@ -0,0 +1,11 @@ +//! Endorsement step for PDP service providers. +//! +//! This module handles endorsing approved PDP service providers in the +//! endorsements contract (ProviderIdSet). Endorsed providers are a privileged +//! subset of approved providers that meet quality and reliability standards. + +mod constants; +mod endorsement; +mod endorsement_step; + +pub use endorsement_step::EndorsementStep; diff --git a/src/commands/start/mod.rs b/src/commands/start/mod.rs index 53079fa..e572f55 100644 --- a/src/commands/start/mod.rs +++ b/src/commands/start/mod.rs @@ -1,4 +1,5 @@ mod curio; +mod endorsement; mod eth_acc_funding; mod foc_deploy; mod foc_deployer; @@ -17,6 +18,7 @@ mod usdfc_funding; mod yugabyte; use curio::CurioStep; +use endorsement::EndorsementStep; use eth_acc_funding::ETHAccFundingStep; use foc_deploy::FOCDeployStep; pub use genesis::ensure_genesis_prerequisites; From 4122f16f754640db520b153370992c1385dd159f Mon Sep 17 00:00:00 2001 From: RedPanda Date: Tue, 3 Feb 2026 20:48:04 +0530 Subject: [PATCH 4/9] feat: integrate endorsement step into startup sequence --- src/commands/start/mod.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/commands/start/mod.rs b/src/commands/start/mod.rs index e572f55..b4bfd41 100644 --- a/src/commands/start/mod.rs +++ b/src/commands/start/mod.rs @@ -296,6 +296,12 @@ fn create_steps( ); let synapse_test_step = SynapseTestE2EStep::new(volumes_dir.to_path_buf(), run_dir.to_path_buf(), notest); + let endorsement_step = EndorsementStep::new( + volumes_dir.to_path_buf(), + run_dir.to_path_buf(), + config.endorsed_pdp_sp_count, + config.active_pdp_sp_count, + ); // Execute all steps // Note: PDP SP registration MUST happen after Curio because it needs @@ -311,6 +317,7 @@ fn create_steps( Box::new(yugabyte_step), Box::new(curio_step), Box::new(pdp_sp_reg_step), + Box::new(endorsement_step), Box::new(synapse_test_step), ] } @@ -366,6 +373,12 @@ fn create_step_epochs( ); let synapse_test_step = SynapseTestE2EStep::new(volumes_dir.to_path_buf(), run_dir.to_path_buf(), notest); + let endorsement_step = EndorsementStep::new( + volumes_dir.to_path_buf(), + run_dir.to_path_buf(), + config.endorsed_pdp_sp_count, + config.active_pdp_sp_count, + ); vec![ // Epoch 1: Prerequisites check (binaries & Docker images - must run first) @@ -391,7 +404,9 @@ fn create_step_epochs( vec![Box::new(curio_step)], // Epoch 8: Register PDP SPs (needs Curio running, for port information) vec![Box::new(pdp_sp_reg_step)], - // Epoch 9: Run Synapse E2E Test + // Epoch 9: Endorse PDP SPs (needs registration complete) + vec![Box::new(endorsement_step)], + // Epoch 10: Run Synapse E2E Test vec![Box::new(synapse_test_step)], ] } From b2689b4512e8d1f5cf97e1f12d6173368a0574c1 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Tue, 3 Feb 2026 20:49:03 +0530 Subject: [PATCH 5/9] fix: remove unused MAX_PROVIDER_ID constant --- src/commands/start/endorsement/constants.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/commands/start/endorsement/constants.rs b/src/commands/start/endorsement/constants.rs index 4bd8be0..d496ec2 100644 --- a/src/commands/start/endorsement/constants.rs +++ b/src/commands/start/endorsement/constants.rs @@ -5,6 +5,3 @@ pub const ENDORSEMENT_CONTAINER_PREFIX: &str = "foc-pdp-endorse"; /// Wait time after endorsement transaction (seconds) pub const ENDORSEMENT_TX_WAIT_SECS: u64 = 10; - -/// Maximum provider ID value (ProviderIdSet limitation) -pub const MAX_PROVIDER_ID: u64 = 0xFFFFFFFF; From 54d9a789947804cc0fb2f98545424fc33fa3bc32 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Wed, 4 Feb 2026 12:15:24 +0530 Subject: [PATCH 6/9] add: cast call fixes for endorsements --- src/commands/start/endorsement/constants.rs | 3 +++ src/commands/start/endorsement/endorsement.rs | 13 ++++++---- .../start/endorsement/endorsement_step.rs | 25 +++++++++++++------ .../pdp_service_provider_step.rs | 6 +++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/commands/start/endorsement/constants.rs b/src/commands/start/endorsement/constants.rs index d496ec2..bbdf644 100644 --- a/src/commands/start/endorsement/constants.rs +++ b/src/commands/start/endorsement/constants.rs @@ -5,3 +5,6 @@ pub const ENDORSEMENT_CONTAINER_PREFIX: &str = "foc-pdp-endorse"; /// Wait time after endorsement transaction (seconds) pub const ENDORSEMENT_TX_WAIT_SECS: u64 = 10; + +/// Gas limit for endorsement transactions on Filecoin FEVM +pub const ENDORSEMENT_GAS_LIMIT: &str = "10000000000"; diff --git a/src/commands/start/endorsement/endorsement.rs b/src/commands/start/endorsement/endorsement.rs index cafea27..32df1e3 100644 --- a/src/commands/start/endorsement/endorsement.rs +++ b/src/commands/start/endorsement/endorsement.rs @@ -1,6 +1,8 @@ //! Business logic for provider endorsement operations. -use super::constants::{ENDORSEMENT_CONTAINER_PREFIX, ENDORSEMENT_TX_WAIT_SECS}; +use super::constants::{ + ENDORSEMENT_CONTAINER_PREFIX, ENDORSEMENT_GAS_LIMIT, ENDORSEMENT_TX_WAIT_SECS, +}; use crate::commands::start::step::SetupContext; use crate::constants::BUILDER_DOCKER_IMAGE; use crate::docker::command_logger::run_and_log_command_strings; @@ -14,7 +16,7 @@ pub struct EndorseParams { pub run_id: String, pub provider_id: u64, pub endorsements_contract_address: String, - pub deployer_foc_address: String, + pub deployer_private_key: String, pub lotus_rpc_url: String, } @@ -34,12 +36,13 @@ pub fn endorse_provider( let cast_cmd = format!( r#"cast send {} "addProviderId(uint256)" {} \ --rpc-url {} \ - --from {} \ - --unlocked"#, + --private-key {} \ + --gas-limit {}"#, params.endorsements_contract_address, params.provider_id, params.lotus_rpc_url, - params.deployer_foc_address + params.deployer_private_key, + ENDORSEMENT_GAS_LIMIT ); let args: Vec = vec![ diff --git a/src/commands/start/endorsement/endorsement_step.rs b/src/commands/start/endorsement/endorsement_step.rs index e318dd9..0ef95a2 100644 --- a/src/commands/start/endorsement/endorsement_step.rs +++ b/src/commands/start/endorsement/endorsement_step.rs @@ -1,6 +1,8 @@ //! Endorsement step implementation. -use super::endorsement::{endorse_provider, verify_endorsement, EndorseParams, VerifyEndorsementParams}; +use super::endorsement::{ + endorse_provider, verify_endorsement, EndorseParams, VerifyEndorsementParams, +}; use crate::commands::start::foc_deploy::contract_addresses::ContractAddresses; use crate::commands::start::lotus_utils; use crate::commands::start::step::{SetupContext, Step}; @@ -21,7 +23,12 @@ pub struct EndorsementStep { impl EndorsementStep { /// Create a new EndorsementStep - pub fn new(_volumes_dir: PathBuf, run_dir: PathBuf, endorsed_sp_count: usize, active_sp_count: usize) -> Self { + pub fn new( + _volumes_dir: PathBuf, + run_dir: PathBuf, + endorsed_sp_count: usize, + active_sp_count: usize, + ) -> Self { Self { run_dir, endorsed_sp_count, @@ -51,8 +58,8 @@ impl EndorsementStep { /// Get deployer address from context fn get_deployer_address(context: &SetupContext) -> Result> { context - .get("deployer_foc_address") - .ok_or("DEPLOYER_FOC address not found in context".into()) + .get("deployer_foc_eth_address") + .ok_or("deployer_foc_eth_address not found in context".into()) } } @@ -80,12 +87,12 @@ impl Step for EndorsementStep { for sp_index in 1..=self.endorsed_sp_count { let pdp_key = format!("pdp_sp_{}_address", sp_index); let provider_id_key = format!("pdp_sp_{}_provider_id", sp_index); - let approved_key = format!("pdp_sp_{}_approved", sp_index); + let approved_key = format!("pdp_sp_{}_is_approved", sp_index); let sp_address = context .get(&pdp_key) .ok_or(format!("{} not found in context", pdp_key))?; - + let provider_id: u64 = context .get(&provider_id_key) .ok_or(format!("{} not found in context", provider_id_key))? @@ -133,6 +140,10 @@ impl Step for EndorsementStep { let deployer_address = Self::get_deployer_address(context)?; + // Get deployer private key + let deployer_private_key = + crate::commands::start::foc_deployer::get_private_key(&deployer_address, "")?; + info!( "Endorsing {} provider(s) in ProviderIdSet contract...", self.endorsed_sp_count @@ -151,7 +162,7 @@ impl Step for EndorsementStep { run_id: run_id.to_string(), provider_id, endorsements_contract_address: endorsements_address.clone(), - deployer_foc_address: deployer_address.clone(), + deployer_private_key: deployer_private_key.clone(), lotus_rpc_url: lotus_rpc_url.clone(), }; diff --git a/src/commands/start/pdp_service_provider/pdp_service_provider_step.rs b/src/commands/start/pdp_service_provider/pdp_service_provider_step.rs index 8c67b8f..72bf7c9 100644 --- a/src/commands/start/pdp_service_provider/pdp_service_provider_step.rs +++ b/src/commands/start/pdp_service_provider/pdp_service_provider_step.rs @@ -279,6 +279,12 @@ impl Step for PdpSpRegistrationStep { payee_address: sp_eth_address.clone(), }; info.save(run_id, *sp_index)?; + + // Store provider_id in context for downstream steps (e.g., endorsement) + context.set( + format!("pdp_sp_{}_provider_id", sp_index), + provider_id.to_string(), + ); } info!( From 54dc4f69a67514148dc3c189b12a932b8bd7d3d3 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Wed, 4 Feb 2026 12:47:26 +0530 Subject: [PATCH 7/9] update: private key fetching logic --- .../start/endorsement/endorsement_step.rs | 2 +- src/commands/start/endorsement/mod.rs | 2 +- .../{endorsement.rs => operations.rs} | 0 src/commands/start/foc_deployer/mod.rs | 39 +++++++++++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) rename src/commands/start/endorsement/{endorsement.rs => operations.rs} (100%) diff --git a/src/commands/start/endorsement/endorsement_step.rs b/src/commands/start/endorsement/endorsement_step.rs index 0ef95a2..1b225e4 100644 --- a/src/commands/start/endorsement/endorsement_step.rs +++ b/src/commands/start/endorsement/endorsement_step.rs @@ -1,6 +1,6 @@ //! Endorsement step implementation. -use super::endorsement::{ +use super::operations::{ endorse_provider, verify_endorsement, EndorseParams, VerifyEndorsementParams, }; use crate::commands::start::foc_deploy::contract_addresses::ContractAddresses; diff --git a/src/commands/start/endorsement/mod.rs b/src/commands/start/endorsement/mod.rs index 79ba298..360c74d 100644 --- a/src/commands/start/endorsement/mod.rs +++ b/src/commands/start/endorsement/mod.rs @@ -5,7 +5,7 @@ //! subset of approved providers that meet quality and reliability standards. mod constants; -mod endorsement; +mod operations; mod endorsement_step; pub use endorsement_step::EndorsementStep; diff --git a/src/commands/start/endorsement/endorsement.rs b/src/commands/start/endorsement/operations.rs similarity index 100% rename from src/commands/start/endorsement/endorsement.rs rename to src/commands/start/endorsement/operations.rs diff --git a/src/commands/start/foc_deployer/mod.rs b/src/commands/start/foc_deployer/mod.rs index 1df6ac3..972f693 100644 --- a/src/commands/start/foc_deployer/mod.rs +++ b/src/commands/start/foc_deployer/mod.rs @@ -23,16 +23,41 @@ pub struct DeploymentResult { pub metadata: FOCMetadata, } -/// Get the private key for an f4 address in hex format (for use with cast/forge) -pub fn get_private_key(f4_address: &str, _lotus_container: &str) -> Result> { +/// Get the private key for an address in hex format (for use with cast/forge) +/// +/// # Arguments +/// * `address` - Either a Filecoin address (t4...) or Ethereum address (0x...) +/// * `_lotus_container` - Unused, kept for API compatibility +/// +/// # Returns +/// The private key as a hex string with 0x prefix +pub fn get_private_key(address: &str, _lotus_container: &str) -> Result> { // Load pre-generated keys let keys = crate::commands::init::keys::load_keys()?; - // Find the key with matching Filecoin address - let key_info = keys - .iter() - .find(|k| k.filecoin_address.as_ref() == Some(&f4_address.to_string())) - .ok_or(format!("Private key not found for address: {}", f4_address))?; + // Determine if the address is Ethereum (0x...) or Filecoin (t4...) + let key_info = if address.starts_with("0x") || address.starts_with("0X") { + // Search by Ethereum address + keys.iter() + .find(|k| { + k.eth_address + .as_ref() + .map(|eth| eth.eq_ignore_ascii_case(address)) + .unwrap_or(false) + }) + .ok_or(format!( + "Private key not found for Ethereum address: {}", + address + ))? + } else { + // Search by Filecoin address (t4...) + keys.iter() + .find(|k| k.filecoin_address.as_ref() == Some(&address.to_string())) + .ok_or(format!( + "Private key not found for Filecoin address: {}", + address + ))? + }; // Return the private key with 0x prefix Ok(format!("0x{}", key_info.private_key)) From 6388991779c2b5369ad9432db9136244cb39c8dd Mon Sep 17 00:00:00 2001 From: RedPanda Date: Wed, 4 Feb 2026 13:09:23 +0530 Subject: [PATCH 8/9] add: fmt --- src/commands/start/endorsement/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/start/endorsement/mod.rs b/src/commands/start/endorsement/mod.rs index 360c74d..4132daa 100644 --- a/src/commands/start/endorsement/mod.rs +++ b/src/commands/start/endorsement/mod.rs @@ -5,7 +5,7 @@ //! subset of approved providers that meet quality and reliability standards. mod constants; -mod operations; mod endorsement_step; +mod operations; pub use endorsement_step::EndorsementStep; From 71f8da770d386405379f64a066e6d6916951c7e2 Mon Sep 17 00:00:00 2001 From: RedPanda Date: Wed, 4 Feb 2026 14:50:29 +0530 Subject: [PATCH 9/9] add: endorsements in devnet-info.json --- examples/devnet-schema.js | 1 + src/commands/start/endorsement/endorsement_step.rs | 2 +- src/external_api/devnet_info.rs | 2 ++ src/external_api/export.rs | 6 ++++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/devnet-schema.js b/examples/devnet-schema.js index 93844a6..d558f4f 100644 --- a/examples/devnet-schema.js +++ b/examples/devnet-schema.js @@ -22,6 +22,7 @@ const CurioInfo = z.object({ container_id: z.string().min(1), container_name: z.string().min(1), is_approved: z.boolean(), + is_endorsed: z.boolean(), yugabyte: YugabyteInfo, }); diff --git a/src/commands/start/endorsement/endorsement_step.rs b/src/commands/start/endorsement/endorsement_step.rs index 1b225e4..d17594c 100644 --- a/src/commands/start/endorsement/endorsement_step.rs +++ b/src/commands/start/endorsement/endorsement_step.rs @@ -220,7 +220,7 @@ impl Step for EndorsementStep { .into()); } - let endorsed_key = format!("pdp_sp_{}_endorsed", sp_index); + let endorsed_key = format!("pdp_sp_{}_is_endorsed", sp_index); context.set(&endorsed_key, "true".to_string()); info!("Provider {} endorsement verified ✓", sp_index); diff --git a/src/external_api/devnet_info.rs b/src/external_api/devnet_info.rs index 1c14639..8980178 100644 --- a/src/external_api/devnet_info.rs +++ b/src/external_api/devnet_info.rs @@ -118,6 +118,8 @@ pub struct CurioInfo { pub container_name: String, /// Whether this provider is approved in FWSS pub is_approved: bool, + /// Whether this provider is endorsed in the Endorsements contract + pub is_endorsed: bool, /// YugabyteDB information for this provider pub yugabyte: YugabyteInfo, } diff --git a/src/external_api/export.rs b/src/external_api/export.rs index c22d8c5..379828b 100644 --- a/src/external_api/export.rs +++ b/src/external_api/export.rs @@ -234,6 +234,11 @@ fn build_single_pdp_service_provider( provider_id ))?; + let is_endorsed = ctx + .get(&format!("pdp_sp_{}_is_endorsed", provider_id)) + .and_then(|v| v.parse::().ok()) + .unwrap_or(false); + let yugabyte = build_yugabyte_info(ctx, provider_id)?; Ok(CurioInfo { @@ -244,6 +249,7 @@ fn build_single_pdp_service_provider( container_id, container_name, is_approved, + is_endorsed, yugabyte, }) }