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/constants.rs b/src/commands/start/endorsement/constants.rs new file mode 100644 index 0000000..bbdf644 --- /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; + +/// Gas limit for endorsement transactions on Filecoin FEVM +pub const ENDORSEMENT_GAS_LIMIT: &str = "10000000000"; diff --git a/src/commands/start/endorsement/endorsement_step.rs b/src/commands/start/endorsement/endorsement_step.rs new file mode 100644 index 0000000..d17594c --- /dev/null +++ b/src/commands/start/endorsement/endorsement_step.rs @@ -0,0 +1,236 @@ +//! Endorsement step implementation. + +use super::operations::{ + 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_eth_address") + .ok_or("deployer_foc_eth_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_{}_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))? + .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)?; + + // 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 + ); + + 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_private_key: deployer_private_key.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_{}_is_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..4132daa --- /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_step; +mod operations; + +pub use endorsement_step::EndorsementStep; diff --git a/src/commands/start/endorsement/operations.rs b/src/commands/start/endorsement/operations.rs new file mode 100644 index 0000000..32df1e3 --- /dev/null +++ b/src/commands/start/endorsement/operations.rs @@ -0,0 +1,190 @@ +//! Business logic for provider endorsement operations. + +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; +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_private_key: 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 {} \ + --private-key {} \ + --gas-limit {}"#, + params.endorsements_contract_address, + params.provider_id, + params.lotus_rpc_url, + params.deployer_private_key, + ENDORSEMENT_GAS_LIMIT + ); + + 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/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)) diff --git a/src/commands/start/mod.rs b/src/commands/start/mod.rs index 3e89c50..b4bfd41 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; @@ -254,6 +256,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) } @@ -293,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 @@ -308,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), ] } @@ -363,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) @@ -388,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)], ] } @@ -431,6 +449,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/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!( 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(); 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 ({})", 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, }) }