diff --git a/examples/doc-snippets/src/architecture_overview_builder_api.rs b/examples/doc-snippets/src/architecture_overview_builder_api.rs index de50079..5c23a9f 100644 --- a/examples/doc-snippets/src/architecture_overview_builder_api.rs +++ b/examples/doc-snippets/src/architecture_overview_builder_api.rs @@ -1,9 +1,11 @@ use std::time::Duration; -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn scenario_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn scenario_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2)) .wallets(50) .transactions_with(|txs| txs.rate(5).users(20)) diff --git a/examples/doc-snippets/src/chaos_workloads_random_restart.rs b/examples/doc-snippets/src/chaos_workloads_random_restart.rs index e7bde64..13204e4 100644 --- a/examples/doc-snippets/src/chaos_workloads_random_restart.rs +++ b/examples/doc-snippets/src/chaos_workloads_random_restart.rs @@ -1,11 +1,11 @@ use std::time::Duration; -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{NodeControlCapability, Scenario, ScenarioBuilder}; use testing_framework_workflows::{ScenarioBuilderExt, workloads::chaos::RandomRestartWorkload}; -pub fn random_restart_plan() -> testing_framework_core::scenario::Scenario< - testing_framework_core::scenario::NodeControlCapability, -> { +use crate::SnippetResult; + +pub fn random_restart_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1)) .enable_node_control() .with_workload(RandomRestartWorkload::new( diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_build.rs b/examples/doc-snippets/src/dsl_cheat_sheet_build.rs index cdbbe0d..f6c3762 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_build.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_build.rs @@ -1,6 +1,8 @@ -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn build_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn build_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)).build() // Construct the final Scenario } diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_build_complete_example.rs b/examples/doc-snippets/src/dsl_cheat_sheet_build_complete_example.rs index 8ae96f7..e69aece 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_build_complete_example.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_build_complete_example.rs @@ -19,7 +19,7 @@ pub async fn run_test() -> Result<()> { }) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(90)) - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_expectations.rs b/examples/doc-snippets/src/dsl_cheat_sheet_expectations.rs index c6ee4b7..ecf2437 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_expectations.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_expectations.rs @@ -1,7 +1,9 @@ -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn expectations_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn expectations_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)) .expect_consensus_liveness() // Assert blocks are produced continuously .build() diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs b/examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs index bdbc042..925a735 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_run_duration.rs @@ -1,9 +1,11 @@ use std::time::Duration; -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn run_duration_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn run_duration_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)) .with_run_duration(Duration::from_secs(120)) // Run for 120 seconds .build() diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_transactions_workload.rs b/examples/doc-snippets/src/dsl_cheat_sheet_transactions_workload.rs index 91acfa0..c4f6fda 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_transactions_workload.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_transactions_workload.rs @@ -1,7 +1,9 @@ -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn transactions_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn transactions_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)) .wallets(50) .transactions_with(|txs| { diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs b/examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs index 994e172..f7d45f0 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_wallets.rs @@ -1,7 +1,9 @@ -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn wallets_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn wallets_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)) .wallets(50) // Seed 50 funded wallet accounts .build() diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs b/examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs index 95ba6fe..935f5a7 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_workload_chaos.rs @@ -3,7 +3,10 @@ use std::time::Duration; use testing_framework_core::scenario::{NodeControlCapability, ScenarioBuilder}; use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt}; -pub fn chaos_plan() -> testing_framework_core::scenario::Scenario { +use crate::SnippetResult; + +pub fn chaos_plan() +-> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2)) .enable_node_control() // Enable node control capability .chaos_with(|c| { diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs b/examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs index 603c89d..6c069cf 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_workload_da.rs @@ -1,7 +1,9 @@ -use testing_framework_core::scenario::ScenarioBuilder; +use testing_framework_core::scenario::{Scenario, ScenarioBuilder}; use testing_framework_workflows::ScenarioBuilderExt; -pub fn da_plan() -> testing_framework_core::scenario::Scenario<()> { +use crate::SnippetResult; + +pub fn da_plan() -> SnippetResult> { ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1)) .wallets(50) .da_with(|da| { diff --git a/examples/doc-snippets/src/dsl_cheat_sheet_workload_execution.rs b/examples/doc-snippets/src/dsl_cheat_sheet_workload_execution.rs index 8975f9c..926d9d4 100644 --- a/examples/doc-snippets/src/dsl_cheat_sheet_workload_execution.rs +++ b/examples/doc-snippets/src/dsl_cheat_sheet_workload_execution.rs @@ -6,7 +6,7 @@ use testing_framework_workflows::ScenarioBuilderExt; pub async fn execution() -> Result<()> { let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)) .expect_consensus_liveness() - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_advanced_aggressive_chaos_test.rs b/examples/doc-snippets/src/examples_advanced_aggressive_chaos_test.rs index 4315859..5ab5c88 100644 --- a/examples/doc-snippets/src/examples_advanced_aggressive_chaos_test.rs +++ b/examples/doc-snippets/src/examples_advanced_aggressive_chaos_test.rs @@ -19,7 +19,7 @@ pub async fn aggressive_chaos_test() -> Result<()> { }) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(180)) - .build(); + .build()?; let deployer = ComposeDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_advanced_load_progression_test.rs b/examples/doc-snippets/src/examples_advanced_load_progression_test.rs index f5771ac..489c993 100644 --- a/examples/doc-snippets/src/examples_advanced_load_progression_test.rs +++ b/examples/doc-snippets/src/examples_advanced_load_progression_test.rs @@ -15,7 +15,7 @@ pub async fn load_progression_test() -> Result<()> { .transactions_with(|txs| txs.rate(rate).users(20)) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(60)) - .build(); + .build()?; let deployer = ComposeDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_advanced_sustained_load_test.rs b/examples/doc-snippets/src/examples_advanced_sustained_load_test.rs index fbc919d..403ae8c 100644 --- a/examples/doc-snippets/src/examples_advanced_sustained_load_test.rs +++ b/examples/doc-snippets/src/examples_advanced_sustained_load_test.rs @@ -12,7 +12,7 @@ pub async fn sustained_load_test() -> Result<()> { .da_with(|da| da.channel_rate(2).blob_rate(3)) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(300)) - .build(); + .build()?; let deployer = ComposeDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_chaos_resilience.rs b/examples/doc-snippets/src/examples_chaos_resilience.rs index d892cfe..09403c7 100644 --- a/examples/doc-snippets/src/examples_chaos_resilience.rs +++ b/examples/doc-snippets/src/examples_chaos_resilience.rs @@ -19,7 +19,7 @@ pub async fn chaos_resilience() -> Result<()> { }) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(120)) - .build(); + .build()?; let deployer = ComposeDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_da_and_transactions.rs b/examples/doc-snippets/src/examples_da_and_transactions.rs index 44a8191..0b4d1b7 100644 --- a/examples/doc-snippets/src/examples_da_and_transactions.rs +++ b/examples/doc-snippets/src/examples_da_and_transactions.rs @@ -12,7 +12,7 @@ pub async fn da_and_transactions() -> Result<()> { .da_with(|da| da.channel_rate(2).blob_rate(2)) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(90)) - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_simple_consensus.rs b/examples/doc-snippets/src/examples_simple_consensus.rs index a5c0f49..4e93766 100644 --- a/examples/doc-snippets/src/examples_simple_consensus.rs +++ b/examples/doc-snippets/src/examples_simple_consensus.rs @@ -9,7 +9,7 @@ pub async fn simple_consensus() -> Result<()> { let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0)) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(30)) - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/examples_transaction_workload.rs b/examples/doc-snippets/src/examples_transaction_workload.rs index fe105d9..e2a8670 100644 --- a/examples/doc-snippets/src/examples_transaction_workload.rs +++ b/examples/doc-snippets/src/examples_transaction_workload.rs @@ -11,7 +11,7 @@ pub async fn transaction_workload() -> Result<()> { .transactions_with(|txs| txs.rate(5).users(10)) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(60)) - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/internal_crate_reference_add_expectation_builder_ext.rs b/examples/doc-snippets/src/internal_crate_reference_add_expectation_builder_ext.rs index be19df7..6085eaf 100644 --- a/examples/doc-snippets/src/internal_crate_reference_add_expectation_builder_ext.rs +++ b/examples/doc-snippets/src/internal_crate_reference_add_expectation_builder_ext.rs @@ -1,5 +1,7 @@ use testing_framework_core::scenario::ScenarioBuilder; +use crate::SnippetResult; + pub trait YourExpectationDslExt: Sized { fn expect_your_condition(self) -> Self; } @@ -10,8 +12,9 @@ impl YourExpectationDslExt for testing_framework_core::scenario::Builder SnippetResult<()> { let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0)) .expect_your_condition() - .build(); + .build()?; + Ok(()) } diff --git a/examples/doc-snippets/src/internal_crate_reference_add_workload_use_in_examples.rs b/examples/doc-snippets/src/internal_crate_reference_add_workload_use_in_examples.rs index 0e1e794..76c41df 100644 --- a/examples/doc-snippets/src/internal_crate_reference_add_workload_use_in_examples.rs +++ b/examples/doc-snippets/src/internal_crate_reference_add_workload_use_in_examples.rs @@ -1,5 +1,7 @@ use testing_framework_core::scenario::ScenarioBuilder; +use crate::SnippetResult; + pub struct YourWorkloadBuilder; impl YourWorkloadBuilder { @@ -24,8 +26,9 @@ impl YourWorkloadDslExt for testing_framework_core::scenario::Builder SnippetResult<()> { let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0)) .your_workload_with(|w| w.some_config()) - .build(); + .build()?; + Ok(()) } diff --git a/examples/doc-snippets/src/lib.rs b/examples/doc-snippets/src/lib.rs index 0b7b592..2754696 100644 --- a/examples/doc-snippets/src/lib.rs +++ b/examples/doc-snippets/src/lib.rs @@ -1,5 +1,7 @@ #![allow(dead_code, unused_imports, unused_variables)] +pub type SnippetResult = Result; + mod architecture_overview_builder_api; mod chaos_workloads_random_restart; mod custom_workload_example_expectation; diff --git a/examples/doc-snippets/src/quickstart_adjust_topology.rs b/examples/doc-snippets/src/quickstart_adjust_topology.rs index b3ac7bf..793b20a 100644 --- a/examples/doc-snippets/src/quickstart_adjust_topology.rs +++ b/examples/doc-snippets/src/quickstart_adjust_topology.rs @@ -6,7 +6,7 @@ pub async fn run_with_env_overrides() -> Result<()> { // Uses NOMOS_DEMO_* env vars (or legacy *_DEMO_* vars) let mut plan = ScenarioBuilder::with_node_counts(3, 2) .with_run_duration(std::time::Duration::from_secs(120)) - .build(); + .build()?; let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/quickstart_core_api_pattern.rs b/examples/doc-snippets/src/quickstart_core_api_pattern.rs index 54135b9..00ece09 100644 --- a/examples/doc-snippets/src/quickstart_core_api_pattern.rs +++ b/examples/doc-snippets/src/quickstart_core_api_pattern.rs @@ -20,7 +20,7 @@ pub async fn run_local_demo() -> Result<()> { }) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(60)) - .build(); + .build()?; // Deploy and run let deployer = LocalDeployer::default(); diff --git a/examples/doc-snippets/src/quickstart_step_6_deploy_and_execute.rs b/examples/doc-snippets/src/quickstart_step_6_deploy_and_execute.rs index 1679793..8b82e50 100644 --- a/examples/doc-snippets/src/quickstart_step_6_deploy_and_execute.rs +++ b/examples/doc-snippets/src/quickstart_step_6_deploy_and_execute.rs @@ -3,7 +3,7 @@ use testing_framework_core::scenario::{Deployer, ScenarioBuilder}; use testing_framework_runner_local::LocalDeployer; pub async fn step_6_deploy_and_execute() -> Result<()> { - let mut plan = ScenarioBuilder::with_node_counts(1, 1).build(); + let mut plan = ScenarioBuilder::with_node_counts(1, 1).build()?; let deployer = LocalDeployer::default(); // Use local process deployer let runner = deployer.deploy(&plan).await?; // Provision infrastructure diff --git a/examples/doc-snippets/src/quickstart_swap_deployer_compose.rs b/examples/doc-snippets/src/quickstart_swap_deployer_compose.rs index 3195a51..5df9522 100644 --- a/examples/doc-snippets/src/quickstart_swap_deployer_compose.rs +++ b/examples/doc-snippets/src/quickstart_swap_deployer_compose.rs @@ -4,7 +4,7 @@ use testing_framework_runner_compose::ComposeDeployer; pub async fn run_with_compose_deployer() -> Result<()> { // ... same scenario definition ... - let mut plan = ScenarioBuilder::with_node_counts(1, 1).build(); + let mut plan = ScenarioBuilder::with_node_counts(1, 1).build()?; let deployer = ComposeDeployer::default(); // Use Docker Compose let runner = deployer.deploy(&plan).await?; diff --git a/examples/doc-snippets/src/testing_philosophy_declarative_over_imperative.rs b/examples/doc-snippets/src/testing_philosophy_declarative_over_imperative.rs index 49796b4..0540082 100644 --- a/examples/doc-snippets/src/testing_philosophy_declarative_over_imperative.rs +++ b/examples/doc-snippets/src/testing_philosophy_declarative_over_imperative.rs @@ -1,16 +1,20 @@ use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; -pub fn declarative_over_imperative() { +use crate::SnippetResult; + +pub fn declarative_over_imperative() -> SnippetResult<()> { // Good: declarative let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1)) .transactions_with(|txs| { txs.rate(5) // 5 transactions per block }) .expect_consensus_liveness() - .build(); + .build()?; // Bad: imperative (framework doesn't work this way) // spawn_validator(); spawn_executor(); // loop { submit_tx(); check_block(); } + + Ok(()) } diff --git a/examples/doc-snippets/src/testing_philosophy_determinism_first.rs b/examples/doc-snippets/src/testing_philosophy_determinism_first.rs index cd779d2..6e3f646 100644 --- a/examples/doc-snippets/src/testing_philosophy_determinism_first.rs +++ b/examples/doc-snippets/src/testing_philosophy_determinism_first.rs @@ -3,14 +3,16 @@ use std::time::Duration; use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt}; -pub fn determinism_first() { +use crate::SnippetResult; + +pub fn determinism_first() -> SnippetResult<()> { // Separate: functional test (deterministic) let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1)) .transactions_with(|txs| { txs.rate(5) // 5 transactions per block }) .expect_consensus_liveness() - .build(); + .build()?; // Separate: chaos test (introduces randomness) let _chaos_plan = @@ -27,5 +29,6 @@ pub fn determinism_first() { txs.rate(5) // 5 transactions per block }) .expect_consensus_liveness() - .build(); + .build()?; + Ok(()) } diff --git a/examples/doc-snippets/src/testing_philosophy_minimum_run_windows.rs b/examples/doc-snippets/src/testing_philosophy_minimum_run_windows.rs index 6ea2724..b9c30f0 100644 --- a/examples/doc-snippets/src/testing_philosophy_minimum_run_windows.rs +++ b/examples/doc-snippets/src/testing_philosophy_minimum_run_windows.rs @@ -3,17 +3,20 @@ use std::time::Duration; use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; -pub fn minimum_run_windows() { +use crate::SnippetResult; + +pub fn minimum_run_windows() -> SnippetResult<()> { // Bad: too short (~2 blocks with default 2s slots, 0.9 coeff) let _too_short = ScenarioBuilder::with_node_counts(1, 0) .with_run_duration(Duration::from_secs(5)) .expect_consensus_liveness() - .build(); + .build()?; // Good: enough blocks for assertions (~27 blocks with default 2s slots, 0.9 // coeff) let _good = ScenarioBuilder::with_node_counts(1, 0) .with_run_duration(Duration::from_secs(60)) .expect_consensus_liveness() - .build(); + .build()?; + Ok(()) } diff --git a/examples/doc-snippets/src/testing_philosophy_protocol_time_not_wall_time.rs b/examples/doc-snippets/src/testing_philosophy_protocol_time_not_wall_time.rs index 7da836a..b993857 100644 --- a/examples/doc-snippets/src/testing_philosophy_protocol_time_not_wall_time.rs +++ b/examples/doc-snippets/src/testing_philosophy_protocol_time_not_wall_time.rs @@ -3,7 +3,9 @@ use std::time::Duration; use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; -pub fn protocol_time_not_wall_time() { +use crate::SnippetResult; + +pub fn protocol_time_not_wall_time() -> SnippetResult<()> { // Good: protocol-oriented thinking let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1)) .transactions_with(|txs| { @@ -11,9 +13,11 @@ pub fn protocol_time_not_wall_time() { }) .with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks .expect_consensus_liveness() // "Did we produce the expected blocks?" - .build(); + .build()?; // Bad: wall-clock assumptions // "I expect exactly 30 blocks in 60 seconds" // This breaks on slow CI where slot timing might drift + + Ok(()) } diff --git a/examples/src/bin/compose_runner.rs b/examples/src/bin/compose_runner.rs index 348885d..32d2ea1 100644 --- a/examples/src/bin/compose_runner.rs +++ b/examples/src/bin/compose_runner.rs @@ -82,7 +82,7 @@ async fn run_compose_case( .da_with(|da| da.channel_rate(DA_CHANNEL_RATE).blob_rate(DA_BLOB_RATE)) .with_run_duration(run_duration) .expect_consensus_liveness() - .build(); + .build()?; let deployer = ComposeDeployer::new(); info!("deploying compose stack"); diff --git a/examples/src/bin/k8s_runner.rs b/examples/src/bin/k8s_runner.rs index 0cc70e2..8661198 100644 --- a/examples/src/bin/k8s_runner.rs +++ b/examples/src/bin/k8s_runner.rs @@ -59,7 +59,7 @@ async fn run_k8s_case(validators: usize, executors: usize, run_duration: Duratio } } - let mut plan = scenario.build(); + let mut plan = scenario.build()?; let deployer = K8sDeployer::new(); info!("deploying k8s stack"); diff --git a/examples/src/bin/local_runner.rs b/examples/src/bin/local_runner.rs index d82f4ff..1565106 100644 --- a/examples/src/bin/local_runner.rs +++ b/examples/src/bin/local_runner.rs @@ -61,7 +61,7 @@ async fn run_local_case(validators: usize, executors: usize, run_duration: Durat .expect_consensus_liveness() }; - let mut plan = scenario.build(); + let mut plan = scenario.build()?; let deployer = LocalDeployer::default().with_membership_check(true); info!("deploying local nodes"); diff --git a/testing-framework/core/src/scenario/definition.rs b/testing-framework/core/src/scenario/definition.rs index 9a00466..70aa200 100644 --- a/testing-framework/core/src/scenario/definition.rs +++ b/testing-framework/core/src/scenario/definition.rs @@ -1,5 +1,6 @@ use std::{num::NonZeroUsize, sync::Arc, time::Duration}; +use thiserror::Error; use tracing::{debug, info}; use super::{ @@ -7,7 +8,7 @@ use super::{ workload::Workload, }; use crate::topology::{ - config::{TopologyBuilder, TopologyConfig}, + config::{TopologyBuildError, TopologyBuilder, TopologyConfig}, configs::{network::Libp2pNetworkLayout, wallet::WalletConfig}, generation::GeneratedTopology, }; @@ -16,6 +17,12 @@ const DEFAULT_FUNDS_PER_WALLET: u64 = 100; const MIN_EXPECTATION_BLOCKS: u32 = 2; const MIN_EXPECTATION_FALLBACK_SECS: u64 = 10; +#[derive(Debug, Error)] +pub enum ScenarioBuildError { + #[error(transparent)] + Topology(#[from] TopologyBuildError), +} + /// Immutable scenario definition shared between the runner, workloads, and /// expectations. pub struct Scenario { @@ -214,7 +221,7 @@ impl Builder { #[must_use] /// Finalize the scenario, computing run metrics and initializing /// components. - pub fn build(self) -> Scenario { + pub fn build(self) -> Result, ScenarioBuildError> { let Self { topology, mut workloads, @@ -224,7 +231,7 @@ impl Builder { .. } = self; - let generated = topology.build(); + let generated = topology.build()?; let duration = enforce_min_duration(&generated, duration); let run_metrics = RunMetrics::from_topology(&generated, duration); initialize_components(&generated, &run_metrics, &mut workloads, &mut expectations); @@ -238,7 +245,13 @@ impl Builder { "scenario built" ); - Scenario::new(generated, workloads, expectations, duration, capabilities) + Ok(Scenario::new( + generated, + workloads, + expectations, + duration, + capabilities, + )) } } diff --git a/testing-framework/core/src/scenario/mod.rs b/testing-framework/core/src/scenario/mod.rs index 88b94e9..6cee1cc 100644 --- a/testing-framework/core/src/scenario/mod.rs +++ b/testing-framework/core/src/scenario/mod.rs @@ -14,7 +14,9 @@ pub type DynError = Box; pub use capabilities::{ NodeControlCapability, NodeControlHandle, ObservabilityCapability, RequiresNodeControl, }; -pub use definition::{Builder, Scenario, ScenarioBuilder, TopologyConfigurator}; +pub use definition::{ + Builder, Scenario, ScenarioBuildError, ScenarioBuilder, TopologyConfigurator, +}; pub use expectation::Expectation; pub use observability::{ObservabilityCapabilityProvider, ObservabilityInputs}; pub use runtime::{ diff --git a/testing-framework/core/src/topology/config.rs b/testing-framework/core/src/topology/config.rs index 7960e10..1b67747 100644 --- a/testing-framework/core/src/topology/config.rs +++ b/testing-framework/core/src/topology/config.rs @@ -7,26 +7,54 @@ use nomos_core::{ use nomos_da_network_core::swarm::DAConnectionPolicySettings; use testing_framework_config::topology::{ configs::{ - api::create_api_configs, - base::{BaseConfigs, build_base_configs}, - consensus::{ConsensusParams, ProviderInfo, create_genesis_tx_with_declarations}, + api::{ApiConfigError, create_api_configs}, + base::{BaseConfigError, BaseConfigs, build_base_configs}, + consensus::{ + ConsensusConfigError, ConsensusParams, ProviderInfo, + create_genesis_tx_with_declarations, + }, da::DaParams, network::{Libp2pNetworkLayout, NetworkParams}, tracing::create_tracing_configs, wallet::WalletConfig, }, - invariants::validate_generated_vectors, + invariants::{TopologyInvariantError, validate_generated_vectors}, }; +use thiserror::Error; use crate::topology::{ configs::{GeneralConfig, time::default_time_config}, generation::{GeneratedNodeConfig, GeneratedTopology, NodeRole}, - utils::{create_kms_configs, resolve_ids, resolve_ports}, + utils::{TopologyResolveError, create_kms_configs, resolve_ids, resolve_ports}, }; const DEFAULT_DA_BALANCER_INTERVAL: Duration = Duration::from_secs(1); const VALIDATOR_EXECUTOR_DA_BALANCER_INTERVAL: Duration = Duration::from_secs(5); +#[derive(Debug, Error)] +pub enum TopologyBuildError { + #[error("topology must include at least one node")] + EmptyParticipants, + #[error(transparent)] + Invariants(#[from] TopologyInvariantError), + #[error(transparent)] + Resolve(#[from] TopologyResolveError), + #[error(transparent)] + Base(#[from] BaseConfigError), + #[error(transparent)] + Api(#[from] ApiConfigError), + #[error(transparent)] + Genesis(#[from] ConsensusConfigError), + #[error("config generation requires at least one consensus config")] + MissingConsensusConfig, + #[error("internal config vector mismatch for {label} (expected {expected}, got {actual})")] + VectorLenMismatch { + label: &'static str, + expected: usize, + actual: usize, + }, +} + /// High-level topology settings used to generate node configs for a scenario. #[derive(Clone)] pub struct TopologyConfig { @@ -96,7 +124,6 @@ impl TopologyConfig { /// Build a topology with explicit validator and executor counts. pub fn with_node_numbers(validators: usize, executors: usize) -> Self { let participants = validators + executors; - assert!(participants > 0, "topology must include at least one node"); let mut da_params = DaParams::default(); let da_nodes = participants; @@ -240,9 +267,8 @@ impl TopologyBuilder { self } - #[must_use] /// Finalize and generate topology and node descriptors. - pub fn build(self) -> GeneratedTopology { + pub fn build(self) -> Result { let Self { config, ids, @@ -251,14 +277,15 @@ impl TopologyBuilder { } = self; let n_participants = config.n_validators + config.n_executors; - assert!(n_participants > 0, "topology must have at least one node"); + if n_participants == 0 { + return Err(TopologyBuildError::EmptyParticipants); + } - let ids = resolve_ids(ids, n_participants); - let da_ports = resolve_ports(da_ports, n_participants, "DA"); - let blend_ports = resolve_ports(blend_ports, n_participants, "Blend"); + let ids = resolve_ids(ids, n_participants)?; + 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"); + validate_generated_vectors(n_participants, &ids, &da_ports, &blend_ports)?; let BaseConfigs { mut consensus_configs, @@ -274,50 +301,56 @@ impl TopologyBuilder { &config.wallet_config, &da_ports, &blend_ports, - ) - .expect("failed to build base configs"); - let api_configs = create_api_configs(&ids).expect("failed to create API configs"); + )?; + let api_configs = create_api_configs(&ids)?; let tracing_configs = create_tracing_configs(&ids); let time_config = default_time_config(); - let mut providers: Vec<_> = da_configs - .iter() - .enumerate() - .map(|(i, da_conf)| ProviderInfo { + let first_consensus = consensus_configs + .first() + .ok_or(TopologyBuildError::MissingConsensusConfig)?; + let mut providers = Vec::with_capacity(da_configs.len() + blend_configs.len()); + for (i, da_conf) in da_configs.iter().enumerate() { + let note = first_consensus + .da_notes + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "da_notes", + expected: da_configs.len(), + actual: first_consensus.da_notes.len(), + })? + .clone(); + providers.push(ProviderInfo { service_type: ServiceType::DataAvailability, provider_sk: da_conf.signer.clone(), zk_sk: da_conf.secret_zk_key.clone(), locator: Locator(da_conf.listening_address.clone()), - note: consensus_configs[0].da_notes[i].clone(), - }) - .collect(); - providers.extend( - blend_configs - .iter() - .enumerate() - .map(|(i, blend_conf)| 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(), - }), - ); + note, + }); + } + for (i, blend_conf) in blend_configs.iter().enumerate() { + let note = first_consensus + .blend_notes + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "blend_notes", + expected: blend_configs.len(), + actual: first_consensus.blend_notes.len(), + })? + .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, + }); + } - let ledger_tx = consensus_configs[0] - .genesis_tx - .mantle_tx() - .ledger_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 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(); } let kms_configs = @@ -327,16 +360,100 @@ impl TopologyBuilder { let mut executors = Vec::with_capacity(config.n_executors); for i in 0..n_participants { + let consensus_config = consensus_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "consensus_configs", + expected: n_participants, + actual: consensus_configs.len(), + })? + .clone(); + let bootstrapping_config = bootstrapping_config + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "bootstrap_configs", + expected: n_participants, + actual: bootstrapping_config.len(), + })? + .clone(); + let da_config = da_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "da_configs", + expected: n_participants, + actual: da_configs.len(), + })? + .clone(); + let network_config = network_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "network_configs", + expected: n_participants, + actual: network_configs.len(), + })? + .clone(); + let blend_config = blend_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "blend_configs", + expected: n_participants, + actual: blend_configs.len(), + })? + .clone(); + let api_config = api_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "api_configs", + expected: n_participants, + actual: api_configs.len(), + })? + .clone(); + let tracing_config = tracing_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "tracing_configs", + expected: n_participants, + actual: tracing_configs.len(), + })? + .clone(); + let kms_config = kms_configs + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "kms_configs", + expected: n_participants, + actual: kms_configs.len(), + })? + .clone(); + let id = *ids.get(i).ok_or(TopologyBuildError::VectorLenMismatch { + label: "ids", + expected: n_participants, + actual: ids.len(), + })?; + let da_port = *da_ports + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "da_ports", + expected: n_participants, + actual: da_ports.len(), + })?; + let blend_port = *blend_ports + .get(i) + .ok_or(TopologyBuildError::VectorLenMismatch { + label: "blend_ports", + expected: n_participants, + actual: blend_ports.len(), + })?; + let general = GeneralConfig { - consensus_config: consensus_configs[i].clone(), - bootstrapping_config: bootstrapping_config[i].clone(), - da_config: da_configs[i].clone(), - network_config: network_configs[i].clone(), - blend_config: blend_configs[i].clone(), - api_config: api_configs[i].clone(), - tracing_config: tracing_configs[i].clone(), + consensus_config, + bootstrapping_config, + da_config, + network_config, + blend_config, + api_config, + tracing_config, time_config: time_config.clone(), - kms_config: kms_configs[i].clone(), + kms_config, }; let role = if i < config.n_validators { @@ -352,10 +469,10 @@ impl TopologyBuilder { let descriptor = GeneratedNodeConfig { role, index, - id: ids[i], + id, general, - da_port: da_ports[i], - blend_port: blend_ports[i], + da_port, + blend_port, }; match role { @@ -364,11 +481,11 @@ impl TopologyBuilder { } } - GeneratedTopology { + Ok(GeneratedTopology { config, validators, executors, - } + }) } #[must_use] diff --git a/testing-framework/core/src/topology/deployment.rs b/testing-framework/core/src/topology/deployment.rs index 504a66d..7395e26 100644 --- a/testing-framework/core/src/topology/deployment.rs +++ b/testing-framework/core/src/topology/deployment.rs @@ -10,7 +10,7 @@ use crate::{ validator::{Validator, create_validator_config}, }, topology::{ - config::{TopologyBuilder, TopologyConfig}, + config::{TopologyBuildError, TopologyBuilder, TopologyConfig}, configs::GeneralConfig, generation::find_expected_peer_counts, readiness::{ @@ -31,13 +31,15 @@ pub type DeployedNodes = (Vec, Vec); #[derive(Debug, Error)] pub enum SpawnTopologyError { + #[error(transparent)] + Build(#[from] TopologyBuildError), #[error(transparent)] Node(#[from] SpawnNodeError), } impl Topology { pub async fn spawn(config: TopologyConfig) -> Result { - let generated = TopologyBuilder::new(config.clone()).build(); + let generated = TopologyBuilder::new(config.clone()).build()?; let n_validators = config.n_validators; let n_executors = config.n_executors; let node_configs = generated @@ -64,7 +66,7 @@ impl Topology { .with_ids(ids.to_vec()) .with_da_ports(da_ports.to_vec()) .with_blend_ports(blend_ports.to_vec()) - .build(); + .build()?; let node_configs = generated .nodes() diff --git a/testing-framework/core/src/topology/utils.rs b/testing-framework/core/src/topology/utils.rs index 952cbe2..1b3150f 100644 --- a/testing-framework/core/src/topology/utils.rs +++ b/testing-framework/core/src/topology/utils.rs @@ -4,6 +4,7 @@ use groth16::fr_to_bytes; use key_management_system_service::{backend::preload::PreloadKMSBackendSettings, keys::Key}; use nomos_utils::net::get_available_udp_port; use rand::{Rng, thread_rng}; +use thiserror::Error; use crate::topology::configs::{ blend::GeneralBlendConfig, da::GeneralDaConfig, wallet::WalletAccount, @@ -52,40 +53,67 @@ pub fn create_kms_configs( .collect() } -pub fn resolve_ids(ids: Option>, count: usize) -> Vec<[u8; 32]> { - ids.map_or_else( - || { +#[derive(Debug, Error)] +pub enum TopologyResolveError { + #[error("expected {expected} ids but got {actual}")] + IdCountMismatch { expected: usize, actual: usize }, + #[error("expected {expected} {label} ports but got {actual}")] + PortCountMismatch { + label: &'static str, + expected: usize, + actual: usize, + }, + #[error("failed to allocate a free UDP port for {label}")] + PortAllocationFailed { label: &'static str }, +} + +pub fn resolve_ids( + ids: Option>, + count: usize, +) -> Result, TopologyResolveError> { + match ids { + Some(ids) => { + if ids.len() != count { + return Err(TopologyResolveError::IdCountMismatch { + expected: count, + actual: ids.len(), + }); + } + Ok(ids) + } + None => { let mut generated = vec![[0; 32]; count]; for id in &mut generated { thread_rng().fill(id); } - generated - }, - |ids| { - assert_eq!( - ids.len(), - count, - "expected {count} ids but got {}", - ids.len() - ); - ids - }, - ) + Ok(generated) + } + } } -pub fn resolve_ports(ports: Option>, count: usize, label: &str) -> Vec { - let resolved = ports.unwrap_or_else(|| { - iter::repeat_with(|| get_available_udp_port().unwrap()) - .take(count) - .collect() - }); - assert_eq!( - resolved.len(), - count, - "expected {count} {label} ports but got {}", - resolved.len() - ); - resolved +pub fn resolve_ports( + ports: Option>, + count: usize, + label: &'static str, +) -> Result, TopologyResolveError> { + let resolved = match ports { + Some(ports) => ports, + None => iter::repeat_with(|| { + get_available_udp_port().ok_or(TopologyResolveError::PortAllocationFailed { label }) + }) + .take(count) + .collect::, _>>()?, + }; + + if resolved.len() != count { + return Err(TopologyResolveError::PortCountMismatch { + label, + expected: count, + actual: resolved.len(), + }); + } + + Ok(resolved) } pub fn multiaddr_port(addr: &nomos_libp2p::Multiaddr) -> Option { diff --git a/testing-framework/cucumber/src/world.rs b/testing-framework/cucumber/src/world.rs index 4362b3f..f4ffaae 100644 --- a/testing-framework/cucumber/src/world.rs +++ b/testing-framework/cucumber/src/world.rs @@ -1,7 +1,9 @@ use std::{env, path::PathBuf, time::Duration}; use cucumber::World; -use testing_framework_core::scenario::{Builder, NodeControlCapability, Scenario, ScenarioBuilder}; +use testing_framework_core::scenario::{ + Builder, NodeControlCapability, Scenario, ScenarioBuildError, ScenarioBuilder, +}; use testing_framework_workflows::{ScenarioBuilderExt as _, expectations::ConsensusLiveness}; use thiserror::Error; @@ -81,6 +83,11 @@ pub enum StepError { InvalidArgument { message: String }, #[error("{message}")] Preflight { message: String }, + #[error("failed to build scenario: {source}")] + ScenarioBuild { + #[source] + source: ScenarioBuildError, + }, #[error("{message}")] RunFailed { message: String }, } @@ -195,14 +202,18 @@ impl TestingFrameworkWorld { pub fn build_local_scenario(&self) -> Result, StepError> { self.preflight(DeployerKind::Local)?; let builder = self.make_builder_for_deployer::<()>(DeployerKind::Local)?; - Ok(builder.build()) + builder + .build() + .map_err(|source| StepError::ScenarioBuild { source }) } pub fn build_compose_scenario(&self) -> Result, StepError> { self.preflight(DeployerKind::Compose)?; let builder = self.make_builder_for_deployer::(DeployerKind::Compose)?; - Ok(builder.build()) + builder + .build() + .map_err(|source| StepError::ScenarioBuild { source }) } pub fn preflight(&self, expected: DeployerKind) -> Result<(), StepError> { diff --git a/testing-framework/deployers/compose/src/deployer/mod.rs b/testing-framework/deployers/compose/src/deployer/mod.rs index 978eb60..605821a 100644 --- a/testing-framework/deployers/compose/src/deployer/mod.rs +++ b/testing-framework/deployers/compose/src/deployer/mod.rs @@ -109,7 +109,9 @@ mod tests { #[test] fn cfgsync_prebuilt_configs_preserve_genesis() { - let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)).build(); + let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)) + .build() + .expect("scenario build should succeed"); let topology = scenario.topology().clone(); let hosts = hosts_from_topology(&topology); let tracing_settings = tracing_settings(&topology); @@ -161,7 +163,9 @@ mod tests { #[test] fn cfgsync_genesis_proofs_verify_against_ledger() { - let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)).build(); + let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)) + .build() + .expect("scenario build should succeed"); let topology = scenario.topology().clone(); let hosts = hosts_from_topology(&topology); let tracing_settings = tracing_settings(&topology); @@ -197,7 +201,9 @@ mod tests { #[test] fn cfgsync_docker_overrides_produce_valid_genesis() { - let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)).build(); + let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)) + .build() + .expect("scenario build should succeed"); let topology = scenario.topology().clone(); let tracing_settings = tracing_settings(&topology); let hosts = docker_style_hosts(&topology); @@ -228,7 +234,9 @@ mod tests { #[test] fn cfgsync_configs_match_topology_ports_and_genesis() { - let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)).build(); + let scenario = ScenarioBuilder::topology_with(|t| t.validators(1).executors(1)) + .build() + .expect("scenario build should succeed"); let topology = scenario.topology().clone(); let hosts = hosts_from_topology(&topology); let tracing_settings = tracing_settings(&topology);