diff --git a/testing-framework/configs/src/topology/configs/mod.rs b/testing-framework/configs/src/topology/configs/mod.rs index a4364fa..2cfc058 100644 --- a/testing-framework/configs/src/topology/configs/mod.rs +++ b/testing-framework/configs/src/topology/configs/mod.rs @@ -24,13 +24,16 @@ use wallet::WalletConfig; use crate::{ nodes::kms::key_id_for_preload_backend, - topology::configs::{ - api::GeneralApiConfig, - bootstrap::{GeneralBootstrapConfig, SHORT_PROLONGED_BOOTSTRAP_PERIOD}, - consensus::ConsensusParams, - da::DaParams, - network::NetworkParams, - time::GeneralTimeConfig, + topology::{ + configs::{ + api::GeneralApiConfig, + bootstrap::{GeneralBootstrapConfig, SHORT_PROLONGED_BOOTSTRAP_PERIOD}, + consensus::ConsensusParams, + da::DaParams, + network::NetworkParams, + time::GeneralTimeConfig, + }, + invariants::validate_generated_vectors, }, }; @@ -85,6 +88,9 @@ pub fn create_general_configs_with_blend_core_subset( blend_ports.push(get_available_udp_port().unwrap()); } + validate_generated_vectors(n_nodes, &ids, &da_ports, &blend_ports) + .expect("invalid generated test topology inputs"); + let consensus_params = ConsensusParams::default_for_participants(n_nodes); let mut consensus_configs = consensus::create_consensus_configs(&ids, &consensus_params, &WalletConfig::default()); diff --git a/testing-framework/configs/src/topology/invariants.rs b/testing-framework/configs/src/topology/invariants.rs new file mode 100644 index 0000000..abbe028 --- /dev/null +++ b/testing-framework/configs/src/topology/invariants.rs @@ -0,0 +1,91 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum TopologyInvariantError { + #[error("participant count must be > 0")] + EmptyParticipants, + #[error("id count {actual} does not match participants {expected}")] + IdCountMismatch { actual: usize, expected: usize }, + #[error("da port count {actual} does not match participants {expected}")] + DaPortCountMismatch { actual: usize, expected: usize }, + #[error("blend port count {actual} does not match participants {expected}")] + BlendPortCountMismatch { actual: usize, expected: usize }, +} + +/// Validate basic invariants shared across all config generation pipelines. +/// +/// This intentionally focuses on "shape" invariants (counts, non-empty) and +/// avoids opinionated checks so behavior stays unchanged. +pub fn validate_node_vectors( + participants: usize, + ids: Option<&Vec<[u8; 32]>>, + da_ports: Option<&Vec>, + blend_ports: Option<&Vec>, +) -> Result<(), TopologyInvariantError> { + if participants == 0 { + return Err(TopologyInvariantError::EmptyParticipants); + } + + if let Some(ids) = ids { + if ids.len() != participants { + return Err(TopologyInvariantError::IdCountMismatch { + actual: ids.len(), + expected: participants, + }); + } + } + + if let Some(ports) = da_ports { + if ports.len() != participants { + return Err(TopologyInvariantError::DaPortCountMismatch { + actual: ports.len(), + expected: participants, + }); + } + } + + if let Some(ports) = blend_ports { + if ports.len() != participants { + return Err(TopologyInvariantError::BlendPortCountMismatch { + actual: ports.len(), + expected: participants, + }); + } + } + + Ok(()) +} + +pub fn validate_generated_vectors( + participants: usize, + ids: &[[u8; 32]], + da_ports: &[u16], + blend_ports: &[u16], +) -> Result<(), TopologyInvariantError> { + if participants == 0 { + return Err(TopologyInvariantError::EmptyParticipants); + } + + if ids.len() != participants { + return Err(TopologyInvariantError::IdCountMismatch { + actual: ids.len(), + expected: participants, + }); + } + + if da_ports.len() != participants { + return Err(TopologyInvariantError::DaPortCountMismatch { + actual: da_ports.len(), + expected: participants, + }); + } + + if blend_ports.len() != participants { + return Err(TopologyInvariantError::BlendPortCountMismatch { + actual: blend_ports.len(), + expected: participants, + }); + } + + Ok(()) +} diff --git a/testing-framework/configs/src/topology/mod.rs b/testing-framework/configs/src/topology/mod.rs index 3810d5b..3017c02 100644 --- a/testing-framework/configs/src/topology/mod.rs +++ b/testing-framework/configs/src/topology/mod.rs @@ -1 +1,2 @@ pub mod configs; +pub mod invariants; diff --git a/testing-framework/core/src/topology/config.rs b/testing-framework/core/src/topology/config.rs index e679a43..aeb152d 100644 --- a/testing-framework/core/src/topology/config.rs +++ b/testing-framework/core/src/topology/config.rs @@ -5,18 +5,21 @@ use nomos_core::{ sdp::{Locator, ServiceType}, }; use nomos_da_network_core::swarm::DAConnectionPolicySettings; -use testing_framework_config::topology::configs::{ - api::create_api_configs, - blend::create_blend_configs, - bootstrap::{SHORT_PROLONGED_BOOTSTRAP_PERIOD, create_bootstrap_configs}, - consensus::{ - ConsensusParams, ProviderInfo, create_consensus_configs, - create_genesis_tx_with_declarations, +use testing_framework_config::topology::{ + configs::{ + api::create_api_configs, + blend::create_blend_configs, + bootstrap::{SHORT_PROLONGED_BOOTSTRAP_PERIOD, create_bootstrap_configs}, + consensus::{ + ConsensusParams, ProviderInfo, create_consensus_configs, + create_genesis_tx_with_declarations, + }, + da::{DaParams, create_da_configs}, + network::{Libp2pNetworkLayout, NetworkParams, create_network_configs}, + tracing::create_tracing_configs, + wallet::WalletConfig, }, - da::{DaParams, create_da_configs}, - network::{Libp2pNetworkLayout, NetworkParams, create_network_configs}, - tracing::create_tracing_configs, - wallet::WalletConfig, + invariants::validate_generated_vectors, }; use crate::topology::{ @@ -258,6 +261,9 @@ impl TopologyBuilder { let da_ports = resolve_ports(da_ports, n_participants, "DA"); let blend_ports = resolve_ports(blend_ports, n_participants, "Blend"); + validate_generated_vectors(n_participants, &ids, &da_ports, &blend_ports) + .expect("invalid generated topology inputs"); + let mut consensus_configs = create_consensus_configs(&ids, &config.consensus_params, &config.wallet_config); let bootstrapping_config = create_bootstrap_configs(&ids, SHORT_PROLONGED_BOOTSTRAP_PERIOD); diff --git a/testing-framework/tools/cfgsync/src/config/validation.rs b/testing-framework/tools/cfgsync/src/config/validation.rs index 5e4b2d7..afaad54 100644 --- a/testing-framework/tools/cfgsync/src/config/validation.rs +++ b/testing-framework/tools/cfgsync/src/config/validation.rs @@ -1,4 +1,7 @@ -use testing_framework_config::topology::configs::consensus::ConsensusParams; +use testing_framework_config::topology::{ + configs::consensus::ConsensusParams, + invariants::{TopologyInvariantError, validate_node_vectors}, +}; use thiserror::Error; use crate::host::Host; @@ -7,12 +10,8 @@ use crate::host::Host; pub enum ValidationError { #[error("host count {actual} does not match participants {expected}")] HostCountMismatch { actual: usize, expected: usize }, - #[error("id count {actual} does not match participants {expected}")] - IdCountMismatch { actual: usize, expected: usize }, - #[error("da port count {actual} does not match participants {expected}")] - DaPortCountMismatch { actual: usize, expected: usize }, - #[error("blend port count {actual} does not match participants {expected}")] - BlendPortCountMismatch { actual: usize, expected: usize }, + #[error(transparent)] + TopologyInvariant(#[from] TopologyInvariantError), } pub fn validate_inputs( @@ -31,32 +30,7 @@ pub fn validate_inputs( }); } - if let Some(ids) = ids { - if ids.len() != expected { - return Err(ValidationError::IdCountMismatch { - actual: ids.len(), - expected, - }); - } - } - - if let Some(ports) = da_ports { - if ports.len() != expected { - return Err(ValidationError::DaPortCountMismatch { - actual: ports.len(), - expected, - }); - } - } - - if let Some(ports) = blend_ports { - if ports.len() != expected { - return Err(ValidationError::BlendPortCountMismatch { - actual: ports.len(), - expected, - }); - } - } + validate_node_vectors(expected, ids, da_ports, blend_ports)?; Ok(()) }