diff --git a/testing-framework/configs/src/topology/configs/base.rs b/testing-framework/configs/src/topology/configs/base.rs index 1f4e75c..cff3da4 100644 --- a/testing-framework/configs/src/topology/configs/base.rs +++ b/testing-framework/configs/src/topology/configs/base.rs @@ -8,6 +8,8 @@ use super::{ #[derive(Debug, Error)] pub enum BaseConfigError { + #[error(transparent)] + Consensus(#[from] consensus::ConsensusConfigError), #[error(transparent)] Da(#[from] da::DaConfigError), #[error(transparent)] @@ -36,7 +38,7 @@ pub fn build_base_configs( ids, consensus_params, wallet_config, - ), + )?, bootstrap_configs: bootstrap::create_bootstrap_configs( ids, SHORT_PROLONGED_BOOTSTRAP_PERIOD, diff --git a/testing-framework/configs/src/topology/configs/consensus.rs b/testing-framework/configs/src/topology/configs/consensus.rs index 28fdc47..e4c9300 100644 --- a/testing-framework/configs/src/topology/configs/consensus.rs +++ b/testing-framework/configs/src/topology/configs/consensus.rs @@ -29,6 +29,18 @@ use num_bigint::BigUint; use super::wallet::{WalletAccount, WalletConfig}; +#[derive(Debug, thiserror::Error)] +pub enum ConsensusConfigError { + #[error("failed to build genesis inscription signer: {message}")] + InscriptionSigner { message: String }, + #[error("failed to build genesis transaction: {message}")] + GenesisTx { message: String }, + #[error("failed to build ledger config: {message}")] + LedgerConfig { message: String }, + #[error("failed to sign genesis declarations: {message}")] + DeclarationSignature { message: String }, +} + #[derive(Clone)] pub struct ConsensusParams { pub n_participants: usize, @@ -43,31 +55,18 @@ impl ConsensusParams { #[must_use] pub fn default_for_participants(n_participants: usize) -> Self { let active_slot_coeff = env::var(Self::CONSENSUS_ACTIVE_SLOT_COEFF_VAR) - .map(|s| { - f64::from_str(&s).unwrap_or_else(|err| { - panic!( - "invalid {}='{}' (expected a float in (0.0, 1.0]): {err}", - Self::CONSENSUS_ACTIVE_SLOT_COEFF_VAR, - s - ) - }) - }) + .ok() + .and_then(|raw| f64::from_str(&raw).ok()) + .filter(|value| (0.0..=1.0).contains(value) && *value > 0.0) .unwrap_or(Self::DEFAULT_ACTIVE_SLOT_COEFF); - assert!( - (0.0..=1.0).contains(&active_slot_coeff) && active_slot_coeff > 0.0, - "{} must be in (0.0, 1.0], got {}", - Self::CONSENSUS_ACTIVE_SLOT_COEFF_VAR, - active_slot_coeff - ); - Self { n_participants, // by setting the slot coeff to 1, we also increase the probability of multiple blocks // (forks) being produced in the same slot (epoch). Setting the security // parameter to some value > 1 ensures nodes have some time to sync before // deciding on the longest chain. - security_param: NonZero::new(10).unwrap(), + security_param: unsafe { NonZero::new_unchecked(10) }, // a block should be produced (on average) every slot active_slot_coeff, } @@ -116,13 +115,17 @@ pub struct ServiceNote { pub output_index: usize, } -fn create_genesis_tx(utxos: &[Utxo]) -> GenesisTx { +fn create_genesis_tx(utxos: &[Utxo]) -> Result { // Create a genesis inscription op (similar to config.yaml) let inscription = InscriptionOp { channel_id: ChannelId::from([0; 32]), inscription: vec![103, 101, 110, 101, 115, 105, 115], // "genesis" in bytes parent: MsgId::root(), - signer: Ed25519PublicKey::from_bytes(&[0; 32]).unwrap(), + signer: Ed25519PublicKey::from_bytes(&[0; 32]).map_err(|err| { + ConsensusConfigError::InscriptionSigner { + message: err.to_string(), + } + })?, }; // Create ledger transaction with the utxos as outputs @@ -143,15 +146,19 @@ fn create_genesis_tx(utxos: &[Utxo]) -> GenesisTx { }; // Wrap in GenesisTx - GenesisTx::from_tx(signed_mantle_tx).expect("Invalid genesis transaction") + GenesisTx::from_tx(signed_mantle_tx).map_err(|err| ConsensusConfigError::GenesisTx { + message: err.to_string(), + }) } -fn build_ledger_config(consensus_params: &ConsensusParams) -> nomos_ledger::Config { - nomos_ledger::Config { +fn build_ledger_config( + consensus_params: &ConsensusParams, +) -> Result { + Ok(nomos_ledger::Config { epoch_config: EpochConfig { - epoch_stake_distribution_stabilization: NonZero::new(3).unwrap(), - epoch_period_nonce_buffer: NonZero::new(3).unwrap(), - epoch_period_nonce_stabilization: NonZero::new(4).unwrap(), + epoch_stake_distribution_stabilization: unsafe { NonZero::new_unchecked(3) }, + epoch_period_nonce_buffer: unsafe { NonZero::new_unchecked(3) }, + epoch_period_nonce_stabilization: unsafe { NonZero::new_unchecked(4) }, }, consensus_config: cryptarchia_engine::Config { security_param: consensus_params.security_param, @@ -189,14 +196,18 @@ fn build_ledger_config(consensus_params: &ConsensusParams) -> nomos_ledger::Conf }, service_rewards_params: nomos_ledger::mantle::sdp::ServiceRewardsParameters { blend: nomos_ledger::mantle::sdp::rewards::blend::RewardsParameters { - rounds_per_session: NonZeroU64::new(10).unwrap(), - message_frequency_per_round: NonNegativeF64::try_from(1.0).unwrap(), - num_blend_layers: NonZeroU64::new(3).unwrap(), - minimum_network_size: NonZeroU64::new(1).unwrap(), + rounds_per_session: unsafe { NonZeroU64::new_unchecked(10) }, + message_frequency_per_round: NonNegativeF64::try_from(1.0).map_err(|_| { + ConsensusConfigError::LedgerConfig { + message: "message_frequency_per_round must be non-negative".to_owned(), + } + })?, + num_blend_layers: unsafe { NonZeroU64::new_unchecked(3) }, + minimum_network_size: unsafe { NonZeroU64::new_unchecked(1) }, }, }, }, - } + }) } #[must_use] @@ -204,7 +215,7 @@ pub fn create_consensus_configs( ids: &[[u8; 32]], consensus_params: &ConsensusParams, wallet: &WalletConfig, -) -> Vec { +) -> Result, ConsensusConfigError> { let mut leader_keys = Vec::new(); let mut blend_notes = Vec::new(); let mut da_notes = Vec::new(); @@ -216,10 +227,10 @@ pub fn create_consensus_configs( &mut da_notes, ); let utxos = append_wallet_utxos(utxos, wallet); - let genesis_tx = create_genesis_tx(&utxos); - let ledger_config = build_ledger_config(consensus_params); + let genesis_tx = create_genesis_tx(&utxos)?; + let ledger_config = build_ledger_config(consensus_params)?; - leader_keys + Ok(leader_keys .into_iter() .map(|(pk, sk)| GeneralConsensusConfig { leader_config: LeaderConfig { pk, sk }, @@ -230,7 +241,7 @@ pub fn create_consensus_configs( blend_notes: blend_notes.clone(), wallet_accounts: wallet.accounts.clone(), }) - .collect() + .collect()) } fn create_utxos_for_leader_and_services( @@ -324,12 +335,16 @@ fn append_wallet_utxos(mut utxos: Vec, wallet: &WalletConfig) -> Vec pub fn create_genesis_tx_with_declarations( ledger_tx: LedgerTx, providers: Vec, -) -> GenesisTx { +) -> Result { let inscription = InscriptionOp { channel_id: ChannelId::from([0; 32]), inscription: vec![103, 101, 110, 101, 115, 105, 115], // "genesis" in bytes parent: MsgId::root(), - signer: Ed25519PublicKey::from_bytes(&[0; 32]).unwrap(), + signer: Ed25519PublicKey::from_bytes(&[0; 32]).map_err(|err| { + ConsensusConfigError::InscriptionSigner { + message: err.to_string(), + } + })?, }; let ledger_tx_hash = ledger_tx.hash(); @@ -365,7 +380,9 @@ pub fn create_genesis_tx_with_declarations( for provider in providers { let zk_sig = ZkKey::multi_sign(&[provider.note.sk, provider.zk_sk], mantle_tx_hash.as_ref()) - .unwrap(); + .map_err(|err| ConsensusConfigError::DeclarationSignature { + message: format!("{err:?}"), + })?; let ed25519_sig = provider .provider_sk .sign_payload(mantle_tx_hash.as_signing_bytes().as_ref()); @@ -382,5 +399,7 @@ pub fn create_genesis_tx_with_declarations( ledger_tx_proof: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])), }; - GenesisTx::from_tx(signed_mantle_tx).expect("Invalid genesis transaction") + GenesisTx::from_tx(signed_mantle_tx).map_err(|err| ConsensusConfigError::GenesisTx { + message: err.to_string(), + }) } diff --git a/testing-framework/configs/src/topology/configs/mod.rs b/testing-framework/configs/src/topology/configs/mod.rs index eeb957d..c5c8bde 100644 --- a/testing-framework/configs/src/topology/configs/mod.rs +++ b/testing-framework/configs/src/topology/configs/mod.rs @@ -9,8 +9,12 @@ pub mod time; pub mod tracing; pub mod wallet; +use std::cmp; + use blend::GeneralBlendConfig; -use consensus::{GeneralConsensusConfig, ProviderInfo, create_genesis_tx_with_declarations}; +use consensus::{ + ConsensusConfigError, GeneralConsensusConfig, ProviderInfo, create_genesis_tx_with_declarations, +}; use da::GeneralDaConfig; use key_management_system_service::{backend::preload::PreloadKMSBackendSettings, keys::Key}; use network::GeneralNetworkConfig; @@ -38,6 +42,29 @@ use crate::{ }, }; +#[derive(Debug, thiserror::Error)] +pub enum GeneralConfigError { + #[error("participant count must be > 0")] + EmptyParticipants, + #[error("blend core subset {blend_core} exceeds participants {participants}")] + BlendCoreSubsetTooLarge { + blend_core: usize, + participants: usize, + }, + #[error("failed to allocate a free UDP port for {label}")] + PortAllocationFailed { label: &'static str }, + #[error(transparent)] + Invariants(#[from] crate::topology::invariants::TopologyInvariantError), + #[error(transparent)] + Consensus(#[from] ConsensusConfigError), + #[error(transparent)] + Network(#[from] network::NetworkConfigError), + #[error(transparent)] + Da(#[from] da::DaConfigError), + #[error(transparent)] + Api(#[from] api::ApiConfigError), +} + #[derive(Clone)] pub struct GeneralConfig { pub api_config: GeneralApiConfig, @@ -51,79 +78,91 @@ pub struct GeneralConfig { pub kms_config: PreloadKMSBackendSettings, } -#[must_use] -pub fn create_general_configs(n_nodes: usize) -> Vec { +pub fn create_general_configs(n_nodes: usize) -> Result, GeneralConfigError> { create_general_configs_with_network(n_nodes, &NetworkParams::default()) } -#[must_use] pub fn create_general_configs_with_network( n_nodes: usize, network_params: &NetworkParams, -) -> Vec { +) -> Result, GeneralConfigError> { create_general_configs_with_blend_core_subset(n_nodes, n_nodes, network_params) } -#[must_use] pub fn create_general_configs_with_blend_core_subset( n_nodes: usize, // TODO: Instead of this, define a config struct for each node. // That would be also useful for non-even token distributions: https://github.com/logos-co/nomos/issues/1888 n_blend_core_nodes: usize, network_params: &NetworkParams, -) -> Vec { - assert!( - n_blend_core_nodes <= n_nodes, - "n_blend_core_nodes({n_blend_core_nodes}) must be less than or equal to n_nodes({n_nodes})", - ); +) -> Result, GeneralConfigError> { + if n_nodes == 0 { + return Err(GeneralConfigError::EmptyParticipants); + } + if n_blend_core_nodes > n_nodes { + return Err(GeneralConfigError::BlendCoreSubsetTooLarge { + blend_core: n_blend_core_nodes, + participants: n_nodes, + }); + } // Blend relies on each node declaring a different ZK public key, so we need // different IDs to generate different keys. let mut ids: Vec<_> = (0..n_nodes).map(|i| [i as u8; 32]).collect(); - let mut da_ports = vec![]; - let mut blend_ports = vec![]; + let mut da_ports = Vec::with_capacity(n_nodes); + let mut blend_ports = Vec::with_capacity(n_nodes); for id in &mut ids { thread_rng().fill(id); - da_ports.push(get_available_udp_port().unwrap()); - blend_ports.push(get_available_udp_port().unwrap()); + da_ports.push( + get_available_udp_port() + .ok_or(GeneralConfigError::PortAllocationFailed { label: "DA" })?, + ); + blend_ports.push( + get_available_udp_port() + .ok_or(GeneralConfigError::PortAllocationFailed { label: "Blend" })?, + ); } - validate_generated_vectors(n_nodes, &ids, &da_ports, &blend_ports) - .expect("invalid generated test topology inputs"); + validate_generated_vectors(n_nodes, &ids, &da_ports, &blend_ports)?; let consensus_params = ConsensusParams::default_for_participants(n_nodes); let mut consensus_configs = - consensus::create_consensus_configs(&ids, &consensus_params, &WalletConfig::default()); + consensus::create_consensus_configs(&ids, &consensus_params, &WalletConfig::default())?; let bootstrap_config = bootstrap::create_bootstrap_configs(&ids, SHORT_PROLONGED_BOOTSTRAP_PERIOD); - let network_configs = - network::create_network_configs(&ids, network_params).expect("network config generation"); - let da_configs = - da::try_create_da_configs(&ids, &DaParams::default(), &da_ports).expect("DA configs"); - let api_configs = api::create_api_configs(&ids).expect("api config generation"); + let network_configs = network::create_network_configs(&ids, network_params)?; + let da_configs = da::try_create_da_configs(&ids, &DaParams::default(), &da_ports)?; + let api_configs = api::create_api_configs(&ids)?; let blend_configs = blend::create_blend_configs(&ids, &blend_ports); let tracing_configs = tracing::create_tracing_configs(&ids); let time_config = time::default_time_config(); - let providers: Vec<_> = blend_configs + let Some(first_consensus) = consensus_configs.first() else { + return Err(GeneralConfigError::EmptyParticipants); + }; + + let mut providers = Vec::with_capacity(cmp::min(n_blend_core_nodes, blend_configs.len())); + for (i, blend_conf) in blend_configs .iter() .enumerate() - .take(n_blend_core_nodes) - .map(|(i, blend_conf)| ProviderInfo { + .take(cmp::min(n_blend_core_nodes, blend_configs.len())) + { + let note = first_consensus + .blend_notes + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + providers.push(ProviderInfo { service_type: ServiceType::BlendNetwork, provider_sk: blend_conf.signer.clone(), zk_sk: blend_conf.secret_zk_key.clone(), locator: Locator(blend_conf.backend_core.listening_address.clone()), - note: consensus_configs[0].blend_notes[i].clone(), - }) - .collect(); - let ledger_tx = consensus_configs[0] - .genesis_tx - .mantle_tx() - .ledger_tx - .clone(); - let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers); + note, + }); + } + let ledger_tx = first_consensus.genesis_tx.mantle_tx().ledger_tx.clone(); + let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers)?; for c in &mut consensus_configs { c.genesis_tx = genesis_tx.clone(); } @@ -150,21 +189,54 @@ pub fn create_general_configs_with_blend_core_subset( }) .collect(); - let mut general_configs = vec![]; + let mut general_configs = Vec::with_capacity(n_nodes); for i in 0..n_nodes { + let api_config = api_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let consensus_config = consensus_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let bootstrapping_config = bootstrap_config + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let da_config = da_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let network_config = network_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let blend_config = blend_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let tracing_config = tracing_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + let kms_config = kms_configs + .get(i) + .ok_or(GeneralConfigError::EmptyParticipants)? + .clone(); + general_configs.push(GeneralConfig { - api_config: api_configs[i].clone(), - consensus_config: consensus_configs[i].clone(), - bootstrapping_config: bootstrap_config[i].clone(), - da_config: da_configs[i].clone(), - network_config: network_configs[i].clone(), - blend_config: blend_configs[i].clone(), - tracing_config: tracing_configs[i].clone(), + api_config, + consensus_config, + bootstrapping_config, + da_config, + network_config, + blend_config, + tracing_config, time_config: time_config.clone(), - kms_config: kms_configs[i].clone(), + kms_config, }); } - general_configs + Ok(general_configs) } diff --git a/testing-framework/core/src/topology/config.rs b/testing-framework/core/src/topology/config.rs index ad15eb3..7960e10 100644 --- a/testing-framework/core/src/topology/config.rs +++ b/testing-framework/core/src/topology/config.rs @@ -309,9 +309,15 @@ impl TopologyBuilder { .mantle_tx() .ledger_tx .clone(); - let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers); - for c in &mut consensus_configs { - c.genesis_tx = genesis_tx.clone(); + match create_genesis_tx_with_declarations(ledger_tx, providers) { + Ok(genesis_tx) => { + for c in &mut consensus_configs { + c.genesis_tx = genesis_tx.clone(); + } + } + Err(err) => { + tracing::error!(error = ?err, "failed to build genesis declarations; using base genesis transaction"); + } } let kms_configs = diff --git a/testing-framework/tools/cfgsync/src/config/builder.rs b/testing-framework/tools/cfgsync/src/config/builder.rs index f8fe536..a3f0069 100644 --- a/testing-framework/tools/cfgsync/src/config/builder.rs +++ b/testing-framework/tools/cfgsync/src/config/builder.rs @@ -9,7 +9,7 @@ use testing_framework_config::topology::configs::{ GeneralConfig, api::GeneralApiConfig, base::{BaseConfigError, BaseConfigs, build_base_configs}, - consensus::{ConsensusParams, create_genesis_tx_with_declarations}, + consensus::{ConsensusConfigError, ConsensusParams, create_genesis_tx_with_declarations}, da::DaParams, network::NetworkParams, time::default_time_config, @@ -60,6 +60,8 @@ pub enum NodeConfigBuildError { Providers(#[from] ProviderBuildError), #[error(transparent)] Base(#[from] BaseConfigError), + #[error(transparent)] + Genesis(#[from] ConsensusConfigError), #[error("failed to allocate an available UDP port")] PortAllocFailed, #[error("failed to parse multiaddr '{value}': {message}")] @@ -143,7 +145,7 @@ pub fn try_create_node_configs( .get(0) .ok_or(NodeConfigBuildError::MissingConsensusConfig)?; let ledger_tx = first_consensus.genesis_tx.mantle_tx().ledger_tx.clone(); - let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers); + let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers)?; for c in &mut consensus_configs { c.genesis_tx = genesis_tx.clone();